AOP
AOP provides a new way to think about program structure to supplement OOP. The key of module in OOP is class, while the key of module in AOP is aspect. Aspects support modularization across multiple types and objects, such as transaction management.
AOP concept
- Aspect -- The process of performing notification operations at pointcuts (class @Aspect containing notifications and pointcuts)
- Connection Points - All Candidates that may be woven into notifications (specific business logic approach)
- Cut-in point -- A join point that satisfies the matching rule (@Pointcut)
- Target object -- object notified by one or more facets
- AOP Agent-Objects Created by AOP Framework Based on Aspect Rules
- Weaving - Creating Notification Object Association Aspects and Other Applications
-
Notification -- Operations on entry points
- Pre-notification - Notification executed before the join point (@Before)
- Postnotification (@AfterReturning)
- Circumferential Notification -- Notification executed before and after method invocation (@Around)
- Exception notification (@AfterThrowing)
- Final Notification - Notification executed after exit from the connection point (@After)
Spring AOP goals
- Pure Java implementation. No special compilation process is required. There is no need to control the class loader hierarchy, which is suitable for use in Servlet containers or application servers.
- Only method execution join points are supported. If fields are needed to intercept and update join points, consider languages such as AspectJ.
- Spring AOP does not provide the most complete AOP implementation. The goal is to provide close integration between AOP implementation and Spring IoC.
AOP proxy
- Spring AOP implements AOP proxy by default using standard JDK dynamic proxy.
- Spring AOP can also use CGLIB proxies.
@ Aspect Style
- Activate @AspectJ support -- Spring automatically generates proxies for notified beans to intercept method calls and ensure on-demand notification execution
@Configuration @EnableAspectJAutoProxy public class AppConfig { }
- Define a facet -- Defining beans using the @AspectJ class in the application context is automatically detected by Spring and used to configure Spring AOP
@Aspect public class NotVeryUsefulAspect { }
- Define an entry point -- determine the join point of interest so that we can control when to execute the notification.
@Pointcut("execution(* transfer(..))")// Pointcut expression private void anyOldTransfer() {}
- Execution -- for matching method execution join points
- within -- Limit matching to join points in a particular type
- this -- Restricting matching to Bean references (AOP agents) is the join point for a given type instance
- Target -- Restricting matching to the target object is the join point for a given type instance
- args -- Limiting matching to parameters is the join point for a given type instance
- @ target -- Connection points that restrict matching to classes that execute objects with annotations of a given type
- @ args -- Connection points that restrict matching to runtime types of actual parameters passed with annotations of a given type
- @ within -- Limit matching to join points with a given annotation type
- @ annotation -- A join point with a given type of annotation for a topic that restricts matching to join points
@Aspect public class SystemArchitecture { @Pointcut("within(com.xyz.someapp.web..*)") public void inWebLayer() {} @Pointcut("within(com.xyz.someapp.service..*)") public void inServiceLayer() {} @Pointcut("within(com.xyz.someapp.dao..*)") public void inDataAccessLayer() {} @Pointcut("execution(* com.xyz.someapp..service.*.*(..))") public void businessService() {} @Pointcut("execution(* com.xyz.someapp.dao.*.*(..))") public void dataAccessOperation() {} }
Execution (modifier? Return type package name? Method name (parameter type) exception type?)
//Intercept the execution of any public method execution(public * *(..)) //Intercept the execution of any method starting with set execution(* set*(..)) //Intercept the execution of any method defined in the AccountService interface execution(* com.xyz.service.AccountService.*(..)) //Intercept the execution of any method defined in the service package execution(* com.xyz.service.*.*(..)) //Intercept the execution of any method defined in a service package or one of its subpackages execution(* com.xyz.service..*.*(..)) //Intercept connection points in service packages within(com.xyz.service.*) //Intercept the join points in a service package or one of its subpackages within(com.xyz.service..*) //Intercepting the Connection Point of the Agent Implementing AccountService Interface this(com.xyz.service.AccountService) //Intercepting Connection Points of Target Objects Implementing AccountService Interface target(com.xyz.service.AccountService) //Interception parameters have only one Serializable connection point args(java.io.Serializable) //Intercept join points with @Transactional annotations for target objects @target(org.springframework.transaction.annotation.Transactional) //The type of declaration intercepting the target object has a join point with the @Transactional annotation @within(org.springframework.transaction.annotation.Transactional) //The interception execution method has a join point with the @Transactional annotation @annotation(org.springframework.transaction.annotation.Transactional) //The interception parameter has only one join point for the @Classified annotation @args(com.xyz.security.Classified)
- Define notifications -- associated with pointcut expressions and run before, after, or before matching methods are executed
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()") public void doAccessCheck() { // ... } @AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()") public void doAccessCheck() { // ... } @AfterReturning( pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()", returning="retVal") public void doAccessCheck(Object retVal) { // ... } @AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()") public void doRecoveryActions() { // ... } @AfterThrowing( pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()", throwing="ex") public void doRecoveryActions(DataAccessException ex) { // ... } @After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()") public void doReleaseLock() { // ... } @Around("com.xyz.myapp.SystemArchitecture.businessService()") public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { // start stopwatch Object retVal = pjp.proceed(); // stop stopwatch return retVal; }
-
JoinPoint parameter - ProceedingJoinPoint is a subclass of JoinPoint
- getArgs(): Returns method parameters
- getThis(): Returns the proxy object
- getTarget(): Returns the target object
- getSignature(): Returns a description of the notified method
- toString(): Print a description of the notified method
- Pass parameters to notifications
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)") public void validateAccount(Account account) { // ... }
@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)") private void accountDataAccessOperation(Account account) {} @Before("accountDataAccessOperation(account)") public void validateAccount(Account account) { // ... }
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Auditable { AuditCode value(); } @Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)") public void audit(Auditable auditable) { AuditCode code = auditable.value(); // ... }
public interface Sample<T> { void sampleGenericMethod(T param); void sampleGenericCollectionMethod(Collection<T> param); } @Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)") public void beforeSampleMethod(MyType param) { // Advice implementation } @Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)") public void beforeSampleMethod(Collection<MyType> param) { // Advice implementation }
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames="bean,auditable") public void audit(Object bean, Auditable auditable) { AuditCode code = auditable.value(); // ... use code and bean } //The first parameter is JoinPoint, ProceedingJoinPoint, or JoinPoint.StaticPart type, which can be omitted in argNames @Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames="bean,auditable") public void audit(JoinPoint jp, Object bean, Auditable auditable) { AuditCode code = auditable.value(); // ... use code, bean, and jp } @Before("com.xyz.lib.Pointcuts.anyPublicMethod()") public void audit(JoinPoint jp) { // ... use jp }
@Around("execution(List<Account> find*(..)) && " + "com.xyz.myapp.SystemArchitecture.inDataAccessLayer() && " + "args(accountHolderNamePattern)") public Object preProcessQueryPattern(ProceedingJoinPoint pjp, String accountHolderNamePattern) throws Throwable { String newPattern = preProcess(accountHolderNamePattern); return pjp.proceed(new Object[] {newPattern}); }
Agency mechanism
- Spring AOP uses JDK dynamic proxy or CGLIB to create proxy for a given target object.
- If the target object implements at least one interface, the JDK dynamic proxy is used. If the target object does not implement any interfaces, a CGLIB proxy is created.
public class SimplePojo implements Pojo { public void foo() { // this next method invocation is a direct call on the 'this' reference this.bar(); } public void bar() { // some logic... } } public class Main { public static void main(String[] args) { ProxyFactory factory = new ProxyFactory(new SimplePojo()); factory.addInterface(Pojo.class); factory.addAdvice(new RetryAdvice()); Pojo pojo = (Pojo) factory.getProxy(); // this is a method call on the proxy! pojo.foo(); } }
API
Pointcut
public interface Pointcut { ClassFilter getClassFilter(); MethodMatcher getMethodMatcher(); } public interface ClassFilter { boolean matches(Class clazz); } public interface MethodMatcher { boolean matches(Method m, Class targetClass); boolean isRuntime(); boolean matches(Method m, Class targetClass, Object[] args); }