来源:Spring
4. Bean 后置处理器
4.1 常见的 Bean 后置处理器
现有如下三个类:
@Slf4j
@ToString
public class Bean1 {
private Bean2 bean2;
@Autowired
public void setBean2(Bean2 bean2) {
log.info("@Autowired 生效: {}", bean2);
this.bean2 = bean2;
}
private Bean3 bean3;
@Resource
public void setBean3(Bean3 bean3) {
log.info("@Resource 生效: {}", bean3);
this.bean3 = bean3;
}
private String home;
@Autowired
public void setHome(@Value("${JAVA_HOME}") String home) {
log.info("@Value 生效: {}", home);
this.home = home;
}
@PostConstruct
public void init() {
log.info("@PostConstruct 生效");
}
@PreDestroy
public void destroy() {
log.info("@PreDestroy 生效");
}
}
public class Bean2 {
}
public class Bean3 {
}
Bean2 和 Bean3 很简单,而在 Bean1 中使用了多个注解以实现 Bean 注入和值注入。
public class A04Application {
public static void main(String[] args) {
// GenericApplicationContext 是一个干净的容器
GenericApplicationContext context = new GenericApplicationContext();
// 用原始方式注册三个 bean
context.registerBean("bean1", Bean1.class);
context.registerBean("bean2", Bean2.class);
context.registerBean("bean3", Bean3.class);
// 初始化容器。执行 beanFactory 后置处理器,添加 bean 后置处理器,初始化所有单例
context.refresh();
// 销毁容器
context.close();
}
}
运行上述方法后,控制台中只打印了与 Spring 相关的日志信息,也就是说 Bean1 中使用的注解并没有生效。
向 GenericApplicationContext 添加一些与 Bean 后置处理器相关的 Bean,使得 Bean1 中使用的注解能够生效。
public static void main(String[] args) {
// --snip--
context.registerBean("bean3", Bean3.class);
// 解析值注入内容
context.getDefaultListableBeanFactory().setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
// @Autowired @Value
context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
context.refresh();
}
test.bean.a04.Bean1 - @Autowired 生效: test.bean.a04.Bean2@29b5cd00
test.bean.a04.Bean1 - @Value 生效: D:\java\JDK1.8.30
@Autowired 和 @Value 注解成功生效,但 @Resource、@PostConstruct 和 @PreDestroy 依旧没有生效,因此还需要添加解析它们的 Bean 后置处理器。
public static void main(String[] args) {
// @Resource @PostConstruct @PreDestroy
context.registerBean(CommonAnnotationBeanPostProcessor.class);
}
test.bean.a04.Bean1 - @Resource 生效: test.bean.a04.Bean3@12cdcf4
test.bean.a04.Bean1 - @Autowired 生效: test.bean.a04.Bean2@6121c9d6
test.bean.a04.Bean1 - @Value 生效: D:\java\JDK1.8.30
test.bean.a04.Bean1 - @PostConstruct 生效
INFO test.bean.a04.Bean1 - @PreDestroy 生效
解析 @ConfigurationProperties
使用 @ConfigurationProperties 可以指定配置信息的前缀,使得配置信息的读取更加简单。比如:
@Getter
@Setter
@ToString
@ConfigurationProperties(prefix = "java")
public class Bean4 {
private String home;
private String version;
}
上述代码用于获取环境变量中 java.home 和 java.version 的信息。
对先前的 main() 方法进行补充:
public static void main(String[] args) {
context.registerBean("bean4", Bean4.class);
System.out.println(context.getBean(Bean4.class));
}
Bean4(home=null, version=null)
Bean4 成功添加到容器中,但值注入失败了,显然也是因为缺少解析 @ConfigurationProperties 注解的后置处理器。
public static void main(String[] args) {
// --snip--
context.registerBean("bean4", Bean4.class);
// --snip--
// @ConfigurationProperties
ConfigurationPropertiesBindingPostProcessor.register(context.getDefaultListableBeanFactory());
System.out.println(context.getBean(Bean4.class));
// --snip--
}
Bean4(home=D:\java\JDK1.8.30\jre, version=1.8.0_30)
4.2 AutowiredAnnotationBeanPostProcessor
通过前文可知 AutowiredAnnotationBeanPostProcessor 用于解析 @Autowired 和 @Value 注解,那它究竟是怎么工作的呢?
public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 注册成品 Bean,不再进行 Bean 的创建、依赖注入、初始化等操作
beanFactory.registerSingleton("bean2", new Bean2());
beanFactory.registerSingleton("bean3", new Bean3());
// @Value
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
// 查看哪些属性、方法加了 @Autowired,这称之为 InjectionMetadata
AutowiredAnnotationBeanPostProcessor postProcessor = new AutowiredAnnotationBeanPostProcessor();
postProcessor.setBeanFactory(beanFactory);
Bean1 bean1 = new Bean1();
System.out.println(bean1);
// 执行依赖注入,@Autowired、@Value
postProcessor.postProcessProperties(null, bean1, "bean1");
System.out.println(bean1);
}
Bean1(bean2=null, bean3=null, home=null)
19:31:28.309 [main] INFO test.bean.a04.Bean1 - @Value 生效: ${JAVA_HOME}
19:31:28.319 [main] INFO test.bean.a04.Bean1 - @Autowired 生效: test.bean.a04.Bean2@5bcab519
Bean1(bean2=test.bean.a04.Bean2@5bcab519, bean3=null, home=${JAVA_HOME})
在未调用 AutowiredAnnotationBeanPostProcessor#postProcessProperties() 方法时,Bean1 中的 bean2、bean3 和 home 都没有注入成功,而在调用之后,成功注入了 bean2 和 home,但 home 的值似乎有点奇怪,并没有打印出前文中相同的值,可能是因为没有成功解析 #{}?
至于 bean3 为什么没注入成功,是因为 bean3 的注入是利用 @Resource,而不是 @Autowired。如果对 Bean1 进行修改:
public class Bean1 {
@Autowired
private Bean3 bean3;
@Resource
public void setBean3(Bean3 bean3) {
log.info("@Resource 生效: {}", bean3);
this.bean3 = bean3;
}
}
再次运行有:
Bean1(bean2=null, bean3=null, home=null)
21:36:36.402 [main] INFO test.bean.a04.Bean1 - @Value 生效: ${JAVA_HOME}
21:36:36.406 [main] INFO test.bean.a04.Bean1 - @Autowired 生效: test.bean.a04.Bean2@490ab905
Bean1(bean2=test.bean.a04.Bean2@490ab905, bean3=test.bean.a04.Bean3@56ac3a89, home=${JAVA_HOME})
成功注入了 bean3。如果想要成功注入 home,则需要在 BeanFactory 中添加 #{} 的解析器:
public static void main(String[] args) {
// --snip--
// ${} 的解析器
beanFactory.addEmbeddedValueResolver(new StandardEnvironment()::resolvePlaceholders);
// --snip--
postProcessor.postProcessProperties(null, bean1, "bean1");
System.out.println(bean1);
}
Bean1(bean2=null, bean3=null, home=null)
test.bean.a04.Bean1 - @Value 生效: D:\java\JDK1.8.30
test.bean.a04.Bean1 - @Autowired 生效: test.bean.a04.Bean2@4fe3c938
Bean1(bean2=test.bean.a04.Bean2@4fe3c938, bean3=test.bean.a04.Bean3@5383967b, home=D:\java\JDK1.8.30)
AutowiredAnnotationBeanPostProcessor#postProcessProperties()
源码如下:
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
metadata.inject(bean, beanName, pvs);
}
catch (BeanCreationException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
}
return pvs;
}
其中的 findAutowiringMetadata() 用于查找指定的 bean 对象中哪些地方使用了 @Autowired、@Value 等与注入相关的注解,并将这些信息封装在 InjectionMetadata 对象中,之后调用其 inject() 方法利用反射完成注入。
findAutowiringMetadata() 方法是一个私有方法,尝试利用反射进行调用并进行断点查看 InjectionMetadata 对象中的信息:
@SneakyThrows
public static void main(String[] args) {
// --snip--
AutowiredAnnotationBeanPostProcessor postProcessor = new AutowiredAnnotationBeanPostProcessor();
postProcessor.setBeanFactory(beanFactory);
Bean1 bean1 = new Bean1();
Method method = AutowiredAnnotationBeanPostProcessor.class.getDeclaredMethod("findAutowiringMetadata", String.class, Class.class, PropertyValues.class);
method.setAccessible(true);
// 获取 Bean1 上加了 @Value、@Autowired 注解的成员变量、方法参数信息
InjectionMetadata metadata = (InjectionMetadata) method.invoke(postProcessor, "bean1", Bean1.class, null);
// 此处断点
System.out.println(metadata);
}
InjectionMetadata 中有一个名为 injectedElements 的集合类型成员变量,根据上图所示,injectedElements 存储了被相关注解标记的成员变量、方法的信息,因为 Bean1 中的 bean3 成员变量、setBean2() 和 setHome() 方法恰好被 @Autowired 注解标记。
然后按照源码一样,调用 InjectionMetadata#inject() 方法进行依赖注入:
@SneakyThrows
public static void main(String[] args) {
// --snip--
// 获取 Bean1 上加了 @Value、@Autowired 注解的成员变量、方法参数信息
InjectionMetadata metadata = (InjectionMetadata) method.invoke(postProcessor, "bean1", Bean1.class, null);
// 调用 InjectionMetadata 来进行依赖注入,注入时按类型查找值
metadata.inject(bean1, "bean1", null);
System.out.println(bean1);
}
test.bean.a04.Bean1 - @Value 生效: D:\java\JDK1.8.30
test.bean.a04.Bean1 - @Autowired 生效: test.bean.a04.Bean2@5383967b
Bean1(bean2=test.bean.a04.Bean2@5383967b, bean3=test.bean.a04.Bean3@2ac273d3, home=D:\java\JDK1.8.30)
调用 inject() 方法后会利用反射进行依赖注入,但在反射之前,肯定得先拿到被注入的对象或值,那这些对象或值是怎么取到的呢?
可以通过以下代码概括:
@SneakyThrows
public static void main(String[] args) {
// --snip--
// 如何按类型查找值
Field bean3 = Bean1.class.getDeclaredField("bean3");
DependencyDescriptor dd1 = new DependencyDescriptor(bean3, false);
Object o1 = beanFactory.doResolveDependency(dd1, null, null, null);
System.out.println(o1);
Method setBean2 = Bean1.class.getDeclaredMethod("setBean2", Bean2.class);
// MethodParameter 构造方法的第二个参数表示需要解析的方法中参数的索引
DependencyDescriptor dd2 = new DependencyDescriptor(new MethodParameter(setBean2, 0), false);
Object o2 = beanFactory.doResolveDependency(dd2, null, null, null);
System.out.println(o2);
Method setHome = Bean1.class.getDeclaredMethod("setHome", String.class);
DependencyDescriptor dd3 = new DependencyDescriptor(new MethodParameter(setHome, 0), true);
Object o3 = beanFactory.doResolveDependency(dd3, null, null, null);
System.out.println(o3);
}
test.bean.a04.Bean3@2ac273d3
test.bean.a04.Bean2@192b07fd
D:\java\JDK1.8.30