Showing posts with label Aspect-oriented Programming. Show all posts
Showing posts with label Aspect-oriented Programming. Show all posts

Tuesday, October 24, 2017

Spring AOP Examples - Core Concepts and Practical Usages

Spring AOP Limits

Spring AOP supports only method execution join points, so it provides advising the execution of methods on Spring beans.

Spring AOP uses standard JDK dynamic proxies for AOP proxies as default. It mean it requires interfaces as base types. Spring is able to use CGLIB to provide AOP proxies against concrete classes, however it is a good practice to program to interfaces.

Core AOP concepts

Aspect
    a modularization of a cross-cutting concern
Join point
    a exposable point during the execution of a program
Advice
    actions taken by an aspect at a particular join point
Pointcut
    a predicate that matches join points
Introduction
    declaring additional methods or fields on behalf of a type
Target Object
    object being advised by one or more aspects
AOP Proxy
    an object created by the AOP framework in order to implement the aspect contracts
Weaving
    linking aspects with other application types or objects to create an advised object

Type of advices


Before
After returning
After throwing
After(final)
Around

Enable AOP

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}

<aop:aspectj-autoproxy/>

Supported Pointcut Designators

execution
within
this
target
args
@target
@args
@within
@annotation


execution(public * *(..))
execution(* set*(..))
execution(* com.xyz.service.AccountService.*(..))
execution(* com.xyz.service.*.*(..))
execution(* com.xyz.service.*.*(..))
within(com.xyz.service.*)
within(com.xyz.service..*)
execution(* com.xyz.sample.aspect.AspectTestInterface+.*(..))

the following could be used in a binding form too:
this(com.xyz.service.AccountService)
target(com.xyz.service.AccountService)
args(java.io.Serializable)
@target(org.springframework.transaction.annotation.Transactional)
@within(org.springframework.transaction.annotation.Transactional)
@annotation(org.springframework.transaction.annotation.Transactional)
@args(com.xyz.security.Classified)
bean(tradeService)
bean(*Service)

Good poitcuts

·        Kinded designators are those which select a particular kind of join point. For example: execution, get, set, call, handler
·        Scoping designators are those which select a group of join points of interest (of probably many kinds). For example: within, withincode
·        Contextual designators are those that match (and optionally bind) based on context. For example: this, target, @annotation
A well written pointcut should try and include at least the first two types (kinded and scoping).

Argument Binding Examples


/**
 * When compiling without debug info, or when interpreting pointcuts at runtime,
 * the names of any arguments used in the advice declaration are not available.
 * Under these circumstances only, it is necessary to provide the arg names in
 * the annotation - these MUST duplicate the names used in the annotated method.
 * Format is a simple comma-separated list.
 */
@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
}

@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
}

@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});
}


Advice Ordering

When two pieces of advice defined in the same aspect both need to run at the same join point, the ordering is undefined. 

When two pieces of advice defined in different aspects both need to run at the same join point, unless you specify otherwise the order of execution is undefined. Or @Order annotation could be used together with @Aspect annotation.

Test Examples


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration()
public class AspectTest {
       @Resource(name = "aspectTestInterfaceImpl")
       private AspectTestInterface ati;
       @Autowired
       private AspectTestInterfaceExt atiExt;

       @Test
       public void testBeforeM1() {
             System.out.println("test");
             ati.m1("m1 run");
             ati.m2("m2 run");
             ati.m3("m3 run");
             ((AspectTestInterface)ati).m2("m2 run");
             atiExt.m1("ext m1 run");
             atiExt.m10("ext m10 run");
             atiExt.m11("ext m11 run");
       }

       @Configuration
       @ComponentScan("com.xyz.sample.aspect")
       @EnableAspectJAutoProxy
       public static class SpringConfig {
             // do nothing.
       }

}

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
public @interface AspectTestAnnotation {
       String name();
}

@AspectTestAnnotation(name="ext")
public @interface AspectTestAnnotation2 {
}

public interface AspectTestInterface {
       String m1(String arg1);
       String m2(String arg1);
       String m3(String arg1);
}

public interface AspectTestInterfaceExt extends AspectTestInterface {
       String m10(String arg1);
       String m11(String arg1);
}

@Component
public class AspectTestInterfaceImpl implements AspectTestInterface {

       @Override
       public String m1(String arg1) {
             System.out.println("m1:" + arg1);
             return "m1:" + arg1;
       }

       @Override
       @AspectTestAnnotation(name="testName")
       public String m2(String arg1) {
             System.out.println("m2:" + arg1);
             return "m2:" + arg1;
       }
      
       @Override
       @AspectTestAnnotation2
       public String m3(String arg1) {
             System.out.println("m3:" + arg1);
             return "m3:" + arg1;
       }
      
       public String m4(String arg1) {
             System.out.println("m4:" + arg1);
             return "m4:" + arg1;
       }
}

@Component
public class AspectTestInterfaceImplExt extends AspectTestInterfaceImpl implements AspectTestInterfaceExt {

       @Override
       public String m10(String arg1) {
             System.out.println("m10:" + arg1);
             return "m10:" + arg1;
       }

       @Override
       public String m11(String arg1) {
             System.out.println("m11:" + arg1);
             return "m11:" + arg1;
       }

}

@Aspect
@Order(1)
@Component
public class AspectTestAspect {
       @Pointcut("execution(* com.xyz.sample.aspect.AspectTestInterface+.*(..))")
       public void methodInAspectTestIntefaceAndSubInterfaces() {
             // do nothing.
       };

       @Pointcut("target(com.xyz.sample.aspect.AspectTestInterface)")
       public void methodInAspectTestInteface() {
             // do nothing.
       };

       @Pointcut("@annotation(com.xyz.sample.aspect.AspectTestAnnotation)")
       public void methodHasAspectTestAnnotation() {
             // do nothing.
       };

       @Before("com.xyz.sample.aspect.AspectTestAspect.methodInAspectTestInteface() && target(bean)")
       public void beforeMethodInAspectTestInteface(Object bean) {
             System.out.println("=1= beforeMethodInAspectTestInteface...");
             if (bean instanceof AspectTestInterface) {
                    System.out.println("=1= the target is an AspectTestInterface.");
             }
             if (bean instanceof AspectTestInterfaceImpl) {
                    System.out.println("=1= the target is an AspectTestInterfaceImpl.");
             }
       }

       @Before("com.xyz.sample.aspect.AspectTestAspect.methodInAspectTestInteface() && com.xyz.sample.aspect.AspectTestAspect.methodHasAspectTestAnnotation() && target(bean) && @annotation(aspectTestAnnotation)")
       public void beforeMethodHasAspectTestAnnotation(Object bean, AspectTestAnnotation aspectTestAnnotation) {
             System.out.println("=2= beforeMethodHasAspectTestAnnotation...");
             if (bean instanceof AspectTestInterface) {
                    System.out.println("=2= the target is an AspectTestInterface.");
             }
             if (bean instanceof AspectTestInterfaceImpl) {
                    System.out.println("=2= the target is an AspectTestInterfaceImpl.");
             }
             System.out.println("=2= anotation name is " + aspectTestAnnotation.name());
       }
      
       @Before("com.xyz.sample.aspect.AspectTestAspect.methodInAspectTestIntefaceAndSubInterfaces()")
       public void beforeMethodInAspectTestIntefaceAndSubInterfaces() {
             System.out.println("=3= beforeMethodInAspectTestIntefaceAndSubInterfaces...");
       }
}