Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

24-Implement Bean Post Processor #41

Merged
merged 11 commits into from
Dec 6, 2024
2 changes: 1 addition & 1 deletion config/checkstyle/checkstyle-xpath-suppressions.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0"?>
<!DOCTYPE suppressions SYSTEM "http://checkstyle.org/dtds/suppressions_1_0.dtd">
<suppressions>
<suppress checks="Javadoc" files=".*Test.*\.java"/>
<suppress checks=".*" files=".*Test.*\.java"/>
</suppressions>
2 changes: 1 addition & 1 deletion config/checkstyle/checkstyle.xml
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
<module name="SimplifyBooleanReturn"/>

<!-- Checks for class design -->
<module name="DesignForExtension"/>
<!-- <module name="DesignForExtension"/>-->
<module name="FinalClass"/>
<module name="HideUtilityClassConstructor"/>
<module name="InterfaceIsType"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package com.codeus.winter.annotation;

import com.codeus.winter.config.BeanFactory;
import com.codeus.winter.config.BeanPostProcessor;
import com.codeus.winter.exception.BeanNotFoundException;
import jakarta.annotation.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;


/**
* BeanPostProcessor implementation that autowires annotated fields, setter methods, and constructor.
*
*/
@SuppressWarnings("java:S3011")
public class AutowiredAnnotationBeanPostProcessor implements BeanPostProcessor {

private BeanFactory beanFactory;

/**
* Set BeanFactory as dependency.
*
* @param beanFactory bean factory
*/
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}

/**
* Resolve dependency injection for constructors/methods/fields with @Autowired annotation.
*
* @param bean bean object
* @param beanName bean name
* @return bean object
*/
@Nullable
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeanNotFoundException {
try {
injectConstructor(bean);
injectMethod(bean);
injectField(bean);
} catch (Exception e) {
throw new BeanNotFoundException("Bean post processing failed: " + beanName, e);
}
return bean;
}

private void injectMethod(Object bean) throws InvocationTargetException, IllegalAccessException {
Class<?> beanType = bean.getClass();
for (Method method : beanType.getDeclaredMethods()) {
if (method.isAnnotationPresent(Autowired.class)) {
for (Parameter parameter : method.getParameters()) {
Object dependency = beanFactory.getBean(parameter.getType());
method.setAccessible(true);
method.invoke(bean, dependency);
}
}
}
}

private void injectField(Object bean) throws IllegalAccessException {
Class<?> beanType = bean.getClass();
for (Field field : beanType.getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
Object dependency = beanFactory.getBean(field.getType());
field.setAccessible(true);
field.set(bean, dependency);
}
}
}

private void injectConstructor(Object bean) throws IllegalAccessException {
Class<?> beanType = bean.getClass();
for (Constructor constructor : beanType.getConstructors()) {
if (constructor.isAnnotationPresent(Autowired.class)) {
for (Parameter parameter : constructor.getParameters()) {
Class<?> type = parameter.getType();
Object dependency = beanFactory.getBean(type);
Field field = getFieldByType(beanType.getDeclaredFields(), type);
if (field != null) {
field.setAccessible(true);
field.set(bean, dependency);
}
}
}
}
}

