具体的差异
参考官方:https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Migration-Guide
中文版
这里只是稍微记录一下我学习看到的
一.@SpringBootApplication
修饰主程序类。可以通过main方法启动项目
单个注释可用于启用这三个功能,即:@SpringBootApplication
@EnableAutoConfiguration:启用弹簧启动的自动配置机制
@ComponentScan: 在应用程序所在的包上启用扫描
@Configuration:允许在上下文中注册额外的 bean 或导入其他配置类
下面展示1.5.9与2.0的区别
SpringBootApplication修饰的类的,这里仅以web为例
1.5.9版本的,自动配置类96,最后过程为20
2.1.8版本的,自动配置类117,最后过滤为22
加载的时候,是先加载自动配置类,再获取主程序main所在的包;
1.5.9的
采用的java逐注解,替代了配置文件;
4.@SpringBootApplication中
@SpringBootConfiguration
@Configuration(spring的注解)
@Component组件
@EnableAutoConfiguration 通过这个注解名就可以直接找到spring.factories中的EnableAutoConfiguration对应的组件
@AutoConfigurationPackage
@Import(AutoConfigurationPackages.Registrar.class)
Registrar作用:将主配置类@SpringBootApplication修饰的类所在的包及子包里面的所有组件扫描到spring容器中;
所以需要将controller或者service类要放在主配置类所在包内才能生效;
@Import({EnableAutoConfigurationImportSelector.class})
作用:获取当前环境需要哪些自动组件,全部把这些自动配置类导入容器中;一开始是获取所有的96个自动配置类全名,最后过滤为20个(当前应用场景)
具体是在META-INF/spring.factories这个配置文件下;EnableAutoConfiguration/中
位置:
(AutoConfigurationImportSelector.selectImports().中的getCandidateConfigurations())
对应spring-boot-autoconfigure-1.5.9.RELEASE.jar;
import:spring注解,给容器导入组件
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
该注解标识的类, 会被 Spring 自动扫描并且装入bean容器
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
传入的是EnableAutoConfiguration.class;
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
ArrayList result = new ArrayList();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
} catch (IOException var8) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
}
}
2.0以后的
2.1.9最新
在boot2.0之前.获取配置文件中的自动配置类是没有缓存的,在2.0加入了缓存
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
二.日志框架
spring默认的是jcl+log4j
而spring boot是slf4+logback,
在spring 5的时候.jcl不在放在核心包中,而是独立了出来,并且默认排除了log4j;
这样在spring boot2.0引入日志包的时候,引用是不一样的;
三.静态资源访问
资源管理:
1)、所有 /webjars/** ,都去 classpath:/META-INF/resources/webjars/ 找资源;
http://localhost:8080/webjars/jquery/3.4.1/jquery.js
2)、"/**" 访问当前项目的任何资源,都去(静态资源的文件夹)找映射
“classpath:/META‐INF/resources/”,
“classpath:/resources/”,
“classpath:/static/”,
“classpath:/public/”
“/”:当前项目的根路径
相同的资源,访问优先级"classpath:/resources/", “classpath:/static/”, “classpath:/public/” }
在spring boot 1.5.x中,resources/static等目录下的静态资源可以直接访问,并且访问路径上不用带static;
* 如:
* http://localhost:8080/asserts/css/signin.css
* http://localhost:8080/webjars/bootstrap/4.3.1/css/bootstrap.css
*
* 在2.0中需要手动添加静态资源拦截路径
* SpringBoot2+中要排除静态资源路径, 因访问时默认会加/static,
*
* 解决方法1:因静态资源的默认目录为resources,static,public等目录
* 那么在写除外路径的时候,不需要带上这个目录前缀static,页面引用资源链接访问也不需要加这个目录static;
* @Override
public void addInterceptors(InterceptorRegistry registry) {
//springmvc需要手动处理
registry.addInterceptor(new loginHandleInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/","/index.html","/user/login")
.excludePathPatterns("/asserts/**","/webjars/**"); //静态资源在2.0需要是手动排除
//排除登陆页面的请求连接
}
*
* 解决方法2:
*自定义静态资源目录 配置spring.mvc.static-path-pattern=/static/**
* 这样就写除外路径的时候就可以直接写/static/**,但是资源链接访问的是需要加/static目录的
* @Override
public void addInterceptors(InterceptorRegistry registry) {
//springmvc需要手动处理
registry.addInterceptor(new loginHandleInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/","/index.html","/user/login")
.excludePathPatterns("/static/**"); //静态资源在2.0需要是手动排除
//排除登陆页面的请求连接
}
四.异常处理
package com.ysy.component;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;
import java.util.Map;
/*在1.5.9版本,org.springframework.boot.autoconfigure.web.DefaultErrorAttributes;中
* @Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,
boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>();
errorAttributes.put("timestamp", new Date());
addStatus(errorAttributes, requestAttributes);
addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
addPath(errorAttributes, requestAttributes);
return errorAttributes;
}
* */
//在2.0版本, org.springframework.boot.web.servlet.error.DefaultErrorAttributes;中
/*
* @Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<>();
errorAttributes.put("timestamp", new Date());
addStatus(errorAttributes, webRequest);
addErrorDetails(errorAttributes, webRequest, includeStackTrace);
addPath(errorAttributes, webRequest);
return errorAttributes;
}
* */
//给容器中加入我们自己定义的ErrorAttributes
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
/* //返回值的map就是页面和json能获取的所有字段
//1.5.9版本
@Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace);
map.put("company","atguigu");
//我们的异常处理器携带的数据
Map<String,Object> ext = (Map<String, Object>) requestAttributes.getAttribute("ext", 0);
map.put("ext",ext);
return map;
}*/
//2.1.8版本
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
map.put("company","atguigu");
//我们的异常处理器携带的数据
Map<String,Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", 0);
map.put("ext",ext);
return map;
}
}
五.内置容器如tomcat的配置修改
配置文件的定义没有变化,但是通知定制器修改发生了变化
//Spring Boot 1.x:
/* @Bean //一定要将这个定制器加入到容器中,对于所有容器生效
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
//定制嵌入式的Servlet容器相关的规则
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.setPort(8083);
}
};
}*/
//在2.x版本改为实现 WebServerFactoryCustomizer 接口的 customize 方法
@Bean
public WebServerFactoryCustomizer webServerFactoryCustomizer() {
return new WebServerFactoryCustomizer() {
@Override
public void customize(WebServerFactory factory) {
ConfigurableWebServerFactory serverFactory =
(ConfigurableWebServerFactory)factory;
//配置优先级高于配置文件
serverFactory.setPort(8081);
}
};
}
//优化写法
@Bean
public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer(){
//WebServerFactoryCustomizer<ConfigurableWebServerFactory> factory = f -> f.setPort(8085);
//return factory;
//最简写法 配置优先级高于配置文件
return f -> f.setPort(8085);
};
再看看定制器
1.5.9
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties
implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered {
/**
* Server HTTP port.
*/
private Integer port;
在2.0.x 去掉了EmbeddedServletContainerCustomizer,取而代之的是WebServerFactoryCustomizer(函数式接口)
@FunctionalInterface
public interface WebServerFactoryCustomizer<T extends WebServerFactory> {
/**
* Customize the specified {@link WebServerFactory}.
* @param factory the web server factory to customize
*/
void customize(T factory);
}
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
/**
* Server HTTP port.
*/
private Integer port;
再看看2个版本的目录结构也不一样:
1.5.9的定制器EmbeddedServletContainerCustomizer目录
这里比如具体的tomcat配置:
基于EmbeddedServletContainerFactory接口
TomcatEmbeddedServletContainerFactory
自动配置类:EmbeddedServletContainerAutoConfiguration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
public class EmbeddedServletContainerAutoConfiguration {
/**
* Nested configuration if Tomcat is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
2.1.8的
定制器WebServerFactoryCustomizer的目录
这里比如具体的tomcat配置:,基于WebServerFactory接口
TomcatServletWebServerFactory
自动配置类应该是:ServletWebServerFactoryAutoConfiguration
@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
@Bean
public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {
return new ServletWebServerFactoryCustomizer(serverProperties);
}
六.默认的连接池发生了变化
性能方面 HikariCP>Druid>tomcat-jdbc>dbcp>c3p0
1.5.9的是tomcat的,2.0.x的是HikariCP
我这里使用的oracle 12c,注意
1.5.9版本的跟2.0.x之后的驱动名不一样:
spring:
datasource:
username: xiaoming
password: 123456
url: jdbc:oracle:thin:@localhost:1521:orcltest
2.0.x的是 driver-class-name: oracle.jdbc.OracleDriver
#1.5.9的是 driver-class-name: oracle.jdbc.driver.OracleDriver
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc8</artifactId>
<version>12.2.0.1</version>
</dependency>
测试代码
@RunWith(SpringRunner.class)
@SpringBootTest
public class Springboot159ApplicationTests {
Logger log= LoggerFactory.getLogger(getClass());
@Autowired
DataSource dataSource;
@Test
public void contextLoads() throws SQLException {
System.out.println(dataSource.getClass());
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
}
展示:
1.5.9
2019-10-05 15:16:28.664 INFO 4536 --- [ main] com.ysy.Springboot159ApplicationTests : info
class org.apache.tomcat.jdbc.pool.DataSource
ProxyConnection[PooledConnection[oracle.jdbc.driver.T4CConnection@4364863]]
2.1.9的
HikariProxyConnection@1139915666 wrapping oracle.jdbc.driver.T4CConnection@3727f0ee
1.5.9的依赖
2.1.9的依赖
七,启动配置原理
关键的几个类
ApplicationContextInitializer
SpringApplicationRunListener
ApplicationRunner
CommandLineRunner
1.5.9的
public static ConfigurableApplicationContext run(Object source, String... args) {
return run(new Object[] { source }, args);
}
然后调用SpringApplication.initialize方法
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
this.webEnvironment = deduceWebEnvironment();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
然后调用SpringApplication的
public ConfigurableApplicationContext run(String... args) {
2.1.9的,类似
SpringApplication下
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
SpringApplication下
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}