文章目录
1. SpringBoot底层注解 @Configuration
1.1 @Configuration 和 @Bean的作用
配置类,必须能让SpringBoot扫描到才能装配到容器中!
创建一个MyConfig类,作为配置类:
- @Configuration注解作用:告诉springboot这是一个配置类。等同于我们创建了一个spring.xml配置文件。
- @Bean注解作用:给容器中添加bean对象(组件)。相当于在spring.xml文件中创建了一个bean对象。
- 方法名就是对应bean对象(组件)的id。
- 返回值类型就是bean对象(组件)的类型。
- 返回的值就是bean对象(组件)在容器中的实例。
- 配置类本身也是一个对象(组件),也会被注入到容器中。
package com.itholmes.boot.config;
import com.itholmes.boot.bean.Pet;
import com.itholmes.boot.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
//@Configuration注解作用:告诉springboot这是一个配置类。等同于我们创建了一个spring.xml配置文件。
@Configuration
public class MyConfig {
/*
@Bean注解作用:给容器中添加bean对象(组件)。相当于在spring.xml文件中创建了一个bean对象。
方法名就是对应bean对象(组件)的id。
返回值类型就是bean对象(组件)的类型。
返回的值就是bean对象(组件)在容器中的实例。
*/
@Bean
public User user01(){
return new User("张三",18);
}
@Bean
public Pet tomcatPet(){
return new Pet("tomcat");
}
}
MainApplication,主程序类中,可以获取对应的bean对象(组件):
- SpringApplication.run(MainApplication.class, args)的返回值就是ioc容器。
- 可以直接getBean获取容器中的对象(组件),验证上面的注解类。
package com.itholmes.boot;
import com.itholmes.boot.bean.Pet;
import com.itholmes.boot.bean.User;
import com.itholmes.boot.config.MyConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(basePackages="com.itholmes")
public class MainApplication {
public static void main(String[] args) {
//1. 返回IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//2. 查看容器里面的组件(就是bean的对象)
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
//3. 从容器中获取组件。
Pet pet = run.getBean("tomcatPet", Pet.class);
User user01 = run.getBean("user01", User.class);
User user02 = run.getBean("user01", User.class);
}
1.2 @Configuration注解的 proxyBeanMethods参数变量
1.2.1 @Configuration(proxyBeanMethods = true)
SpringBoot2的@Configuration比SpringBoot1版本多了一个proxyBeanMethods 参数。
proxyBeanMethods注解,英文直译过来就是代理bean方法。其实就是告诉SpringBoot当前的配置类是否生成代理对象。
从容器中,获取配置类对象,如果@Configuration(proxyBeanMethods = true(默认就是true)) ,那么生成的就是代理对象。代理对象每次执行方法的时候都是先去容器中拿对象(组件),没有才会创建。
// 返回IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
// 配置类本身也是一个对象(组件)
MyConfig myConfig = run.getBean(MyConfig.class);
System.out.println(myConfig);
//com.itholmes.boot.config.MyConfig$$EnhancerBySpringCGLIB$$f2269e91@5e7c141d
打印出的代理对象,一般都会带着$$这样的标识。
这里的代理对象,无论调用多少次对象中的方法,拿到的依然是组件里面的单例对象。
总之,被@Configuration(proxyBeanMethods = true)修饰的配置类,SpringBoot会生成一个代理对象,每一个代理对象都先会先去容器中拿对象,也就是所谓的单例模式的效果。
1.2.2 @Configuration(proxyBeanMethods = false)
@Configuration(proxyBeanMethods = false),设置为了false,就不会在容器中创建代理对象。当然每一次调用方法,都会产生一个新的对象,也就是契合了多例模式。
SpringBoot2 @Configuration新增的两个模式:
- full (全配置模式) proxyBeanMethods = true , 被修饰的配置类,SpringBoot会生成一个代理对象并装配到容器中。每次代理对象,都会先检查容器有没有。
- Lite (轻量级配置模式) proxyBeanMethods = false , 不会生成代理对象。
full模式,因为只要外部调用代理对象方法,代理对象会不断的检查容器中是否有,这个过程很浪费时间。
1.3 什么时候proxyBeanMethods设置为true,什么时候设置false?
当有容器中,有组件(对象)依赖的时候,会将proxyBeanMethods设置为true。
当有容器中,没有组件(对象)依赖会设置为false。
什么是组件依赖?
- 相当于我在xml文件中创建了两个bean,但是其中有一个bean对象的内部属性依赖于另一bean。
例如:
在User实体类中,创建一个Pet实体类的成员变量。
package com.itholmes.boot.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
String name;
int age;
Pet pet;
}
MyConfig类的proxyBeanMethods设置为true,就可以识别到容器中的对象。这样就可以实现组件依赖。
package com.itholmes.boot.config;
import com.itholmes.boot.bean.Pet;
import com.itholmes.boot.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = true)
public class MyConfig {
@Bean
public User user01(){
User user = new User();
user.setName("张三");
user.setAge(18);
//依赖于tomcatPet方法,注入容器的对象。
user.setPet(tomcatPet());
return user;
}
@Bean
public Pet tomcatPet(){
return new Pet("tomcat");
}
}
package com.itholmes.boot;
import com.itholmes.boot.bean.Pet;
import com.itholmes.boot.bean.User;
import com.itholmes.boot.config.MyConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(basePackages="com.itholmes")
public class MainApplication {
public static void main(String[] args) {
//返回IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//查看容器里面的组件(就是bean的对象)
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
//从容器中获取组件。
Pet pet = run.getBean("tomcatPet", Pet.class);
User user = myConfig.user01();
System.out.println("测试是否是同一个bean对象:"+(user.getPet() == pet));
//测试是否是同一个bean对象:true
}
}
2. SpringBoot底层注解 @import注解
@Import({User.class,Dog.class})就会给容器注入一个无参的User类和Dog类。
- 在任何类都可以用它修饰装配到容器中,前提是这个类一定要被SpringBoot扫描到!
- 被装配的类的名字是对应的全类名(路径加类名),例如:com.itholmes.boot.bean.User。
package com.itholmes.boot.config;
import com.itholmes.boot.bean.Dog;
import com.itholmes.boot.bean.Pet;
import com.itholmes.boot.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/*
@Import({User.class,Dog.class})
给容器中自动创建出这两个类型的组件。
*/
@Import({User.class, Dog.class})
@Configuration(proxyBeanMethods = true)
public class MyConfig {
@Bean
public User user01(){
User user = new User();
user.setName("张三");
user.setAge(18);
//依赖于tomcatPet方法,注入容器的对象。
user.setPet(tomcatPet());
return user;
}
@Bean
public Pet tomcatPet(){
return new Pet("tomcat");
}
}
主程序类,测试:
package com.itholmes.boot;
import com.itholmes.boot.bean.Pet;
import com.itholmes.boot.bean.User;
import com.itholmes.boot.config.MyConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(basePackages="com.itholmes")
public class MainApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
Pet pet = run.getBean("tomcatPet", Pet.class);
User user01 = run.getBean("user01", User.class);
//getBeanNamesForType(xxx)方法会获取所有xxx.class类型的参数。
String[] users = run.getBeanNamesForType(User.class);
for (String user : users) {
System.out.println(user);
}
//com.itholmes.boot.bean.User 一个是对应@import注入
//user01 一个是对应@Bean注入
}
}
3. SpringBoot底层注解 @Conditional注解
@Conditional注解:条件装配,满足Conditional指定的条件,则进行组件装配和注入。
再例如,@ConditionalOnBean注解使用。
- @ConditionalOnBean(name=“tom”)注解:意思是当name中的有id为tom的对象组件的时候,再装配user01这个组件对象。
package com.itholmes.boot.config;
import com.itholmes.boot.bean.Pet;
import com.itholmes.boot.bean.User;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/*
@ConditionalOnBean注解:意思是当name中的有id为tom的对象组件的时候,再装配user01这个组件对象。
*/
//修饰到类上面,就是如果容器中,没有id为tom的对象组件,则类下面方法都不会装配到容器中。
//@ConditionalOnBean(name="tom")
@Configuration(proxyBeanMethods = true)
public class MyConfig {
@ConditionalOnBean(name="tom") //修饰到方法上,如果容器中,没有id为tom对象组件,则该方法不会装配到容器中。
@Bean("user")
public User user01(){
User user = new User();
user.setName("张三");
user.setAge(18);
//依赖于tomcatPet方法,注入容器的对象。
user.setPet(tomcatPet());
return user;
}
//@Bean("tom")//这里注释了@Bean注解,这样该方法就不会被装配到容器中。
public Pet tomcatPet(){
return new Pet("tomcat");
}
}
package com.itholmes.boot;
import com.itholmes.boot.bean.Pet;
import com.itholmes.boot.bean.User;
import com.itholmes.boot.config.MyConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(basePackages="com.itholmes")
public class MainApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//containsBean方法:判断容器中是否包含一个id为tom的组件对象。
boolean user = run.containsBean("user");
System.out.println("判断容器中,是否还有user的组件对象:"+user);
boolean tom = run.containsBean("tomcatPet");
System.out.println("判断容器中,是否还有tom的组件对象:"+tom);
//判断容器中,是否还有user的组件对象:false
//判断容器中,是否还有tom的组件对象:false
}
}
这些当什么什么才会装配到容器。一般用在SpringBoot的底层中,经常使用!
4. SpringBoot底层注解 @ImportResource
有些使用,可能还是会用到xml配置文件,那么想要让该xml配置,装配到springboot的ioc容器中的话,就可以使用@ImportResource注解来装配。
5. SpringBoot底层注解 @ConfigurationProperties
5.1 方式一:@Component注解 + @ConfigurationProperties注解
方式一:两个注解都用到实体类中。
@Component注解 + @ConfigurationProperties注解:
- 使用@Component注解装配到容器中。
- @ConfigurationProperties(prefix = “mycar”)注解:
就可以在application.properties配置文件中拿到前缀为mycar对应属性的值。将这些值注入到对应的容器组件对象中。
创建一个Car类:
package com.itholmes.boot.bean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/*
只有容器中的组件,才会拥有SpringBoot提供的强大功能。所以使用@Component注解装配到容器中。
@ConfigurationProperties(prefix = "mycar")注解:
就可以在application.properties配置文件中拿到前缀为mycar对应属性的值。将这些值注入到对应的组件对象中。
*/
@Component//先将当前的类,装配到容器中。
@ConfigurationProperties(prefix = "mycar")
public class Car {
private String brand;
private Integer price;
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
@Override
public String toString() {
return "Car{" +
"brand='" + brand + '\'' +
", price=" + price +
'}';
}
}
application.properties配置文件:
# 修改端口号
server.port=8888
# 相当于将下面的两个属性内容,注入到了对应的组件对象中。
mycar.brand=BYD
mycar.price=100000
这样就相当于在容器中装配了对象,并且给该对象注入了相关信息。
5.1 方式二:@EnableConfigurationProperties(Car.class)注解 + @ConfigurationProperties注解
方式二:@ConfigurationProperties注解用于实体类,@EnableConfigurationProperties(Car.class)注解用于配置类中。
- @ConfigurationProperties注解作用同上。
- @EnableConfigurationProperties(Car.class)注解,1.开启Car配置绑定功能,也就是读取properties文件。2.把这个Car这个组件自动注册到容器中。
Car实体类:
package com.itholmes.boot.bean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/*
只有容器中的组件,才会拥有SpringBoot提供的强大功能。所以使用@Component注解装配到容器中,如果配置类中有@EnableConfigurationProperties(Car.class)也可以是可以装配到容器中,并且注入属性的。
@ConfigurationProperties(prefix = "mycar")注解:
就可以在application.properties配置文件中拿到前缀为mycar对应属性的值。将这些值注入到对应的容器组件对象中。
*/
@ConfigurationProperties(prefix = "mycar")
public class Car {
private String brand;
private Integer price;
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
@Override
public String toString() {
return "Car{" +
"brand='" + brand + '\'' +
", price=" + price +
'}';
}
}
MyConfig配置类:
package com.itholmes.boot.config;
import com.itholmes.boot.bean.Car;
import com.itholmes.boot.bean.Pet;
import com.itholmes.boot.bean.User;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
/*
@EnableConfigurationProperties注解作用:
1.开启Car配置绑定功能。
2.把这个Car这个组件自动注册到容器中。
*/
@EnableConfigurationProperties(Car.class)
@Configuration(proxyBeanMethods = true)
public class MyConfig {
@ConditionalOnBean(name="tom") //修饰到方法上,如果容器中,没有id为tom对象组件,则该方法不会注入到容器中。
@Bean("user")
public User user01(){
User user = new User();
user.setName("张三");
user.setAge(18);
user.setPet(tomcatPet());
return user;
}
//@Bean("tom")
public Pet tomcatPet(){
return new Pet("tomcat");
}
}
6. @SpringBootApplication注解源码分析
6.1 @SpringBootConfiguration作用
@SpringBootApplication注解源码上,还有其他三个注解:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
)
@SpringBootConfiguration注解源码分析:
//三个元注解。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
//说明代表当前是一个配置类。
@Configuration
@Indexed
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
也就是说主程序本身就是一个配置类:
6.2 @ComponentScan注解
@ComponentScan注解,只是指定SpringBoot应该扫描哪些包。
6.3 @EnableAutoConfiguration注解
@EnableAutoConfiguration注解源码如下:
// 四个元注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
//@EnableAutoConfiguration注解主要的功能也是是现在下面这几个上面。
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
那就继续分析@AutoConfigurationPackage和@Import注解。
@AutoConfigurationPackage注解分析:自动配置包。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
//@Import({Registrar.class})注解作用:给容器中导入Registrar.class这个类的组件(对象)
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
@Import({Registrar.class})注解作用:给容器中导入Registrar.class这个类的组件(对象)。
Registrar.class类负责给容器中导入一系列组件。
- 从AnnotationMetadata metadata(是注解的元信息,也就是@AutoConfigurationPackage注解)中拿到要扫描的包名。
- BeanDefinitionRegistry是一个接口,它定义了关于 BeanDefinition 的注册、移除、查询等一系列的操作。
也就是@AutoConfigurationPackage注解本身的做就是负责扫描包下的组件对象,并装配。
@Import({AutoConfigurationImportSelector.class})注解分析:
AutoConfigurationImportSelector.class类源码中有一个方法:
//AnnotationMetadata annotationMetadata 得到注解的元信息。
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
//getAutoConfigurationEntry(annotationMetadata)方法很关键!
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
就是利用getAutoConfigurationEntry(annotationMetadata)方法来给容器批量导入一些组件。
在getAutoConfigurationEntry方法中:
- 调用List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);获取到所有需要导入容器中的配置类。
- 可以debug中step into一下,一层层的看源码。
- 先会看到List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());。
- loadFactoryNames方法中,有loadSpringFactories方法。
- private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader),发现就是一个工厂加载,从而得到所有组件。
- 在loadSpringFactories里面有一个classLoader.getResources(“META-INF/spring.factories”);作用是加载一个META-INF/spring.factories的配置文件
- 在spring-boot-autoconfigure:2.6.4.jar包下,也有对应的META-INF/spring.factories文件,也就是文件里面写死了springboot一启动就要给容器中加载所有配置类。
按需开启自动配置:
- 虽然我们100多个场景的所有自动配置启动的时候默认全部加载。但是最终会按需配置!
- 按需配置,就是通过@Conditional一系列的条件装配,来按需引入。
7. SpringBoot的自动配置流程
springboot的自动配置,离不开spring-boot-autoconfigure的jar包。配置的流程就是根据spring-boot-autoconfigure中的spring.factories中的内容,以及spring-boot-autoconfigure包中下面的配置类。如下图:
例如:spring-boot-autoconfigure包下的autoconfigure/web/servlet包中DispatcherServletAutoConfiguration类下:
有一个MultipartResolver多文件处理器方法。
@Bean
@ConditionalOnBean({MultipartResolver.class}) //当容器中有这个类型组件时。
@ConditionalOnMissingBean(
name = {"multipartResolver"}
)//当容器中没有这个名字multipartResolver的组件或者对象时,就是如果名字不相同。
//SpringBoot也会找到这个方法,就是为了防止有写用户配置的MultipartResolver文件上传解析器了。。
//给@Bean标注的方法传入对象参数,这个参数的值就会从容器中找到。
public MultipartResolver multipartResolver(MultipartResolver resolver){
return resolver;
}
像ssm中,HttpEncodingAutoConfiguration解决乱码问题的内容,SpringBoot都在spring-boot-autoconfigure包下配置好了。
通过上面了解,SpringBoot的设计模式是:
SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了那么就会使用用户配置的组件对象。
实现这种效果,就是@ConditionalOnMissingBean注解的功劳。判断容器中是否有这个bean对象。
还有很多xxxProperties类,这些类在SpringBoot的核心配置文件application.properties中拿到相关的属性配置。
当我们想要修改一些内容信息时,例如:修改HttpEncodingAutoConfiguration的编码格式,就可以如下图直接在application.properties中配置就可以:
8. 总结 自动配置流程原理
** 自动配置流程就是:**
- SpringBoot先加载所有的自动配置类,自动配置类的格式一般是:xxxAutoConfiguration。
- 每个配置类按照条件装配进行生效,默认都会绑定配置文件指定的值。绑定的类的格式一般是:xxxProperties,xxxProperties回去application.properties中找配置数据。
- 生效的配置类就会给容器中装配很多组件。
- 只要容器中有这些组件,相当于这些功能就有了。
- 定制化配置:
- 用户直接自己@Bean替换底层的组件。
- 用户去看这个组件是获取的配置文件什么值就去修改。
先是一个xxxAutoConfiguration的配置类 ==》 然后导入很多组件 ==》这些组件在从xxxProperties中拿值 ==》再去application.properties配置文件。