7.Spring底层原理之BeanFactory后处理器,模拟@Bean
上一节我们讲了如何模拟@ComponentScan进行组件扫描,这一节,我们模拟@Bean,来创建Bean。
模拟@Bean
我们还是用以前的方法,先获取Config类的元信息。把@Bean标注的方法信息都拿到
public class A05Application {
public static final Logger log = LoggerFactory.getLogger(A05Application.class);
public static void main(String[] args) throws IOException {
GenericApplicationContext context = new GenericApplicationContext();
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
MetadataReader metadataReader = factory.getMetadataReader(new ClassPathResource("com/zhaojun/springsource/a05/Config.class"));
Set<MethodMetadata> methods = metadataReader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
for (MethodMetadata method : methods) {
System.out.println(method);
}
context.refresh();
context.close();
}
}
输出
com.zhaojun.springsource.a05.Config.bean1()
com.zhaojun.springsource.a05.Config.sqlSessionFactoryBean(javax.sql.DataSource)
com.zhaojun.springsource.a05.Config.dataSource()
这样我们就获取到了被@Bean注释的方法。
这里需要了解的就是,Config类充当的角色是一个工厂的角色,@Bean标注的方法是一个工厂方法。我们去创建beanDefinition的时候和以前有一些不一样。接下来我们就根据上面获取的方法信息,获取beanDefinition。
public static void main(String[] args) throws IOException {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("config", Config.class);
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
MetadataReader metadataReader = factory.getMetadataReader(new ClassPathResource("com/zhaojun/springsource/a05/Config.class"));
Set<MethodMetadata> methods = metadataReader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
for (MethodMetadata method : methods) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
builder.setFactoryMethodOnBean(method.getMethodName(),"config");
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
context.getDefaultListableBeanFactory().registerBeanDefinition(method.getMethodName(),beanDefinition);
}
context.refresh();
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
context.close();
}
上边我们说过Config类充当工厂的角色,所以builder.setFactoryMethodOnBean方法的两个参数,自热而然的就容易理解了。
这时候运行会报错。
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'sqlSessionFactoryBean': Unsatisfied dependency expressed through method 'sqlSessionFactoryBean' parameter 0: Ambiguous argument values for parameter of type [javax.sql.DataSource] - did you specify the correct bean references as arguments?
这是因为在创建sqlSessionFactoryBean时,需要注入dataSource的。这里我们要需要指定,自动装配的模式,让Spring能够知道如何装配参数。
我们在builder.setFactoryMethodOnBean(method.getMethodName(),"config");
后面在增加一行代码。
builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
默认为
AbstractBeanDefinition.AUTOWIRE_NO
不装配,对于工厂方法和构造方法,选用AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR
装配模式。
重新运行
11:12:46.179 [main] DEBUG org.springframework.context.support.GenericApplicationContext - Refreshing org.springframework.context.support.GenericApplicationContext@42607a4f
11:12:46.219 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'config'
11:12:46.231 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean1'
11:12:46.232 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'sqlSessionFactoryBean'
11:12:46.235 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'dataSource'
11:12:46.255 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'sqlSessionFactoryBean' via factory method to bean named 'dataSource'
11:12:46.260 [main] DEBUG org.apache.ibatis.logging.LogFactory - Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
11:12:46.263 [main] DEBUG org.mybatis.spring.SqlSessionFactoryBean - Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration
11:12:46.309 [main] DEBUG org.mybatis.spring.SqlSessionFactoryBean - Property 'mapperLocations' was not specified.
config
bean1
sqlSessionFactoryBean
dataSource
11:12:46.337 [main] DEBUG org.springframework.context.support.GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@42607a4f, started on Wed May 11 11:12:46 CST 2022
11:12:46.338 [main] INFO com.alibaba.druid.pool.DruidDataSource - {dataSource-0} closing ...
被@Bean标注的方法都解析正确了。但是dataSource()方法的initMethod
参数没有被解析。现在我们添加解析它的代码。
public static void main(String[] args) throws IOException {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("config", Config.class);
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
MetadataReader metadataReader = factory.getMetadataReader(new ClassPathResource("com/zhaojun/springsource/a05/Config.class"));
Set<MethodMetadata> methods = metadataReader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
for (MethodMetadata method : methods) {
// 获取initMethod参数配置的值 若没有配置返回空字符串
String initMethod = method.getAllAnnotationAttributes(Bean.class.getName()).get("initMethod").get(0).toString();
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
builder.setFactoryMethodOnBean(method.getMethodName(),"config");
builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
// 若有配置,设置InitMethodName
if (initMethod.length() > 0){
builder.setInitMethodName(initMethod);
}
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
context.getDefaultListableBeanFactory().registerBeanDefinition(method.getMethodName(),beanDefinition);
}
context.refresh();
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
context.close();
}
输出结果中可以看到
11:26:26.291 [main] INFO com.alibaba.druid.pool.DruidDataSource - {dataSource-1} inited
对其他属性作解析也类似。
独立封装为后处理器
和之前一样,实现BeanFactoryPostProcessor接口
public class AtBeanPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
MetadataReader metadataReader = factory.getMetadataReader(new ClassPathResource("com/zhaojun/springsource/a05/Config.class"));
Set<MethodMetadata> methods = metadataReader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
for (MethodMetadata method : methods) {
String initMethod = method.getAllAnnotationAttributes(Bean.class.getName()).get("initMethod").get(0).toString();
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
builder.setFactoryMethodOnBean(method.getMethodName(),"config");
builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
if (initMethod.length() > 0){
builder.setInitMethodName(initMethod);
}
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
if (beanFactory instanceof DefaultListableBeanFactory){
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory;
defaultListableBeanFactory.registerBeanDefinition(method.getMethodName(),beanDefinition);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
然后在main方法中注册即可
public static void main(String[] args) throws IOException {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("config", Config.class);
context.registerBean(AtBeanPostProcessor.class);
context.refresh();
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
context.close();
}
总结
这一章要理解的就是配置类为工厂,@Bean注释的方法是工厂方法。