Bean injection using custom annotation (BeanPostProcessor)

Annotations are used to provide a piece of additional information to a class/field/method to be used by a program. Custom annotation is not a new thing to our developers now, you’ll find a lot of articles about it. Having said that, with this article, I am trying to share how can we inject a particular implementation for your interface by just annotating it with the custom annotation.

What is BeanPostProcessor?

It is an interface that we can implement to put some custom logic while instantiating a bean. It taps into the bean creation lifecycle and provides us the flexibility to have any custom logic around it.

It has 2 methods that are invoked in any bean creation lifecycle:

  1. postProcessAfterInitialization() It is invoked after bean creation.
  2. postProcessBeforeInitialization() It is invoked just before the bean creation. So if you need to inject your specific bean for the given interface. your custom logic goes here.

There are a couple of steps involved to create a custom annotation for a field. Let’s understand them one by one.

[Step1] Create a file to define your custom annotation.

@Documented
@Target({ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomMock {
}

[Step2] Create a class (must be a spring bean) to override the bean creation lifecycle.

It uses a field callback class to put a custom logic using Reflection.

@Component
public class CustomMockBeanPostProcessor implements BeanPostProcessor {
@Autowired
private ConfigurableListableBeanFactory configurableBeanFactory;

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean.getClass().isAnnotationPresent(RunWith.class)) {
ReflectionUtils.doWithFields(bean.getClass(), new CustomMockFieldCallBack(configurableBeanFactory, bean));
}
return bean;
}
}

The above implementation has some really important things to comprehend. We are not adding any custom logic to theafterInitialization() as we do not intend to modify anything once the bean is created or returned. However, beforeInitialization() has custom logic as we have to return our own fake bean whenever a field is annotated with @CustomMock.

This implementation gets invoked for every bean present in our application. So It becomes inevitable for us to have a proper validation/constraint before we apply custom logic to any bean.

In our case, we have a validation that works only for fields of the classes which are annotated with @RunWith annotation. For other classes, all beans will be created with the default flow.

[Step3] Create a class for the custom mock field callback where the actual custom logic resides.

public class CustomMockFieldCallBack implements ReflectionUtils.FieldCallback {

private ConfigurableListableBeanFactory configurableBeanFactory;
private Object bean;

public CustomWiredFieldCallBack(ConfigurableListableBeanFactory configurableBeanFactory, Object bean) {
this.configurableBeanFactory = configurableBeanFactory;
this.bean = bean;
}

@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
if (field.isAnnotationPresent(CustomMock.class)) {
Class<?> interface = field.getType();
ReflectionUtils.makeAccessible(field);
Object mockImplementation = getBeanInstance(InterfaceMockBeanMapper.getMockBeanName(interface), interface);
field.set(bean, mockImplementation);
}

}

public Object getBeanInstance(String beanName, Class interface) {
Object mockImplementation = null;
if (!configurableBeanFactory.containsBean(beanName)) {
Object newInstance = null;
try {
Constructor<?> ctr = interface.getConstructor();
newInstance = ctr.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}

mockImplementation = configurableBeanFactory.initializeBean(newInstance, beanName);
configurableBeanFactory.autowireBeanProperties(mockImplementation, AutowireCapableBeanFactory.AUTOWIRE_BY_NAME, true);
configurableBeanFactory.registerSingleton(beanName, mockImplementation);
} else {
mockImplementation = configurableBeanFactory.getBean(beanName);
}
return mockImplementation;
}
}
  1. The above class leverages Java reflection to access the private field of a class and assigns the target bean to it.
  2. It also has a validation on the field that field must be annotated with @CustomMock annotation to work.
  3. InterfaceMockBeanMapper is used to fetch the mock bean name for the given interface. Based on the bean name, a bean will be returned from the getBeanInstance() If the given bean does not exist, a new bean with the given name is created and then returned.

How to use @CustomMock in our code?

@RunWith(SpringRunner.class)
public FeatureTestClass {
   @CustomMock
   private Interface1 bean1;
   @CustomMock
   private Interface2 bean2;
   @Test
   public void testFeatureClassMethod() {
        FeatureClass featureClass = new FeatureClass(bean1, bean2);
        featureClass.method();
        ...
        //assert
   }
}

Thanks for reading!!

Level Up Coding

Thanks for being a part of our community! Before you go:


Bean injection using custom annotation (BeanPostProcessor) was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.


This content originally appeared on Level Up Coding - Medium and was authored by Shivanshu Goyal

Annotations are used to provide a piece of additional information to a class/field/method to be used by a program. Custom annotation is not a new thing to our developers now, you’ll find a lot of articles about it. Having said that, with this article, I am trying to share how can we inject a particular implementation for your interface by just annotating it with the custom annotation.

What is BeanPostProcessor?

It is an interface that we can implement to put some custom logic while instantiating a bean. It taps into the bean creation lifecycle and provides us the flexibility to have any custom logic around it.

It has 2 methods that are invoked in any bean creation lifecycle:

  1. postProcessAfterInitialization() It is invoked after bean creation.
  2. postProcessBeforeInitialization() It is invoked just before the bean creation. So if you need to inject your specific bean for the given interface. your custom logic goes here.

There are a couple of steps involved to create a custom annotation for a field. Let's understand them one by one.

[Step1] Create a file to define your custom annotation.

