From 72c261cf1444844cc97cbbf1f68a43498e0d8519 Mon Sep 17 00:00:00 2001 From: andrewpikozh <66901090+andrewpikozh@users.noreply.github.com> Date: Wed, 29 Nov 2023 23:12:43 +0200 Subject: [PATCH] BLZT-71 add PostConstruct functionality * BLZT-71 add annotation * BLZT-71 add PostConstruct functionality * BLZT-71 refactoring * BLZT-71 add documentation * BLZT-71 fix imports in ReflectionUtils * BLZT-71 fixes --- Core.md | 14 ++-- .../bring/core/annotation/PostConstruct.java | 58 ++++++++++++++++ .../impl/PostConstructBeanPostProcessor.java | 28 ++++++++ .../exception/PostConstructException.java | 8 +++ .../bring/core/utils/ReflectionUtils.java | 10 +++ .../PostConstructBeanPostProcessorTest.java | 37 +++++++++++ .../negative/CustomPostConstruct.java | 17 +++++ .../positive/CustomPostConstruct.java | 17 +++++ features/core/PostConstruct.md | 66 +++++++++++++++++++ 9 files changed, 248 insertions(+), 7 deletions(-) create mode 100644 core/src/main/java/com/bobocode/bring/core/annotation/PostConstruct.java create mode 100644 core/src/main/java/com/bobocode/bring/core/bpp/impl/PostConstructBeanPostProcessor.java create mode 100644 core/src/main/java/com/bobocode/bring/core/exception/PostConstructException.java create mode 100644 core/src/test/java/com/bobocode/bring/core/bpp/impl/PostConstructBeanPostProcessorTest.java create mode 100644 core/src/test/java/testdata/postconstruct/negative/CustomPostConstruct.java create mode 100644 core/src/test/java/testdata/postconstruct/positive/CustomPostConstruct.java create mode 100644 features/core/PostConstruct.md diff --git a/Core.md b/Core.md index d134be2d..0e835089 100644 --- a/Core.md +++ b/Core.md @@ -22,14 +22,14 @@ It initializes the list with default post-processors such as the ScheduleBeanPos If we need diagram classes we should use Wiki and add link to it. - Dependency Injection - - [Constructor](core/Constructor.md) - - [Setter](core/Setter.md) - - [Field](core/Field.md) + - [Constructor](/features/core/Constructor.md) + - [Setter](/features/core/Setter.md) + - [Field](/features/core/Field.md) - Collections - @Primary - @Qualifier - @Order - - [@Value](core/Value.md) + - [@Value](/features/core/Value.md) - Prototype Beans into a Singleton @@ -40,7 +40,7 @@ If we need diagram classes we should use Wiki and add link to it. - Dependency Injection exceptions - - [Circular Dependencies](core/CircularDependencies.md) + - [Circular Dependencies](/features/core/CircularDependencies.md) - No such bean exception - No unique bean exception - No constructor with Autowired annotation @@ -48,7 +48,7 @@ If we need diagram classes we should use Wiki and add link to it. - addition items: - - [Scheduling](core/Scheduling.md) + - [Scheduling](/features/core/Scheduling.md) - Properties file support @@ -60,6 +60,6 @@ If we need diagram classes we should use Wiki and add link to it. - [Logging](/features/core/Logging.md) - - PostConstruct + - [@PostConstruct](/features/core/PostConstruct.md) - PreDestroy - Logo \ No newline at end of file diff --git a/core/src/main/java/com/bobocode/bring/core/annotation/PostConstruct.java b/core/src/main/java/com/bobocode/bring/core/annotation/PostConstruct.java new file mode 100644 index 00000000..e97681f5 --- /dev/null +++ b/core/src/main/java/com/bobocode/bring/core/annotation/PostConstruct.java @@ -0,0 +1,58 @@ +package com.bobocode.bring.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + *

The {@code PostConstruct} annotation is used on a method that needs + * to be executed after dependency injection is done to perform any + * initialization. This method is called immediately after the bean's + * properties have been set and the bean has been placed into the + * Bring container.

+ * + *

Methods annotated with {@code @PostConstruct} are invoked only once + * in the bean's lifecycle, and they provide a convenient way to + * initialize resources or perform any setup logic that is required + * before the bean is ready for use.

+ * + *

The method annotated with {@code @PostConstruct} must be non-static + * and should not have any parameters, as it is meant to be an + * initialization callback method for the bean instance. If multiple + * methods are annotated with {@code @PostConstruct} within a single + * class, the order of execution is not guaranteed.

