@AutoConfigurationPackage
是在springboot启动类注解@SpringBootApplication
下的@EnableAutoConfiguration
下。@AutoConfigurationPackage
作用是指定springboot扫描包,默认就是扫描启动类同包下的类。可以通过@AutoConfigurationPackage
来附加其他路径,然后springboot同样会进行扫描。
扫描包就是指的这个包下使用@Service、@Controller…等注解的会被存入Spring容器。
目录
- 一、观察@AutoConfigurationPackage结构
- 二、@Import通过ImportBeanDefinitionRegistrar接口注入
- 三、分析Registrar结构
- 四、分析BeanDefinitionRegistry
- 五、深入分析Registrar类
- 六、@AutoConfigurationPackage的两个属性
- 七、包名的作用
一、观察@AutoConfigurationPackage结构
源码当中除了@Import注解以外,其他四个注解都是Java当中的元注解,所以间接的可以理解为他就是@Import扩展注解:
- @Target({ElementType.TYPE}): 使用范围接口、类、枚举、注解
- @Retention(RetentionPolicy.RUNTIME): @Retention是用来修饰注解的生命周期的,RetentionPolicy.RUNTIME代表的是不仅被保存到class文件中,jvm加载class文件之后,仍然存在;一直有效!
- @Documented: @Documented和@Deprecated注解长得有点像,@Deprecated是用来标注某个类或者方法不建议再继续使用,@Documented只能用在注解上,如果一个注解@B,被@Documented标注,那么被@B修饰的类,生成Javadoc文档时,会显示@B。
- @Inherited: 如果一个类用上了@Inherited修饰的注解,那么其子类也会继承这个注解。
想深入了解元注解的可以看一下这一篇文章:https://blog.csdn.net/weixin_43888891/article/details/126963074
通过源码不难发现,他的核心就是@Import({Registrar.class})
,再直白点说就是Registrar
类
Registrar
是AutoConfigurationPackages
当中的一个内部类,注意这个类实现了ImportBeanDefinitionRegistrar
接口。通过@Import注入对象一共有四种方式,通过ImportBeanDefinitionRegistrar
接口的实现类注入就是其中一种方式!
二、@Import通过ImportBeanDefinitionRegistrar接口注入
对@Import注解不是很了解的一定要先了解@Import注解
首先我们先自定义个类,然后通过@Import通过ImportBeanDefinitionRegistrar接口注入的方式将TestBean4放入到容器当中。
public class TestBean4 {
@Override
public String toString() {
return super.toString() + "--我是TestBean4";
}
}
自定义ImportBeanDefinitionRegistrar 实现类,当然可能您对这块代码还不是很了解,不要慌,接着往下读!
public class ImportBeanByImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(TestBean4.class);
registry.registerBeanDefinition("TestBean4", rootBeanDefinition);
}
}
将Myconfig
放到springboot
扫描的路径当中去,这样启动项目就可以将TestBean4
给注入到容器当中。
@Configuration
@Import({TestBean1.class,
ImportBeanByConfig.class,
ImportBeanByImportSelector.class,
ImportBeanByImportBeanDefinitionRegistrar.class})
public class Myconfig {
}
对上面@Import注入简单分析一下
通过打断点,可以看出来AnnotationMetadata
其实就是@Import所在的类当中的元数据。这个元数据就是@Import所在的类信息,例如这个类的全类名以及所用到了哪些注解等等…
三、分析Registrar结构
通过以上示例我们最起码掌握了重要的一点,只要使用@Import注入ImportBeanDefinitionRegistrar实现类, 那他就会执行
registerBeanDefinitions
方法
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
}
}
接下来重点看
registerBeanDefinitions
方法,他一共有两个参数AnnotationMetadata
和BeanDefinitionRegistry
。
四、分析BeanDefinitionRegistry
BeanDefinitionRegistry
也是registerBeanDefinitions方法当中一个参数,相对来说比较复杂点了,首先他是一个接口,我们通过断点可以看到他传过来的是DefaultListableBeanFactory
实现类!其次我们打开可以看到,他的beanDefinitionMap
属性会发现,我们项目注册的bean竟然就都在这个map当中,没错这确实是spring
当中最核心的内容,IOC容器!
我们打开
DefaultListableBeanFactory
可以发现key是个String类型,就是我们经常所说的Bean 的名称,value 就是对象本身,当然要知道一个正常的对象,他是有很多属性的,例如是否是单例的,以及是否是懒加载的,等等。所以Java就将真正的对象封装成了BeanDefinition
对象。BeanDefinition
存储着真正的对象,以及对象的相关属性。
重新熟悉一下@Import注入ImportBeanDefinitionRegistrar实现类
读到这,相信大家应该知道下面的代码原理了。其实下面代码特别简单,只要设置的@Import注入的是
ImportBeanDefinitionRegistrar实现类
,他就会执行registerBeanDefinitions
方法,并且会传进来两个参数。而这两个参数就是上面讲过的。
通过以上得知map当中的value是BeanDefinition
对象,而BeanDefinition
是一个接口,而RootBeanDefinition
就是BeanDefinition
其中一个实现类。
public class ImportBeanByImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(TestBean4.class);
registry.registerBeanDefinition("TestBean4", rootBeanDefinition);
}
}
registerBeanDefinition
方法就是将对象存入到了map当中。
五、深入分析Registrar类
首先他会执行register方法。register方法有两个参数,一个是BeanDefinitionRegistry ,还有一个是可变长参数。
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
通过打断点得知,packageNames
传的就是启动类所在的包名,然后这个方法就是判断容器是否注入了BasePackagesBeanDefinition
。如果注入了就从容器获取,并将包名赋值给他的basePackages属性,然后执行这块的时候其实是并没有注册的,所以直接执行else。else就是直接创建一个BasePackagesBeanDefinition
对象,然后将包名赋值给他的basePackages
属性。
basePackages
是个set集合,但是容器当中始终只有一个BasePackagesBeanDefinition
对象,也就是只要代码当中添加@AutoConfigurationPackage
注解,就会将注解所在的包名添加到basePackages
集合当中。
六、@AutoConfigurationPackage的两个属性
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
basePackages就是指定包名,默认是@AutoConfigurationPackage注解所在的包。
@AutoConfigurationPackage(basePackages = "com.gzl")
basePackageClasses 指定一个类,就是读取这个类所在的包。
@AutoConfigurationPackage(basePackageClasses = ImportBeanByConfig.class)
七、包名的作用
其实这个就是springboot默认的扫描包。也就是这个包下的只要使用注入容器相关的注解,就会被放入到IOC容器当中。如下示例,ImportBeanByConfig
并不和启动类注解同包,以至于根本就扫描不到他,可以通过@AutoConfigurationPackage(basePackageClasses = ImportBeanByConfig.class)
,将这个注解放到可以被扫描的类当中即可,启动类是不可以存放的,因为启动类本身就已经有@AutoConfigurationPackage
注解了,可以放到@Configuration
修饰的类当中,这样项目启动后就会将ImportBeanByConfig
注入到容器当中了。
@Configuration
@AutoConfigurationPackage(basePackageClasses = ImportBeanByConfig.class)
public class Myconfig {
}