04SpringBoot 启动类配置及自动配置原理
上一章中我们提到,springboot通过父项目依赖和starter的场景启动器来管理和启动相关组件,以 spring-boot-starter-web 场景管理器为例,它能够为提供 Web 开发场景所需要的几乎所有依赖,因此在使用 Spring Boot 开发 Web 项目时,只需要引入该 Starter 即可,而不需要额外导入 Web 服务器和其他的 Web 依赖。但我们在启动springbo
目录
5.自动配置原理-注解 @EnableAutoConfiguration(重头戏)
5.1.1注解@AutoConfigurationPackage
5.1.2注解@Import({AutoConfigurationImportSelector.class})
6. @ImportResource导入spring的配置类
上一章中我们提到,springboot通过父项目依赖和starter的场景启动器来管理和启动相关组件,以 spring-boot-starter-web 场景管理器为例,它能够为提供 Web 开发场景所需要的几乎所有依赖,因此在使用 Spring Boot 开发 Web 项目时,只需要引入该 Starter 即可,而不需要额外导入 Web 服务器和其他的 Web 依赖。
但我们在启动springboot项目时候,springboot是如何按需加载所需要的自动配置配置项?引入哪些场景,这些场景的自动配置才会开始呢?下面以springboot启动类注解来说明。
1.SpringBoot启动类配置
默认情况下,Spring Boot 项目会创建一个名为 ***Application 的主程序启动类 ,该类中使用了 @SpringBootApplication来开启 Spring Boot 的自动配置,另外该启动类中包含一个 main() 方法,用来启动该项目。代码如下:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//1、返回IOC容器
ConfigurableApplicationContext run = SpringApplication.run(DemoApplication.class, args);
//2、查看容器中的组件
String[] names =run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
}
其中 @SpringBootApplication可以使用其核心的三个注解替换,但要指定xxxApplication所在的包路径,代码如下:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.example.demo")//注意,这里要指定DemoApplication所在的包
public class DemoApplication {
public static void main(String[] args) {
//1、返回IOC容器
ConfigurableApplicationContext run = SpringApplication.run(DemoApplication.class, args);
//2、查看容器中的组件
String[] names =run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
}
通过上述结果可以看到springboot为我们添加好了所有开发中常用的组件,包括自动配置SpringMvC的全套组件如dispatcherServlet、字符编码过滤器characterEncodingFilter、web常用场景组件如beanNameViewResolver等,如下:
自动配置SpringMvC
- 引入SpringMVC全套组件
- 自动配置好SpringMlVC常用组件(功能)
自动配好wWeb常见功能,如:字符编码问题
- SpringBoot帮我们配置好了所有web开发的常见场景
默认的包结构
- 主程序所在目录的包及所有子包里面的组件都会被默认扫描进来。无需以前的包扫描配置
- 要改变扫描路径使用@SpringBootApplication(scanBasePackages="xxx.xxx") 或者@ComponentScan("xxx.xxx")指定扫描路径
2.启动类注解@SpringBootApplication
上述中springboot为我们添加好了所有开发中常用的组件实现主要取决于@SpringBootApplication注解。@SpringBootApplication注解的作用介绍如下:
@SpringBootApplication 注解是一个组合注解,包含三个核心注解:
- @SpringBootConfiguration
- @EnableAutoConfiguration
- @ComponentScan
@SpringBootApplication 注解标注的类 表明 该类 是 springBoot的 主配置类;
@SpringBootApplication 注解标注的类 中的 main() 方法是 Spring Boo t应用的启动入口;
@SpringBootApplication 能够扫描 Spring组件 并 自动配置Spring Boot。
@SpringBootApplication 注解代码如下:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {...}
3.注解@CmponentScan(了解)
@CmponentScan标注用于指定要扫描的包,将哪些包下面的带有实例化注解的类进行扫描,生成组件,交给SpringIOC容器管理。参数是包路径。
如果用在主启动类中,包路径必须是主启动类所在的包,否则扫描不到主启动类,无法启动报错。
4.注解@SpringBootConfiguration
@Configuration是Spring底层的一个注解,使用@Configuration标注的类,说明该类是配置类。
前面内容中提到 @SpringBootApplication 注解标注的类 表明该类 是 springBoot的 主配置类,原因在于 @SpringBootApplication 的三大核心注解之一@SpringBootConfiguration。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {...}
通过观察发现 @SpringBootConfiguration 是标注在 @SpringBootApplication上面,@SpringBootConfiguration所标注的类说明该类是配置类。因此说明@SpringBootApplication也是配置类。
进入@SpringBootConfiguration注解的中查看源码,如下:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {....}
通过观察源码,可以发现 @Configuration标注在 @SpringBootConfiguration类上,说明该类SpringBootConfiguration也是配置类,是springBoot的一个配置类;
因此@Configuration标注的类@SpringBootConfiguration是配置类,@SpringBootConfiguration标注的类@SpringBootApplication是配置类,@SpringBootApplication标注的类如:DemoApplication.主程序类也是配置类。说明@SpringBootApplication标注的类可以作为配置类springBoot的一个配置类。
4.1注解 @Configuration
@Configuration底层代码如下:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(
annotation = Component.class
)
String value() default "";
boolean proxyBeanMethods() default true;
}
@Configuration是配置类注解,是spring的底层注解;从@Component可以看出配置类也是一个组件;这个注解@Configuration用在类上,表示告诉spring这个类是配置类===配置文件。
@Configuration // 告诉spring这是一个配置类 == 配置文件
public class DemoConfig {
@Bean // 给容器中添加组件,以方法名为组件id,返回的值就是组件
public User user(){
return new User();
}
}上述代码等价与在xml中配置bean标签
<bean id="user" class="com.example.demo.bean.User"></bean>
@Configuration修饰的类,默认带有参数proxyBeanMethods,参数proxyBeanMethods默认值是true。
proxyBeanMethods=true:如果为true值,springboot总会检查Bean这个组件在容器中是否存在,如果存在则不会新创建对象。配置类对象Configuration是被cglib代理类增强的代理对象。调用配置类中的注册方法如user()得到的都是同一个bean对象。即@Configuration默认创建的bean对象是单例的。
@Configuration //默认使用属性@Configuration(proxyBeanMethods=true)
public class DemoConfig {
@Bean // 给容器中添加组件,以方法名为组件id,返回的值就是组件
public User user(){
return new User();
}
}测试类
@SpringBootApplication public class DemoApplication { public static void main(String[] args) { //1、返回IOC容器 ConfigurableApplicationContext run = SpringApplication.run(DemoApplication.class, args); //2、查看容器中的组件 String[] names = run.getBeanDefinitionNames(); for (String name : names) { System.out.println(name); } //配置类的测试使用 //从容器中获取组件 //从容器中获取配置类组件,配置类demoConfig组件 是一个代理对象 DemoConfig demoConfig = run.getBean(DemoConfig.class); System.out.println(demoConfig); //在 proxyBeanMethods=true 情况下,spring 会检查容器是否存在 //在 proxyBeanMethods=true 情况下, 不存在则创建新对象 User user0 = demoConfig.user(); / run.getBean("user",User.class); //在 proxyBeanMethods=true 情况下, 存在则 不 创建新对象 User user1 = demoConfig.user(); / run.getBean("user",User.class); // 结果为 true 说明二者是同一个对象 是单例对象 System.out.println(user0==user1); } }
proxyBeanMethods=false:如果为fasle值,则springboot不会检查Bean这个组件在容器中是否存在,每次都会创建一个对象,配置类对象不是被cglib代理类增强的代理对象,调用配置类中的注册方法都是得到的是不同的bean对象。
@Configuration (proxyBeanMethods=false)
public class DemoConfig {
@Bean // 给容器中添加组件,以方法名为组件id,返回的值就是组件
public User user(){
return new User();
}
}测试类
@SpringBootApplication public class DemoApplication { public static void main(String[] args) { //1、返回IOC容器 ConfigurableApplicationContext run = SpringApplication.run(DemoApplication.class, args); //配置类的测试使用 //从容器中获取组件 //从容器中获取配置类组件,配置类demoConfig组件 是一个代理对象 DemoConfig demoConfig = run.getBean(DemoConfig.class); System.out.println(demoConfig); //在 proxyBeanMethods=false 情况下,spring 不会检查容器是否存在,直接创建新对象 User user0 = demoConfig.user(); / run.getBean("user",User.class); //在 proxyBeanMethods=false 情况下,spring 不会检查容器是否存在,直接创建新对象 User user1 = demoConfig.user(); / run.getBean("user",User.class); // 结果为 false 说明二者 不是同一个对象 是多例对象 System.out.println(user0==user1); } }
小结:proxyBeanMethods=true 和false的区别;
full:全配置、lite:轻量级配置
proxyBeanMethods=true 每次都会到容器中检查是否存在这个javaBean。启动比较慢。就是full:全配置。
proxyBeanMethods=false每次都会跳过到容器中检查是否存在这个过程,启动比较快。就是lite:轻量级配置
因此,如果给容器只是注册组件,这些组件又不被依赖,此时是用false,springboot启动比较快,加载也比较快。如果这些组件经常被依赖,此时使用true。
5.自动配置原理-注解 @EnableAutoConfiguration(重头戏)
在讲 @EnableAutoConfiguration 之前,先回顾下 @Import注解,@Import是spring的底层注解,主要用于给容器中自动创建组件,例如:创建一个Book类,使用@Import(Book.class)进行导入。
@SpringBootApplication @Import(Book.class) public class DemoApplication { public static void main(String[] args) { //1、返回IOC容器 ConfigurableApplicationContext run = SpringApplication.run(DemoApplication.class, args); //2、查看容器中的组件 String[] names = run.getBeanNamesForType(Book.class); for (String name : names) { System.out.println(name); } Book book = run.getBean("cn.edu.tyut.springboot.entity.Book",Book.class); System.out.println(book); } }
通过上述代码观察到:@Import注解作用是给容器中自动创建组件,例如后面要讲到的,导入自动配置选择器组件:@Import({AutoConfigurationImportSelector.class}) ,也可以导入多个组件,组件之间用逗号隔开。导入组件在IOC容器中的名字默认是类的全限定名,例如:Book book = run.getBean("cn.edu.tyut.springboot.entity.Book",Book.class);
5.1注解@EnableAutoConfiguration
前面内容中提到:@SpringBootApplication 能够扫描 Spring组件 并 自动配置Spring Boot。主要取决于 @SpringBootApplication 的 核心注解 @EnableAutoConfiguration。
@EnableAutoConfiguration:开启自动配置功能; 以前需要配置的东西,现在springBoot帮我们自动配置,通过这个注解开启自动配置;@EnableAutoConfiguration注解代码如下:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
通过观察,@EnableAutoConfiguration类中使用了两个注解,分别是@AutoConfigurationPackage与@Import({AutoConfigurationImportSelector.class})。
5.1.1注解@AutoConfigurationPackage
- 作用:自动配置包,将主配置类(@SpringBootApplication标注的类)的所在包下的所有子包内的所有组件全部加入到spring容器中;
- @AutoConfigurationPackage能够自动配置的原因是注解类中有注解@Import(AutoConfigurationPackages.Registrar.class)。
- @Import(AutoConfigurationPackages.Registrar.class) 给容器导入一个组件Registrar;再由组件 Registrar 导入一系列其他的组件;
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage {......}
- 组件 Registrar 中 register方法进行批量导入指定路径下的组件
register方法进行批量导入指定路径下的组件具体执行流程:
- 首先得到元数据metedata 即 com.example.demo.DemoAppliaction
- 在new PackageImports(metadata) 中 通过 AutoConfigurationPackage.class.getName() 类名 org.springframework.boot.autoconfigure.AutoConfigurationPackage
- metedata元数据根据AutoConfigurationPackage类名获取注解标签basePackageClasses和basePackages。
- 根据注解标签 basePackages 获取所在包名,如果为空,则使用ClassUtils.getPackageName()获取。
- 最终得到主程序所在的包 com.example.demo
- 注册 主程序 DemoAppliaction 所在的包路径 com.example.demo下所有的组件。
因此我们说以上流程就是 @AutoConfigurationPackage自动配置包,将主配置类(@SpringBootApplication标注的类)的所在包下的所有子包内的所有组件全部加入到spring容器中的过程和原因。
5.1.2注解@Import({AutoConfigurationImportSelector.class})
- spring底层注解@import,给容器导入一个组件;导入的组件由AutoConfigurationImportSelector.class提供;AutoConfigurationImportSelector批量导入组件,它决定导入哪些组件,过程如下图源码:
通过源码发现:
- 通过getAutoConfigurationEntry(annotationMetadata)方法导入一些组件。
- 通过List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)方法获取所有要导入的组件。
- 通过List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader)
- 通过Map<String, List<String>> loadSpringFactories(ClassLoader classLoader)
- 从META-INF/spring.factories位置加载一个文件,就是默认扫描当前系统所有META-INF/spring.factories位置的文件。
- 重点是spring-boot-autoconfigure-2.6.7.jar包下的META-INF/spring.factories文件,里面配置好了要自动加载的组件有哪些。如下图:
6. @ImportResource导入spring的配置类
@ImportResource用来导入spring的配置文件,例如@ImportResource(“classpath:springbean.xml”)
步骤1:service类
public class MyServicve { }
步骤2:创建配置文件
在resources目录下新建一个名为beans.xml的XML自定义配置文件,在该配置文件中通过配置向Spring容器中添加MyConfigServicve类对象。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id=" myConfigServicve " class="com.example.demo.services.MyServicve" /> </beans>
步骤3:在启动类上引入XML文件
@SpringBootApplication @ImportResource("classpath:beans.xml") public class DemoApplication { public static void main(String[] args) { //1、返回IOC容器 ConfigurableApplicationContext run = SpringApplication.run(DemoApplication.class, args); //2、查看容器中的组件 MyServicve servicve = run.getBean(MyServicve.class); System.out.println(servicve); } }
7. @Conditional注解
此注解是满足某些条件时候按条件进行组件注入。其有很多衍生注解,主要用于条件判断,例如
@ConditionalOnBean、@ConditionalOnMissingBean、@ConditionalOnClass、@ConditionalOnMissingClass、、@ConditionalOnJava等,可以用在类上,也可以用在方法上。用在类上表示如果容器中存在其注解指定的bean组件或class时,类中所有的组件才能进行注入到 IOC 容器中。或容器中没有其注解中指定的bean组件或者class时,类中所有组件才能进行注入到IOC 容器中。用在方法上,只对当前的方法起作用。
例如:如下面案例,如果注解 @ConditionalOnBean(name="book")用在加类上,如果容器中存在有book组件,类中的所有组件(例如组件user、book、dept)才会注入到容器中。如果注解 @ConditionalOnBean(name="book")用到方法user上,如果有book组件时候,组件user、才会注入到容器中。
public class User {
public User() {
System.out.println("创建user bean");
}
}public class Book {
public Book(){
System.out.println("创建book bean");
}
}
public class Dept {
public Dept(){
System.out.println("创建dept bean");
}
}类加上注解 @ConditionalOnBean(name="book"),如果有book组件,类中的所有组件(例如组件user)才生效,这些组件才会注入到容器中。
但在进行注册时候,容器中没有book组件,因此不会注册组件 book、user、dept到容器中,见下面测试。@Configuration ()
@ConditionalOnBean(name="book")
public class DemoConfig {
@Bean // 给容器中添加组件,以方法名为组件id,返回的值就是组件
public User user(){User user =new User();
user.setBook(book);
return new User();
}@Bean // 给容器中添加组件,以方法名为组件id,返回的值就是组件
public Dept dept(){Dept dept=new Dept ();
return dept;
}@Bean // 给容器中添加组件,以方法名为组件id,返回的值就是组件
public Book book(){Book book=new Book ();
return book;
}}
@SpringBootApplication public class DemoApplication { public static void main(String[] args) { //1、返回IOC容器 ConfigurableApplicationContext run = SpringApplication.run(DemoApplication .class, args); /** * 2、判断容器中是否存在 这些组件。 * 如果@ConditionalOnBean(name="book")指定name="book"的组件不在容器中存在, * 则@ConditionalOnBean(name="book")所标识的类中的组件user、dept、book不会注入到容器。 */ Boolean isContainUser = run.containsBean("user"); Boolean isContainBook = run.containsBean("book"); Boolean isContainDept = run.containsBean("dept"); System.out.println(isContainUser);// 结果为 false System.out.println(isContainBook);// 结果为 false System.out.println(isContainDept);// 结果为 false } }
如果注解 @ConditionalOnBean(name="book")用到user方法上,实际运行过程中开始的时候容器中是没有book组件的,因此组件user不会注入到容器中,但不会影响dept和book组件的注入,因为book 和 dept 组件并不受条件限制。
@Configuration (
public class DemoConfig {
@ConditionalOnBean(name="book")
@Bean // 给容器中添加组件,以方法名为组件id,返回的值就是组件
public User user(){User user =new User();
user.setBook(book);
return new User();
}@Bean // 给容器中添加组件,以方法名为组件id,返回的值就是组件
public Dept dept(){Dept dept=new Dept ();
return dept;
}@Bean // 给容器中添加组件,以方法名为组件id,返回的值就是组件
public Book book(){Book book=new Book ();
return book;
}}
@SpringBootApplication public class DemoApplication { public static void main(String[] args) { //1、返回IOC容器 ConfigurableApplicationContext run = SpringApplication.run(DemoApplication .class, args); /** * 2、判断容器中是否存在 这些组件。 * 如果@ConditionalOnBean(name="book")指定name="book"的组件不在容器中存在, * 则@ConditionalOnBean(name="book")所标识的方组件user不会注入到容器。但未 * 标识的dept、book组件会注入到容器中。 */ Boolean isContainUser = run.containsBean("user"); Boolean isContainBook = run.containsBean("book"); Boolean isContainDept = run.containsBean("dept"); System.out.println(isContainUser);// 结果为 false System.out.println(isContainBook);// 结果为 true System.out.println(isContainDept);// 结果为 true } }
另外注意特殊情况:如果注解 @ConditionalOnBean(name="book")用到user方法上,但组件book在 组件 user 之前,则运行时候,先注册组件book,在检查user方法时候,此时容器中已经有组件book,因此组件user会注入到容器中。
@Configuration (
public class DemoConfig {
@Bean // 给容器中添加组件,以方法名为组件id,返回的值就是组件
public Book book(){Book book=new Book ();
return book;
}@ConditionalOnBean(name="book")
@Bean // 给容器中添加组件,以方法名为组件id,返回的值就是组件
public User user(){User user =new User();
user.setBook(book);
return new User();
}@Bean // 给容器中添加组件,以方法名为组件id,返回的值就是组件
public Dept dept(){Dept dept=new Dept ();
return dept;
}}
@SpringBootApplication public class DemoApplication { public static void main(String[] args) { //1、返回IOC容器 ConfigurableApplicationContext run = SpringApplication.run(DemoApplication .class, args); /** * 2、判断容器中是否存在 这些组件。 * 如果@ConditionalOnBean(name="book")指定name="book"的组件不在容器中存在, * 则@ConditionalOnBean(name="book")所标识的方组件user不会注入到容器。但未 * 标识的dept、book组件会注入到容器中。 */ Boolean isContainUser = run.containsBean("user"); Boolean isContainBook = run.containsBean("book"); Boolean isContainDept = run.containsBean("dept"); System.out.println(isContainUser);// 结果为 true System.out.println(isContainBook);// 结果为 true System.out.println(isContainDept);// 结果为 true } }
05全局配置文件application.properties详解
https://blog.csdn.net/qq_41946216/article/details/124769491?spm=1001.2014.3001.5501
更多推荐
所有评论(0)