1 注解说明
我们知道 Spring 最核心的内容就是 IOC,包括 AOP 也是依托于IOC,而提到 IOC 就必然离不开 bean。 将 bean 实例注入到 IOC 容器中的两个常见的注解便是 @Component 和 @Bean。
1.1 注解 @Component
@Component 放置在类上面,需要配合@ComponentScan 注解使用,默认是单例,可配合 @Scope (“”) 设置为其他作用域。这个应用太过平常,这里不再赘述,包括它的一些常见的派生注解
@Controller、@Service、@Repository、@Configuration
1.2 注解 @Bean
与@Component不同,@Bean 放在方法上,能够根据配置参数生成bean 实例,如果未通过 @Bean(name=“”) 指定 bean 的名称,则默认与标注的方法名相同。@Bean 注解可以指定初始化和销毁方法,如 @Bean (name=“myBean”,initMethod=“myInit”,destroyMethod=“myDestory”)。
@Bean 注解同样默认作用域为单例 singleton 作用域,可通过 @Scope (“”) 设置为其他作用域。
1.3 注解 @Import
除了常见了@Component 和 @Bean,业务代码里几乎不会用到的 @Import 注解同样可以将实例加入 IOC 容器中,这个@Import 注解就是 springboot 自动装配原理的主角。
@Import 只能用在类上,尤其适用于导入第三方包的bean对象。使用方法如下:
例如,有DemoOne.java 和DemoTwo.java
@Import({ DemoOne.class,DemoTwo.class... })
通过 “DemoOne.class” 参数,可以直接将DemoOne 的实例注入到 IOC 容器中;此外如果DemoTwo.java 实现Spring 提供的以下两个特殊的接口之一,也可以间接的注入DemoTwo中自定义配置的其他类。
1、实现 ImportSelector 接口的类
2、实现 ImportBeanDefinitionRegistrar 接口的类
2 @Import注解使用
我们知道 springboot中,默认Application.java 所在包下的 Component都会被注入IOC容器。
如果我们在Application.java的一个平级目录下创建一个 ClonetxConfiguration,那么idea就会提示 Application context not configured for this file。
@Configuration
//Application context not configured for this file
public class ClonetxConfiguration {
@Bean
public ClonetxBean clonetxBean(){
ClonetxBean clonetxBean = new ClonetxBean();
clonetxBean.setValue("123");
return clonetxBean;
}
}
上面我们提到,@Configuration 是 @Component 的派生注解,那么想要令其生效,就可以通过增加包扫描路径配置。@ComponentScan 示例如下:
@SpringBootApplication
@ComponentScan(basePackages = {"cn.clonetx","com.cloneli"})
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(Application.class, args);
}
}
get http://localhost:8080/aaa 会打印 123
@RestController
public class TestController {
@Resource
private ClonetxBean clonetxBean;
@GetMapping("aaa")
public void te() {
System.out.println(clonetxBean.getValue());
}
}
2.1 @Import 直接导入
除了上面的方方式,我们也可以通过 @Import 来达到目的,这种方式不需要@Component,所有有没有 @Configuration 注解都不影响。
@SpringBootApplication
//@ComponentScan(basePackages = {"cn.clonetx","com.cloneli"})
@Import(value = {ClonetxConfiguration.class})
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(Application.class, args);
}
}
//@Configuration
public class ClonetxConfiguration {
@Bean
public ClonetxBean clonetxBean(){
ClonetxBean clonetxBean = new ClonetxBean();
clonetxBean.setValue("123");
return clonetxBean;
}
}
get http://localhost:8080/aaa 一样会打印 123
2.2 实现 ImportSelector
实现 ImportSelector 接口并在接口 selectImports 方法中返回配置类的全类名数组,间接注册 selectImports方法返回的class全类名数组。
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{ClonetxConfiguration.class.getName()};
}
}
@Import 的配置也需要改为 ImportSelector 的实现类,由于selectImports方法返回的是数组,显然可以一次配置多个。
@SpringBootApplication
//@Import(value = {ClonetxConfiguration.class})
@Import(value = {MyImportSelector.class})
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(Application.class, args);
}
}
get http://localhost:8080/aaa 仍然会打印 123
2.3 实现 ImportBeanDefinitionRegistrar
这种类似于 ImportSelector 的用法,但可以更灵活自定义 Bean 的描述信息注册给 Ioc 容器,如示例:
public class MyImportBeanDefitionRegister implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//指定bean定义信息
RootBeanDefinition beanDefinition = new RootBeanDefinition(CommonBean.class);
MutablePropertyValues propertys = new MutablePropertyValues();
//设置属性值 number
propertys.add("number","this is number");
//设置属性值 value
PropertyValue property = new PropertyValue("value","this is value");
propertys.addPropertyValue(property);
beanDefinition.setPropertyValues(propertys);
//注册bean,指定bean名字(id)
registry.registerBeanDefinition("commonBean", beanDefinition);
BeanDefinition rootBeanDefinition = new RootBeanDefinition(ClonetxConfiguration.class);
registry.registerBeanDefinition("clonetxConfiguration", rootBeanDefinition);
}
}
public class CommonBean {
private String value;
private String number;
//省略setter、getter
}
同样,@Import 的配置需要改为 MyImportBeanDefitionRegister.class
@SpringBootApplication
@Import(value = {MyImportBeanDefitionRegister.class})
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(Application.class, args);
}
}
get http://localhost:8080/aaa 仍然会打印 123 和 this is number;this is value
@RestController
public class TestController {
@Resource
private ClonetxBean clonetxBean;
@Resource
private CommonBean commonBean;
@GetMapping("aaa")
public void te() {
System.out.println(clonetxBean.getValue());
System.out.println(commonBean.getNumber() +"; "+ commonBean.getValue());
}
}
2.4 @EnableXXX
@Import 在 Spring 的源码中是很普遍的,尤其是实现接口 ImportSelector的方式,但是在普通业务开发中很少直接使用,我们常用的是 @EnableXXX 注解。如果查看这些注解的定义就会发现,其实它们都是使用的@Import
@EnableCaching
@EnableAsync
@EnableConfigurationProperties
@EnableScheduling
那我们就自己尝试定义一个@Enable 注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({HelloConfiguration.class})
public @interface EnableHello {
}
public class HelloConfiguration {
@Bean
public Hello hello() {
Hello hello = new Hello();
hello.setName("java");
return hello;
}
}
public class Hello {
private String name;
public void sayHello(){
System.out.println("Hello "+ name);
}
//省略setter和 getter
}
最后来测试一下,get http://localhost:8080/aaa 就会打印 Hello java
@RestController
@EnableHello
public class TestController {
@Resource
private Hello hello;
@GetMapping("aaa")
public void te() {
hello.sayHello();
}
}
3 自动装配原理
观察@SpringBootApplication 注解。我们简单说一下@Inherited 和 @Target(ElementType.TYPE),后面着重分析@EnableAutoConfiguration
@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.1 @Inherited
被 @Inherited修饰的注解可以被子类继承 。我们知道@SpringBootApplication 注解放置在 Application.java 上面,如果这时候我们新增一个ApplicationImpl.java 继承Application.java,仍然可以正常启动,这是因为子类对象通过反射也可以拿到 @SpringBootApplication 注解。
//@SpringBootApplication
public class ApplicationImpl extends Application {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(ApplicationImpl.class, args);
}
}
也就是说,ApplicationImpl 默认就是被 @SpringBootApplication 修饰的。
比如我们业务常用的事务注解。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
3.2 @SpringBootConfiguration
这个没什么特别的,实际上就是个 @Configuration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
3.3 @EnableAutoConfiguration
这里我们看到了亲切的 @Import 注解,@AutoConfigurationPackage我们后面再说。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
3.4 AutoConfigurationImportSelector
查看源码我们可以知道,AutoConfigurationImportSelector 实现了 DeferredImportSelector 接口,这里截取了部分代码。通过断点会发现一个奇怪的现象,它并没有执行 ImportSelector 的实现 selectImports 方法,而是执行了内部类AutoConfigurationGroup 的 process 方法,具体为什么可以去查看 springboot 启动流程的源码寻找答案,这里就不展开了。
无论执行哪个方法,都是通过调用 getAutoConfigurationEntry 方法来处理的,分析 getAutoConfigurationEntry 的逻辑,首先是根据“org.springframework.boot.autoconfigure.EnableAutoConfiguration”从某个地方获取了一个全类名数组,然后又对这个数据进行了排除和过滤操作,最后返回了剩下的全类名数组。
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
......
//N ImportSelector 的实现,但实际并没有执行
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 从"META-INF/spring.factories" 中找到对应的后选配置类全路径
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 去重
configurations = removeDuplicates(configurations);
// 去掉我们排除的配置
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// 过滤掉不存在的配置类
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
//N 获取侯选配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
Class<?> springFactoriesLoaderFactoryClass = getSpringFactoriesLoaderFactoryClass();
ClassLoader beanClassLoader = getBeanClassLoader();
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(springFactoriesLoaderFactoryClass,
beanClassLoader);
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;
}
//根据 org.springframework.boot.autoconfigure.EnableAutoConfiguration 查找
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
......
private static class AutoConfigurationGroup implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
......
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
() -> String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
......
}
}
进一步跟踪到 getCandidateConfigurations 方法可以知道,上面提到的某个地方是在SpringFactoriesLoader中,我们继续查看SpringFactoriesLoader的相关代码。
3.5 SpringFactoriesLoader
到这里我们就知道,它是通过查询 jar 的resources 下面的 “META-INF/spring.factories” 来获取全类名的。
public final class SpringFactoriesLoader {
......
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
//N 从扫描的到的spring.factories 中找到 factoryClassName 所对应的配置值,即配置类的全限定名集合
//N 然后 SpringBoot 通过这些全限定名进行类加载(反射),将这些自动配置类添加到 Spring 容器中。
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
//N 扫描ClassPath下的所有jar包的"META-INF/spring.factories" 文件
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);
}
}
}
3.6 META-INF/spring.factories
我们打开springboot自动配置的jar包看下spring.factories 的内容
这里截取了部分代码,观察 Auto Configure 部分,可以发现它的 key 值就是org.springframework.boot.autoconfigure.EnableAutoConfiguration,value 是我们常用组件的配置类全类名,例如redis、RabbitMQ等等。
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
......
3.7 自定义spring.factories
通过上面的分析我们已经知道,springboot 使用@Import 的手段,那么就自定义一个spring.factories试试。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d9KEwSPR-1656393963763)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220627180016000.png)]
# my spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.clonetx.ClonetxConfiguration
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(Application.class, args);
}
}
get http://localhost:8080/aaa 成功打印 123
3.8 @AutoConfigurationPackage
到这里,如果不去深究 springboot 的启动流程,自动装配的内容也就差不多了,最后我们再来看下注解@AutoConfigurationPackage,可以看到还是 @Import 的应用。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
这里是通过实现 ImportBeanDefinitionRegistrar 接口的方式注册bean的。首先获取了@SpringBootApplication注解标注类的包路径,也就是Application.java 所在的包路径,然后调用 register 方法,给IOC注册了一系列的bean,这一系列的bean其实就是包下所有的Component ,这就是为什么主类所在包下的bean会被默认注入IOC。
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//metadata 其实就是 @AutoConfigurationPackage,但是因为我们最终使用的组合后的 @SpringBootApplication,所以这里获取的包命就是 Application 所在的包名
register(registry, new PackageImport(metadata).getPackageName());
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImport(metadata));
}
}
这里额外说一下,上文提到@SpringBootApplication 因为组合了 @Inherited,所以是可以被子类继承的,所以ApplicationImpl 所在的包路径下的bean 一样会被注入IOC容器。
public abstract class AutoConfigurationPackages {
private static final String BEAN = AutoConfigurationPackages.class.getName();
......
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
if (registry.containsBeanDefinition(BEAN)) {
BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
}
else {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(BasePackages.class);
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(BEAN, beanDefinition);
}
}
......
}