+ * + *

Example:

+ *
+ * {@code
+ * import com.bobocode.bring.core.annotation.PostConstruct;
+ *
+ * public class ExampleBean {
+ *
+ *     private String message;
+ *
+ *     @PostConstruct
+ *     public void init() {
+ *         message = "Hello, this is an example!";
+ *         // Additional initialization logic
+ *     }
+ *
+ *     public String getMessage() {
+ *         return message;
+ *     }
+ * }
+ * }
+ * 
+ * + *

In this example, the {@code init} method will be automatically + * invoked after the {@code ExampleBean} is constructed, providing a + * way to perform custom initialization logic.

+ * + * @author Blyzhnytsia Team + * @since 1.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface PostConstruct { +} diff --git a/core/src/main/java/com/bobocode/bring/core/bpp/impl/PostConstructBeanPostProcessor.java b/core/src/main/java/com/bobocode/bring/core/bpp/impl/PostConstructBeanPostProcessor.java new file mode 100644 index 00000000..2ba3bd16 --- /dev/null +++ b/core/src/main/java/com/bobocode/bring/core/bpp/impl/PostConstructBeanPostProcessor.java @@ -0,0 +1,28 @@ +package com.bobocode.bring.core.bpp.impl; + +import com.bobocode.bring.core.annotation.BeanProcessor; +import com.bobocode.bring.core.annotation.PostConstruct; +import com.bobocode.bring.core.bpp.BeanPostProcessor; +import com.bobocode.bring.core.exception.PostConstructException; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import static com.bobocode.bring.core.utils.ReflectionUtils.processBeanPostProcessorAnnotation; + + +@BeanProcessor +public class PostConstructBeanPostProcessor implements BeanPostProcessor { + + @Override + public Object postProcessInitialization(Object bean, String beanName) { + Method[] declaredMethods = bean.getClass().getMethods(); + try { + processBeanPostProcessorAnnotation(bean, declaredMethods, PostConstruct.class); + } catch (Exception exception) { + throw new PostConstructException(exception); + } + + return BeanPostProcessor.super.postProcessInitialization(bean, beanName); + } +} diff --git a/core/src/main/java/com/bobocode/bring/core/exception/PostConstructException.java b/core/src/main/java/com/bobocode/bring/core/exception/PostConstructException.java new file mode 100644 index 00000000..636a7c8a --- /dev/null +++ b/core/src/main/java/com/bobocode/bring/core/exception/PostConstructException.java @@ -0,0 +1,8 @@ +package com.bobocode.bring.core.exception; + +public class PostConstructException extends RuntimeException { + public static String POST_CONSTRUCT_EXCEPTION_MESSAGE ="@PostConstruct should be added to method without parameters"; + public PostConstructException(Throwable cause) { + super(POST_CONSTRUCT_EXCEPTION_MESSAGE, cause); + } +} diff --git a/core/src/main/java/com/bobocode/bring/core/utils/ReflectionUtils.java b/core/src/main/java/com/bobocode/bring/core/utils/ReflectionUtils.java index 5869f088..319b20dc 100644 --- a/core/src/main/java/com/bobocode/bring/core/utils/ReflectionUtils.java +++ b/core/src/main/java/com/bobocode/bring/core/utils/ReflectionUtils.java @@ -143,6 +143,16 @@ public static Supplier createNewInstance(Constructor constructor, Obj }; } + public static void processBeanPostProcessorAnnotation(Object bean, + Method[] declaredMethods, + Class annotation) throws ReflectiveOperationException { + for (Method declaredMethod : declaredMethods) { + if (declaredMethod.isAnnotationPresent(annotation)) { + declaredMethod.invoke(bean); + } + } + } + private static class QualifierAnnotationParanamer extends AnnotationParanamer { public QualifierAnnotationParanamer(Paranamer fallback) { diff --git a/core/src/test/java/com/bobocode/bring/core/bpp/impl/PostConstructBeanPostProcessorTest.java b/core/src/test/java/com/bobocode/bring/core/bpp/impl/PostConstructBeanPostProcessorTest.java new file mode 100644 index 00000000..b2a4f731 --- /dev/null +++ b/core/src/test/java/com/bobocode/bring/core/bpp/impl/PostConstructBeanPostProcessorTest.java @@ -0,0 +1,37 @@ +package com.bobocode.bring.core.bpp.impl; + +import com.bobocode.bring.core.BringApplication; +import com.bobocode.bring.core.exception.PostConstructException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; +import testdata.postconstruct.positive.CustomPostConstruct; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class PostConstructBeanPostProcessorTest { + + @Test + void shouldFillMessage_postProcessInitialization() { + //given + var bringApplicationContext = BringApplication.run("testdata.postconstruct.positive"); + + //when + var myCustomPostConstruct = bringApplicationContext.getBean(CustomPostConstruct.class); + + //then + assertThat(myCustomPostConstruct.getMessage()).isEqualTo("Hello!"); + } + + @Test + void shouldThrowException_postProcessInitialization() { + //given + var expectedMessage = "@PostConstruct should be added to method without parameters"; + // when + Executable executable = () -> BringApplication.run("testdata.postconstruct.negative"); + + // then + PostConstructException postConstructException = assertThrows(PostConstructException.class, executable); + assertThat(postConstructException.getMessage()).isEqualTo(expectedMessage); + } +} \ No newline at end of file diff --git a/core/src/test/java/testdata/postconstruct/negative/CustomPostConstruct.java b/core/src/test/java/testdata/postconstruct/negative/CustomPostConstruct.java new file mode 100644 index 00000000..b9eb34fb --- /dev/null +++ b/core/src/test/java/testdata/postconstruct/negative/CustomPostConstruct.java @@ -0,0 +1,17 @@ +package testdata.postconstruct.negative; + +import com.bobocode.bring.core.annotation.Component; +import com.bobocode.bring.core.annotation.PostConstruct; +import lombok.Getter; + +@Getter +@Component +public class CustomPostConstruct { + + private String message; + + @PostConstruct + public void fillMessage(String invalidParam) { + message = "Hello!"; + } +} diff --git a/core/src/test/java/testdata/postconstruct/positive/CustomPostConstruct.java b/core/src/test/java/testdata/postconstruct/positive/CustomPostConstruct.java new file mode 100644 index 00000000..d9a7fff8 --- /dev/null +++ b/core/src/test/java/testdata/postconstruct/positive/CustomPostConstruct.java @@ -0,0 +1,17 @@ +package testdata.postconstruct.positive; + +import com.bobocode.bring.core.annotation.Component; +import com.bobocode.bring.core.annotation.PostConstruct; +import lombok.Getter; + +@Component +@Getter +public class CustomPostConstruct { + + private String message; + + @PostConstruct + public void fillMessage() { + message = "Hello!"; + } +} diff --git a/features/core/PostConstruct.md b/features/core/PostConstruct.md new file mode 100644 index 00000000..43e1c0d2 --- /dev/null +++ b/features/core/PostConstruct.md @@ -0,0 +1,66 @@ +# @PostConstruct + +## Introduction + +In the Bring framework, `@PostConstruct` is a method-level annotation used to indicate that a method should be invoked immediately after an instance of the bean is constructed, and before any other initialization logic occurs. +## Usage + +To use `@PostConstruct`, follow these steps: + +1. **Add the Annotation**: Place the `@PostConstruct` annotation on a method within your Bring bean class. + + ```java + import com.bobocode.bring.core.annotation.PostConstruct; + + public class MyBean { + + @PostConstruct + public void init() { + // Initialization logic here + // This method will be called after bean instantiation. + } + } + ``` + + +2. **Invoke the Bring Container**: Make sure that you obtain the bean from the Bring container. The `@PostConstruct` annotated method will be automatically invoked. + + ```java + import com.bobocode.bring.core.BringApplication; + + public class MyApp { + public static void main(String[] args) { + var bringApplicationContext = BringApplication.run("your.path"); + var myBean = bringApplicationContext.getBean(CustomPostConstruct.class); + // Your bean is now initialized, and @PostConstruct method has been called. + } + } + ``` + +## Important Points + +- The method annotated with `@PostConstruct` must not have any parameters. +- This annotation is generally used in conjunction with the `@Component` stereotype annotations (e.g., `@Service`, `@Repository`, `@Controller`) or in configuration classes. +- The `@PostConstruct` method will be invoked after the bean has been constructed and before any custom initialization logic specified in the bean definition. + +## Example + +Here is a simple example of a class using `@PostConstruct`: + +```java +import com.bobocode.bring.core.annotation.PostConstruct; + +public class ExampleBean { + + private String message; + + @PostConstruct + public void init() { + message = "Hello, this is an example!"; + // Additional initialization logic + } + + public String getMessage() { + return message; + } +}