Spring源码分析---Bean 后置处理器 04

来源: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
  • 33
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值