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
}

In the old XML days I would configure the real repository as an anonymous inner bean. Is it possible to do something similar with the new Java configuration approach? Instantiating the real repository inside the timedRepository factory method is not an option because I want Spring to pick up on annotations on RealRepository.

The motivation is to avoid any other beans to get hold of the real repository implementation. I should also mention that both beans implement a Repository interface that'll be used by any beans depending on the repository (they should not have to know about TimedRepository or RealRepository.

Kimble
  • 7,295
  • 4
  • 54
  • 77

3 Answers3

0

I dont think theres an equivalent to inner or local beans when using java based configuration. I'd probably try to create the RealRepository in the TimedRepositories bean method as well by asking for all dependencies in the method signature. But if you really need to have spring to take care of the RealRepository dependencies you need to use the 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;
        }
    }

}
Stefan Podkowinski
  • 5,206
  • 1
  • 20
  • 25
  • Spring does have (some sort) of a inner bean concept for Java based configuration, but it's a bit awkward, `Bean` annotated method can call each other (look at the `Bean` javadoc for more details). – Kimble Nov 28 '12 at 11:53
  • Still both @beans would be exposed through the application context which is not what you want. In contrast true inner beans are only available to the enclosing bean. – Stefan Podkowinski Nov 28 '12 at 12:09
0

You can just instantiate the beans manually:

public class BeanThatDependsOnRealRepository() {

  private final Repository repository;

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

This is essentially what an anonymous inner bean does in XML. You've just explicitly constructed it and obtained its dependencies from Spring in the constructor of the enclosing class.

Alex Barnes
  • 7,174
  • 1
  • 30
  • 50
0

Late answer, but this is possible in Spring Core 4+ (and possibly Spring Core 3) with some trickery.

While standard Spring semantics do not support inner bean creation using JavaConfig, the internal functionality around inner beans can be taken advantage of to produce the same results.

Inner beans are produced during property value resolution by the BeanDefinitionValueResolver (see BeanDefinitionValueResolver#resolveValueIfNecessary). The concept of "inner beans" within Spring is primarily enclosed within this value resolver (which is the only producer of inner beans) and within bean factories under the term "contained beans" (from parent class DefaultSingletonBeanRegistry).

We can trick Spring into producing additional inner beans by defining a property as a BeanDefinition, according to the resolution strategy presented in 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
    }
}

The obvious issue with this solution is that it requires manual processing through a BeanDefinitionRegistryPostProcessor, which is a lot of work for a small gain here. What I would suggest instead is the following:

  • Create a custom annotation (e.g., @InnerBean)
  • Attach this annotation to methods in @Configuration classes and candidate component classes where desired
  • Adapt the BeanDefinitionRegistryPostProcessor to scan classes for @InnerBean-annotated static methods (component-classes should be scanned in #postProcessBeanFactory and configuration classes in #postProcessBeanDefinitionRegistry)
  • Attach the bean definition to the containing bean definition's autowired constructor fields (or setter fields if that is your convention)

The following is an example:

@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()));
        }
    }
}

There will be a few benefits and caveats of doing this. One large benefit is that the bean lifecycle will be managed by the Spring IoC container, meaning that lifecycle callbacks (such as @PostConstruct and @PreDestroy) will be called. The bean can be automatically managed according to the lifecycle of the parent. Caveats include that the beans cannot be injected as factory-method parameters (although with a bit of work you might able to fix this) and that AOP proxying will not be applied to these methods within @Configuration classes (i.e., realRepository() should never be called as it will not reference the singleton inner bean -- instead, the instance field should always be referenced). Further proxying (similar to ConfigurationClassEnhancer.BeanMethodInterceptor) would need to be added in order to apply this.

Mike Hill
  • 3,622
  • 23
  • 27