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