private Field getFieldByType(Field[] declaredFields, Class<?> type) {
Field field = null;
for (Field declaredField : declaredFields) {
if (declaredField.getType().equals(type)) {
field = declaredField;
} else {
throw new IllegalArgumentException(String.format("Field '%s' not found", type));
}
}
return field;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.codeus.winter.annotation;

import com.codeus.winter.config.BeanPostProcessor;
import com.codeus.winter.config.DestructionBeanPostProcessor;
import com.codeus.winter.exception.BeanNotFoundException;
import jakarta.annotation.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
* BeanPostProcessor implementation that invokes annotated init and destroy methods. Processes
* methods that annotated with @PostConstruct and @PreDestroy classes
*/
@SuppressWarnings("java:S3011")
public class InitDestroyAnnotationBeanPostProcessor implements BeanPostProcessor,
DestructionBeanPostProcessor {

/**
* Invoke method that annotated with @PostConstruct after bean properties set.
*
* @param bean bean object
* @param beanName bean name
* @return bean object
*/
@Nullable
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeanNotFoundException {
try {
processInitMethod(bean);
} catch (Exception e) {
throw new BeanNotFoundException("Invocation of init method failed: " + beanName, e);
}
return bean;
}

private void processInitMethod(Object bean)
throws InvocationTargetException, IllegalAccessException {
Class<?> beanType = bean.getClass();
for (Method method : beanType.getDeclaredMethods()) {
if (method.isAnnotationPresent(PostConstruct.class)) {
method.setAccessible(true);
method.invoke(bean);
}
}
}

/**
* Invoke method that annotated with @PreDestroy before destroy bean.
*
* @param bean bean object
* @param beanName bean name
*/
@Override
public void postProcessBeforeDestruction(Object bean, String beanName)
throws BeanNotFoundException {
try {
processDestroyMethod(bean);
} catch (Exception e) {
throw new BeanNotFoundException(
"Failed to invoke destroy method on bean with name: " + beanName, e);
}
}

private void processDestroyMethod(Object bean)
throws InvocationTargetException, IllegalAccessException {
Class<?> beanType = bean.getClass();
for (Method method : beanType.getDeclaredMethods()) {
if (method.isAnnotationPresent(PreDestroy.class)) {
method.setAccessible(true);
method.invoke(bean);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.codeus.winter.config;


import com.codeus.winter.exception.BeanNotFoundException;

/**
* Subinterface of {@link BeanPostProcessor} that adds a before-destruction callback.
*
* <p>The typical usage will be to invoke custom destruction callbacks on
* specific bean types, matching corresponding initialization callbacks.
*/
public interface DestructionBeanPostProcessor extends BeanPostProcessor {

/**
* Apply this BeanPostProcessor to the given bean instance before its
* destruction, e.g. invoking custom destruction callbacks.
* <p>Like DisposableBean's {@code destroy} and a custom destroy method, this
* callback will only apply to beans which the container fully manages the
* lifecycle for. This is usually the case for singletons and scoped beans.
* @param bean the bean instance to be destroyed
* @param beanName the name of the bean
* @throws BeanNotFoundException in case of errors
*/
void postProcessBeforeDestruction(Object bean, String beanName) throws BeanNotFoundException;

/**
* Determine whether the given bean instance requires destruction by this
* post-processor.
* <p>The default implementation returns {@code true}. If a pre-5 implementation
* of {@code DestructionAwareBeanPostProcessor} does not provide a concrete
* implementation of this method, Spring silently assumes {@code true} as well.
* @param bean the bean instance to check
* @return {@code true} if {@link #postProcessBeforeDestruction} is supposed to
* be called for this bean instance eventually, or {@code false} if not needed
* @since 4.3
*/
default boolean requiresDestruction(Object bean) {
return true;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.codeus.winter.annotation;

import static org.junit.jupiter.api.Assertions.assertEquals;

import com.codeus.winter.config.DefaultBeanFactory;
import java.lang.reflect.Field;
import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.reflections.Reflections;

class AutowiredAnnotationBeanPostProcessorTest {

private static final String AUTOWIRED_FIELD_BEAN_NAME = "AutowiredFieldTestClass";
private static final String AUTOWIRED_CONSTRUCTOR_BEAN_NAME = "AutowiredConstructorTestClass";
private static final String AUTOWIRED_METHOD_BEAN_NAME = "AutowiredMethodTestClass";
private DefaultBeanFactory beanFactory;
private AutowiredAnnotationBeanPostProcessor postProcessor;

@BeforeEach
void setUpBeforeClass() {
beanFactory = new DefaultBeanFactory();
postProcessor = new AutowiredAnnotationBeanPostProcessor();
postProcessor.setBeanFactory(beanFactory);
createBeanTestClass();
}

@Test
void injectField() {
// given
var bean = beanFactory.getBean(AutowiredFieldTestClass.class);

// when
Object actual = postProcessor.postProcessBeforeInitialization(bean,
AUTOWIRED_FIELD_BEAN_NAME);

// then
Field dependency = actual.getClass().getDeclaredFields()[0];
dependency.setAccessible(true);
assertEquals(DependencyTestClass.class, dependency.getType());
}

@Test
void injectConstructor() {
// given
var bean = beanFactory.getBean(AutowiredConstructorTestClass.class);

// when
Object actual = postProcessor.postProcessBeforeInitialization(bean,
AUTOWIRED_CONSTRUCTOR_BEAN_NAME);

// then
Field dependency = actual.getClass().getDeclaredFields()[0];
dependency.setAccessible(true);
assertEquals(DependencyTestClass.class, dependency.getType());
}

@Test
void injectMethod() {
// given
var bean = beanFactory.getBean(AutowiredMethodTestClass.class);

// when
Object actual = postProcessor.postProcessBeforeInitialization(bean,
AUTOWIRED_METHOD_BEAN_NAME);

// then
Field dependency = actual.getClass().getDeclaredFields()[0];
dependency.setAccessible(true);
assertEquals(DependencyTestClass.class, dependency.getType());
}

private void createBeanTestClass() {
Reflections refelections = new Reflections("com.codeus.winter.annotation");
Set<Class<?>> classes = refelections.getTypesAnnotatedWith(Component.class);
classes.forEach(clazz -> {
try {
beanFactory.createBean(clazz);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.codeus.winter.annotation;

@Component
public class AutowiredConstructorTestClass {

private DependencyTestClass field;

public AutowiredConstructorTestClass() {
}

@Autowired
public AutowiredConstructorTestClass(DependencyTestClass field) {
this.field = field;
}

public DependencyTestClass getField() {
return field;
}

public void setField(DependencyTestClass field) {
this.field = field;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.codeus.winter.annotation;

@Component
public class AutowiredFieldTestClass {

@Autowired
private DependencyTestClass field;


public AutowiredFieldTestClass() {
// default constructor
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.codeus.winter.annotation;

@Component
public class AutowiredMethodTestClass {

private DependencyTestClass field;

public AutowiredMethodTestClass() {
// default constructor
}

@Autowired
public void setField(DependencyTestClass field) {
this.field = field;
}

public DependencyTestClass getField() {
return field;
}
}
Loading
Loading