演示3 - 模拟解析 @Bean
其过程就是把 @Bean 标注的方法变为BeanDefinition 最终加入到容器
- @Configuration 标注的类相当于一个工厂
- 里面的 @Bean 其实就充当了一个共工厂方法
- 所以在创建的时候我们用的是工厂方法的模式来创建BeanDeification
步骤如下
1. 拿到我们目标类中的注解信息类信息等
第一二行代码与组件扫描类似不在过多解释,这里我们简单些,把路径路径写死了(注意修改为自己类的路径)
第三行的意思为获取带有@Bean注解的方法,我们解读一下代码:
reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
- 根据reader获取与注解相关的元数据也就是 getAnnotationMetadata()
- 在其基础上在获得被注解标注的方法(我们这里用的是工厂方法创建,所以获取方法详细可以自行查看Config类)
- 被哪个注解标注呢,getAnnotatedMethods()参数里给出Bean.class.getName(),被@Bean标注的。
实现代码如下:
打印查看一下结果(发现被@Bean注解标注的方法都被找了出来):
2.根据找的的方法信息创建BeanDefinition
比先前组件扫描创建BeanDefinition
- 少了在创建BeanDefinition指定的名字
- 多了builder.setFactoryMethodOnBean(method.getMethodName(), “config”); 因为要先创建工厂,工厂方法才能调用
我们用的是工厂方法创捷,类名不用指定,因为没用,我们创建的对象与例如工厂方法config的类名无关
对于上图(参数需要自动装配)我们需要稍微特殊处理不然会报错,需要指定自动装配(对于构造方法,工厂方法参数自动装配模式选择 AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR )
代码实现如下:
4.演示一下@Bean属性的解析过程(这里演示设置初始化方法名字)
- 先拿到@Bean属性
String initMethod = method.getAnnotationAttributes(Bean.class.getName()).get(“initMethod”).toString();
- 在判断一下哪个@Bean有配置,有的话就把修改初始化方法名字改一下
最后注册到容器中即可:
代码:
A05Bean
package com.itheima.a05;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import java.io.IOException;
import java.util.Set;
/*
BeanFactory 后处理器的作用
*/
// 研究 @Bean 标注后被创建流程
public class A05Bean {
private static final Logger log = LoggerFactory.getLogger(A05Bean.class);
public static void main(String[] args) throws IOException {
// ⬇️GenericApplicationContext 是一个【干净】的容器
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("config", Config.class);
// BeanFactory 后处理器
// context.registerBean(ConfigurationClassPostProcessor.class); // 可以解析 @ComponentScan @Bean @Import @ImportResoure
// context.registerBean(MapperScannerConfigurer.class, (BeanDefinition bd) -> {
// bd.getPropertyValues().add("basePackage", "com.itheima.a05.mapper");
// } ); // @MapperScanner 里面也是用这个类
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
MetadataReader reader = factory.getMetadataReader(new ClassPathResource("com/itheima/a05/Config.class"));
Set<MethodMetadata> methods = reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
for (MethodMetadata method : methods) {
System.out.println(method);
// config 中 @Bean(initMethod = "init")
String initMethod = method.getAnnotationAttributes(Bean.class.getName()).get("initMethod").toString();
// 我们用的是工厂方法创捷,类名不用指定,因为没用,我们创建的对象与例如工厂方法config的类名无关
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
// 定义了一个一些config的工厂方法
builder.setFactoryMethodOnBean(method.getMethodName(), "config");
AbstractBeanDefinition bd = builder.getBeanDefinition();
// 指定自动装配模式,解决sqlSessionFactoryBean出错(可以尝试注释这行代码运行试一试)
builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
if (initMethod.length() > 0) {
builder.setInitMethodName(initMethod);
}
context.getDefaultListableBeanFactory().registerBeanDefinition(method.getMethodName(), bd);
}
// ⬇️初始化容器
context.refresh();
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
// ⬇️销毁容器
context.close();
/*
学到了什么
a. @ComponentScan, @Bean, @Mapper 等注解的解析属于核心容器(即 BeanFactory)的扩展功能
b. 这些扩展功能由不同的 BeanFactory 后处理器来完成, 其实主要就是补充了一些 bean 定义
*/
}
}
Config
package com.itheima.a05;
import com.alibaba.druid.pool.DruidDataSource;
import com.itheima.a05.mapper.Mapper1;
import com.itheima.a05.mapper.Mapper2;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
/**
* @Configuration 标注的类相当于一个工厂
* 里面的 @Bean 其实就充当了一个共工厂方法
* 所以在创建的时候我们用的是工厂方法的模式来创建BeanDeification
*/
@Configuration
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;
}
}
结果与上面相同;
收获
- 进一步熟悉注解元数据(AnnotationMetadata)获取方法上注解信息