From 9474ea9eef461e227810bba1ce16df85c5ffe6c0 Mon Sep 17 00:00:00 2001 From: Andrew Pikozh Date: Wed, 29 Nov 2023 07:40:27 +0200 Subject: [PATCH 1/6] BLZT-71 add annotation --- .../bobocode/bring/core/annotation/PostConstruct.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 core/src/main/java/com/bobocode/bring/core/annotation/PostConstruct.java 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..376361b7 --- /dev/null +++ b/core/src/main/java/com/bobocode/bring/core/annotation/PostConstruct.java @@ -0,0 +1,11 @@ +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; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface PostConstruct { +} From 4ee12a0e4fbbeeef06de96383013372f168eba88 Mon Sep 17 00:00:00 2001 From: Andrew Pikozh Date: Wed, 29 Nov 2023 11:31:48 +0200 Subject: [PATCH 2/6] BLZT-71 add PostConstruct functionality --- .../impl/PostConstructBeanPostProcessor.java | 29 +++++++++++++++ .../exception/PostConstructException.java | 8 ++++ .../PostConstructBeanPostProcessorTest.java | 37 +++++++++++++++++++ .../negative/CustomPostConstruct.java | 17 +++++++++ .../positive/CustomPostConstruct.java | 17 +++++++++ 5 files changed, 108 insertions(+) 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 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..fbba74d7 --- /dev/null +++ b/core/src/main/java/com/bobocode/bring/core/bpp/impl/PostConstructBeanPostProcessor.java @@ -0,0 +1,29 @@ +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.reflect.Method; + + +@BeanProcessor +public class PostConstructBeanPostProcessor implements BeanPostProcessor { + + @Override + public Object postProcessInitialization(Object bean, String beanName) { + Method[] declaredMethods = bean.getClass().getMethods(); + for (Method declaredMethod : declaredMethods) { + if (declaredMethod.isAnnotationPresent(PostConstruct.class)) { + try { + declaredMethod.invoke(bean); + } 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/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..ea65e493 --- /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() { + // when + Executable executable = () -> { + //given + BringApplication.run("testdata.postconstruct.negative"); + }; + + // then + assertThrows(PostConstructException.class, executable); + } +} \ 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!"; + } +} From 2a5236c8e9d2b8dac90112991f0a0908dd136829 Mon Sep 17 00:00:00 2001 From: Andrew Pikozh Date: Wed, 29 Nov 2023 19:14:05 +0200 Subject: [PATCH 3/6] BLZT-71 refactoring --- Core.md | 2 +- .../impl/PostConstructBeanPostProcessor.java | 15 +++--- .../bring/core/utils/ReflectionUtils.java | 46 ++++++++++++------- features/core/PostConstruct.md | 0 4 files changed, 37 insertions(+), 26 deletions(-) create mode 100644 features/core/PostConstruct.md diff --git a/Core.md b/Core.md index daa0a2f2..d97cfd50 100644 --- a/Core.md +++ b/Core.md @@ -56,7 +56,7 @@ If we need diagram classes we should use Wiki and add link to it. - Logging - - PostConstruct (the last week) //TODO + - PostConstruct (/features/core/Constructor.md) - PreDestroy (the last week) //TODO - Logo (the last week) //TODO - ComponentScan //TODO \ No newline at end of file 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 index fbba74d7..2ba3bd16 100644 --- 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 @@ -5,8 +5,11 @@ 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 { @@ -14,14 +17,10 @@ public class PostConstructBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessInitialization(Object bean, String beanName) { Method[] declaredMethods = bean.getClass().getMethods(); - for (Method declaredMethod : declaredMethods) { - if (declaredMethod.isAnnotationPresent(PostConstruct.class)) { - try { - declaredMethod.invoke(bean); - } catch (Exception exception) { - throw new PostConstructException(exception); - } - } + 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/utils/ReflectionUtils.java b/core/src/main/java/com/bobocode/bring/core/utils/ReflectionUtils.java index 0299ba10..a34edec5 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 @@ -5,6 +5,7 @@ import com.bobocode.bring.core.context.type.OrderComparator; import com.bobocode.bring.core.exception.BeanPostProcessorConstructionLimitationException; import com.bobocode.bring.core.exception.BringGeneralException; +import com.bobocode.bring.core.exception.PostConstructException; import com.thoughtworks.paranamer.*; import lombok.SneakyThrows; import lombok.experimental.UtilityClass; @@ -59,11 +60,11 @@ public static void setField(Field field, Object obj, Object value) { field.setAccessible(true); field.set(obj, value); } - + public static List getParameterNames(AccessibleObject methodOrConstructor) { return Arrays.stream(info.lookupParameterNames(methodOrConstructor)).toList(); } - + public static int extractParameterPosition(Parameter parameter) { String name = parameter.getName(); return Integer.parseInt(name.substring(name.indexOf(ARG) + ARG.length())); @@ -72,19 +73,19 @@ public static int extractParameterPosition(Parameter parameter) { @SneakyThrows public static List> extractImplClasses(ParameterizedType genericType, Reflections reflections, List> createdBeanAnnotations) { - Type actualTypeArgument = genericType.getActualTypeArguments()[0]; - if (actualTypeArgument instanceof Class actualTypeArgumentClass) { - String name = actualTypeArgumentClass.getName(); - Class interfaceClass = Class.forName(name); - - return (List>) reflections.getSubTypesOf(interfaceClass) - .stream() - .filter(implementation -> isImplementationAnnotated(implementation, createdBeanAnnotations)) - .sorted(ORDER_COMPARATOR) - .toList(); - } - return Collections.emptyList(); + Type actualTypeArgument = genericType.getActualTypeArguments()[0]; + if (actualTypeArgument instanceof Class actualTypeArgumentClass) { + String name = actualTypeArgumentClass.getName(); + Class interfaceClass = Class.forName(name); + + return (List>) reflections.getSubTypesOf(interfaceClass) + .stream() + .filter(implementation -> isImplementationAnnotated(implementation, createdBeanAnnotations)) + .sorted(ORDER_COMPARATOR) + .toList(); } + return Collections.emptyList(); + } private static boolean isImplementationAnnotated(Class implementation, List> createdBeanAnnotations) { return Arrays.stream(implementation.getAnnotations()) @@ -102,6 +103,7 @@ public static Supplier invokeBeanMethod(Method method, Object obj, Objec }; } + public static Supplier createNewInstance(Constructor constructor, Object[] args, Class clazz, boolean proxy) { return () -> { @@ -116,9 +118,19 @@ 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) { super(fallback); } @@ -132,7 +144,7 @@ protected String getNamedValue(Annotation ann) { return null; } } - + @Override protected boolean isNamed(Annotation ann) { return Objects.equals(Qualifier.class, ann.annotationType()); diff --git a/features/core/PostConstruct.md b/features/core/PostConstruct.md new file mode 100644 index 00000000..e69de29b From a8b2444a8746768a05e8bde4e24560978d7de3db Mon Sep 17 00:00:00 2001 From: Andrew Pikozh Date: Wed, 29 Nov 2023 19:49:31 +0200 Subject: [PATCH 4/6] BLZT-71 add documentation --- Core.md | 14 ++-- .../bring/core/annotation/PostConstruct.java | 47 +++++++++++++ features/core/PostConstruct.md | 66 +++++++++++++++++++ 3 files changed, 120 insertions(+), 7 deletions(-) diff --git a/Core.md b/Core.md index 09c2a127..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 (/features/core/Constructor.md) + - [@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 index 376361b7..e97681f5 100644 --- a/core/src/main/java/com/bobocode/bring/core/annotation/PostConstruct.java +++ b/core/src/main/java/com/bobocode/bring/core/annotation/PostConstruct.java @@ -5,6 +5,53 @@ 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/features/core/PostConstruct.md b/features/core/PostConstruct.md index e69de29b..9b46d835 100644 --- a/features/core/PostConstruct.md +++ 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 org.springframework.context.annotation.AnnotationConfigApplicationContext; + + 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; + } +} From 6649a700f7bfb9035475aba13be5a74f5553d850 Mon Sep 17 00:00:00 2001 From: Andrew Pikozh Date: Wed, 29 Nov 2023 19:55:39 +0200 Subject: [PATCH 5/6] BLZT-71 fix imports in ReflectionUtils --- .../java/com/bobocode/bring/core/utils/ReflectionUtils.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 50c64f1d..c0ca8e61 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 @@ -5,8 +5,10 @@ import com.bobocode.bring.core.context.type.OrderComparator; import com.bobocode.bring.core.exception.BeanPostProcessorConstructionLimitationException; import com.bobocode.bring.core.exception.BringGeneralException; -import com.bobocode.bring.core.exception.PostConstructException; -import com.thoughtworks.paranamer.*; +import com.thoughtworks.paranamer.AnnotationParanamer; +import com.thoughtworks.paranamer.BytecodeReadingParanamer; +import com.thoughtworks.paranamer.CachingParanamer; +import com.thoughtworks.paranamer.Paranamer; import lombok.SneakyThrows; import lombok.experimental.UtilityClass; import lombok.extern.slf4j.Slf4j; From 68c59dc1a95973d2a91a6b366dd56babad5b4c71 Mon Sep 17 00:00:00 2001 From: Andrew Pikozh Date: Wed, 29 Nov 2023 22:34:15 +0200 Subject: [PATCH 6/6] BLZT-71 fixes --- .../bpp/impl/PostConstructBeanPostProcessorTest.java | 10 +++++----- features/core/PostConstruct.md | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) 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 index ea65e493..b2a4f731 100644 --- 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 @@ -25,13 +25,13 @@ void shouldFillMessage_postProcessInitialization() { @Test void shouldThrowException_postProcessInitialization() { + //given + var expectedMessage = "@PostConstruct should be added to method without parameters"; // when - Executable executable = () -> { - //given - BringApplication.run("testdata.postconstruct.negative"); - }; + Executable executable = () -> BringApplication.run("testdata.postconstruct.negative"); // then - assertThrows(PostConstructException.class, executable); + PostConstructException postConstructException = assertThrows(PostConstructException.class, executable); + assertThat(postConstructException.getMessage()).isEqualTo(expectedMessage); } } \ No newline at end of file diff --git a/features/core/PostConstruct.md b/features/core/PostConstruct.md index 9b46d835..43e1c0d2 100644 --- a/features/core/PostConstruct.md +++ b/features/core/PostConstruct.md @@ -26,7 +26,7 @@ To use `@PostConstruct`, follow these steps: 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 org.springframework.context.annotation.AnnotationConfigApplicationContext; + import com.bobocode.bring.core.BringApplication; public class MyApp { public static void main(String[] args) {