11月23
用 security加redis写项目时一直报这个错误,说是没有set方法?我用的data注解怎么可能

这个bug找了一下午,终于找到原因了:redis序列化会查询所有以get和set开头的方法,而我的user继承了security的UserDetails,多了一个集合类型的getAuthorities方法,所有导致无法序列化,使用的是jackson的序列化器

解决办法:
加上@JsonIgnore注解设置不序列化

@JsonIgnore
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        ArrayList<NdpAuthority> grantedAuthorities = CollectionUtil.newArrayList();
        if (ObjUtil.isNotEmpty(roles)) {
            roles.forEach(dict -> {
                String roleName = dict.getStr(CommonConstant.NAME);
                NdpAuthority ndpAuthority = new NdpAuthority(roleName);
                grantedAuthorities.add(ndpAuthority);
            });
        }
        return grantedAuthorities;
    }

加个属性,写个对应的set方法
11月23
在实际项目开发过程中,我们有时候需要让项目在启动时执行特定方法。如要实现这些功能:
提前加载相应的数据到缓存中;
检查当前项目运行环境;
检查程序授权信息,若未授权则不能使用后续功能;
执行某个特定方法;

一直想整理一下Springboot启动后自执行某段代码或者方法相关的点,囿于时间问题及担心理解不是太全面所以没整理,后来一想先整理出来,将这一方面的东西记录下来。其实这篇文章不应该体现spring boot,因为自动执行方式不仅限于spring boot,java spring细化到bean的初始化都可以完成。

一、java自身的启动时加载方式

1、static代码块
static静态代码块,在类加载的时候即自动执行。 由于静态代码块没有名字,我们并不能主动调用,它会在类加载的时候,自动执行。所以静态代码块,可以更早的给类中的静态属性,进行初始化赋值操作。并且,静态代码块只会自动被执行一次,因为JVM在一次运行中,对一个类只会加载一次!

2、构造函数constructor
在对象初始化时执行。执行顺序在static静态代码块之后。

3、@PostConstruct注解
@PostConstruct
public void testInit() {
    System.out.printin("postConstruct“);
}
@PostConstruct注解使用在方法上,这个方法在对象依赖注入初始化之后执行。执行节点在BeanPostProcessor的postProcessBeforeInitialization之后,在postProcessAfterInitialization之前。
很多人以为该注解是Spring提供的。其实是Java自己的注解。Java中该注解的说明:@PostConstruct该注解被用来修饰一个非静态的void()方法。被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。
注意:加了postconstruct注解的方法,如果执行失败,整个程序会无法正常启动!这个方法执行不完,整个程序也启动不了!也不建议将耗时逻辑放到这里面来。

二、Servlet加载的方式
1、实现ServletContextListener接口contextInitialized方法
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

@Slf4j
@Component
public class ServletContextListenerImpl implements ServletContextListener {
    /**
     * 在初始化Web应用程序中的任何过滤器或Servlet之前,将通知所有ServletContextListener上下文初始化。
     */
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        log.info("启动时自动执行 ServletContextListener 的 contextInitialized 方法");
    }
}
注意:该方法会在填充完普通Bean的属性,但是还没有进行Bean的初始化之前执行

三、Spring启动时加载方式
1、实现ServletContextAware接口setServletContext 方法
@Component
public class StartInitServletContext implements ServletContextAware {

    @Override
    public void setServletContext(ServletContext servletContext) {
        System.out.println("StartInitServletContext: 开始处理事情");
    }
}

2、ApplicationListener监听器
创建Listener的方式有两种:
方式1:编程实现ApplicationListener接口
方式2:使用@EventListener注解
注意监听的事件,通常是ApplicationStartedEvent 或者ApplicationReadyEvent,其他的事件可能无法注入bean。

编程实现ApplicationListener接口
/**
* 监听的是 ApplicationStartedEvent 事件,
* 则 ApplicationListener 一定会在 CommandLineRunner 和 ApplicationRunner 之前执行;
*/
@Component
public class StartInitStartedEvent implements ApplicationListener<ApplicationStartedEvent> {
    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        System.out.println("StartInitStartedEvent:开始处理事情");
    }
}
/**
* 监听的是 ApplicationReadyEvent 事件,
* 则 ApplicationListener 一定会在 CommandLineRunner 和 ApplicationRunner 之后执行;
*/
@Component
public class StartInitReadyEvent implements ApplicationListener<ApplicationReadyEvent> {
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        System.out.println("StartInitReadyEvent: 开始处理事情");
    }
}
实现 public interface ApplicationListener<E extends ApplicationEvent> 接口,监听 ApplicationEvent 及其下面的子事件:
/**
* 事件监听器一般是由开发者自己定义
* 定义事件监听器
*/
@Component
//@Lazy
public class MyApplicationListener implements ApplicationListener{

