1、Boot骨架项目
厌倦了IDEA快速生成SpringBoot带来的各种无用文件?
Linux可以使用下面的方式,获取SpringBoot最简单的骨架项目(只包含一个pom.xml),也可以使用 浏览器,Postman 等工具
curl -G https://start.spring.io/pom.xml -d dependencies=web,mysql,mybatis -o pom.xml
再使用如下命令,使用IDEA直接打开
2、Boot War项目
- 创建模块,区别在于打包方式选择 war
- 接下来勾选 Spring Web 支持
编写控制器
@Controller public class MyController { @RequestMapping("/hello") public String abc() { System.out.println("进入了控制器"); return "hello"; } }
编写 jsp 视图,新建 webapp 目录和一个 hello.jsp 文件,注意文件名与控制器方法返回的视图逻辑名一致
src |- main |- java |- resources |- webapp |- hello.jsp
配置视图路径,打开 application.properties 文件
spring.mvc.view.prefix=/ spring.mvc.view.suffix=.jsp
测试:
如果用 mvn 插件 mvn spring-boot:run
或 main 方法测试
必须添加如下依赖,因为此时用的还是内嵌 tomcat,而内嵌 tomcat 默认不带 jasper(用来解析 jsp)
<dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <scope>provided</scope> </dependency>
也可以使用 Idea 配置 tomcat 来测试,此时用的是外置 tomcat
- 注意:骨架生成的代码中,多了一个 ServletInitializer,它的作用就是配置外置 Tomcat 使用的,在外置 Tomcat 启动后,去调用它创建和运行 SpringApplication
3、Boot 启动过程
分为两个阶段,一个是执行 SpringApplication构造方法,一个是执行run 方法
阶段一:SpringApplication 构造方法
记录 BeanDefinition 源
@Configuration public class A39_1 { public static void main(String[] args) { System.out.println("1. 演示获取 Bean Definition 源"); SpringApplication spring = new SpringApplication(A39_1.class); // 添加来源 spring.setSources(Collections.singleton("classpath:b01.xml")); // 创建和初始化spring容器 ConfigurableApplicationContext context = spring.run(args); for (String name : context.getBeanDefinitionNames()) { System.out.println("name: " + name + " 来源:" + context.getBeanFactory().getBeanDefinition(name).getResourceDescription()); } context.close(); } static class Bean1 { } static class Bean2 { } static class Bean3 { } @Bean public Bean2 bean2() { return new Bean2(); } @Bean public TomcatServletWebServerFactory servletWebServerFactory() { return new TomcatServletWebServerFactory(); } }
输出
``java . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _
| \ \ \ \ \/ )| |)| | | | | || (| | ) ) ) ) ‘ |_| .|| ||| |\, | / / / / =========||==============|__/=///_/ :: Spring Boot :: (v2.5.4)
2022-05-27 02:08:33.109 INFO 24196 —- [ main] com.example.test39.A39_1 : Starting A39_1 using Java 1.8.0_281 on wumengxiaodeMBP with PID 24196 (/Users/shaw/java/spring_boot_test/target/classes started by shaw in /Users/shaw/java/spring_boot_test) 2022-05-27 02:08:33.112 INFO 24196 —- [ main] com.example.test39.A39_1 : No active profile set, falling back to default profiles: default 2022-05-27 02:08:33.476 INFO 24196 —- [ main] com.example.test39.A39_1 : Started A39_1 in 0.812 seconds (JVM running for 1.16) name: org.springframework.context.annotation.internalConfigurationAnnotationProcessor 来源:null name: org.springframework.context.annotation.internalAutowiredAnnotationProcessor 来源:null name: org.springframework.context.annotation.internalCommonAnnotationProcessor 来源:null name: org.springframework.context.event.internalEventListenerProcessor 来源:null name: org.springframework.context.event.internalEventListenerFactory 来源:null name: a39_1 来源:null name: bean1 来源:class path resource [b01.xml] name: org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory 来源:null name: bean2 来源:com.example.test39.A39_1
2. 推断应用类型
```java
@Configuration
public class A39_1 {
public static void main(String[] args) throws Exception {
Method deduceFromClasspath = WebApplicationType.class.getDeclaredMethod("deduceFromClasspath");
deduceFromClasspath.setAccessible(true);
System.out.println("应用类型为:" + deduceFromClasspath.invoke(null));
}
}
输出
应用类型为:SERVLET
记录 ApplicationContext 初始化器 ```java @Configuration public class A39_1 {
public static void main(String[] args) throws Exception {
SpringApplication spring = new SpringApplication(A39_1.class); System.out.println("3. 演示 ApplicationContext 初始化器"); spring.addInitializers(configurableApplicationContext -> { if (configurableApplicationContext instanceof GenericApplicationContext) { GenericApplicationContext gac = (GenericApplicationContext) configurableApplicationContext; gac.registerBean("bean3", Bean3.class); } }); // 创建和初始化spring容器 ConfigurableApplicationContext context = spring.run(args);
for (String name : context.getBeanDefinitionNames()) {
System.out.println("name: " + name + " 来源:" + context.getBeanFactory().getBeanDefinition(name).getResourceDescription());
}
context.close();
}
static class Bean1 {
}
static class Bean2 {
}
static class Bean3 {
}
@Bean
public Bean2 bean2() {
return new Bean2();
}
@Bean
public TomcatServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
}
输出
```java
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.5.4)
2022-05-27 02:40:55.061 INFO 24459 --- [ main] com.example.test39.A39_1 : Starting A39_1 using Java 1.8.0_281 on wumengxiaodeMBP with PID 24459 (/Users/shaw/java/spring_boot_test/target/classes started by shaw in /Users/shaw/java/spring_boot_test)
2022-05-27 02:40:55.064 INFO 24459 --- [ main] com.example.test39.A39_1 : No active profile set, falling back to default profiles: default
2022-05-27 02:40:55.480 INFO 24459 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2022-05-27 02:40:55.493 INFO 24459 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2022-05-27 02:40:55.493 INFO 24459 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.52]
2022-05-27 02:40:55.574 INFO 24459 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2022-05-27 02:40:55.574 INFO 24459 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 484 ms
2022-05-27 02:40:55.645 INFO 24459 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2022-05-27 02:40:55.653 INFO 24459 --- [ main] com.example.test39.A39_1 : Started A39_1 in 1.062 seconds (JVM running for 1.567)
name: org.springframework.context.annotation.internalConfigurationAnnotationProcessor 来源:null
name: org.springframework.context.annotation.internalAutowiredAnnotationProcessor 来源:null
name: org.springframework.context.annotation.internalCommonAnnotationProcessor 来源:null
name: org.springframework.context.event.internalEventListenerProcessor 来源:null
name: org.springframework.context.event.internalEventListenerFactory 来源:null
name: bean3 来源:null
name: a39_1 来源:null
name: org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory 来源:null
name: bean2 来源:com.example.test39.A39_1
name: servletWebServerFactory 来源:com.example.test39.A39_1
记录监听器 ```java @Configuration public class A39_1 {
public static void main(String[] args) throws Exception {
SpringApplication spring = new SpringApplication(A39_1.class); System.out.println("4. 演示监听器与事件"); spring.addListeners(event -> System.out.println("\t事件为:" + event.getClass()));
// 创建和初始化spring容器
ConfigurableApplicationContext context = spring.run(args);
context.close();
}
输出
```java
4. 演示监听器与事件
事件为:class org.springframework.boot.context.event.ApplicationStartingEvent
事件为:class org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.5.4)
事件为:class org.springframework.boot.context.event.ApplicationContextInitializedEvent
2022-05-27 02:44:36.823 INFO 24479 --- [ main] com.example.test39.A39_1 : Starting A39_1 using Java 1.8.0_281 on wumengxiaodeMBP with PID 24479 (/Users/shaw/java/spring_boot_test/target/classes started by shaw in /Users/shaw/java/spring_boot_test)
2022-05-27 02:44:36.826 INFO 24479 --- [ main] com.example.test39.A39_1 : No active profile set, falling back to default profiles: default
事件为:class org.springframework.boot.context.event.ApplicationPreparedEvent
2022-05-27 02:44:37.225 INFO 24479 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2022-05-27 02:44:37.237 INFO 24479 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2022-05-27 02:44:37.237 INFO 24479 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.52]
2022-05-27 02:44:37.312 INFO 24479 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2022-05-27 02:44:37.312 INFO 24479 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 460 ms
2022-05-27 02:44:37.369 INFO 24479 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
事件为:class org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent
事件为:class org.springframework.context.event.ContextRefreshedEvent
2022-05-27 02:44:37.375 INFO 24479 --- [ main] com.example.test39.A39_1 : Started A39_1 in 0.989 seconds (JVM running for 1.343)
事件为:class org.springframework.boot.context.event.ApplicationStartedEvent
事件为:class org.springframework.boot.availability.AvailabilityChangeEvent
事件为:class org.springframework.boot.context.event.ApplicationReadyEvent
事件为:class org.springframework.boot.availability.AvailabilityChangeEvent
事件为:class org.springframework.boot.availability.AvailabilityChangeEvent
事件为:class org.springframework.context.event.ContextClosedEvent
推断主启动类
@Configuration public class A39_1 { public static void main(String[] args) throws Exception { SpringApplication spring = new SpringApplication(A39_1.class); System.out.println("5. 演示主类推断"); Method deduceMainApplicationClass = SpringApplication.class.getDeclaredMethod("deduceMainApplicationClass"); deduceMainApplicationClass.setAccessible(true); System.out.println("主类是:"+deduceMainApplicationClass.invoke(spring)); // 创建和初始化spring容器 ConfigurableApplicationContext context = spring.run(args); context.close(); } }
输出 ```java
- 演示主类推断 主类是:class com.example.test39.A39_1 ```
总结:
SpringApplication 构造方法中所做的操作
1. 可以有多种源用来加载 bean 定义
2. 应用类型推断
3. 容器初始化器
4. 演示启动各阶段事件
5. 演示主类推断
阶段二:执行 run 方法
- run方法首先要获得
SpringApplicationRunListeners
(名字取得不好,实际是事件发布器 ),用来发布事件SpringApplicationRunListener
是个接口,具体实现类维护在了spring.factory文件中,可以通过SpringFactoriesLoader.loadFactoryNames
来读取这个文件中的内容
获得了
SpringApplicationRunListener
事件发布器后,通过application starting
发布启动事件 ```java public class A39_2 {public static void main(String[] args) throws Exception { // 添加 app 监听器 SpringApplication app = new SpringApplication(); app.addListeners(e -> System.out.println(e.getClass()));
// 获取事件发送器实现类名 List
loadFactoryNames = SpringFactoriesLoader.loadFactoryNames(SpringApplicationRunListener.class, A39_2.class.getClassLoader()); for (String name : loadFactoryNames) { System.out.println(name); // 拿到发布器的全路径后,通过反射获取 Class<?> clazz = Class.forName(name); // 通过两个参数的构造器初始化 Constructor<?> constructor = clazz.getConstructor(SpringApplication.class, String[].class); SpringApplicationRunListener publisher = (SpringApplicationRunListener) constructor.newInstance(app, args); DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext(); // 发布事件 publisher.starting(bootstrapContext);
} }
}
2. 封装启动 args
```java
public class A39_3 {
@SuppressWarnings("all")
public static void main(String[] args) throws Exception {
SpringApplication app = new SpringApplication();
app.addInitializers(new ApplicationContextInitializer<ConfigurableApplicationContext>() {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("执行初始化器增强...");
}
});
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 2. 封装启动 args");
DefaultApplicationArguments arguments = new DefaultApplicationArguments(args);
}
}
准备 Environment 添加命令行参数(*)
public class Step3 { public static void main(String[] args) { AbstractEnvironment environment = new ApplicationEnvironment(); // 系统环境变量,将多个来源的配置信息整合到一起,比如properties,yml // 添加命令行来源,在spring中,命令行来源的优先级默认是最高的 environment.getPropertySources().addFirst(new SimpleCommandLinePropertySource(args)); // 添加配置文件来源,在spring中,配置文件来源优先级是最低的 environment.getPropertySources().addLast(new ResourcePropertySource(new ClassPathResource("application.properties"))); // 获取env中包含的来源信息 for (PropertySource<?> propertySource : environment.getPropertySources()) { System.out.println(propertySource.getName()); } System.out.println(environment.getProperty("server.port")); } }
添加命令行参数
添加配置文件参数
输出 ```java commandLineArgs systemProperties systemEnvironment class path resource [application.yml] 02:26:53.659 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Found key ‘server.port’ in PropertySource ‘commandLineArgs’ with value of type String 8070
Process finished with exit code 0
- 输出的来源中,如果存在相同的配置信息,取值顺序按照从上到下的优先级
4. ConfigurationPropertySources 处理(*)
- 发布 application environment 已准备事件
```java
public class Step4 {
public static void main(String[] args) throws IOException {
AbstractEnvironment env = new StandardServletEnvironment();
env.getPropertySources().addLast(new ResourcePropertySource(new ClassPathResource("step4.properties")));
// ConfigurationPropertySource.attch();
System.out.println(env.getProperty("user.first-name"));
System.out.println(env.getProperty("user.middle-name"));
System.out.println(env.getProperty("user.last-name"));
}
}
输出
02:36:54.741 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Found key 'user.first-name' in PropertySource 'class path resource [step4.properties]' with value of type String
George
null
null
通过添加ConfigurationPropertySource.attch();
该方法会添加一个额外的来源,用来处理配置文件参数名格式问题。
通过 EnvironmentPostProcessorApplicationListener 进行 env 后处理(*)
- application.properties,由 StandardConfigDataLocationResolver 解析
spring.application.json ```java public class Step5 { public static void main(String[] args) { SpringApplication app = new SpringApplication(); AbstractEnvironment env = new StandardServletEnvironment();
System.out.println(“>>>>>>>>>>>>>>>>增强前”); for (PropertySource<?> propertySource : env.getPropertySources()) {
System.out.println(propertySource.getName());
} EnvironmentPostProcessor processor = new ConfigDataEnvironmentPostProcessor(new DeferredLogs(), new DefaultBootstrapContext()); EnvironmentPostProcessor processor1 = new RandomValuePropertySourceEnvironmentPostProcessor(new DeferredLog()); processor.postProcessEnvironment(env, app); processor1.postProcessEnvironment(env, app); System.out.println(“>>>>>>>>>>>>>>>>增强后”); for (PropertySource<?> propertySource : env.getPropertySources()) {
System.out.println(propertySource.getName());
} System.out.println(env.getProperty(“server.port”));
System.out.println(env.getProperty(“random.int”)); }
}
输出
```java
>>>>>>>>>>>>>>>>增强前
systemProperties
systemEnvironment
>>>>>>>>>>>>>>>>增强后
systemProperties
systemEnvironment
random
Config resource 'class path resource [application.properties]' via location 'optional:classpath:/'
Config resource 'class path resource [application.yml]' via location 'optional:classpath:/'
03:14:47.843 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Found key 'server.port' in PropertySource 'Config resource 'class path resource [application.properties]' via location 'optional:classpath:/'' with value of type String
8090
03:14:47.843 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Found key 'random.int' in PropertySource 'random' with value of type Integer
195222868
- spring通过
EnvironmentPostProcessor
环境后置处理器对env进行增强,进一步补充source,application.properties
就是这一步处理的 - spring将所有的后置处理器也定义到了spring.factories文件中
上述 环境后置处理器的创建 是由EnvironmentPostProcessorApplicationListener事件监听器完成的
public class Step5 { public static void main(String[] args) { SpringApplication app = new SpringApplication(); AbstractEnvironment env = new StandardServletEnvironment(); // 注册EnvironmentPostProcessorApplicationListener事件监听器 app.addListeners(new EnvironmentPostProcessorApplicationListener()); // 通过发送一个 环境准备完成事件,触发 EnvironmentPostProcessorApplicationListener事件监听器创建动作 EventPublishingRunListener publish = new EventPublishingRunListener(app, args); publish.environmentPrepared(new DefaultBootstrapContext(), env); }
绑定 spring.main 到 SpringApplication 对象(*)
- 先讲解一下@ConfigurationProperties的原理 ```java public class Step6 {
public static void main(String[] args) throws IOException {
SpringApplication app = new SpringApplication(); AbstractEnvironment env = new StandardServletEnvironment(); env.getPropertySources().addLast(new ResourcePropertySource(new ClassPathResource("step4.properties"))); User user = Binder.get(env).bind("user", User.class).get(); System.out.println(user);
}
static class User {
private String firstName;
private String middleName;
private String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getMiddleName() {
return middleName;
}
public void setMiddleName(String middleName) {
this.middleName = middleName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
@Override
public String toString() {
return "User{" +
"firstName='" + firstName + '\'' +
", middleName='" + middleName + '\'' +
", lastName='" + lastName + '\'' +
'}';
}
}
}
输出
```java
User{firstName='George', middleName='Walker', lastName='Bush'}
- 通过
Binder.get(env).bind("user", User.class)
可以将环境中的配置信息和Bean的属性进行绑定 - 在配置文件维护springApplication的属性,可以将值绑定到springApplication对象中 ```java public static void main(String[] args) throws IOException { SpringApplication app = new SpringApplication(); AbstractEnvironment env = new StandardServletEnvironment(); env.getPropertySources().addLast(new ResourcePropertySource(new ClassPathResource(“step6.properties”))); Binder.get(env).bind(“spring.main”, Bindable.ofInstance(app)); System.out.println(app); }
// 配置文件 spring.main.banner-mode=off spring.main.lazy-initialization=true
通过debug查看属性<br />
7. 打印 banner(*)

8. 创建容器
- 根据构造方法中推断的应用类型,创建相应的容器
```java
public class A39_3 {
@SuppressWarnings("all")
public static void main(String[] args) throws Exception {
SpringApplication app = new SpringApplication();
app.addInitializers(new ApplicationContextInitializer<ConfigurableApplicationContext>() {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("执行初始化器增强...");
}
});
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 8. 创建容器");
GenericApplicationContext context = createApplicationContext(WebApplicationType.SERVLET);
}
}
private static GenericApplicationContext createApplicationContext(WebApplicationType type) {
GenericApplicationContext context = null;
switch (type) {
case SERVLET:
return new AnnotationConfigServletWebServerApplicationContext();
case REACTIVE:
return new AnnotationConfigReactiveWebServerApplicationContext();
case NONE:
return new AnnotationConfigApplicationContext();
}
return context;
}
准备容器
遍历之前添加的容器初始化器,回调初始化器的初始化方法,发布 application context 已初始化事件
public class A39_3 { @SuppressWarnings("all") public static void main(String[] args) throws Exception { SpringApplication app = new SpringApplication(); app.addInitializers(new ApplicationContextInitializer<ConfigurableApplicationContext>() { @Override public void initialize(ConfigurableApplicationContext applicationContext) { System.out.println("执行初始化器增强..."); } }); System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 8. 创建容器"); GenericApplicationContext context = createApplicationContext(WebApplicationType.SERVLET); System.out.println(">>>>>>>>>>>>>>>>>>>>>>>> 9. 准备容器"); for (ApplicationContextInitializer initializer : app.getInitializers()) { initializer.initialize(context); } } }
加载 bean 定义
从各种来源加载各种Bean定义,发布 application prepared 事件 ```java public class A39_3 { @SuppressWarnings(“all”) public static void main(String[] args) throws Exception { SpringApplication app = new SpringApplication(); app.addInitializers(new ApplicationContextInitializer
() { @Override public void initialize(ConfigurableApplicationContext applicationContext) { System.out.println("执行初始化器增强..."); }
});
System.out.println(“>>>>>>>>>>>>>>>>>>>>>>>> 2. 封装启动 args”); DefaultApplicationArguments arguments = new DefaultApplicationArguments(args);
System.out.println(“>>>>>>>>>>>>>>>>>>>>>>>> 8. 创建容器”); GenericApplicationContext context = createApplicationContext(WebApplicationType.SERVLET);
System.out.println(“>>>>>>>>>>>>>>>>>>>>>>>> 9. 准备容器”); for (ApplicationContextInitializer initializer : app.getInitializers()) {
initializer.initialize(context);
}
System.out.println(“>>>>>>>>>>>>>>>>>>>>>>>> 10. 加载 bean 定义”);
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory(); // 读取配置类来源的Bean定义 AnnotatedBeanDefinitionReader reader1 = new AnnotatedBeanDefinitionReader(beanFactory); // 读取xml来源的Bean定义 XmlBeanDefinitionReader reader2 = new XmlBeanDefinitionReader(beanFactory); // 扫描指定包下的Bean定义 ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanFactory);
reader1.register(Config.class); reader2.loadBeanDefinitions(new ClassPathResource(“b03.xml”)); scanner.scan(“com.itheima.a39.sub”);
for (String name : context.getBeanDefinitionNames()) {
System.out.println("name:" + name + " 来源:" + beanFactory.getBeanDefinition(name).getResourceDescription());
} } }
@Configuration static class Config { @Bean public Bean5 bean5() { return new Bean5(); }
@Bean
public ServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
@Bean
public CommandLineRunner commandLineRunner() {
return new CommandLineRunner() {
@Override
public void run(String... args) throws Exception {
System.out.println("commandLineRunner()..." + Arrays.toString(args));
}
};
}
}
11. refresh 容器
- 发布 application started 事件
```java
context.refresh();
执行 runner
- 发布 application ready 事件
这其中有异常,发布 application failed 事件 ```java public class A39_3 { @SuppressWarnings(“all”) public static void main(String[] args) throws Exception { SpringApplication app = new SpringApplication(); app.addInitializers(new ApplicationContextInitializer
() { @Override public void initialize(ConfigurableApplicationContext applicationContext) { System.out.println("执行初始化器增强..."); }
});
System.out.println(“>>>>>>>>>>>>>>>>>>>>>>>> 2. 封装启动 args”); DefaultApplicationArguments arguments = new DefaultApplicationArguments(args);
System.out.println(“>>>>>>>>>>>>>>>>>>>>>>>> 8. 创建容器”); GenericApplicationContext context = createApplicationContext(WebApplicationType.SERVLET);
System.out.println(“>>>>>>>>>>>>>>>>>>>>>>>> 9. 准备容器”); for (ApplicationContextInitializer initializer : app.getInitializers()) {
initializer.initialize(context);
}
System.out.println(“>>>>>>>>>>>>>>>>>>>>>>>> 10. 加载 bean 定义”); DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory(); AnnotatedBeanDefinitionReader reader1 = new AnnotatedBeanDefinitionReader(beanFactory); XmlBeanDefinitionReader reader2 = new XmlBeanDefinitionReader(beanFactory); ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanFactory);
reader1.register(Config.class); reader2.loadBeanDefinitions(new ClassPathResource(“b03.xml”)); scanner.scan(“com.itheima.a39.sub”);
System.out.println(“>>>>>>>>>>>>>>>>>>>>>>>> 11. refresh 容器”); context.refresh();
for (String name : context.getBeanDefinitionNames()) {
System.out.println("name:" + name + " 来源:" + beanFactory.getBeanDefinition(name).getResourceDescription());
}
System.out.println(“>>>>>>>>>>>>>>>>>>>>>>>> 12. 执行 runner”); for (CommandLineRunner runner : context.getBeansOfType(CommandLineRunner.class).values()) {
runner.run(args);
}
for (ApplicationRunner runner : context.getBeansOfType(ApplicationRunner.class).values()) {
runner.run(arguments);
}
} }
@Configuration static class Config { @Bean public Bean5 bean5() { return new Bean5(); }
@Bean
public ServletWebServerFactory servletWebServerFactory() {
return new TomcatServletWebServerFactory();
}
@Bean
public CommandLineRunner commandLineRunner() {
return new CommandLineRunner() {
@Override
public void run(String... args) throws Exception {
System.out.println("commandLineRunner()..." + Arrays.toString(args));
}
};
}
@Bean
public ApplicationRunner applicationRunner() {
return new ApplicationRunner() {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("applicationRunner()..." + Arrays.toString(args.getSourceArgs()));
System.out.println(args.getOptionNames());
System.out.println(args.getOptionValues("server.port"));
System.out.println(args.getNonOptionArgs());
}
};
}
}
添加运行参数<br /><br />输出
```java
>>>>>>>>>>>>>>>>>>>>>>>> 12. 执行 runner
commandLineRunner()...[--server.port=8080, debug]
applicationRunner()...[--server.port=8080, debug]
[server.port]
[8080]
[debug]
- runner运行在容器启动完毕之后,可以在runner中添加自己的逻辑
CommandLineRunner
的参数一般是main方法的参数ApplicationRunner
的参数需要经过封装,详情看第二步
4、Tomcat 内嵌容器
5、Boot 自动配置
演示 - 自动配置类原理
- 假设已有第三方的两个自动配置类
```java
// 第三方的配置类1
@Configuration
static class AutoConfiguration1 {
@Bean
public Bean1 bean1() {
} }return new Bean1("第三方");
// 第三方的配置类2 @Configuration static class AutoConfiguration2 { @Bean public Bean2 bean2() { return new Bean2(); } }
static class Bean1 { private String name;
public Bean1(String name) {
this.name = name;
}
@Override
public String toString() {
return "Bean1{" +
"name='" + name + '\'' +
'}';
}
}
static class Bean2 {
}
- 本项目想要引入第三方的配置类,通过@Import导入第三方的配置类
```java
// 本项目配置类
@Configuration
@Import({AutoConfiguration1.class, AutoConfiguration2.class})
static class Config {
}
通过
ConfigurationClassPostProcessor
Bean工厂后置处理器,解析@Import注解 ```java public class A41_1 {public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext(); context.registerBean(Config.class); // 解析@Import,需要导入Bean工厂后处理器 context.registerBean(ConfigurationClassPostProcessor.class); context.refresh(); for (String name : context.getBeanDefinitionNames()) { System.out.println(name); }
}
// 本项目配置类 @Configuration @Import({AutoConfiguration1.class, AutoConfiguration2.class}) static class Config {
}
// 第三方的配置类1 @Configuration static class AutoConfiguration1 {
@Bean public Bean1 bean1() { return new Bean1("第三方"); }
}
// 第三方的配置类2
@Configuration
static class AutoConfiguration2 {
@Bean
public Bean2 bean2() {
return new Bean2();
}
}
static class Bean1 {
private String name;
public Bean1(String name) {
this.name = name;
}
@Override
public String toString() {
return "Bean1{" +
"name='" + name + '\'' +
'}';
}
}
static class Bean2 {
}
}
输出
```java
com.example.test39.A41_1$Config
org.springframework.context.annotation.ConfigurationClassPostProcessor
com.example.test39.A41_1$AutoConfiguration1
bean1
com.example.test39.A41_1$AutoConfiguration2
bean
- 实现
@Import({AutoConfiguration1.class, AutoConfiguration2.class})
中导入的类,通过配置文件的方式引入 ```java // 本项目配置类 @Configuration @Import(MyImportSelector.class) static class Config {
}
// 继承导入选择器,实现查询导入类的方法 static class MyImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { // 加载spring.factories文件内容 return SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, MyImportSelector.class.getClassLoader()).toArray(new String[0]); } }
- 提供一个配置文件 META-INF/spring.factories,key 为导入器类名,值为多个自动配置类名,用逗号分隔
```xml
com.example.test39.A41_1$MyImportSelector=\
com.example.test39.A41_1.AutoConfiguration1,\
com.example.test39.A41_1.AutoConfiguration2
- 注意,
SpringFactoriesLoader
不仅会找当前项目中的spring.factories文件,还会找项目关联的jar中的spring.factories文件
- 在本项目中添加一个和第三方bean名字相同的Bean,观察最终获取到的Bean
```java
// 本项目配置类
@Configuration
@Import(MyImportSelector.class)
static class Config {
@Bean
public Bean1 bean1() {
} }return new Bean1("本项目");
static class MyImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { return SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, MyImportSelector.class.getClassLoader()).toArray(new String[0]); } }
// 第三方的配置类1 @Configuration static class AutoConfiguration1 { @Bean public Bean1 bean1() { return new Bean1(“第三方”); } }
// 第三方的配置类2 @Configuration static class AutoConfiguration2 { @Bean public Bean2 bean2() { return new Bean2(); } }
public static void main(String[] args) { GenericApplicationContext context = new GenericApplicationContext(); context.registerBean(Config.class); // 解析@Import,需要导入Bean工厂后处理器 context.registerBean(ConfigurationClassPostProcessor.class);
context.refresh();
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>");
System.out.println(context.getBean("bean1"));
}
- 输出
```java
com.example.test39.A41_1$Config
org.springframework.context.annotation.ConfigurationClassPostProcessor
com.example.test39.A41_1$AutoConfiguration1
bean1
com.example.test39.A41_1$AutoConfiguration2
bean2
>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Bean1{name='本项目'}
- spring会先解析@Import注解内的Bean,再解析配置类中的Bean
- Bean工厂中后注册的Bean,会同名覆盖先注册的
通过
context.setAllowBeanDefinitionOverriding(false);
设置不允许覆盖,则会报错,springboot中就是设置的不允许覆盖Exception in thread "main" org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'bean1' defined in com.example.test39.A41_1$Config: Cannot register bean definition [Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=com.example.test39.A41_1$Config; factoryMethodName=bean1; initMethodName=null; destroyMethodName=(inferred); defined in com.example.test39.A41_1$Config] for bean 'bean1': There is already [Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=com.example.test39.A41_1$AutoConfiguration1; factoryMethodName=bean1; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [com/example/test39/A41_1$AutoConfiguration1.class]] bound.
在日常开发中,本项目的优先级应该会高于第三方。可以使用
DeferredImportSelector
接口,调整解析的顺序,deferred即延迟导入static class MyImportSelector implements DeferredImportSelector { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { return SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, MyImportSelector.class.getClassLoader()).toArray(new String[0]); } }
当本项目中没有导入时,才使用第三方的导入。可以使用@ConditionalOnMissingBean ,当Bean不存在时才加载
// 第三方的配置类1 @Configuration static class AutoConfiguration1 { @Bean @ConditionalOnMissingBean public Bean1 bean1() { return new Bean1("第三方"); } }
总结:
- 自动配置类本质上就是一个配置类而已,只是用 META-INF/spring.factories 管理,与应用配置类解耦
- @Enable 打头的注解本质是利用了 @Import
- @Import 配合 DeferredImportSelector 即可实现导入,selectImports 方法的返回值即为要导入的配置类名
- DeferredImportSelector 的导入会在最后执行,为的是让其它配置优先解析
下面介绍一些典型的自动配置实现:
1、AopAutoConfiguration
新建测试类,导入自动配置类相关的Bean
public class TestAopAuto { public static void main(String[] args) { GenericApplicationContext context = new GenericApplicationContext(); // 该工具类可以注册一些常用的=后置处理器 AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory()); context.registerBean(Config.class); context.refresh(); for (String name : context.getBeanDefinitionNames()) { System.out.println(name); } } @Configuration @Import(MyImportSelector.class) static class Config { } static class MyImportSelector implements DeferredImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { // 导入自动配置类相关的Bean return new String[]{AopAutoConfiguration.class.getName()}; } } }
输出
org.springframework.context.annotation.internalConfigurationAnnotationProcessor org.springframework.context.annotation.internalAutowiredAnnotationProcessor org.springframework.context.annotation.internalCommonAnnotationProcessor org.springframework.context.event.internalEventListenerProcessor org.springframework.context.event.internalEventListenerFactory com.example.test39.TestAopAuto$Config // 下面这些是AopAutoConfiguration导入的Bean,internalAutoProxyCreator才是我们最终要的,剩下的三个其实是条件装配的中间过程 org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$AspectJAutoProxyingConfiguration$CglibAutoProxyConfiguration org.springframework.aop.config.internalAutoProxyCreator org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$AspectJAutoProxyingConfiguration org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
查看
AopAutoConfiguration
源码,查看如何加载的Bean
AopAutoConfiguration
内部使用了条件判断来决定如何加载Bean
- 如果设置
spring.aop.auto=false
,就不会加载 ```java public static void main(String[] args) { GenericApplicationContext context = new GenericApplicationContext(); StandardEnvironment env = new StandardEnvironment(); env.getPropertySources().addLast(new SimpleCommandLinePropertySource(“—spring.aop.auto=false”)); context.setEnvironment(env); // 该工具类可以注册一些常用的=后置处理器 AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory()); context.registerBean(Config.class); context.refresh(); for (String name : context.getBeanDefinitionNames()) {
}System.out.println(name);
}
输出
```java
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
com.example.test39.TestAopAuto$Config
- 判断加载的类
总结:
- AOP 自动配置类为
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
- 可以通过
spring.aop.auto=false
禁用 aop 自动配置 - AOP 自动配置的本质是通过
@EnableAspectJAutoProxy
来开启了自动代理,如果在引导类上自己添加了@EnableAspectJAutoProxy
那么以自己添加的为准 @EnableAspectJAutoProxy
的本质是向容器中添加了AnnotationAwareAspectJAutoProxyCreator
这个 bean 后处理器,它能够找到容器中所有切面,并为匹配切点的目标类创建代理,创建代理的工作一般是在 bean 的初始化阶段完成的
接下来的自动配置加载Bean的过程和上面类似,可以自己查看对应的规则:
public class TestDataSourceAuto {
@SuppressWarnings("all")
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
StandardEnvironment env = new StandardEnvironment();
env.getPropertySources().addLast(new SimpleCommandLinePropertySource(
"--spring.datasource.url=jdbc:mysql://localhost:3306/test",
"--spring.datasource.username=root",
"--spring.datasource.password=rootroot"
));
context.setEnvironment(env);
AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
context.registerBean(Config.class);
String packageName = TestDataSourceAuto.class.getPackage().getName();
System.out.println("当前包名:" + packageName);
AutoConfigurationPackages.register(context.getDefaultListableBeanFactory(),
packageName);
context.refresh();
for (String name : context.getBeanDefinitionNames()) {
String resourceDescription = context.getBeanDefinition(name).getResourceDescription();
if (resourceDescription != null)
System.out.println(name + " 来源:" + resourceDescription);
}
}
@Configuration
@Import(MyImportSelector.class)
static class Config {
}
static class MyImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{
DataSourceAutoConfiguration.class.getName(),
MybatisAutoConfiguration.class.getName(),
DataSourceTransactionManagerAutoConfiguration.class.getName(),
TransactionAutoConfiguration.class.getName()
};
}
}
}
2、DataSourceAutoConfiguration
- 对应的自动配置类为:org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
- 它内部采用了条件装配,通过检查容器的 bean,以及类路径下的 class,来决定该 @Bean 是否生效
简单说明一下,Spring Boot 支持两大类数据源:
- EmbeddedDatabase - 内嵌数据库连接池
- PooledDataSource - 非内嵌数据库连接池
PooledDataSource 又支持如下数据源:
- hikari 提供的 HikariDataSource
- tomcat-jdbc 提供的 DataSource
- dbcp2 提供的 BasicDataSource
- oracle 提供的 PoolDataSourceImpl
3、MybatisAutoConfiguration
- MyBatis 自动配置类为
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
- 它主要配置了两个 bean
- SqlSessionFactory - MyBatis 核心对象,用来创建 SqlSession
- SqlSessionTemplate - SqlSession 的实现,此实现会与当前线程绑定
- 用 ImportBeanDefinitionRegistrar 的方式扫描所有标注了 @Mapper 注解的接口
- 用 AutoConfigurationPackages 来确定扫描的包
- 还有一个相关的 bean:MybatisProperties,它会读取配置文件中带
mybatis.
前缀的配置项进行定制配置
@MapperScan 注解的作用与 MybatisAutoConfiguration 类似,会注册 MapperScannerConfigurer 有如下区别
- @MapperScan 扫描具体包(当然也可以配置关注哪个注解)
- @MapperScan 如果不指定扫描具体包,则会把引导类范围内,所有接口当做 Mapper 接口
- MybatisAutoConfiguration 关注的是所有标注 @Mapper 注解的接口,会忽略掉非 @Mapper 标注的接口
这里有同学有疑问,之前介绍的都是将具体类交给 Spring 管理,怎么到了 MyBatis 这儿,接口就可以被管理呢?
- 其实并非将接口交给 Spring 管理,而是每个接口会对应一个 MapperFactoryBean,是后者被 Spring 所管理,接口只是作为 MapperFactoryBean 的一个属性来配置
4、TransactionAutoConfiguration
- 事务自动配置类有两个:
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration
- 前者配置了 DataSourceTransactionManager 用来执行事务的提交、回滚操作
- 后者功能上对标 @EnableTransactionManagement,包含以下三个 bean
- BeanFactoryTransactionAttributeSourceAdvisor 事务切面类,包含通知和切点
- TransactionInterceptor 事务通知类,由它在目标方法调用前后加入事务操作
- AnnotationTransactionAttributeSource 会解析 @Transactional 及事务属性,也包含了切点功能
- 如果自己配置了 DataSourceTransactionManager 或是在引导类加了 @EnableTransactionManagement,则以自己配置的为准
5、MVC自动配置类
ServletWebServerFactoryAutoConfiguration
-
DispatcherServletAutoConfiguration
提供 DispatcherServlet
提供 DispatcherServletRegistrationBean
WebMvcAutoConfiguration
配置 DispatcherServlet 的各项组件,提供的 bean 见过的有
提供的 bean 有 BasicErrorController
MultipartAutoConfiguration
它提供了 org.springframework.web.multipart.support.StandardServletMultipartResolver
该 bean 用来解析 multipart/form-data 格式的数据
HttpEncodingAutoConfiguration
POST 请求参数如果有中文,无需特殊设置,这是因为 Spring Boot 已经配置了 org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter
- 对应配置 server.servlet.encoding.charset=UTF-8,默认就是 UTF-8
- 当然,它只影响非 json 格式的数据
6、自定义自动配置类
在演示-自动配置类原理的基础上,如何才能让springboot自动读取到我们自己的配置类呢?
- 其实很简单,springboot也是通过
@Import
和ImportSelector
到配置文件中读取要加载的Bean,只不过它读取的key和我们不同
所以,只要我们的key也配置成
EnableAutoConfiguration
,springboot就能加载我们的配置类了@SpringBootApplication public class App { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(App.class, args); for (String name : context.getBeanDefinitionNames()) { System.out.println(name); } } }
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.example.test39.A41_1.AutoConfiguration1,\ com.example.test39.A41_1.AutoConfiguration2
输出
6、条件装配底层
条件装配的底层是本质上是 @Conditional 与 Condition,这两个注解。引入自动配置类时,期望满足一定条件才能被 Spring 管理,不满足则不管理,怎么做呢?
比如条件是【类路径下必须有 dataSource】这个 bean ,怎么做呢?
首先编写条件判断类,它实现 Condition 接口,编写条件判断逻辑
static class MyCondition1 implements Condition { // 如果存在 Druid 依赖,条件成立 public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return ClassUtils.isPresent("com.alibaba.druid.pool.DruidDataSource", null); } }
其次,在要导入的自动配置类上添加
@Conditional(MyCondition1.class)
,将来此类被导入时就会做条件检查@Configuration // 第三方的配置类 @Conditional(MyCondition1.class) // 加入条件 static class AutoConfiguration1 { @Bean public Bean1 bean1() { return new Bean1(); } }