Bean后置处理器与注解的关系
首先以一个没有添加额外的后置处理器来说明:️GenericApplicationContext
GenericApplicationContext context = new GenericApplicationContext(); public class Bean1 { private static final Logger log = LoggerFactory.getLogger(Bean1.class); private Bean2 bean2; @Autowired public void setBean2(Bean2 bean2) { log.debug("@Autowired 生效: {}", bean2); this.bean2 = bean2; } @Autowired private Bean3 bean3; @Resource public void setBean3(Bean3 bean3) { log.debug("@Resource 生效: {}", bean3); this.bean3 = bean3; } private String home; @Autowired public void setHome(@Value("${JAVA_HOME}") String home) { log.debug("@Value 生效: {}", home); this.home = home; } @PostConstruct public void init() { log.debug("@PostConstruct 生效"); } @PreDestroy public void destroy() { log.debug("@PreDestroy 生效"); } @Override public String toString() { return "Bean1{" + "bean2=" + bean2 + ", bean3=" + bean3 + ", home='" + home + '\'' + '}'; } } public class Bean2 { } public class Bean3 { } // ⬇️用原始方法注册三个 bean context.registerBean("bean1", Bean1.class); context.registerBean("bean2", Bean2.class); context.registerBean("bean3", Bean3.class); context.refresh(); // 执行beanFactory后处理器, 添加bean后处理器, 初始化所有单例
运行发现,bean1 bean2 bean3 并没有被注册成功!
通过分析原因是因为 bean1 中得 @Autowired 和 @ value并没有生效,那么接下来启用它
// 自动装配候选者的解析器 context.getDefaultListableBeanFactory().setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()); context.registerBean(AutowiredAnnotationBeanPostProcessor.class); // @Autowired @Value
此时 bean 中的 @Autowired 和 @Value 就被注入了
[DEBUG] 21:07:55.642 [main] com.itheima.a04.Bean1 - @Value 生效: C:\Program Files\Java\jdk1.8.0_231 [DEBUG] 21:07:55.647 [main] com.itheima.a04.Bean1 - @Autowired 生效: com.itheima.a04.Bean2@120f102b
而如果想要@Resource @PostConstruct @PreDestroy 也被注入,需要 启动这个
context.registerBean(CommonAnnotationBeanPostProcessor.class); // @Resource @PostConstruct @PreDestroy
效果如下:
[DEBUG] 21:12:03.082 [main] com.itheima.a04.Bean1 - @Resource 生效: com.itheima.a04.Bean3@37858383 [DEBUG] 21:12:03.093 [main] com.itheima.a04.Bean1 - @Autowired 生效: com.itheima.a04.Bean2@59af0466 [DEBUG] 21:12:03.102 [main] com.itheima.a04.Bean1 - @Value 生效: C:\Program Files\Java\jdk1.8.0_231 [DEBUG] 21:12:03.102 [main] com.itheima.a04.Bean1 - @PostConstruct 生效 [DEBUG] 21:12:03.186 [main] com.itheima.a04.Bean1 - @PreDestroy 生效
通过上述其实可以看到, @Autowired 等注解的解析 属于 bean 生命周期阶段 (依赖注入,初始化)的扩展功能,而这些扩展功能由bean的后处理器完成。
并且仔细观察输出的结果可以发现,先 @Resource 后 @Autowired,这是其源码的执行顺序有关,其@Resource逻辑判断在@Autowired之前。
以上是加载一些 普通类,但是对于加载配置类 其实还是不行的
@ConfigurationProperties(prefix = "java") public class Bean4 { private String home; private String version; public String getHome() { return home; } public void setHome(String home) { this.home = home; } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } @Override public String toString() { return "Bean4{" + "home='" + home + '\'' + ", version='" + version + '\'' + '}'; } } context.registerBean("bean4", Bean4.class); System.out.println(context.getBean(Bean4.class));
发现其配置类中并没有成功加载上对应的home 和 version
Bean4{home='null', version='null'}
其实在这里还是需要添加一个新的后置处理器:
ConfigurationPropertiesBindingPostProcessor.register(context.getDefaultListableBeanFactory());
其实到这里,本质就是说明了常用注解 与 后置处理器之间的关系,需要加入对应的后置处理器,才会让这些注解生效。
详解Bean后置处理器生效的过程
还是以上面的代码举例:
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); beanFactory.registerSingleton("bean2", new Bean2()); // 创建过程,依赖注入,初始化 beanFactory.registerSingleton("bean3", new Bean3()); beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()); // @Value
实际上刚才让 @Autowired 和 @Value 起作用的是AutowiredAnnotationBeanPostProcessor.class
那么接下来就进入内部来看看具体的实现流程吧。
// 查找哪些属性、方法加了 @Autowired, 这称之为 InjectionMetadata AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor(); processor.setBeanFactory(beanFactory); // 自己创建的,依赖注入等都不会生效 Bean1 bean1 = new Bean1(); System.out.println(bean1); processor.postProcessProperties(null, bean1, "bean1"); // 执行依赖注入 @Autowired @Value System.out.println(bean1);
输出:
Bean1{bean2=null, bean3=null, home='null'} [DEBUG] 21:32:35.864 [main] com.itheima.a04.Bean1 - @Value 生效: ${JAVA_HOME} [DEBUG] 21:32:35.873 [main] com.itheima.a04.Bean1 - @Autowired 生效: com.itheima.a04.Bean2@4562e04d Bean1{bean2=com.itheima.a04.Bean2@4562e04d, bean3=com.itheima.a04.Bean3@2a65fe7c, home='${JAVA_HOME}'}
其实可以发现,当processor.postProcessProperties(null, bean1, “bean1”); 之后,实际上bean1已经被注入了
而后进入到postProcessProperties方法查看
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) { InjectionMetadata metadata = this.findAutowiringMetadata(beanName, bean.getClass(), pvs); try { metadata.inject(bean, beanName, pvs); return pvs; } catch (BeanCreationException var6) { throw var6; } catch (Throwable var7) { throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", var7); } }
可以看到,发现发现findAutowiringMetadata 其实也就是那些属性,那些方法参数上由Autowired注解,找到以后,封装到InjectionMetadata中,然后Inject进去。
进入findAutowiringMetadata里面查看发现:
private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) { String cacheKey = StringUtils.hasLength(beanName) ? beanName : clazz.getName(); InjectionMetadata metadata = (InjectionMetadata)this.injectionMetadataCache.get(cacheKey); if (InjectionMetadata.needsRefresh(metadata, clazz)) { synchronized(this.injectionMetadataCache) { metadata = (InjectionMetadata)this.injectionMetadataCache.get(cacheKey); if (InjectionMetadata.needsRefresh(metadata, clazz)) { if (metadata != null) { metadata.clear(pvs); } metadata = this.buildAutowiringMetadata(clazz); this.injectionMetadataCache.put(cacheKey, metadata); } } } return metadata; }
其是private,那么我们首先用反射的方式获取到这个方法:
// 拿到私有方法 Method findAutowiringMetadata = AutowiredAnnotationBeanPostProcessor.class.getDeclaredMethod("findAutowiringMetadata", String.class, Class.class, PropertyValues.class); findAutowiringMetadata.setAccessible(true);
// 这行代码其实就是去执行了findAutowiringMetadata,分析bean1这个类中,有那个有autowire修饰 解析器收集起来 InjectionMetadata metadata = (InjectionMetadata) findAutowiringMetadata.invoke(processor, "bean1", Bean1.class, null);// 获取 Bean1 上加了 @Value @Autowired 的成员变量,方法参数信息 // 由于有些方法并没有重写toString,所以需要加 断点来测试 System.out.println(metadata);
断点如图所示:
此时其实就是找到了所有的@Autowired
此时调用 InjectionMetadata 来进行依赖注入, 注入时按类型查找值
metadata.inject(bean1, "bean1", null); System.out.println(bean1);
而inject做了什么呢?首先通过反射获取到成员变量或者方法
Field bean3 = Bean1.class.getDeclaredField("bean3"); DependencyDescriptor dd1 = new DependencyDescriptor(bean3, false); // 根据成员变量的信息得到类型 Object o = beanFactory.doResolveDependency(dd1, null, null, null); System.out.println(o);
这段代码其实也就是inject的本质了,首先通过反射API来获取Bean1类中名为“bean3”的成员变量的Field对象,然后Spring内部封装了一个DependencyDescriptor 对象来描述bean3成员变量的依赖,最后,会调用doResolveDependency()方法来解析 bean3的依赖,这意味着Spring会尝试查找并注入名为“bean3”的成员变量所需的Bean,然后打印返回值,这主要是 “bean3”的依赖,也就是所需要的 Bean的实例。
而@Autowired的方法也大同小异:
Method setBean2 = Bean1.class.getDeclaredMethod("setBean2", Bean2.class); DependencyDescriptor dd2 = new DependencyDescriptor(new MethodParameter(setBean2, 0), true); Object o1 = beanFactory.doResolveDependency(dd2, null, null, null); System.out.println(o1); Method setHome = Bean1.class.getDeclaredMethod("setHome", String.class); DependencyDescriptor dd3 = new DependencyDescriptor(new MethodParameter(setHome, 0), true); Object o2 = beanFactory.doResolveDependency(dd3, null, null, null); System.out.println(o2);
BeanFactory后置处理器与注解关系
还是以一个例子来讲解吧
GenericApplicationContext context = new GenericApplicationContext(); context.registerBean("config", Config.class); @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; } } public class Bean1 { private static final Logger log = LoggerFactory.getLogger(Bean1.class); public Bean1() { log.debug("我被 Spring 管理啦"); } } context.refresh(); for (String name : context.getBeanDefinitionNames()) { System.out.println(name); }
实际上输出只会打印出来config,并没有其他的,结合Bean后置处理器与注解的关系,我们可以轻易的想到,一定是缺少了什么后置处理器,所以导致其他的输出不出来。
context.registerBean(ConfigurationClassPostProcessor.class); // @ComponentScan @Bean @Import @ImportResource
发现输出结果和我们预期的是一致的:
[DEBUG] 22:07:04.915 [main] com.itheima.a05.component.Bean2 - 我被 Spring 管理啦 [DEBUG] 22:07:04.919 [main] com.itheima.a05.component.Bean3 - 我被 Spring 管理啦 [DEBUG] 22:07:04.928 [main] com.itheima.a05.Bean1 - 我被 Spring 管理啦 [INFO ] 22:07:05.039 [main] c.a.druid.pool.DruidDataSource - {dataSource-1} inited config org.springframework.context.annotation.ConfigurationClassPostProcessor bean2 bean3 bean1 sqlSessionFactoryBean dataSource [INFO ] 22:07:05.107 [main] c.a.druid.pool.DruidDataSource - {dataSource-1} closing ... [INFO ] 22:07:05.108 [main] c.a.druid.pool.DruidDataSource - {dataSource-1} closed
以上其实就将我们的beanFactory 配置好了,config里面的配置都被注册成了bean,用过Mybatis的都知道,最终我们使用的是Mapper接口,如果我们配置了Mapper接口,那么是否会被注册进来呢?
@Mapper public interface Mapper1 { } @Mapper public interface Mapper2 { }
测试发现并没有,所以还需要配置另一种后置处理器
context.registerBean(MapperScannerConfigurer.class, bd -> { // @MapperScanner 自动配置间接的用到了这个 bd.getPropertyValues().add("basePackage", "com.itheima.a05.mapper"); });
此时在结果中可以看到,mapper1 mapper2都被注册进来了。
通过上文可以看到@ComponentScan, @Bean, @Mapper 等注解的解析属于核心容器(即 BeanFactory)的扩展功能,这些扩展功能由不同的 BeanFactory 后处理器来完成, 其实主要就是补充了一些 bean 定义。
详解BeanFactory后置处理器生效的过程
@ComponentScan
public class ComponentScanPostProcessor implements BeanDefinitionRegistryPostProcessor { @Override // context.refresh 这个方法在所有的bean定义都已经被加载并且bean实例化之前调用。它允许你在实例化之前对bean工厂进行后置处理操作 public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { } // 模拟解析 @Override // 这个方法在所有的bean定义被加载,但是还未实例化之前调用。它允许你对注册的bean定义进行修改、添加或删除。 public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException { try { // 扫描Config类里面的ComponentScan注解 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); // 用于生成Bean名称的策略类 AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator(); for (Resource resource : resources) { // System.out.println(resource); // 读取每一个class 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())) { /** 创建一个通用的Bean定义实例,该实例基于提供的类名(reader.getClassMetadata().getClassName())创建。 具体来说,它会为给定的类名创建一个RootBeanDefinition实例,该实例包含了类的元数据信息。 */ AbstractBeanDefinition bd = BeanDefinitionBuilder .genericBeanDefinition(reader.getClassMetadata().getClassName()) .getBeanDefinition(); /** 然后,使用generateBeanName()方法生成一个唯一的Bean名称。这个方法会根据给定的Bean定义 和Bean工厂的规则来生成一个合适的名称。生成的名称通常会基于类名,并在名称冲突时添加序号以保证唯一性。 */ String name = generator.generateBeanName(bd, beanFactory); /** 最后,通过beanFactory.registerBeanDefinition()方法将生成的Bean定义注册到Bean工厂中, 使得该Bean能够被Spring容器管理和使用。 */ beanFactory.registerBeanDefinition(name, bd); } } } } } catch (IOException e) { e.printStackTrace(); } } } context.registerBean(ComponentScanPostProcessor.class); // 解析 @ComponentScan
其实本质上就是读取@ComponentScan 注解里面的包结构,然后遍历每一个包路径里面所有的资源,如果对应的资源有@Component 或者器 派生类的话,就将其注入进去即可。
输出:
com.itheima.a05.component classpath*:com/itheima/a05/component/**/*.class [DEBUG] 22:18:26.118 [main] com.itheima.a05.component.Bean2 - 我被 Spring 管理啦 [DEBUG] 22:18:26.121 [main] com.itheima.a05.component.Bean3 - 我被 Spring 管理啦 config com.itheima.a05.ComponentScanPostProcessor bean2 bean3
@Bean
上面的输出其实并没有把config里面的三个bean也注入进去,所以还需要完善bean的注入:
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")); 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(); // 定义config里面的工厂方法 builder.setFactoryMethodOnBean(method.getMethodName(), "config"); // 自动装配,因为sqlSessionFactoryBean 有参数,需要将dataSource 装配进来 // 构造方法的参数,工厂方法的参数,自动装配,选择装配模式AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR); if (initMethod.length() > 0) { /** builder.setInitMethodName(initMethod) 方法的作用是将初始化方法名称设置到 BeanDefinitionBuilder 中, 让 Spring 容器在创建 bean 实例之后,自动调用这个指定的初始化方法。 具体而言,当使用 BeanDefinitionBuilder 构建一个 BeanDefinition 对象时,可以通过该方法来设置创建的 bean 需要执行的初始化方法名称,从而在创建 bean 实例时自动调用这个方法。 */ builder.setInitMethodName(initMethod); } AbstractBeanDefinition bd = builder.getBeanDefinition(); beanFactory.registerBeanDefinition(method.getMethodName(), bd); } } catch (IOException e) { e.printStackTrace(); } } } context.registerBean(AtBeanPostProcessor.class); // 解析 @Bean
本质上的解析其实也都是类似的,不用的在于,没有遍历包路径这一层,只需要遍历指定 config.class里面的方法即可。
此时其实打印输出就可以看到里面的bean也被注入了。
@Mapper
mapper接口其实和前两个还不太一样,比如将其注册在BeanFactory中:
@Bean public MapperFactoryBean<Mapper1> mapper1(SqlSessionFactory sqlSessionFactory) { MapperFactoryBean<Mapper1> factory = new MapperFactoryBean<>(Mapper1.class); factory.setSqlSessionFactory(sqlSessionFactory); return factory; } @Bean public MapperFactoryBean<Mapper2> mapper2(SqlSessionFactory sqlSessionFactory) { MapperFactoryBean<Mapper2> factory = new MapperFactoryBean<>(Mapper2.class); factory.setSqlSessionFactory(sqlSessionFactory); return factory; }
这样mapper其实也是能被注入的,但是如果手动的将 mapper包下的所有接口都这样注入,岂不是要麻烦死,而且也不符合自动注入的思想对吧?那么可以这样:
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()) { /** @Bean public MapperFactoryBean<Mapper2> mapper2(SqlSessionFactory sqlSessionFactory) { MapperFactoryBean<Mapper2> factory = new MapperFactoryBean<>(Mapper2.class); factory.setSqlSessionFactory(sqlSessionFactory); return factory; } */ AbstractBeanDefinition bd = BeanDefinitionBuilder.genericBeanDefinition(MapperFactoryBean.class) .addConstructorArgValue(classMetadata.getClassName()) .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE) .getBeanDefinition(); AbstractBeanDefinition bd2 = BeanDefinitionBuilder.genericBeanDefinition(classMetadata.getClassName()).getBeanDefinition(); /** 如果直接使用bd 来生成名字,由于 MapperFactoryBean.class 所以 两个mapper 都是 叫 MapperFactoryBean 所以参考spring源码 以classMetadata.getClassName() 作为名字生成bean */ String name = generator.generateBeanName(bd2, beanFactory); beanFactory.registerBeanDefinition(name, bd); } } } catch (IOException e) { e.printStackTrace(); } } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { } }
其实本质上还是和前两种是一样的,但是在这里有一个坑,就是命名上,如果直接使用bd 来生成名字,由于 MapperFactoryBean.class是唯一的 所以 两个mapper 都是 叫 MapperFactoryBean,所以在这里,参考Spring源码,用第二个AbstractBeanDefinition 以classMetadata.getClassName() 作为名字生成bean。这样就不会出现重名的问题了。