  @Override
  public void onApplicationEvent(ApplicationEvent event) {
    event = (PayloadApplicationEvent)event;
    System.out.println(((PayloadApplicationEvent<?>) event).getPayload());
  }
}

@EventListener方式
将要执行的方法所在的类交个Spring容器扫描(@Component),并且在要执行的方法上添加@EventListener注解执行
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class EventListenerTest {
    @EventListener
    public void onApplicationEvent(ContextRefreshedEvent event) {
        log.info("启动时自动执行  @EventListener 注解方法");
    }
}

3、InitializingBean接口
@Component
@Data
public class TestInit implements InitializingBean,eanFactoryAware,ApplicationContextAware{
    private BeanFactory beanFactory:
    private ApplicationContext applicationContext;
    private String name;
@Override
public voidafterPropertiesSet(hrows Exception {
    System.out.printin("InIt heck");
}
}
Spring为bean提供了两种初始化bean的方式,实现InitializingBean接口,实现afterPropertiesSet方法,或者在配置文件中通过init-method指定,两种方式可以同时使用。
实现InitializingBean接口是直接调用afterPropertiesSet方法,比通过反射调用init-method指定的方法效率要高一点,但是init-method方式消除了对spring的依赖。先调用afterPropertieSet()方法,然后再调用init-method中指定的方法。

4、ApplicationRunner和CommandLineRunner
SpringBoot提供了两个接口来实现Spring容器启动完成后执行的功能,两个接口分别为CommandLineRunner和ApplicationRunner。这两个接口需要实现一个run方法,将代码在run中实现即可。这两个接口功能基本一致,其区别在于run方法的入参。ApplicationRunner的run方法入参为ApplicationArguments,为CommandLineRunner的run方法入参为String数组。
10月11
${ew.customSqlSegment} 会直接在前面添加 where

@Select(select * from a ${ew.customSqlSegment})
List<a> getHeck(@Param(Constants.WRAPPER)QueryWrapper queryWrapper)

${ew.sqlSegment} 就只有条件语句

@Select(select * from a where ${ew.sqlSegment})
List<a> getHeck(@Param(Constants.WRAPPER)QueryWrapper queryWrapper)

${ew.sqlSelect} 就是 queryWrapper.select(****) 你所定义需要查询的字段

@Select(select ${ew.sqlSelect} from a )
List<a> getHeck(@Param(Constants.WRAPPER)QueryWrapper queryWrapper)
9月4
错误信息:
Could not read JSON: Cannot construct instance of java.util.ArrayList$SubList(no Creators, like default construct, exist): no default no-arguments constructor found

原因是读取Redis缓存时,报错异常导致!

原因是缓存中是集合ArrayList中含有SubList,因为SubList不能序列化和反序列化,导致解析失败。

解决办法:

1、若存在使用SubList方法,只需要 重新new 下:

原代码:       resultList = regionDistributionVOList.subList(ZERO, FOUR);

改正后:       resultList = new ArrayList<>(regionDistributionVOList.subList(ZERO, FOUR));
或者:            resultList.addAll(regionDistributionVOList.subList(ZERO, FOUR));

2、若通过 Lists.partition(ZERO, TEN)获取的,则需要将subList转为ArrayList

用: Lists.newArrayList(subList)
8月24
首先经过了解查看源码,@Scheduled是单线程的,如果有多个定时任务,势必需要前一个任务执行完才会执行后面的任务
点击在新窗口中浏览此图片

所以我们有三种方法解决定时任务线程池配置解决多个定时任务阻塞问题
1、重写SchedulingConfigurer#configureTasks(),直接实现SchedulingConfigurer这个接口,设置taskScheduler
2、也可以配置文件配置,Spring Boot quartz 已经提供了一个配置用来配置线程池的大小 spring.task.scheduling.pool.size=10
3、配置线程池,再使用@Async开启异步任务

package com.nine.rivers.apps.core.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

import java.util.concurrent.ThreadPoolExecutor;

import static com.nine.rivers.apps.core.constants.NumberConstant.*;

/**
* 定时任务线程池配置解决多个定时任务阻塞问题
* 三种方法,任选期一:
* <p>1、重写SchedulingConfigurer#configureTasks(),直接实现SchedulingConfigurer这个接口,设置taskScheduler
* <p>2、也可以配置文件配置,Spring Boot quartz 已经提供了一个配置用来配置线程池的大小 spring.task.scheduling.pool.size=10
* <p>3、配置线程池,再使用@Async开启异步任务
*
* @author heck
* @date 2023/08/24
*/
@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(taskScheduler());
    }

    /**
     * 覆盖taskScheduler
     */
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(SIX);
        taskScheduler.setThreadNamePrefix("ndp-apps-scheduler-pool-");
        return taskScheduler;
    }

}

