BeanFactory 后处理器的作用
首先我们先大概了解一下作用
-
ConfigurationClassPostProcessor 可以解析
-
@ComponentScan
-
@Bean
-
@Import
-
@ImportResource
-
-
MapperScannerConfigurer 可以解析
-
Mapper 接口
-
ConfigurationClassPostProcessor的演示
我们先演示ConfigurationClassPostProcessor的实现,先准备好可被管理的bean对象
public class Bean1 {
private static final Logger log = LoggerFactory.getLogger(Bean1.class);
public Bean1() {
log.debug("我被 Spring 管理啦");
}
}
component包下:
@Component
public class Bean2 {
private static final Logger log = LoggerFactory.getLogger(Bean2.class);
public Bean2() {
log.debug("我被 Spring 管理啦");
}
}
@Controller
public class Bean3 {
private static final Logger log = LoggerFactory.getLogger(Bean3.class);
public Bean3() {
log.debug("我被 Spring 管理啦");
}
}
public class Bean4 {
private static final Logger log = LoggerFactory.getLogger(Bean4.class);
public Bean4() {
log.debug("我被 Spring 管理啦");
}
}
配置类(这个类我们可以看作为一个工厂对象,里面的方法可以看作为工厂方法,类上加了componenyscan注解扫描component包下所有的类,改类方法中注册了SqlSessionFactoryBean,DruidDataSource)设置的数据库连接用户名密码要填正确:
@Configuration
@ComponentScan("com.sky.component")
public class Config {
@Bean
public Bean1 bean1() {
return new Bean1();
}
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
@Bean(initMethod = "init")
public DruidDataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
}
}
我们需引入依赖:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
此时准备工作已经做好了,我们开始创建启动类(我们手动注入了config类的bean,初始化容器会自动创建所有的单例bean)
public class Spring05Application {
private static final Logger log = LoggerFactory.getLogger(Spring05Application.class);
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("config", Config.class);
//初始化容器
context.refresh();
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
//销毁容器
context.close();
}
}
获取容器中的bean,观察结果
可以发现只有我们手动注册的bean,由于我们创建的容器内部并没有默认的后处理器,componentscan,以及bean注解都没有被解析
此时我们往容器中添加bean工厂的后处理器ConfigurationClassPostProcessor,观察控制台输出结果
context.registerBean(ConfigurationClassPostProcessor.class);
由于该处理器可以解析@ComponentScan @Bean @Import@ImportResource ,我们可以看到容器中多出了该后处理器,以及component包下交给了容器管理的bean以及我们在配置类创建的三个bean对象
MapperScannerConfigurer的演示
我们先准备类
mapper包下(我们创建了两个接口以及一个类):
@Mapper
public interface Mapper1 {
}
@Mapper
public interface Mapper2 {
}
public class Mapper3 {
}
在启动类容器中添加该后处理器观察结果(由于容器并不知道mapper包在哪,我们需要添加包的路径)
context.registerBean(MapperScannerConfigurer.class,bd->{
bd.getPropertyValues().add("basePackage", "com.sky.mapper");
});
由于 添加了MapperScannerConfigurer,容器内部就多出了很多默认的后处理器,他能解析 @MapperScanner注解,可以发现mapper注解的类成功加入容器中,此时为了更深入的理解,我们需要手动去创建后处理器用来扫描这些注解
收获
-
@ComponentScan, @Bean, @Mapper 等注解的解析属于核心容器(即 BeanFactory)的扩展功能
-
这些扩展功能由不同的 BeanFactory 后处理器来完成,其实主要就是补充了一些 bean 定义
手动创建ComponentScanPostProcessor(扫描@componentScan注解)
创建类实现BeanDefinitionRegistryPostProcessor接口
public class ComponentScanPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override // context.refresh
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
try {
ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
if (componentScan != null) {
for (String p : componentScan.basePackages()) {
System.out.println(p);
// com.itheima.a05.component -> classpath*:com/itheima/a05/component/**/*.class
String path = "classpath*:" + p.replace(".", "/") + "/**/*.class";
System.out.println(path);
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
Resource[] resources = new PathMatchingResourcePatternResolver().getResources(path);
AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
for (Resource resource : resources) {
// System.out.println(resource);
MetadataReader reader = factory.getMetadataReader(resource);
// System.out.println("类名:" + reader.getClassMetadata().getClassName());
AnnotationMetadata annotationMetadata = reader.getAnnotationMetadata();
// System.out.println("是否加了 @Component:" + annotationMetadata.hasAnnotation(Component.class.getName()));
// System.out.println("是否加了 @Component 派生:" + annotationMetadata.hasMetaAnnotation(Component.class.getName()));
if (annotationMetadata.hasAnnotation(Component.class.getName())
|| annotationMetadata.hasMetaAnnotation(Component.class.getName())) {
AbstractBeanDefinition bd = BeanDefinitionBuilder
.genericBeanDefinition(reader.getClassMetadata().getClassName())
.getBeanDefinition();
String name = generator.generateBeanName(bd, beanFactory);
beanFactory.registerBeanDefinition(name, bd);
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
解释:
1.我们要解析注解,首先我们要知道哪些类上有这个注解,通过工具类AnnotationUtils找到获取config上的componentScan注解
2.获取到注解后我们就要获取他扫描的包com.sky.component,获取之后我们要将他拼接为路径,才可以被扫描
3.利用一个扫描路径的方法PathMatchingResourcePatternResolver(),可以扫描到路径里的所有得类,获取到了所有的类,我们就要去找哪些类上添加了@component注解
4.遍历我们扫描的路径获取到的类源,再通过CachingMetadataReaderFactory里的getMetadataReader获取到元数据
5.通过getAnnotationMetadata()获取到元数据上的注解信息
6.判断注解是否hasAnnotation(Component.class.getName())或者hasMetaAnnotation(Component.class.getName())
7.有的话我们就利用BeanDefinitionBuilder创建出BeanDefinition对象
8.通过AnnotationBeanNameGenerator以类名作为创建的bean对象名称
9.将创建好的BeanDefinition对象注入到bean工厂中
此时我们解析componentScan注解的后处理器已经制作好了
我们可以再启动类的容器中添加该后处理器,观察结果
可以看到添加了component包下添加了该注解的类都成功注入了bean工厂中
收获
-
Spring 操作元数据的工具类 CachingMetadataReaderFactory
-
通过注解元数据(AnnotationMetadata)获取直接或间接标注的注解信息
-
通过类元数据(ClassMetadata)获取类名,AnnotationBeanNameGenerator 生成 bean 名
-
解析元数据是基于 ASM 技术
手动创建后处理器模拟解析 @Bean
创建类AtBeanPostProcessor并实现BeanDefinitionRegistryPostProcessor接口
public class AtBeanPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
try {
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
MetadataReader reader = factory.getMetadataReader(new ClassPathResource("com/sky/Config.class"));
Set<MethodMetadata> methods = reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
for (MethodMetadata method : methods) {
System.out.println(method);
String initMethod = method.getAnnotationAttributes(Bean.class.getName()).get("initMethod").toString();
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
builder.setFactoryMethodOnBean(method.getMethodName(), "config");
builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
if (initMethod.length() > 0) {
builder.setInitMethodName(initMethod);
}
AbstractBeanDefinition bd = builder.getBeanDefinition();
beanFactory.registerBeanDefinition(method.getMethodName(), bd);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
@bean注解的解析相比@componentScan要简单一些
解释:
1.通过CachingMetadataReaderFactory对象获取到元数据(需要指定扫描的包)
2.获取到元数据后我们可以获取到哪些方法上添加了bean注解,的到一个方法元数据集合
3.遍历集合,创建BeanDefinitionBuilder对象,由于某些方法上bean注解有其他属性
4.通过getAnnotationAttributes并指定哪个注解的里的什么属性,获取它的值转换成字符串存储起来
5.通过BeanDefinitionBuilder对象创建BeanDefinition,由于有的方法通过参数注入了其他bean对象,我们可以在创建bean的时候指定自动装配,通过一个常量指定按构造方法自动装配AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR,获取到的bean注解上的属性值是一个初始化方法我们可以通过setInitMethodName方法来设置
6.创建好的Bean将他们注入到bean工厂中
可以看到druid的初始化方法运行了,以及bean1,sqlsessionfactorybean和datasuorce的bean 对象成功注入了容器当中。
收获
-
进一步熟悉注解元数据(AnnotationMetadata)获取方法上注解信息
手动创建MapperPostProcessor (解析解析 Mapper 接口 )
创建类MapperPostProcessor并实现BeanDefinitionRegistryPostProcessor接口
public class MapperPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
try {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("classpath:com/itheima/a05/mapper/**/*.class");
AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
for (Resource resource : resources) {
MetadataReader reader = factory.getMetadataReader(resource);
ClassMetadata classMetadata = reader.getClassMetadata();
if (classMetadata.isInterface()) {
AbstractBeanDefinition bd = BeanDefinitionBuilder.genericBeanDefinition(MapperFactoryBean.class)
.addConstructorArgValue(classMetadata.getClassName())
.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)
.getBeanDefinition();
AbstractBeanDefinition bd2 = BeanDefinitionBuilder.genericBeanDefinition(classMetadata.getClassName()).getBeanDefinition();
String name = generator.generateBeanName(bd2, beanFactory);
beanFactory.registerBeanDefinition(name, bd);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
mapperscan注解和component注解十分相似,只是在创建bean对象名称有所不同,他是又创建了一个BeanDefinitionBuilder用来获取类的名称,利用 AnnotationBeanNameGenerator创建出bean的名称再注入容器中,过多的细节我就不再阐述了
可以看到mapper的bean对象成功注入
收获
-
Mapper 接口被 Spring 管理的本质:实际是被作为 MapperFactoryBean 注册到容器中
-
Spring 的诡异做法,根据接口生成的 BeanDefinition 仅为根据接口名生成 bean 名
学到了什么
a. @ComponentScan, @Bean, @Mapper 等注解的解析属于核心容器(即 BeanFactory)的扩展功能
b. 这些扩展功能由不同的 BeanFactory 后处理器来完成, 其实主要就是补充了一些 bean 定义