7
@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.

4

3 回答 3

0

在使用基于 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;
        }
    }

}
于 2012-11-28T11:31:40.337 回答
0

您可以手动实例化 bean:

public class BeanThatDependsOnRealRepository() {

  private final Repository repository;

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

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

于 2012-11-28T15:46:16.990 回答
0

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

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

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

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

@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
    }
}

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

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

下面是一个例子:

@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)才能应用此功能。

于 2018-07-31T20:47:45.453 回答