方法三

    /**
     * 配置线程池,再使用@Async开启异步任务
     */
    @Bean
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor poolTaskExecutor = new ThreadPoolTaskExecutor();

        poolTaskExecutor.setCorePoolSize(FOUR);
        poolTaskExecutor.setMaxPoolSize(SIX);
        // 设置线程活跃时间(秒)
        poolTaskExecutor.setKeepAliveSeconds(TWO * SIX * TEN);
        // 设置队列容量
        poolTaskExecutor.setQueueCapacity(FOUR * TEN);

        poolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 等待所有任务结束后再关闭线程池
        poolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);

        return poolTaskExecutor;
    }
8月21
错误原因和现象
在执行shell脚本的时候,报错:/bin/bash^M: bad interpreter: No such file or directory。
是由于该脚本文件是在Windows平台编写,然后在MacOS或者Kylin-Server平台中执行。
在Windows平台上文件是dos格式,换行符尾\r\n,而MacOS或者Kylin-Server平台文件是unix格式,换行符尾\n。因此在运行脚本文件时,不能正确解析\r,导致编译错误、运行失败。

解决方案

在MacOS或者Kylin-Server平台上,打开命令行工具执行命令vim *.sh(编辑你的脚本文件),然后执行命令:set ff=unix,最后执行:wq( 保存退出)即可。
7月24
方式一:SM2密钥在线生成
SM2密钥在线生成工具

如果你没线下生成工具,可用下面2种线上生成方式之一:

