【问题标题】:Spring 3.1 Java configuration and inner beansSpring 3.1 Java 配置和内部 bean
【发布时间】:2012-11-16 04:28:15
【问题描述】:
@Bean
public TimedRepository timedRepository(RealRepository repo) {
    return new TimedRepository(repo, timer); // Adds some metrics
}

@Bean
public RealRepository realRepository(DataSource ds) {
    return new RealRepository(ds); // The real jdbc implementation
}

在过去的 XML 时代,我会将真实存储库配置为匿名内部 bean。是否可以使用新的 Java 配置方法做类似的事情?在timedRepository 工厂方法中实例化真正的存储库不是一个选项,因为我希望Spring 接收RealRepository 上的注释。

动机是避免使用任何其他 bean 来获取真正的存储库实现。我还应该提到,两个 bean 都实现了一个 Repository 接口,任何 bean 都将使用该接口,具体取决于存储库(它们不必知道 TimedRepositoryRealRepository

【问题讨论】:

    标签: java spring


    【解决方案1】:

    迟到的答案,但这在 Spring Core 4+(可能还有 Spring Core 3)中是可能的,但有一些技巧。

    虽然标准 Spring 语义不支持使用 JavaConfig 创建内部 bean,但可以利用内部 bean 周围的内部功能来产生相同的结果。

    在属性值解析期间由BeanDefinitionValueResolver 生成内部bean(请参阅BeanDefinitionValueResolver#resolveValueIfNecessary)。 Spring 中“内部 bean”的概念主要包含在这个值解析器(它是内部 bean 的唯一生产者)和术语“包含的 bean”(来自父类 DefaultSingletonBeanRegistry)下的 bean 工厂中。

    根据BeanDefinitionValueResolver 中提出的解析策略,我们可以通过将属性定义为BeanDefinition 来欺骗Spring 生成额外的内部bean:

    @Configuration
    public class MyConfiguration {
    
        private static Logger logger = LoggerFactory.getLogger(MyConfiguration.class);
    
        private RealRepository realRepository;
        private Timer timer;
    
    
        public MyConfiguration(@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") RealRepository realRepository, Timer timer) {
            this.realRepository = realRepository;
            this.timer = timer;
            logger.info("Constructed MyConfiguration {}", this);
        }
    
    
        @Bean
        public TimedRepository timedRepository() {
            TimedRepository timedRepository = new TimedRepository(this.realRepository, this.timer);
            logger.info("Created timed repo: {}", timedRepository);
            return timedRepository;
        }
    
    
        public RealRepository realRepository(DataSource dataSource) {
            RealRepository realRepository = new RealRepository(dataSource);
            logger.info("Created real repo: {}", realRepository);
            return realRepository;
        }
    
    
        @Override
        public String toString() {
            return "MyConfiguration{" +
                    "realRepository=" + realRepository +
                    ", timer=" + timer +
                    '}';
        }
    }
    
    @Component
    public class InnerBeanInjectionBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered {
    
        @Override
        public int getOrder() {
            // Preempt execution of org.springframework.context.annotation.ConfigurationClassPostProcessor
            return 0;
        }
    
    
        @Override
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
            String[] beanDefinitionNameList = ((ConfigurableListableBeanFactory) registry).getBeanNamesForType(MyConfiguration.class, true, false);
            assert beanDefinitionNameList.length == 1;
            BeanDefinition configurationBeanDefinition = registry.getBeanDefinition(beanDefinitionNameList[0]);
            BeanDefinition realRepositoryBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(MyConfiguration.class)
                    .setScope(BeanDefinition.SCOPE_SINGLETON)
                    .setFactoryMethod("realRepository")
                    .setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR)
                    .getBeanDefinition();
            configurationBeanDefinition.getConstructorArgumentValues()
                    .addGenericArgumentValue(realRepositoryBeanDefinition);
        }
    
    
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
            // Do nothing
        }
    }
    

    此解决方案的明显问题是它需要通过BeanDefinitionRegistryPostProcessor 进行手动处理,这在此需要大量工作才能获得小幅收益。我会建议如下:

    • 创建自定义注释(例如,@InnerBean
    • 将此注解附加到@Configuration 类和候选组件类中的所需方法
    • 调整BeanDefinitionRegistryPostProcessor 以扫描类以查找@InnerBean 注释的静态方法(应在#postProcessBeanFactory 中扫描组件类,在#postProcessBeanDefinitionRegistry 中扫描配置类)
    • 将 bean 定义附加到包含 bean 定义的自动装配的构造函数字段(或设置器字段,如果这是您的约定)

    以下是一个例子:

    @Target(ElementType.METHOD)
    public @interface InnerBean {
    }
    
    @Configuration
    public class MyConfiguration {
    
        private static Logger logger = LoggerFactory.getLogger(MyConfiguration.class);
    
        private RealRepository realRepository;
        private Timer timer;
    
    
        public MyConfiguration(@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") RealRepository realRepository, Timer timer) {
            this.realRepository = realRepository;
            this.timer = timer;
            logger.info("Constructed MyConfiguration {}", this);
        }
    
    
        @Bean
        public TimedRepository timedRepository() {
            TimedRepository timedRepository = new TimedRepository(this.realRepository, this.timer);
            logger.info("Created timed repo: {}", timedRepository);
            return timedRepository;
        }
    
    
        @InnerBean
        public static RealRepository realRepository(DataSource dataSource) {
            RealRepository realRepository = new RealRepository(dataSource);
            logger.info("Created real repo: {}", realRepository);
            return realRepository;
        }
    
    
        @Override
        public String toString() {
            return "MyConfiguration{" +
                    "realRepository=" + realRepository +
                    ", timer=" + timer +
                    '}';
        }
    }
    
    @Component
    public class InnerBeanInjectionBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered {
    
        private static Logger logger = LoggerFactory.getLogger(InnerBeanInjectionBeanFactoryPostProcessor.class);
    
        private Set<BeanDefinition> processedBeanDefinitionSet = new HashSet<>();
    
    
        @Override
        public int getOrder() {
            // Preempt execution of org.springframework.context.annotation.ConfigurationClassPostProcessor
            return 0;
        }
    
    
        @Override
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
            ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory) registry;
            String[] configBeanDefinitionNames = beanFactory.getBeanNamesForAnnotation(Configuration.class);
            Arrays.stream(configBeanDefinitionNames)
                    .map(beanFactory::getBeanDefinition)
                    .filter(this::isCandidateBean)
                    .peek(this.processedBeanDefinitionSet::add)
                    .forEach(this::autowireInnerBeans);
        }
    
    
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
            Arrays.stream(beanFactory.getBeanDefinitionNames())
                    .map(beanFactory::getBeanDefinition)
                    .filter(this::isCandidateBean)
                    .filter(beanDefinition -> !this.processedBeanDefinitionSet.contains(beanDefinition))
                    .forEach(this::autowireInnerBeans);
        }
    
    
        private boolean isCandidateBean(BeanDefinition beanDefinition) {
            return beanDefinition.getBeanClassName() != null && beanDefinition.getBeanClassName().startsWith("com.example.demo.");
        }
    
    
        private void autowireInnerBeans(BeanDefinition beanDefinition) {
            // Get @InnerBean methods
            assert beanDefinition instanceof AnnotatedBeanDefinition;
            AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) beanDefinition;
            Set<MethodMetadata> innerBeanMethods = annotatedBeanDefinition.getMetadata().getAnnotatedMethods(InnerBean.class.getName());
    
            // Attach inner beans as constructor parameters
            for (MethodMetadata method : innerBeanMethods) {
                String innerBeanName = method.getMethodName();
                if (!method.isStatic()) {
                    logger.error("@InnerBean definition [{}] is non-static. Inner beans must be defined using static factory methods.", innerBeanName);
                    continue;
                }
    
                BeanDefinition innerBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(beanDefinition.getBeanClassName())
                        .setScope(BeanDefinition.SCOPE_SINGLETON)
                        .setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR)
                        .setFactoryMethod(innerBeanName)
                        .getBeanDefinition();
                beanDefinition.getConstructorArgumentValues()
                        .addGenericArgumentValue(new ConstructorArgumentValues.ValueHolder(innerBeanDefinition, method.getReturnTypeName(), method.getMethodName()));
            }
        }
    }
    

    这样做会有一些好处和注意事项。一大好处是 bean 生命周期将由 Spring IoC 容器管理,这意味着将调用生命周期回调(例如 @PostConstruct@PreDestroy)。 bean 可以根据 parent 的生命周期进行自动管理。需要注意的是,bean 不能作为工厂方法参数注入(尽管您可能需要做一些工作来解决这个问题)并且 AOP 代理不会应用于 @Configuration 类中的这些方法(即 realRepository() 应该永远不会被调用,因为它不会引用单例内部 bean——相反,应该始终引用实例字段)。需要添加进一步的代理(类似于ConfigurationClassEnhancer.BeanMethodInterceptor)才能应用此功能。

    【讨论】:

      【解决方案2】:

      您可以手动实例化 bean:

      public class BeanThatDependsOnRealRepository() {
      
        private final Repository repository;
      
        @Inject
        public BeanThatDependsOnRealRepository(DataSource dataSource) {
          this.repository = new RealRepository(dataSource);
        }
      }
      

      这本质上就是匿名内部 bean 在 XML 中所做的事情。您刚刚在封闭类的构造函数中显式构造了它并从 Spring 中获取了它的依赖关系。

      【讨论】:

      • 如果你这样做,Spring 不会接受像 Transactional 这样的注释。
      • 没错。如果你想让 Spring 代理你的对象,它们必须在上下文中定义。
      【解决方案3】:

      当使用基于 java 的配置时,我认为没有等效于内部或本地 bean。我可能会尝试在 TimedRepositories bean 方法中创建 RealRepository,方法是询问方法签名中的所有依赖项。但是如果你真的需要 spring 来处理 RealRepository 依赖项,你需要使用 bean factory。

      @RunWith(SpringJUnit4ClassRunner.class)
      @ContextConfiguration(loader=AnnotationConfigContextLoader.class)
      public class ConfigTest {
      
          @Autowired TimedRepository timedRepo;
      
          @Test
          public void testRepository() {
              Assert.assertNotNull(timedRepo);
          }
      
      
          @Configuration
          static class TimedRepositoryConfiguration {
      
              @Autowired
              private AutowireCapableBeanFactory beanFactory;
      
              @Bean
              public TimedRepository timedRepository() {
                  RealRepository realRepository = (RealRepository) beanFactory.createBean(RealRepository.class, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, true);
                  return new TimedRepository(realRepository);
              }
      
              public RealRepository realRepository() {
                  return new RealRepository();
              }
          }
      
          static class RealRepository {
      
          }
      
          static class TimedRepository {
      
              private RealRepository realRepo;
      
              public TimedRepository(RealRepository r) {
                  this.realRepo = r;
              }
          }
      
      }
      

      【讨论】:

      • Spring 确实有(某种)用于基于 Java 的配置的内部 bean 概念,但有点尴尬,Bean 带注释的方法可以相互调用(查看Bean javadoc 了解更多信息详细信息)。
      • 两个@beans 仍然会通过应用程序上下文公开,这不是您想要的。相比之下,真正的内部 bean 仅对封闭 bean 可用。
      猜你喜欢
      • 2020-10-14
      • 1970-01-01
      • 2023-03-02
      • 1970-01-01
      • 2012-10-28
      • 1970-01-01
      • 2012-04-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多