个人学习整理
参考的原内容来自b站雷神SpringBoot视频和相关笔记
https://www.bilibili.com/video/BV19K4y1L7MT
https://www.yuque.com/atguigu/springboot/qb7hy2#Lv0Fg
正如其名称一样,当组件满足了@Conditional给定的条件时才会进行装配到容器。
@ConditionalOnBean
注解的作用是当注解参数对应的bean不存在时,则其标注的Bean也不会被注册。
具体示例:
首先创建一个SpringBoot项目,加入Spring Web
依赖,然后创建对应的类,具体目录如下(红色框为修改或添加部分):
package com.doper.pojo;
public class User {
private String name;
public User(String name) {
this.name = name;
}
public User() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
package com.doper.pojo;
public class Book {
private String name;
public Book() {
}
public Book(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Book{" +
"name=" + name +
'}';
}
}
package com.doper.config;
import com.doper.pojo.Book;
import com.doper.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Config {
@Bean("user")
public User user() {
return new User("法外狂徒张三");
}
@Bean("book")
public Book book() {
return new Book("法外狂徒的自我修养");
}
}
package com.doper;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class ConditionalTestApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(ConditionalTestApplication.class, args);
boolean haveUser = run.containsBean("user"); //容器内是否注册了user Bean
boolean haveBook = run.containsBean("book"); //容器内是否注册了book Bean
System.out.println("容器是否有user bean? : " + haveUser);
System.out.println("容器是否有book bean? : " + haveBook);
}
}
运行项目,控制台输出结果:
可以看到容器内确实注册了user和book
下一步修改Config文件
如图所示,注释user()方法的@Bean注解,此时它只是Config类下的一个普通方法,并且在book()方法上面增加@ConditionalOnBean(name = "user")
。此时再运行一下项目
此时发现user bean不存在(因为@Bean注解已经被注释掉了),并且book bean也不存在(@ConditionalOnBean注解的作用)
扩展: 使用@ConditionalOnBean时要注意bean的声明顺序
我们继续改变Config,如下:
我们恢复user()上的@Bean注解,此时运行项目,结果如下:
两个bean都可以顺利被注册。
但这是如果调换一下两个bean的声明顺序,如下
此时得到如下结果:
可以看到book bean不能被创建,因为它是从上往下顺序注册到容器的的,在注册book时user bean还没被注册,@ConditionalOnBean
条件不成立,因为book bean不会被注册。
作用和@ConditionalOnBean
相反
首先在1.1中的项目增加如下类和代码(红色部分为增加或者修改)
// 增加如下bean,这里直接在Student类上增加@Compoent注解也可以
@Bean("student")
public Student student() {
return new Student();
}
package com.doper.pojo;
import org.springframework.boot.context.properties.ConfigurationProperties;
//使用该注解开启配置绑定,配置的属性前缀为student
@ConfigurationProperties(prefix = "student")
public class Student {
private int id;
private String name;
public Student() {
}
public Student(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
使用yaml格式的配置也行
student.id=1
student.name=张三
运行项目,结果如下:
配置文件中的属性已经被自动配置到容器的bean里面了。
如果中文乱码了,原因是编码格式不支持,在idea可以通过打开如下配置解决
这种情况适用于@Autowired
自动注入的情况,但本身容器内并不会注册这个bean。
@EnableConfigurationProperties(Student.class)
Autowired
启动项目,得到如下结果
浏览器:
控制台:
结果可见控制器的@Autowired
已经生效完成自动注入,但容器内并不存在这个bean.
在在了解前置知识中几个注解的作用后就可以来看SpringBoot自动装配的原理了,这里由于知识有限,只能结合视频及笔记列个大概。
点开启动类的@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 {
//...
}
这里前面4个为元注解
@Target
:描述注解的范围,即注解在哪里可以使用。 @Retention
: 描述注解的生命周期,即其保留的时间长短。这里为RUNTIME即运行时有效@Documented
: 生成doc文档时保留其他注解,用于辅助说明@Inherited
: 注释类型会被子类自动继承。然而自动装配的重点为如下三个注解
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
//...
}
包扫描注解
自动装配的关键
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
//...
}
除去上面4个元注解,现在逐层看@AutoConfigurationPackage
和@Import(AutoConfigurationImportSelector.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
//...
}
这里重点在@Import(AutoConfigurationPackages.Registrar.class)
@Import
的作用是为容器导入组件,这里导入了AutoConfigurationPackages.Registrar
类,打开AutoConfigurationPackages.Registrar
查看
/**
* {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
* configuration.
*/
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
//将某个包的所有组件进行注册
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
在register打断点,调试,查看相关信息
这里可以看到new PackageImports(metadata).getPackageNames()
的值为com.doper
,即我们主启动类所在的包。所以这里就将我们包下的组件导入。继续进入。
这里可以看到我们的com.doper
包也被注册进入。
/**
* Register a new bean definition with this registry.
* Must support RootBeanDefinition and ChildBeanDefinition.
...
*/
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException;
点进AutoConfigurationImportSelector
类,看到如下方法
这里规定了要导入哪些包,并把包名String保存到Stirng数组然后返回,而这些包名String都由getAutoConfigurationEntry
方法获得。点进该方法查看
这里关注红色框这一行,它通过getCandidateConfigurations
方法获得所要导入的包名,继续查看
如注释所写,这个方法就是通过SpringFactoriesLoader
类来返回需要自动装配的类名,继续查看SpringFactoriesLoader
的loadFactoryNames
方法
直接看loadSpringFactories
方法,里面在getResources
方法通过加载所有路径为META-INT/spring.factories
中的资源来得到要加载的类名。如
spring-boot-autoconfigure-2.6.2.jar
包下的META-INT/spring.factories
文件,里面列出了需要自动装配的类的类名。
接下来打点验证是否加载了这个配置文件下的类
可以看到控制台中要自动装配的类名数组确实和配置文件中的配置项是对应的。并且后缀都是XXXAutoConfigutation
但要注意的是,这里面列出的配置项虽然已经全被加载进来,但是不会全部配置,实际上它是按需配置的,这里就涉及到前置知识
中的条件装配。
这里打开CacheAutoConfiguration
类查看
可以看到这里使用了@ConditionalOnClass
,@ConditionalOnBean
,@ConditionalOnMissingBean
等条件装配注解。这里报红是因为我们没有导入相关的类,因此在实际自动装配阶段该bean就不会被自动装配。
在底层中虽然它是自动装配,但是我们也可以刚过修改配置文件application.properties
配置项来进行调整,这里以DispatcherServletAutoConfiguration
为例
通过上述1.1.2节的例子,可以看到这里DispatcherServlet的属性都是由配置类来决定的。若在配置文件中有配置项,则会在载入时去加载这些属性到XXXXProperties
中,然后再取出其相关属性然后设置。
@EnableAutoConfiguration
注解的作用由以下两个注解决定
@AutoConfigurationPackage
: 自动扫描我们启动类所在的包(例子中为com.doper
)@Import(AutoConfigurationImportSelector.class)
: 加载所有路径为META-INT/spring.factories
的资源,加载所有需要自动装配的类名。但是在条件装配注解的作用下,并不会去配置所有的组件中。并且我们还可以通过application.properties
配置文件来配置我们的组件属性。(以下总结来自链接笔记)