1. sm2密钥在线生成(const.net.cn

2. web encrypt(webencrypt.org

方式一:生成SM2公私钥(.pem格式)
一.系统环境
系统环境:windows系统。
二.工具软件
工具软件:Win64OpenSSL。
三.生成SM2公私钥
步骤一:在windows操作系统上安装Win64OpenSSL软件;
步骤二:打开Win64OpenSSL软件,首先生成私钥,命令为:ecparam -genkey -name SM2 -out priv.key;
7月17
Linux 系统中,Spring Boot 应用以 java -jar 命令启动时,会在操作系统的 /tmp 目录下生成一个 tomcat(或 undertow )临时目录,上传的文件先要转换成临时文件保存在这个文件夹下面。由于临时 /tmp 目录下的文件,在长时间(10天)没有使用的情况下,系统执行了 tmp 目录清理服务(systemd-tmpfiles-clean.service),导致 /tmp/undertow...8090 文件被清理,然而在上传的时候,undertow 服务器需要创建/tmp/undertow...8090/undertow...upload 临时文件,但是调用 Files.createFile(...) 的时候就会发现找不到父目录,才导致了以上的错误。

具体错误日志(参考)
undertow

java.nio.file.NoSuchFileException: /tmp/undertow.17753558642503713859.8085/undertow7370242804103803588upload

Tomcat

The temporary upload location [/tmp/tomcat.7957874575370093230.8088/work/Tomcat/localhost/ROOT] is not valid

重现方法
找到类 io.undertow.server.handlers.form.MultiPartParserDefinition
定位到如下代码

@Override
public void beginPart(final HeaderMap headers) {
    this.currentFileSize = 0;
    this.headers = headers;
    final String disposition = headers.getFirst(Headers.CONTENT_DISPOSITION);
    if (disposition != null) {
        if (disposition.startsWith("form-data")) {
            currentName = Headers.extractQuotedValueFromHeader(disposition, "name");
            fileName = Headers.extractQuotedValueFromHeaderWithEncoding(disposition, "filename");
            if (fileName != null && fileSizeThreshold == 0) {
                try {
                    if (tempFileLocation != null) {
                        file = Files.createTempFile(tempFileLocation, "undertow", "upload");
                    } else {
                        file = Files.createTempFile("undertow", "upload");
                    }
                    createdFiles.add(file);
                    fileChannel = FileChannel.open(file, StandardOpenOption.READ, StandardOpenOption.WRITE);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

在 createdFiles.add(file); 处打断点,复制file的 path 的值找到该文件并将其删除;放开断点,错误重现;
6月30
在做javaweb项目时,关于统一错误页面在开发的过程中就做过编码,并且一直都很有效,像500,404,403等常规错误码都能得到有效处理,但是400却不行,而且还暴露tomcat的版本信息,这是很严重的安全漏洞
解决方法百度和测试很多,总结如下:

问题产生:
根据rfc规范,url中不允许有 |,{,}等特殊字符,但在实际生产中还是有些url有可能携带有这些字符,特别是|还是较为常见的。在tomcat升级到7以后,对url字符的检查都变严格了,如果出现这类字符,服务器tomcat将直接返回400状态码。

方法一:降低tomcat的版本
开发人员增加一项设置,允许配置在url可以出现的特殊字符,但也仅限于|,{,}三种,见:http://tomcat.apache.org/tomcat-8.0-doc/config/systemprops.html#Other
该项设置在以下版本的tomcat中有效:

- 8.5.x for 8.5.12 onwards

- 8.0.x for 8.0.42 onwards

- 7.0.x for 7.0.76 onwards
- 这个只是允许出现一些特殊字符,并没有说是全部特殊字符。
好像是tomcat7.9以上的版本,都不支持请求链接上带有特殊字符.否则会报400错误,
tomcat请求中包含特殊字符 [] | {} 发送get请求失败:
原因:
这是因为Tomcat严格按照 RFC 3986规范进行访问解析,而 RFC 3986规范定义了Url中只允许包含英文字母(a-zA-Z)、数字(0-9)、-_.~4个特殊字符以及所有保留字符(RFC3986中指定了以下字符为保留字符:! * ’ ( ) ; : @ & = + $ , / ? # [ ])。传入的参数中有"[]"、""不在RFC3986中的保留字段中,所以会报这个错。

方法二:修改server.xml
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" relaxedQueryChars="[]|{}^&#x5c;&#x60;&quot;&lt;&gt;" redirectPort="8443" />
方法三:修改server.xml(推荐方法)
在Host里加入:
<Valve className="org.apache.catalina.valves.ErrorReportValve"  showReport="false" showServerInfo="false" />
方法三 修改后只会显示出HTTP 400 错误,不会打印堆栈和tomcat版本号信息
6月29
linux同步时间命令:

1、hwclock命令,可以让系统时间和硬件时间的同步,例“hwclock -w”或“hwclock -s”;
2、ntpdate命令,可以让不同机器间同步时间。


在Windwos中,系统时间的设置很简单,界面操作,通俗易懂,而且设置后,重启,关机都没关系。系统时间会自动保存在BIOS时钟里面,启动计算机的时候,系统会自动在BIOS里面取硬件时间,以保证时间的不间断。

但在Linux下,默认情况下,系统时间和硬件时间并不会自动同步。在Linux运行过程中,系统时间和硬件时间以异步的方式运行,互不干扰。硬件时间的运行,是靠BIOS电池来维持,而系统时间,是用CPU Tick来维持的。在系统开机的时候,会自动从BIOS中取得硬件时间,设置为系统时间。

1. Linux系统时间的设置
在Linux中设置系统时间,可以用date命令:

//查看时间

[root@node1 ~]# date

Tue Feb 25 20:15:18 CST 2014

//修改时间

[root@node1 ~]# date -s "20140225 20:16:00"  #yyyymmdd hh:mm:ss

Tue Feb 25 20:16:00 CST 2014

//date 有多种时间格式可接受,查看date --help

2. Linux硬件时间的设置
硬件时间的设置,可以用hwclock或者clock命令。两者基本相同,只用一个就行,只不过clock命令除了支持x86硬件体系外,还支持Alpha硬件体系。

//查看硬件时间可以是用hwclock ,hwclock --show 或者 hwclock -r

[root@node1 ~]# hwclock --show

Tue 25 Feb 2014 08:21:14 PM CST -0.327068 seconds

//设置硬件时间

[root@node1 ~]# hwclock --set --date "20140225 20:23:00"

[root@node1 ~]# hwclock

Tue 25 Feb 2014 08:23:04 PM CST -0.750440 seconds

3. 系统时间和硬件时间的同步
同步系统时间和硬件时间,可以使用hwclock命令。

//以系统时间为基准,修改硬件时间

[root@node1 ~]# hwclock --systohc <== sys(系统时间)to(写到)hc(Hard Clock)

//或者

[root@node1 ~]# hwclock -w

//以硬件时间为基准,修改系统时间

[root@node1 ~]# hwclock --hctosys

//或者

[root@node1 ~]# hwclock -s
分页: 2/66 第一页 上页 1 2 3 4 5 6 7 8 9 10 下页 最后页 [ 显示模式: 摘要 | 列表 ]