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注释的方法是工厂方法。


7.Spring底层原理之BeanFactory后处理器,模拟@Bean
https://www.zhaojun.inkhttps://www.zhaojun.ink/archives/spring-beanfactory-bean
作者
卑微幻想家
发布于
2022-05-11
许可协议