Mirak
Mirak

Reputation: 59

Use custom annotation on @bean factory methods

Curently I use @CustomAnnotation on the classes directly, and use a BeanFactoryPostProcessor to change the bean definition of the annotated beans to my needs.

    @CustomAnnotation
    public class MyBean implements IMybean{
    }

    @Configuration
    public class MyConfiguration {

       @Bean
        public MyBean myBean(){
            return new myBean();
        }

    }

What I want to do is instead be able to put the @CustomAnnotation on @Bean method of the configuration file, like this:

    public class MyBean implements IMybean{
    }

    @Configuration
    public class MyConfiguration {

        @Bean
        @CustomAnnotation
        public MyBean myBean(){
            return new myBean();
        }

     }

From the BeanDefinition I can get from the beanFactory, I know I can get the factory bean and the factory method that creates myBean, and check if there is a @CustomAnnotation on the method.

What I am not sure is if doing that would break any spring principles, or if it is a regular thing to do.

My original intention is working. However I have another issue now. I can't autowire the bean that comes from the factory where I want, with the type I want. There are problems to resolve the depencies. This is the test code I use to try to solve the issue. test code on github

@Configuration
public class MainConfiguration implements BeanDefinitionRegistryPostProcessor, Ordered {

    private SayenBeanDefinitionRegistryPostProcessor sayenBeanDefinitionRegistryPostProcessor = new SayenBeanDefinitionRegistryPostProcessor();

    public int getOrder() {
        return sayenBeanDefinitionRegistryPostProcessor.getOrder();
    }

    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        sayenBeanDefinitionRegistryPostProcessor.postProcessBeanFactory(beanFactory);
    }

    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
        sayenBeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry(beanFactory);
    }

    @Bean
    public AutowiredBean autowiredBean() {
        return new AutowiredBean();
    }

    @Bean
    @Transform(type = MegaSuperKarim.class)
    public Karim Karim() {
        return new Karim();
    }

    @Bean
    @Transform(type = SuperGuillaume.class)
    public Guillaume Guillaume() {
        return new Guillaume();
    }

    @Bean
    public Yoann Yoann() {
        return new Yoann();
    }

    @Bean
    public Nicolas Nicolas() {
        return new Nicolas();
    }

    @Bean
    public BeanHolder beanHolder() {
        return new BeanHolder();
    }
}

public class TransformFactoryBean implements FactoryBean<Object> {

    @Autowired
    private AutowiredBean pouet;

    private Class<?> objectType;

    boolean singleton = true;

    @Override
    public Object getObject() throws Exception {
        return objectType.getConstructor().newInstance();
    }

    @Override
    public Class<?> getObjectType() {
        return objectType;
    }

    @Override
    public boolean isSingleton() {
        return singleton;
    }

    public void setObjectType(Class<?> objectType) {
        this.objectType = objectType;
    }

    public AutowiredBean getPouet() {
        return pouet;
    }

    public void setSingleton(boolean singleton) {
        this.singleton = singleton;
    }

}

public class SayenBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor, Ordered {

    private static Logger logger = LoggerFactory.getLogger(MainConfiguration.class);

    @Override
    public int getOrder() {
        return 0;
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        logger.debug("rien");
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
        //DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) beanFactory;

        for (String originalBeanName : beanFactory.getBeanDefinitionNames()) {
            BeanDefinition originalBeanDefinition = beanFactory.getBeanDefinition(originalBeanName);
            logger.debug("original beanName=" + originalBeanName + ", " + originalBeanDefinition.toString());
            if (originalBeanDefinition.isAbstract()) {
                continue;
            }

            Transform sayenAnnotation = getMethodAnnotation(beanFactory, originalBeanDefinition);

            /*if (sayenAnnotation == null) {
                Class<?> originalBeanClass = beanFactory.getType(originalBeanName);
                sayenAnnotation = AnnotationUtils.findAnnotation(originalBeanClass, Transform.class);
                */if (sayenAnnotation == null) {
                    continue;
                }/*
            }*/

            Class<? extends Sayan> sayenClass = sayenAnnotation.type();

            RootBeanDefinition sayenFactoryBeanDefinition = new RootBeanDefinition(TransformFactoryBean.class, 3/*Autowire.BY_TYPE.value()*/, true);
            sayenFactoryBeanDefinition.getPropertyValues().add("objectType", sayenClass);
            sayenFactoryBeanDefinition.getPropertyValues().add("singleton", true);

            String factoryBeanName = originalBeanName;

            logger.debug("remove beanName=" + originalBeanName + ", " + originalBeanDefinition.toString());
            beanFactory.removeBeanDefinition(originalBeanName);

            logger.debug("register beanName=" + factoryBeanName + ", " + sayenFactoryBeanDefinition.toString());
            beanFactory.registerBeanDefinition(factoryBeanName, sayenFactoryBeanDefinition);

        }

    }

    private Transform getMethodAnnotation(BeanDefinitionRegistry beanFactory, BeanDefinition originalBeanDefinition) {
        String originalBeanFactoryBeanName = originalBeanDefinition.getFactoryBeanName();
        String originalBeanFactoryMethodName = originalBeanDefinition.getFactoryMethodName();

        if (originalBeanFactoryBeanName == null || originalBeanFactoryMethodName == null) {
            return null;
        }

        Class<?> originalBeanFactoryBeanClass = ClassUtils.getUserClass(((DefaultListableBeanFactory)beanFactory).getType(originalBeanFactoryBeanName));
        try {
            Method method = originalBeanFactoryBeanClass.getMethod(originalBeanFactoryMethodName);
            return AnnotationUtils.getAnnotation(method, Transform.class);
        } catch (SecurityException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }

    }

Upvotes: 3

Views: 1139

Answers (1)

Ian Mc
Ian Mc

Reputation: 5829

If the only purpose of @CustomAnnotation is to effect how the class/bean is created, then you have taken the correct approach. In this case, the scope of the annotation should be at creation (which is where you have moved it to in the @Configuration MyConfiguration) and not the class scope (@Target(ElementType.TYPE)).
You are effectively saying that @CustomAnnotation has no further use for MyBean after creation, and your framework will never again need to examine MyBean for this annotation. This also implies that @CustomAnnotation might be used for creation of other beans (MyBean2, etc).

Upvotes: 1

Related Questions