如果不小心,可能实例化两个ApplicationListener监听器对象
Springboot中在spring.factories
中已经配置了监听器,如果再加上@Configuration
或者@Component
注解会导致实例化两个监听器,一个bean的名称是全类名,另一个bean的名称是类名首字母小写。可能导致监听的事件逻辑执行两次!!!
//@Configuration
public class EventListener implements ApplicationListener<SpringApplicationEvent> {
public EventListener() {
System.out.println(EventListener.class.getName() + "init...");
}
@Override
public void onApplicationEvent(SpringApplicationEvent event) {
System.out.println("事件执行:" + event.getClass().getName());
}
}
使用@Component注解会导致spring.factories中的EnableAutoConfiguration无效
在spring.factories中通过org.springframework.boot.autoconfigure.EnableAutoConfiguration配置自动装配类时,bean名称是全类名。
- 加上如下两个注解都不会出现被实例化两次的问题:
@Configuration
,@Component
- 使用
@Component
注解会导致在spring.factories
中配置的无效,bean名称变为类名首字母小写,@Configuration
不会,
原因是在做类路径bean扫描时,会过滤掉含@Configuration
并配置在spring.factories
中的类,参见:org.springframework.boot.autoconfigure.AutoConfigurationExcludeFilter
,由AutoConfigurationImportSelector
去加载.
@Configuration
//@Component
public class MyCustomAutoConfiguration {
public MyCustomAutoConfiguration() {
System.out.println(MyCustomAutoConfiguration.class.getName() + " init...");
}
}
为什么类上标记@Component
会导致其在spring.factories
中配置无效 ???
原因在于Spring执行包扫描的时候,会将扫描的类包装成ConfigurationClass
进行解析,解析之前会从Map(ConfigurationClassParser#configurationClasses
)中获取判断之前是否已经解析过,如果已经解析过则跳过,解析之后会存放到Map中保存。当使用@Component
会被Spring扫描到, 之后AutoConfigurationImportSelector
加载spring.factories
中的EnableAutoConfiguration
时虽然能加载到,但是在处理时判断之前实际上已经处理过,所以不再被处理。
org.springframework.web.servlet.config.annotation.WebMvcConfigurer 的变更
WebMvcConfigurer是一个接口,用于自定义HttpMessageConverter,自定义json转换工具(fastjson),ViewResolver视图解析器,Interceptor拦截器等
去除其低版本中的适配器org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
,直接采用java8中的default关键字实现。
SpringBoot中有该接口的一个默认实现
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter
在低版本中并没有准确的控制多个WebMvcConfigurer之间的顺序, 自定义的WebConfig可能在SpringBoot默认提供的之前执行,升级SpringBoot2.x之后可能在其之后执行。
原因: SpringBoot2.x中对其默认实现添加了@Order(0)注解用于排序,
解决办法: 同样使用@Order注解控制自定义的WebMvcConfigurer执行顺序
SpringBoot2.1.3依赖Sping5.1.5版本,而Spring5.0开始支持webFlux反应式编程,是Spring5.0开始一个重大的亮点
SpringBoot2.1 兼容Java8,同时也支持了Java11
SpringBoot2.1.3 部分依赖项变更
SpringBoot中默认情况下已经配置了常用依赖包的版本号,在实际开发过程开发人员只需要指定依赖的groupId和artifactId即可,当父pom中指定版本号时,不建议子模块指定版本。
如果子模块中指明了,version,那么框架升级后,工程使用的依赖任然会是旧的版本号,可能存在兼容性问题。
其中值得注意的是: SpringBoot2.1.3中的配置的jedis版本是2.9.1,存在bug
精简控制台日志信息
例如:控制台不再打印出Controller中映射的Url信息,并且打印出web应用的根目录
Tomcat started on port(s): 8080 (http) with context path ‘/boot2’
设置web工程根目录配置项发生变更
server.context-path=/boot1
server.servlet.context-path=/boot2
SpringBoot2.x中废弃了一些配置项,可通过其提供的包来帮助检查
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-properties-migrator</artifactId>
<optional>true</optional>
</dependency>
该依赖只需要开发时使用即可,修改完新的配置项时,直接移除该依赖,不需要打包到项目
SpringBoot2.x 对时间序列化格式默认不再是timestamps时间戳,而是yyyy-MM-ddTHH:mm:ss:SSS+0000
SpringBoot中默认使用jackson做为json序列化,反序列化工具,
在SpringBoot1.5.x中,向前端返回的对象包含时间属性时,时间被序列化为时间戳
。
{"username":"小明","password":"titans1.x","birthday":1594448070750}
但是在SpringBoot2.1.x中,时间被序列化为yyyy-MM-ddTHH:mm:ss:SSS+000
格式
{"username":"小明","password":"titans2.x","birthday":"2020-07-11T06:14:33.729+0000"}
解决方案:如果仍然需要将时间序列化为时间戳,需要添加如下配置项
spring.jackson.serialization.write-dates-as-timestamps=true
SpringBoot2.x 中的Binder工具类
可以将指定前缀的属性值绑定到一个实体类上,前提是需要将org.springframework.core.env.Environment作为参数
MyPropertyConfig myPropertyConfig = Binder.get(environment)
.bind("my.customer-tag-config.target", MyPropertyConfig.class)
.orElse(null);
SpringBoot中配置项优先级问题
- Environment是Spring中的一个接口,主要用于获取Spring整个应用启动、运行过程中获取配置信息。
- 其实现类通过List保存多个PropertySource,一个PropertySource相当于一个Map,其以key-value的形式保存配置属性信息。
- 当通过Environment#getProperty(key)获取一个属性值时,内部实际是遍历List中的PropertySource,通过PropertySource获取key-value而List的有序性则实现类不通配置的优先级。
内嵌容器包结构调整
SpringBoot1.5.17中应用的类型分为两种Web类型的应用,普通类型的应用(运行结束进程退出)
而在SpringBoot2.x中增加了第三种应用类型Reactive,即反应式编程。因此对内嵌的容器包结构进行调整
SpringBoot1.x
org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory
SpringBoot2.x
org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory
org.springframework.boot.web.reactive.server.ReactiveWebServerFactory
SpringBoot应用类型推断与手动指定
SpringBoot在启动时会根据类路径上存在的包来判断改应该属于哪种类型,(Servlet web类型,Reactive web类型,普通类型)
当这些包都存在时,SpringBoot推断出的应用类型可能非项目中真实需要的,则需要手动指定。
SpringBoot1.x
spring.main.web-environment=true/false
SpringBoot2.x
spring.main.web-application-type=none
spring.main.web-application-type=servlet
spring.main.web-application-type=reactive
@ConditionalOnBean判断条件变化
@ConditionalOnBean现在的判断条件由OR变为了AND。
在配置类中定义Bean,如果使用@ConditionalOnBean依赖的也是配置类中Bean,则执行结果不可控,和配置类加载顺序有关,
参数文章: SpringBoot中@ConditionalOnBean实现原理
如果要实现Or的效果,可以通过继承AnyNestedCondition实现。
ApplicationStartedEvent事件执行时机发生变化
注意 org.springframework.boot.context.event.ApplicationStartedEvent 事件的变化
SpringBoot1.5.17
org.springframework.boot.context.event.ApplicationStartedEvent
org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent
org.springframework.boot.context.event.ApplicationPreparedEvent
org.springframework.boot.context.event.ApplicationReadyEvent
SpringBoot2.1.3
org.springframework.boot.context.event.ApplicationStartingEvent
org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent
org.springframework.boot.context.event.ApplicationContextInitializedEvent
org.springframework.boot.context.event.ApplicationPreparedEvent
org.springframework.boot.context.event.ApplicationStartedEvent
org.springframework.boot.context.event.ApplicationReadyEvent
SpringBoot2.x 松散绑定
SpringBoot2.0 松散绑定,不支持key中间带有驼峰命名
会抛出如下异常
Description:
Configuration property name 'customerTagConfig' is not valid:
Invalid characters: 'T', 'C'
Bean: testController
Reason: Canonical names should be kebab-case ('-' separated), lowercase alpha-numeric characters and must start with a letter
Action:
修改方案:
SpringBoot2.x默认设置不允许出现相同beanName的BeanDefinition
如果项目中出现了相同beanName的Bean定义,则会抛出DefinitionOverrideException,而在低版本SpringBoot中则不会。Spring默认允许Bean被覆盖
解决办法:
1、(建议方案)查找项目出现相同beanName的原因,并做相应的修改
2、通过配置允许覆盖相同beanName的beanDefinition: spring.main.allow-bean-definition-overriding=true
SpringMVC请求路径匹配默认行为改变
在2.0之前的版本中,SpringMVC默认会使用.* suffix匹配请求路径,也就是如果Controller请求方法上设置 /person 路径时等同于 /person.* ,当请求url是/person.pdf 或是 /person.json时都是映射到 /person 请求。在SpringBoot2.0中默认关闭了*.suffix匹配规则,如果请求的url是/person.pdf 将不会再匹配到/person。
http返回406案例:在一次开发过程中,前端定义了名称为error.js 的静态文件放在SpringBoot的静态资源中,而SpringBoot中有个默认路径为/error的BasicErrorController用于处理异常请求,当前端请求error.js时http返回码是406,原因在于请求error.js浏览器希望得到的返回头是application/JavaScript,而/error.js正好匹配/error.*,因此被BasicErrorController的/error处理返回的是 application/json,导致406问题。
参考类:
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#getMappingForMethod
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#addMatchingMappings
org.springframework.web.servlet.mvc.condition.PatternsRequestCondition#getMatchingPattern
将Filter,Servlet中的转发类型改为DispatcherType.REQUEST
在原生Servlet编程,使用web.xml向tomcat容器中添加Sevrlet时,默认转发类型为DispatcherType.REQUEST,
但是SpringBoot1.5.17中,如果通过SpringBoot提供方式向web容器添加Sevlet,默认转发类型是
DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.REQUEST
SpringBoot2.1.3中,为了和原生默认配置一致,默认转发类型改为DispatcherType.REQUEST
Auto-configuration排序
@AutoConfigureOrder 默认值由Ordered.LOWEST_PRECEDENCE变为0。 (#10142)
SpringBoot2.1.2默认线程池
SpringBoot2.1.2 中默认有两出会开启线程池
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
创建Bean org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
用于异步任务:例如@Async注解的方法
@Service
public class UserInfoService implements UserInfoServiceI{
/**
* 默认线程数8,达到30s后线程销毁,再次创建的线程id会递增
*/
@Async
@Override
public void addUser(UserInfo user) {
log.info("保存用户信息:{}", user.getUsername());
}
}
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration
创建Bean org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler
用于定时任务:例如@Scheduled(initialDelay = 3000, fixedDelay = 2000)
/**
* 默认线程数 1
*
* @see TaskSchedulingAutoConfiguration#taskScheduler(TaskSchedulerBuilder)
*/
@Scheduled(initialDelay = 3000, fixedDelay = 2000)
public void print1() {
log.info("当前时间:{}", LocalDateTime.now());
}
这两个Bean都实现了org.springframework.core.task.AsyncListenableTaskExecutor和
org.springframework.scheduling.SchedulingTaskExecutor, 所以程序中不能通过这两个类型及其父类类型注入bean
SpringBoot1.x中没有自定义线程池,而是采用的Spring自定义的
Spring会为异步任务创建一个 无限大小的线程池,容易出线程池爆满,参考:org.springframework.core.task.SimpleAsyncTaskExecutor,一般在开发时会自定义线程策略
Spring会为定时任务创建一个只有一条线程的线程池,参考 ScheduledTaskRegistrar#scheduleTasks()
SpringBoot2.x 支持Redis缓存设置,1.5x不支持
spring.cache.redis.cache-null-values=true
spring.cache.redis.use-key-prefix=true
spring.cache.redis.key-prefix=默认为缓存名+:: 例如 myCache::
spring.cache.redis.time-to-live=0
缺点:这是全局设置,无法针对单个缓存设置
不再支持Guava缓存
org.springframework.cache.guava.GuavaCacheManager
替代方案
org.springframework.cache.caffeine.CaffeineCacheManager
默认代理策略
Spring中默认代理策略是:如果是接口实现类怎么通过JDK代理,如果代理普通类则通过Cglib代理。
Spring Boot2.x 现在默认是使用CGLIB代理,同时包含AOP支持。
如果需要基于JDK接口的代理策略,需要把spring.aop.proxy-target-class设置为false。
SpringBoot2.0开始, Redis客户端默认使用Lettuce,并且配置项发生变更
SpringBoot2.0 开始,支持jedis和Lettuce两种客户端,默认使用Lettuce客户端,
SpringBoot1.5.x中默认的Redis客户端时Jedis,如下是其配置项,但是在SpringBoot2.x中已经被废弃
spring.redis.pool.max-active=8
spring.redis.pool.max-idle=8
spring.redis.pool.max-wait=-1
spring.redis.pool.min-idle=0
Lettuce说明
Lettuce 官方文档:https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/reference/html/appendix-application-properties.html#cache-properties
如上是Lettuce客户端的官方说明,Lettuce是线程安全的,一个连接可以在多线程之间共享,因此一般情况下不需要设置使用连接池,并且连接池对性能影响不大。
SpringBoot2.1.3并没有为Lettuce开放Lettuce线程的配置项,ioThreadPoolSize和computationThreadPoolSize默认情况下都是cpu逻辑处理器数量。
修改Lettuce线程池数量
虽然SpringBoot没有提供修改Lettuce线程池的配置项,但是我们可以在配置类中配置如下的Bean来控制Lettuce线程数量。
@Bean(destroyMethod = "shutdown")
public DefaultClientResources lettuceClientResources() {
int cpuSize = Runtime.getRuntime().availableProcessors();
DefaultClientResources.Builder builder = DefaultClientResources.builder();
builder.computationThreadPoolSize(cpuSize);
builder.ioThreadPoolSize(cpuSize);
return builder.build();
}
Lettuce 使用连接池
少数情况下,我们可能需要使用Lettuce连接池配置,添加如下依赖,并且针对Lettuce连接池做相应的配置
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
lettuce 连接池配置
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.min-idle=1
spring.redis.lettuce.shutdown-timeout=1000ms