Aspect-Oriented Programming: Proxy Factory and Annotation-Based AOP Implementation
Dynamic Aspect Weaving
AOP fundamentally separates business logic from cross-cutting concerns. This separation provides several advantages:
- Aspect code needs to be written only once
- Developers focus solely on business logic without duplicating cross-cutting code
- Aspects are dynamically woven into business code at runtime
This article explores two approaches to implementing this separation.
AOP Implementation Using Proxy Factory
The following demonstrates implementing AOP with Spring and JDK dynamic proxy.
Step 1: Create the Aspect Class
package com.example.aop;
import org.springframework.stereotype.Component;
@Component
public class TransactionAspect {
public void startTransaction() {
System.out.println("Transaction started");
}
public void endTransaction() {
System.out.println("Transaction completed");
}
}
Step 2: Define the Service Interface and Implementation
package com.example.aop;
public interface UserService {
void create();
void remove();
}
package com.example.aop;
import javax.annotation.Resource;
import org.springframework.stereotype.Component;
@Component
public class UserServiceImpl implements UserService {
@Resource
private TransactionAspect transactionAspect;
@Override
public void create() {
System.out.println("Business logic: create user");
}
@Override
public void remove() {
System.out.println("Business logic: remove user");
}
}
Step 3: Create the Proxy Factory
package com.example.aop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ServiceProxyFactory {
static Object target;
static TransactionAspect aspect;
public static Object createProxy(Object targetObject, TransactionAspect targetAspect) {
target = targetObject;
aspect = targetAspect;
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
aspect.startTransaction();
Object result = method.invoke(target, args);
aspect.endTransaction();
return result;
}
});
}
}
Step 4: Configure Spring XML
<!-- Enable annotation scanning -->
<context:component-scan base-package="com.example.aop"></context:component-scan>
<!-- Configure proxy bean -->
<bean id="userServiceProxy" class="com.example.aop.ServiceProxyFactory" factory-method="createProxy">
<constructor-arg index="0" ref="userServiceImpl"></constructor-arg>
<constructor-arg index="1" ref="transactionAspect"></constructor-arg>
</bean>
Step 5: Test the Proxy
@Test
public void testProxy() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService service = (UserService) ctx.getBean("userServiceProxy");
service.create();
service.remove();
}
Annotation-Based AOP
Spring provides robust annotation support for AOP, enabling declarative aspect configuraton.
Step 1: Add Dependencies
Include the following JARs: spring-aop-x.x.x.RELEASE.jar, aopalliance.jar, aspectjweaver.jar, and aspectjrt.jar.
Step 2: Configure Spring XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<context:component-scan base-package="com.example.aop.annotation"></context:component-scan>
</beans>
Step 3: AOP Annotations Reference
| Annotation | Purpose |
|---|---|
| @Aspect | Marks a class as an aspect |
| @Pointcut("execution(* com.example.aop.annotation..(..))") | Defines pointcut expression |
| @Before("pointCut()") | Executes before target method |
| @After("pointCut()") | Executes after target method (always) |
| @AfterReturning("pointCut()") | Executes after method returns (not on exception) |
| @AfterThrowing("pointCut()") | Executes when exception occurs |
| @Around("pointCut()") | Wraps around target method execution |
Step 4: Create the Aspect Class
package com.example.aop.annotation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class LoggingAspect {
@Pointcut("execution(* com.example.aop.annotation.*.*(..))")
public void allMethods() { }
@Before("allMethods()")
public void beforeAdvice() {
System.out.println("Before: Starting operation");
}
@After("allMethods()")
public void afterAdvice() {
System.out.println("After: Operation completed");
}
@AfterReturning("allMethods()")
public void afterSuccess() {
System.out.println("AfterReturning: Normal completion");
}
@AfterThrowing("allMethods()")
public void afterException() {
System.out.println("AfterThrowing: Exception occurred");
}
@Around("allMethods()")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("Around: Before proceed");
Object result = pjp.proceed();
System.out.println("Around: After proceed");
return result;
}
}
Step 5: Define the Service
package com.example.aop.annotation;
public interface UserService {
void create();
void remove();
}
package com.example.aop.annotation;
import javax.annotation.Resource;
import org.springframework.stereotype.Component;
@Component
public class UserServiceImpl implements UserService {
@Override
public void create() {
System.out.println("Business logic: create user");
}
@Override
public void remove() {
System.out.println("Business logic: remove user");
}
}
Step 6: Verify Execution Order
@Test
public void testAnnotationAop() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService service = (UserService) ctx.getBean("userServiceImpl");
System.out.println(service.getClass());
service.create();
service.remove();
}
Execution Order Analysis
The actual execution sequence when all advice types are present:
- Around (before proceed)
- Before
- Target method executes
- After
- AfterReturning
- Around (after proceed)
Key observations:
- Around (before) executes before Before
- Around (after) executes after AfterReturning
- After always executes regardless of success or failure
- AfterReturning executes only on normal completion
- AfterThrowing executes only when an exception is thrown
Proxy selection behavior: When the target object implements an interface, Spring defaults to JDK dynamic proxy. When no interface exists, Spring uses CGLIB proxy.