文章目录
【探索Spring底层】BeanFactory后处理器
1.常见的工厂后处理器
常见的工厂后处理器有
- ConfigurationClassPostProcessor
- MapperScannerConfigurer
1.1 ConfigurationClassPostProcessor
ConfigurationClassPostProcessor是Spring中最重要的后置处理器,没有之一
它的作用是解析@ComponentScan @Bean @Import @ImportResource
环境准备
@Configuration
@ComponentScan("com.itheima.a05.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.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
}
这里com.itheima.a05.component包里面有三个Bean
分别是Bean2、Bean3、Bean4
@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 管理啦");
}
}
public class A05 {
private static final Logger log = LoggerFactory.getLogger(A05.class);
public static void main(String[] args) throws IOException {
// ⬇️GenericApplicationContext 是一个【干净】的容器
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("config", Config.class);
// ⬇️初始化容器
context.refresh();
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
}
}
运行程序,这里只有config这个Bean
context.registerBean(ConfigurationClassPostProcessor.class);
接着将ConfigurationClassPostProcessor这个BeanFactory后处理器注册到容器中
再次运行程序
容器中多了Bean2和Bean3,说明@ComponentScan注解生效
并且Config里面得三个@Bean注解也生效了
1.2 MapperScannerConfigurer
MapperScannerConfigurer这个作用是用来扫描Mybatis的@Mapper
也就是解析@MapperScanner(但是这种用法已经少用了)
这里准备了两个Mapper
context.registerBean(MapperScannerConfigurer.class, bd -> {
bd.getPropertyValues().add("basePackage", "com.itheima.a05.mapper");
});
规定包扫描的路径
这时候运行程序,就可以发现Mapper1和Mapper2也被注册到容器中了
而下面的那些Bean后处理器则是MapperScannerConfigurer给我们添加的(间接)
2. 手撕两个BeanFactory后处理器
2.1 手撕ConfigurationClassPostProcessor
首先创建一个类,实现BeanDefinitionRegistryPostProcessor接口,这里需要重写两个方法
- postProcessBeanFactory:是在context.refresh,也就是初始化容器的时候被调用
- postProcessBeanDefinitionRegistry:这个方法是在Bean定义信息即将被加载,但是Bean实例并未创建的时候执行的,在这个时机,就可以添加解析@ComponentScan注解的功能
首先,第一步利用AnnotationUtils的findAnnotation在Config类中寻找注解@ComponentScan
接着,第二步根据ComponentScan里面的basePackages,也就是包名,比如com.itheima.a05.component,拼接成路径,因为可能有很多个,所以需要用循环来遍历一下
**然后,第三步new一个PathMatchingResourcePatternResolver对象,利用getResources获取上面拼接好的路径的资源,接着遍历这些资源(class文件)。**PathMatchingResourcePatternResolver是实现了ResourcePatternResolver接口的一个类,所以有getResources方法
而第四步就是,利用CachingMetadataReaderFactory对象(也就是factory,前面已经new出来了,利用他读取class文件),利用它的getMetadataReader方法读取每一个class文件
接下来第五步,利用前面getMetadataReader读取的每一个类源文件返回的MetadataReader对象中的getAnnotationMetadata方法读取这个类中的注解信息
第六步则是判断这些注解信息里面是否有@Component注解(hasAnnotation方法)或者@Component的派生注解(hasMetaAnnotation方法,比如@Controller)
第七步,如果有这些注解,就利用BeanDefinitionBuilder的genericBeanDefinition方法创建这个Bean,创建的时候需要指定类名,可以利用前面第四步获取的MetadataReader对象获取到类名(reader.getClassMetadata().getClassName()),最后getBeanDefinition就拿到Bean的定义了
**第八步,利用AnnotationBeanNameGenerator工具(这个继承了BeanNameGenerator),分析@Component注解,然后给生成对应的Bean名字 **
第九步,利用beanFactory将Bean注册到容器中
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);
String path = "classpath*:" + p.replace(".", "/") + "/**/*.class";
System.out.println(path);
//Spring的一个工具。也用来读取类的源文件
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
//第三步
Resource[] resources = new PathMatchingResourcePatternResolver().getResources(path);
AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
for (Resource resource : resources) {
//第四步
MetadataReader reader = factory.getMetadataReader(resource);
//第五步
AnnotationMetadata annotationMetadata = reader.getAnnotationMetadata();
//第六步
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();
}
}
}
2.2 模拟@Bean标注的工厂方法的解析
想要解析@Bean注解标注的工厂方法,与解析@ComponentScan有点类似
首先实现了BeanDefinitionRegistryPostProcessor接口,重写了postProcessBeanFactory方法和postProcessBeanDefinitionRegistry方法
首先,新建一个CachingMetadataReaderFactory,这个类是用来读取源文件的,然后用getAnnotationMetadata拿到所有跟注解相关的源数据,这里返回结果是一个Set集合
接着遍历这个结合,对里面的方法进行一系列的操作
-
获取注解的属性,像上面的DruidDataSource就会有这个initMethod属性,可以借助get方法获取
@Bean(initMethod = "init") public DruidDataSource dataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setUrl("jdbc:mysql://localhost:3306/test"); dataSource.setUsername("root"); dataSource.setPassword("root"); return dataSource; }
-
创建BeanDefinitionBuilder,在容器的实现也用到了BeanDefinitionBuilder,那时候需要将指定类名或class文件,但是这里是利用工厂方式来创建Bean,所以不需要指定类名
-
利用BeanDefinitionBuilder对象的setFactoryMethodOnBean设置工厂方法,第一个参数是参数为方法名字,既然是工厂方法,当然是要被调用才能创建出一个对象,所以需要new出这个工厂,然后利用这个工厂调用这个工厂方法,这样才能得到这个工厂方法生产出来的对象,所以第二个参数就是这个工厂config
-
并且如果工厂方法有参数的话,例如需要开启自动装配才可以,默认是不开启自动装配的,所以参数的对象默认为null
@Bean public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSource); return sqlSessionFactoryBean; }
-
接着拿到AbstractBeanDefinition(利用BeanDefinitionBuilder对象的getBeanDefinition方法),将这个方法注入到beanFactory中即可
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/itheima/a05/Config.class"));
//拿到相关getAnnotationMetadata
//getAnnotationMetadata拿到所有跟注解相关的源数据
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();
}
}
}
2.3 @Mapper注解的底层
在Spring底层,@Mapper是这样实现的
- new一个MapperFactoryBean,构造参数为添加了@Mapper注解的class
- 给MapperFactoryBean设置SqlSessionFactory对象
@Bean
public MapperFactoryBean<Mapper1> mapper1(SqlSessionFactory sqlSessionFactory) {
MapperFactoryBean<Mapper1> factory = new MapperFactoryBean<>(Mapper1.class);
factory.setSqlSessionFactory(sqlSessionFactory);
return factory;
}
但是这里是一个个地添加,Spring底层也是这样的,不过比较麻烦。
2.4 手撕MapperFactoryBean
MapperFactoryBean的构建和前面@Bean这些的实现也是十分类似的
-
首先获取class文件
-
然后遍历这些文件
-
利用CachingMetadataReaderFactory对象读class文件的信息
-
接着读取类相关的信息
-
判断类是否是接口
-
是接口的话,使用BeanDefinitionBuilder的genericBeanDefinition方法将MapperFactoryBean定义Bean
-
接着需要获取这个类的类名,作为构造参数;并且开启自动装配,因为这个工厂方法需要一个参数SqlSessionFactory对象
-
然后就利用根据接口的名字来生成一个AbstractBeanDefinition对象,但是这个AbstractBeanDefinition对象并不是添加到容器中的,只是为了生成名字,然后将这个名字作为beab的名字将bean注入到容器中
- 注意:这里并不像前面那种利用AnnotationBeanNameGenerator对象来为bean构建一个bean名字,因为每个接口构建的BeanDefinition都是MapperFactoryBean,利用AnnotationBeanNameGenerator生成的bean名字都会是mapperFactoryBean,因此会导致其他接口无法注入到容器中。
public class MapperPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
try {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
//获取class文件
Resource[] resources = resolver.getResources("classpath:com/itheima/a05/mapper/**/*.class");
AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
//遍历文件
for (Resource resource : resources) {
//读取class信息
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 {
}
}