spring之你所不知道的@Configuration - SpringForAll社区

spring 3.0 之后出提供了 @Configuration@Bean注解,我们可以通过编码的方式达到和 XML 配置等同的效果。 我们可以把 @Configuration看作成 <beans>标签,把 @Bean看作成 <bean>标签。

案例分析

一个汽车配置类 CarConfiguration,包含 3 个工厂方法( @Bean),分别是创建一个 Engine对象和两个 Car对象,在创建 Car对象方法内调用创建 Engine对象的方法。

示例代码如下:

  1. @Configuration
  2. public class CarConfiguration {
  3. /**
  4. * 发动机
  5. */
  6. public static class Engine {
  7. public Engine() {
  8. System.out.println("Engine");
  9. }
  10. }
  11. /**
  12. * 汽车
  13. */
  14. public static class Car {
  15. private String name;
  16. private Engine engine;
  17. public Car(String name, Engine engine) {
  18. this.name = name;
  19. this.engine = engine;
  20. }
  21. public String getName() {
  22. return name;
  23. }
  24. }
  25. /**
  26. * 造一个引擎
  27. */
  28. @Bean
  29. public Engine engine() {
  30. return new Engine();
  31. }
  32. /**
  33. * 造一辆 LP 700-4
  34. */
  35. @Bean
  36. public Car aventador() {
  37. return new Car("LP700-4", engine());
  38. }
  39. /**
  40. * 造一辆 X6
  41. */
  42. @Bean
  43. public Car bmw() {
  44. return new Car("X6", engine());
  45. }
  46. }
    • *

那么问题来了, System.out.println("Engine")这行代码会被执行几次?

揭密问题之前,我们来做个推理。我们知道 spring 中 bean 的 scope 默认是 singleton。上述代码上我们并没有在 engine()方法中指定其 scope,按照单例的语义,那么只应该创建 Engine对象一次。若因为 engine()方法被调用多次而创建了多个 Engine对象那岂不是违背了 scope 为 singleton 本身的语义。

带着问题,我们把程序跑起来,验证一下结果,果然和猜想中的一致,字符串 Engine只会被打印一次。

那么问题又来了,spring 是如何做到的呢?

AOP! 对,我们很容易联想到 spring 中的 AOP,通过 AOP 我可以增强类的行为,诸如大家都很熟悉的声明式事物。

为了验证猜想,我们在 pom 中添加依赖 spring-boot-starter-actuator,通过浏览器访问 http://<host>:<port>/beans 来查看 CarConfiguration的相关信息,如下:

  1. {
  2. bean: "com.owenxh.springadvancedexamples.configure.CarConfiguration",
  3. aliases: [ ],
  4. scope: "singleton",
  5. type: "com.owenxh.springadvancedexamples.configure.CarConfiguration$$EnhancerBySpringCGLIB$$7c72aec8",
  6. resource: "null",
  7. dependencies: [ ]
  8. }

我们看到了 CarConfiguration$$EnhancerBySpringCGLIB,这表明 CarConfiguration已经被 spring 通过 CGLIB 来进行增强了。我们现在可以确定 spring 通过 aop 对标注了 @Bean的工厂方法进行了拦截处以保证 scope 的语义正确性。

注:为了方便访问 /beans, 我们需要在 properties文件中添加如下配置:
  1. management.security.enabled = false

新的问题又来了,spring 在什么时候对 @Configuration类进行增强的呢?

这就需要去翻 spring 的源码了。

源码解析

长话短说,最终我们找到两个类: ConfigurationClassPostProcessorConfigurationClassEnhancer

ConfigurationClassPostProcessorBeanDefinitionRegistryPostProcessor(从 BeanFactoryPostProcessor派生)接口的实现,在 AbstractApplicationContext.refresh()方法中被触发执行。

看一下 ConfigurationClassPostProcessor中的方法 postProcessBeanFactory(ConfigurableListableBeanFactorybeanFactory)的代码:

  1. public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
  2. int factoryId = System.identityHashCode(beanFactory);
  3. if (this.factoriesPostProcessed.contains(factoryId)) {
  4. throw new IllegalStateException(
  5. "postProcessBeanFactory already called on this post-processor against " + beanFactory);
  6. }
  7. this.factoriesPostProcessed.add(factoryId);
  8. if (!this.registriesPostProcessed.contains(factoryId)) {
  9. // BeanDefinitionRegistryPostProcessor hook apparently not supported...
  10. // Simply call processConfigurationClasses lazily at this point then.
  11. processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
  12. }
  13. // 增强 Configuration 类
  14. enhanceConfigurationClasses(beanFactory);
  15. beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
  16. }

enhanceConfigurationClasses(beanFactory)这一行代码中用到了前面提到的 ConfigurationClassEnhancer对 Configuration 类进行增强(代码过长,不贴了)。 增强后的 Configuration 内置了拦截器 BeanMethodInterceptorBeanFactoryAwareMethodInterceptor,这两个都是 ConfigurationClassEnhancer中的内部类,我们来分别介绍。

  • BeanFactoryAwareMethodInterceptor
增强后的 Configuration 类实现 EnhancedConfiguration接口(也是 ConfigurationClassEnhancer中的内部类), BeanFactoryAwareMethodInterceptor的作用就是给增强后的 Configuration 类注入 BeanFactory

EnhancedConfiguration代码如下:

  1. public interface EnhancedConfiguration extends BeanFactoryAware {
  2. }
  • BeanMethodInterceptor
BeanMethodInterceptor中的逻辑就是决定直接执行 @Bean工厂方法创建 bean 或者从 BeanFactory中来获取 bean,核心代码如下:
  1. public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
  2. MethodProxy cglibMethodProxy) throws Throwable {
  3. ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
  4. String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);
  5. // Determine whether this bean is a scoped-proxy
  6. Scope scope = AnnotatedElementUtils.findMergedAnnotation(beanMethod, Scope.class);
  7. if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) {
  8. String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
  9. if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
  10. beanName = scopedBeanName;
  11. }
  12. }
  13. if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) &&
  14. factoryContainsBean(beanFactory, beanName)) {
  15. Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
  16. if (factoryBean instanceof ScopedProxyFactoryBean) {
  17. // Scoped proxy factory beans are a special case and should not be further proxied
  18. }
  19. else {
  20. // It is a candidate FactoryBean - go ahead with enhancement
  21. return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);
  22. }
  23. }
  24. if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
  25. // The factory is calling the bean method in order to instantiate and register the bean
  26. // (i.e. via a getBean() call) -> invoke the super implementation of the method to actually
  27. // create the bean instance.
  28. // 省略 log 代码
  29. return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
  30. }
  31. return obtainBeanInstanceFromFactory(beanMethod, beanMethodArgs, beanFactory, beanName);
  32. }

小结

通过以上案例分析以及对源码的解析,我们进一步了解了 @Configuration注解。spring 通过 CGLIB 对标注了 @Configuration注解的 Configuration 类进行了增强,增加后的后被注入 BeanFactory,spring 会对 @Bean方法进行拦截,根据触发点的不同,以决定直接执行工厂方法还是从 BeanFactory中获取 bean。

推荐:Spring Cloud Alibaba基础教程:使用Nacos实现服务注册与发现

上一篇:Sentinel Dashboard中修改规则优雅同步到Apollo

 关注公众号

点击原文阅读更多


Original url: Access
Created at: 2019-05-10 14:09:41
Category: default
Tags: none

请先后发表评论
  • 最新评论
  • 总共0条评论