@Documented
@Target({ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomMock {
}

[Step2] Create a class (must be a spring bean) to override the bean creation lifecycle.

It uses a field callback class to put a custom logic using Reflection.

@Component
public class CustomMockBeanPostProcessor implements BeanPostProcessor {
@Autowired
private ConfigurableListableBeanFactory configurableBeanFactory;

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean.getClass().isAnnotationPresent(RunWith.class)) {
ReflectionUtils.doWithFields(bean.getClass(), new CustomMockFieldCallBack(configurableBeanFactory, bean));
}
return bean;
}
}

The above implementation has some really important things to comprehend. We are not adding any custom logic to theafterInitialization() as we do not intend to modify anything once the bean is created or returned. However, beforeInitialization() has custom logic as we have to return our own fake bean whenever a field is annotated with @CustomMock.

This implementation gets invoked for every bean present in our application. So It becomes inevitable for us to have a proper validation/constraint before we apply custom logic to any bean.

In our case, we have a validation that works only for fields of the classes which are annotated with @RunWith annotation. For other classes, all beans will be created with the default flow.

[Step3] Create a class for the custom mock field callback where the actual custom logic resides.

public class CustomMockFieldCallBack implements ReflectionUtils.FieldCallback {

private ConfigurableListableBeanFactory configurableBeanFactory;
private Object bean;

public CustomWiredFieldCallBack(ConfigurableListableBeanFactory configurableBeanFactory, Object bean) {
this.configurableBeanFactory = configurableBeanFactory;
this.bean = bean;
}

@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
if (field.isAnnotationPresent(CustomMock.class)) {
Class<?> interface = field.getType();
ReflectionUtils.makeAccessible(field);
Object mockImplementation = getBeanInstance(InterfaceMockBeanMapper.getMockBeanName(interface), interface);
field.set(bean, mockImplementation);
}

}

public Object getBeanInstance(String beanName, Class interface) {
Object mockImplementation = null;
if (!configurableBeanFactory.containsBean(beanName)) {
Object newInstance = null;
try {
Constructor<?> ctr = interface.getConstructor();
newInstance = ctr.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}

mockImplementation = configurableBeanFactory.initializeBean(newInstance, beanName);
configurableBeanFactory.autowireBeanProperties(mockImplementation, AutowireCapableBeanFactory.AUTOWIRE_BY_NAME, true);
configurableBeanFactory.registerSingleton(beanName, mockImplementation);
} else {
mockImplementation = configurableBeanFactory.getBean(beanName);
}
return mockImplementation;
}
}
  1. The above class leverages Java reflection to access the private field of a class and assigns the target bean to it.
  2. It also has a validation on the field that field must be annotated with @CustomMock annotation to work.
  3. InterfaceMockBeanMapper is used to fetch the mock bean name for the given interface. Based on the bean name, a bean will be returned from the getBeanInstance() If the given bean does not exist, a new bean with the given name is created and then returned.

How to use @CustomMock in our code?

@RunWith(SpringRunner.class)
public FeatureTestClass {
   @CustomMock
   private Interface1 bean1;
   @CustomMock
   private Interface2 bean2;
   @Test
   public void testFeatureClassMethod() {
        FeatureClass featureClass = new FeatureClass(bean1, bean2);
        featureClass.method();
        ...
        //assert
   }
}

Thanks for reading!!

Level Up Coding

Thanks for being a part of our community! Before you go:


Bean injection using custom annotation (BeanPostProcessor) was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.


This content originally appeared on Level Up Coding - Medium and was authored by Shivanshu Goyal


Print Share Comment Cite Upload Translate Updates
APA

Shivanshu Goyal | Sciencx (2022-07-19T14:51:02+00:00) Bean injection using custom annotation (BeanPostProcessor). Retrieved from https://www.scien.cx/2022/07/19/bean-injection-using-custom-annotation-beanpostprocessor/

MLA
" » Bean injection using custom annotation (BeanPostProcessor)." Shivanshu Goyal | Sciencx - Tuesday July 19, 2022, https://www.scien.cx/2022/07/19/bean-injection-using-custom-annotation-beanpostprocessor/
HARVARD
Shivanshu Goyal | Sciencx Tuesday July 19, 2022 » Bean injection using custom annotation (BeanPostProcessor)., viewed ,<https://www.scien.cx/2022/07/19/bean-injection-using-custom-annotation-beanpostprocessor/>
VANCOUVER
Shivanshu Goyal | Sciencx - » Bean injection using custom annotation (BeanPostProcessor). [Internet]. [Accessed ]. Available from: https://www.scien.cx/2022/07/19/bean-injection-using-custom-annotation-beanpostprocessor/
CHICAGO
" » Bean injection using custom annotation (BeanPostProcessor)." Shivanshu Goyal | Sciencx - Accessed . https://www.scien.cx/2022/07/19/bean-injection-using-custom-annotation-beanpostprocessor/
IEEE
" » Bean injection using custom annotation (BeanPostProcessor)." Shivanshu Goyal | Sciencx [Online]. Available: https://www.scien.cx/2022/07/19/bean-injection-using-custom-annotation-beanpostprocessor/. [Accessed: ]
rf:citation
» Bean injection using custom annotation (BeanPostProcessor) | Shivanshu Goyal | Sciencx | https://www.scien.cx/2022/07/19/bean-injection-using-custom-annotation-beanpostprocessor/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.