From f6806580ae1fb46b111128931a4cb9295fb67543 Mon Sep 17 00:00:00 2001
From: ovidiumihaitacu <138307181+ovidiumihaitacu@users.noreply.github.com>
Date: Mon, 22 Jan 2024 20:09:24 +0200
Subject: [PATCH] [BAEL-7257] Monkey-Patching in Java (#15700)
---
patterns-modules/monkey-patching/README.md | 1 +
patterns-modules/monkey-patching/pom.xml | 44 +++++++++++++++++++
.../baeldung/monkey/patching/Application.java | 12 +++++
.../patching/aop/BeanConfiguration.java | 15 +++++++
.../monkey/patching/aop/LoggingAspect.java | 22 ++++++++++
.../patching/converter/MoneyConverter.java | 5 +++
.../converter/MoneyConverterImpl.java | 15 +++++++
.../decorator/MoneyConverterDecorator.java | 21 +++++++++
.../proxy/LoggingInvocationHandler.java | 21 +++++++++
.../aop/LoggingAspectIntegrationTest.java | 35 +++++++++++++++
.../converter/MoneyConverterUnitTest.java | 17 +++++++
.../MoneyConverterDecoratorUnitTest.java | 28 ++++++++++++
.../LoggingInvocationHandlerUnitTest.java | 34 ++++++++++++++
.../reflection/ReflectionUnitTest.java | 24 ++++++++++
patterns-modules/pom.xml | 1 +
15 files changed, 295 insertions(+)
create mode 100644 patterns-modules/monkey-patching/README.md
create mode 100644 patterns-modules/monkey-patching/pom.xml
create mode 100644 patterns-modules/monkey-patching/src/main/java/com/baeldung/monkey/patching/Application.java
create mode 100644 patterns-modules/monkey-patching/src/main/java/com/baeldung/monkey/patching/aop/BeanConfiguration.java
create mode 100644 patterns-modules/monkey-patching/src/main/java/com/baeldung/monkey/patching/aop/LoggingAspect.java
create mode 100644 patterns-modules/monkey-patching/src/main/java/com/baeldung/monkey/patching/converter/MoneyConverter.java
create mode 100644 patterns-modules/monkey-patching/src/main/java/com/baeldung/monkey/patching/converter/MoneyConverterImpl.java
create mode 100644 patterns-modules/monkey-patching/src/main/java/com/baeldung/monkey/patching/decorator/MoneyConverterDecorator.java
create mode 100644 patterns-modules/monkey-patching/src/main/java/com/baeldung/monkey/patching/proxy/LoggingInvocationHandler.java
create mode 100644 patterns-modules/monkey-patching/src/test/java/com/baeldung/monkey/patching/aop/LoggingAspectIntegrationTest.java
create mode 100644 patterns-modules/monkey-patching/src/test/java/com/baeldung/monkey/patching/converter/MoneyConverterUnitTest.java
create mode 100644 patterns-modules/monkey-patching/src/test/java/com/baeldung/monkey/patching/decorator/MoneyConverterDecoratorUnitTest.java
create mode 100644 patterns-modules/monkey-patching/src/test/java/com/baeldung/monkey/patching/proxy/LoggingInvocationHandlerUnitTest.java
create mode 100644 patterns-modules/monkey-patching/src/test/java/com/baeldung/monkey/patching/reflection/ReflectionUnitTest.java
diff --git a/patterns-modules/monkey-patching/README.md b/patterns-modules/monkey-patching/README.md
new file mode 100644
index 0000000000..7d843af9ea
--- /dev/null
+++ b/patterns-modules/monkey-patching/README.md
@@ -0,0 +1 @@
+### Relevant Articles:
diff --git a/patterns-modules/monkey-patching/pom.xml b/patterns-modules/monkey-patching/pom.xml
new file mode 100644
index 0000000000..e7fae87646
--- /dev/null
+++ b/patterns-modules/monkey-patching/pom.xml
@@ -0,0 +1,44 @@
+
+
+ 4.0.0
+ com.baeldung
+ monkey-patching
+ 1.0.0-SNAPSHOT
+ monkey-patching
+ jar
+
+
+ com.baeldung
+ patterns-modules
+ 1.0.0-SNAPSHOT
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ 2.7.0
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ 2.7.0
+
+
+ org.springframework.boot
+ spring-boot-starter-aop
+ 2.7.0
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/patterns-modules/monkey-patching/src/main/java/com/baeldung/monkey/patching/Application.java b/patterns-modules/monkey-patching/src/main/java/com/baeldung/monkey/patching/Application.java
new file mode 100644
index 0000000000..6c586b1c84
--- /dev/null
+++ b/patterns-modules/monkey-patching/src/main/java/com/baeldung/monkey/patching/Application.java
@@ -0,0 +1,12 @@
+package com.baeldung.monkey.patching;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+}
\ No newline at end of file
diff --git a/patterns-modules/monkey-patching/src/main/java/com/baeldung/monkey/patching/aop/BeanConfiguration.java b/patterns-modules/monkey-patching/src/main/java/com/baeldung/monkey/patching/aop/BeanConfiguration.java
new file mode 100644
index 0000000000..dff55dc008
--- /dev/null
+++ b/patterns-modules/monkey-patching/src/main/java/com/baeldung/monkey/patching/aop/BeanConfiguration.java
@@ -0,0 +1,15 @@
+package com.baeldung.monkey.patching.aop;
+
+import com.baeldung.monkey.patching.converter.MoneyConverter;
+import com.baeldung.monkey.patching.converter.MoneyConverterImpl;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class BeanConfiguration {
+
+ @Bean
+ public MoneyConverter moneyConverter() {
+ return new MoneyConverterImpl();
+ }
+}
diff --git a/patterns-modules/monkey-patching/src/main/java/com/baeldung/monkey/patching/aop/LoggingAspect.java b/patterns-modules/monkey-patching/src/main/java/com/baeldung/monkey/patching/aop/LoggingAspect.java
new file mode 100644
index 0000000000..bbceb581c6
--- /dev/null
+++ b/patterns-modules/monkey-patching/src/main/java/com/baeldung/monkey/patching/aop/LoggingAspect.java
@@ -0,0 +1,22 @@
+package com.baeldung.monkey.patching.aop;
+
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.After;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.springframework.stereotype.Component;
+
+@Aspect
+@Component
+public class LoggingAspect {
+
+ @Before("execution(* com.baeldung.monkey.patching.converter.MoneyConverter.convertEURtoUSD(..))")
+ public void beforeConvertEURtoUSD(JoinPoint joinPoint) {
+ System.out.println("Before method: " + joinPoint.getSignature().getName());
+ }
+
+ @After("execution(* com.baeldung.monkey.patching.converter.MoneyConverter.convertEURtoUSD(..))")
+ public void afterConvertEURtoUSD(JoinPoint joinPoint) {
+ System.out.println("After method: " + joinPoint.getSignature().getName());
+ }
+}
\ No newline at end of file
diff --git a/patterns-modules/monkey-patching/src/main/java/com/baeldung/monkey/patching/converter/MoneyConverter.java b/patterns-modules/monkey-patching/src/main/java/com/baeldung/monkey/patching/converter/MoneyConverter.java
new file mode 100644
index 0000000000..97e32aaa61
--- /dev/null
+++ b/patterns-modules/monkey-patching/src/main/java/com/baeldung/monkey/patching/converter/MoneyConverter.java
@@ -0,0 +1,5 @@
+package com.baeldung.monkey.patching.converter;
+
+public interface MoneyConverter {
+ double convertEURtoUSD(double amount);
+}
diff --git a/patterns-modules/monkey-patching/src/main/java/com/baeldung/monkey/patching/converter/MoneyConverterImpl.java b/patterns-modules/monkey-patching/src/main/java/com/baeldung/monkey/patching/converter/MoneyConverterImpl.java
new file mode 100644
index 0000000000..86071341c9
--- /dev/null
+++ b/patterns-modules/monkey-patching/src/main/java/com/baeldung/monkey/patching/converter/MoneyConverterImpl.java
@@ -0,0 +1,15 @@
+package com.baeldung.monkey.patching.converter;
+
+public class MoneyConverterImpl implements MoneyConverter {
+
+ private final double conversionRate;
+
+ public MoneyConverterImpl() {
+ this.conversionRate = 1.10;
+ }
+
+ @Override
+ public double convertEURtoUSD(double amount) {
+ return amount * conversionRate;
+ }
+}
diff --git a/patterns-modules/monkey-patching/src/main/java/com/baeldung/monkey/patching/decorator/MoneyConverterDecorator.java b/patterns-modules/monkey-patching/src/main/java/com/baeldung/monkey/patching/decorator/MoneyConverterDecorator.java
new file mode 100644
index 0000000000..81c86f136e
--- /dev/null
+++ b/patterns-modules/monkey-patching/src/main/java/com/baeldung/monkey/patching/decorator/MoneyConverterDecorator.java
@@ -0,0 +1,21 @@
+package com.baeldung.monkey.patching.decorator;
+
+import com.baeldung.monkey.patching.converter.MoneyConverter;
+
+public class MoneyConverterDecorator implements MoneyConverter {
+
+ private final MoneyConverter moneyConverter;
+
+ public MoneyConverterDecorator(MoneyConverter moneyConverter) {
+ this.moneyConverter = moneyConverter;
+ }
+
+ @Override
+ public double convertEURtoUSD(double amount) {
+
+ System.out.println("Before method: convertEURtoUSD");
+ double result = moneyConverter.convertEURtoUSD(amount);
+ System.out.println("After method: convertEURtoUSD");
+ return result;
+ }
+}
diff --git a/patterns-modules/monkey-patching/src/main/java/com/baeldung/monkey/patching/proxy/LoggingInvocationHandler.java b/patterns-modules/monkey-patching/src/main/java/com/baeldung/monkey/patching/proxy/LoggingInvocationHandler.java
new file mode 100644
index 0000000000..266738e514
--- /dev/null
+++ b/patterns-modules/monkey-patching/src/main/java/com/baeldung/monkey/patching/proxy/LoggingInvocationHandler.java
@@ -0,0 +1,21 @@
+package com.baeldung.monkey.patching.proxy;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+
+public class LoggingInvocationHandler implements InvocationHandler {
+
+ private final Object target;
+
+ public LoggingInvocationHandler(Object target) {
+ this.target = target;
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ System.out.println("Before method: " + method.getName());
+ Object result = method.invoke(target, args);
+ System.out.println("After method: " + method.getName());
+ return result;
+ }
+}
diff --git a/patterns-modules/monkey-patching/src/test/java/com/baeldung/monkey/patching/aop/LoggingAspectIntegrationTest.java b/patterns-modules/monkey-patching/src/test/java/com/baeldung/monkey/patching/aop/LoggingAspectIntegrationTest.java
new file mode 100644
index 0000000000..da185f654a
--- /dev/null
+++ b/patterns-modules/monkey-patching/src/test/java/com/baeldung/monkey/patching/aop/LoggingAspectIntegrationTest.java
@@ -0,0 +1,35 @@
+package com.baeldung.monkey.patching.aop;
+
+import com.baeldung.monkey.patching.converter.MoneyConverter;
+import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import static org.junit.Assert.assertTrue;
+
+@SpringBootTest
+@RunWith(SpringRunner.class)
+public class LoggingAspectIntegrationTest {
+
+ @Autowired
+ private MoneyConverter moneyConverter;
+
+ @Test
+ public void whenMethodCalled_thenSurroundedByLogs() {
+ ByteArrayOutputStream logOutputStream = new ByteArrayOutputStream();
+ System.setOut(new PrintStream(logOutputStream));
+
+ double result = moneyConverter.convertEURtoUSD(10);
+
+ Assertions.assertEquals(11, result);
+ String logOutput = logOutputStream.toString();
+ assertTrue(logOutput.contains("Before method: convertEURtoUSD"));
+ assertTrue(logOutput.contains("After method: convertEURtoUSD"));
+ }
+}
diff --git a/patterns-modules/monkey-patching/src/test/java/com/baeldung/monkey/patching/converter/MoneyConverterUnitTest.java b/patterns-modules/monkey-patching/src/test/java/com/baeldung/monkey/patching/converter/MoneyConverterUnitTest.java
new file mode 100644
index 0000000000..bf6cfe58e8
--- /dev/null
+++ b/patterns-modules/monkey-patching/src/test/java/com/baeldung/monkey/patching/converter/MoneyConverterUnitTest.java
@@ -0,0 +1,17 @@
+package com.baeldung.monkey.patching.converter;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class MoneyConverterUnitTest {
+
+ @Test
+ void whenMoneyConverter_thenResultIsCorrect() {
+ MoneyConverterImpl moneyConverter = new MoneyConverterImpl();
+
+ double result = moneyConverter.convertEURtoUSD(10);
+
+ assertEquals(11, result);
+ }
+}
\ No newline at end of file
diff --git a/patterns-modules/monkey-patching/src/test/java/com/baeldung/monkey/patching/decorator/MoneyConverterDecoratorUnitTest.java b/patterns-modules/monkey-patching/src/test/java/com/baeldung/monkey/patching/decorator/MoneyConverterDecoratorUnitTest.java
new file mode 100644
index 0000000000..24013f0ce7
--- /dev/null
+++ b/patterns-modules/monkey-patching/src/test/java/com/baeldung/monkey/patching/decorator/MoneyConverterDecoratorUnitTest.java
@@ -0,0 +1,28 @@
+package com.baeldung.monkey.patching.decorator;
+
+import com.baeldung.monkey.patching.converter.MoneyConverter;
+import com.baeldung.monkey.patching.converter.MoneyConverterImpl;
+import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import static org.junit.Assert.assertTrue;
+
+public class MoneyConverterDecoratorUnitTest {
+
+ @Test
+ public void whenMethodCalled_thenSurroundedByLogs() {
+ ByteArrayOutputStream logOutputStream = new ByteArrayOutputStream();
+ System.setOut(new PrintStream(logOutputStream));
+ MoneyConverter moneyConverter = new MoneyConverterDecorator(new MoneyConverterImpl());
+
+ double result = moneyConverter.convertEURtoUSD(10);
+
+ Assertions.assertEquals(11, result);
+ String logOutput = logOutputStream.toString();
+ assertTrue(logOutput.contains("Before method: convertEURtoUSD"));
+ assertTrue(logOutput.contains("After method: convertEURtoUSD"));
+ }
+}
diff --git a/patterns-modules/monkey-patching/src/test/java/com/baeldung/monkey/patching/proxy/LoggingInvocationHandlerUnitTest.java b/patterns-modules/monkey-patching/src/test/java/com/baeldung/monkey/patching/proxy/LoggingInvocationHandlerUnitTest.java
new file mode 100644
index 0000000000..7b6f49b9c5
--- /dev/null
+++ b/patterns-modules/monkey-patching/src/test/java/com/baeldung/monkey/patching/proxy/LoggingInvocationHandlerUnitTest.java
@@ -0,0 +1,34 @@
+package com.baeldung.monkey.patching.proxy;
+
+import com.baeldung.monkey.patching.converter.MoneyConverter;
+import com.baeldung.monkey.patching.converter.MoneyConverterImpl;
+import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.lang.reflect.Proxy;
+
+import static org.junit.Assert.assertTrue;
+
+public class LoggingInvocationHandlerUnitTest {
+
+ @Test
+ public void whenMethodCalled_thenSurroundedByLogs() {
+ ByteArrayOutputStream logOutputStream = new ByteArrayOutputStream();
+ System.setOut(new PrintStream(logOutputStream));
+ MoneyConverter moneyConverter = new MoneyConverterImpl();
+ MoneyConverter proxy = (MoneyConverter) Proxy.newProxyInstance(
+ MoneyConverter.class.getClassLoader(),
+ new Class[]{MoneyConverter.class},
+ new LoggingInvocationHandler(moneyConverter)
+ );
+
+ double result = proxy.convertEURtoUSD(10);
+
+ Assertions.assertEquals(11, result);
+ String logOutput = logOutputStream.toString();
+ assertTrue(logOutput.contains("Before method: convertEURtoUSD"));
+ assertTrue(logOutput.contains("After method: convertEURtoUSD"));
+ }
+}
diff --git a/patterns-modules/monkey-patching/src/test/java/com/baeldung/monkey/patching/reflection/ReflectionUnitTest.java b/patterns-modules/monkey-patching/src/test/java/com/baeldung/monkey/patching/reflection/ReflectionUnitTest.java
new file mode 100644
index 0000000000..4729f05802
--- /dev/null
+++ b/patterns-modules/monkey-patching/src/test/java/com/baeldung/monkey/patching/reflection/ReflectionUnitTest.java
@@ -0,0 +1,24 @@
+package com.baeldung.monkey.patching.reflection;
+
+import com.baeldung.monkey.patching.converter.MoneyConverter;
+import com.baeldung.monkey.patching.converter.MoneyConverterImpl;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class ReflectionUnitTest {
+
+@Test
+public void givenPrivateField_whenUsingReflection_thenBehaviorCanBeChanged() throws IllegalAccessException, NoSuchFieldException {
+ MoneyConverter moneyConvertor = new MoneyConverterImpl();
+
+ Field conversionRate = MoneyConverterImpl.class.getDeclaredField("conversionRate");
+ conversionRate.setAccessible(true);
+ conversionRate.set(moneyConvertor, 1.2);
+ double result = moneyConvertor.convertEURtoUSD(10);
+
+ assertEquals(12, result);
+}
+}
diff --git a/patterns-modules/pom.xml b/patterns-modules/pom.xml
index 7dd26ac31c..a32779a9d8 100644
--- a/patterns-modules/pom.xml
+++ b/patterns-modules/pom.xml
@@ -35,6 +35,7 @@
idd
intercepting-filter
solid
+ monkey-patching