Summary of AOP Face-Oriented Programming for SpringBoot

Keywords: Programming Java Spring JDK

1. What is AOP

Aspect-oriented programming (AOP), also translated as Aspect-oriented programming and Profile-oriented programming, is a program design idea in computer science. It aims to further separate cross-cutting concerns from business entities to improve the modularity of program code.By adding an additional Advice mechanism to the existing code, code blocks declared as "Pointcut" can be uniformly managed and decorated, such as "adding background logs for methods that start with'set*'for all method names".This idea enables developers to add functionality (such as logging) that is less closely related to the core business logic of the code to their programs without compromising the readability of the business code.The facet-oriented programming design idea is also the basis of facet-oriented software development.

Aspect-oriented programming divides code logic into different modules (that is, Concern s), a specific piece of logical functionality.Almost all programming ideas involve the categorization of code functions, encapsulating concerns into separate Abstract modules (functions, procedures, modules, classes, methods, etc.), which can be further implemented, encapsulated, and rewritten.Part of the focus "Cross cutting" program code contains several modules, that is, multiple modules, which are called "Cross-cutting concerns".

Logging is a typical case of crosscutting concerns because it often spans every business module in the system, namely "crosscutting" all the classes and method bodies that have log requirements.For a credit card application, deposit, withdrawal, and billing management are its core concerns, and logging and persistence will become cross-cutting concerns that cut across the entire object structure.

The concept of facet derives from improvements in object-oriented programming, but is not limited to this. It can also be used to improve traditional functions.Programming concepts related to facets also include meta-object protocols, Subject s, Mixin s, and Delegate s.

2. Concepts related to AOP

  • Aspect: An Aspect declaration is similar to a class declaration in Java in that it contains some Pointcut s and corresponding Advice s.
  • joint point: Represents a point that is explicitly defined in a program, typically including method calls, access to class members, execution of exception handler blocks, etc. It can also nest other joint points within itself.
  • Pointcut: Represents a set of joint point s that are either grouped together through logical relationships or assembled through wildcards, regular expressions, etc. It defines where the corresponding Advice will occur.
  • Advice: Advice defines what a program point defined in a Pointcut does, distinguishing before, after, and around each joint point from code that executes instead.
  • Target: Target object woven into Advice.

Weaving: The process of connecting Aspect s to other objects and creating Adviced object s

3. Spring AOP

Spring AOP is implemented using pure Java and does not require a specialized compilation process or a special class loader, it weaves enhanced code into the target class by proxy at run time.Spring AOP, IoC, and AspJ can be seamlessly integrated in Spring.Spring AOP is built on a dynamic proxy basis, so Spring's support for AOP is limited to method interception.There are two ways to dynamically proxy in Java: JDK dynamic proxy and CGLib dynamic proxy

  • jdk proxy

The java dynamic proxy uses reflection to generate an anonymous class that implements the proxy interface and calls InvokeHandler to process it before calling a specific method.


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * <p>
 *
 * @author leone
 * @since 2018-11-09
 **/
public class JdkProxy {

    interface IUserService {
        Integer delete(Integer userId);
    }

    static class UserServiceImpl implements IUserService {
        @Override
        public Integer delete(Integer userId) {
            // business
            System.out.println("delete user");
            return userId;
        }
    }

    // Customize InvocationHandler
    static class UserServiceProxy implements InvocationHandler {
        // Target object
        private Object target;

        public UserServiceProxy(Object target) {
            this.target = target;
        }

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("------Before method call---------");
            //Execute the appropriate target method
            Object result = method.invoke(target, args);
            System.out.println("------After method call---------");
            return result;
        }
    }

    public static void main(String[] args) {
        IUserService userService = new UserServiceImpl();
        // Create Call Handling Class
        UserServiceProxy handler = new UserServiceProxy(userService);
        // Get proxy class instance
        IUserService proxy = (IUserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(),
                                 new Class[]{IUserService.class}, handler);
        // Calling methods of proxy classes
        Integer userId = proxy.delete(3);
        System.out.println(userId);
    }

}
  • cglib proxy

The cglib dynamic proxy uses the asm open source package to load the class file of the proxy object class and to generate subclasses by modifying its byte code.

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * <p>
 *
 * @author leone
 * @since 2018-11-09
 **/
public class CglibProxy {

    static class UserService implements MethodInterceptor {

        private Object target;

        /**
         * Business methods
         *
         * @param userId
         * @return
         */
        public Integer delete(Integer userId) {
            System.out.println("delete user");
            return userId;
        }

        /**
         * Generating proxy classes using the Enhancer class
         *
         * @param target
         * @return
         */
        public Object getInstance(Object target) {
            this.target = target;
            // Create enhancers to create dynamic proxy classes
            Enhancer enhancer = new Enhancer();
            // Specify the business class to proxy for the enhancer (that is, specify the parent for the proxy class generated below)
            enhancer.setSuperclass(target.getClass());
            // Set callback: CallBack is called for all method calls on the proxy class, and callback needs to implement intercept() method to block
            enhancer.setCallback(this);
            // Create dynamic proxy class object and return
            return enhancer.create();
        }


        /**
         * @param o
         * @param method
         * @param objects
         * @param methodProxy
         * @return
         * @throws Throwable
         */
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) 
                                throws Throwable {
            System.out.println("------Before method call---------");
            Object object = methodProxy.invokeSuper(o, objects);
            System.out.println("------After method call---------");
            return object;
        }

    }

    public static void main(String[] args) {
        UserService userService = new UserService();
        UserService proxy = (UserService) userService.getInstance(userService);
        Integer userId = proxy.delete(2);
        System.out.println(userId);
    }

}

1. If the target object implements the interface, the dynamic proxy of JDK will be used by default to implement AOP, and CGLIB can be enforced to implement AOP.
2. If the target object does not implement the interface, the CGLIB library must be used, and spring will automatically convert between the JDK dynamic proxy and the CGLIB

4. SpringBoot introduces AOP dependency

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

5. Main Types of Advice

  • @Before: This annotated method executes before the business module code executes and cannot prevent the business module from executing unless an exception is thrown;
  • @AfterReturning: The annotated method is executed after the business module code is executed;
  • @AfterThrowing: The annotated method is executed after a business module throws a specified exception;
  • @After: The annotated method is executed after all Advice executions are completed, regardless of whether the business module throws an exception or not, similar to finally's function;
  • @Around: This annotation is the most powerful way to write code that is executed by a package business module. It can pass in a ProceedingJoinPoint code that calls a business module, either pre-call or post-call logic, which can be written in this method, or even block the call of a business module based on certain conditions.
  • @DeclareParents: This is an Introduction-type model used on property declarations to add new interfaces and corresponding implementations for specified business modules.

6. Tangent expression

1. Wildcards

  • [*] Matches any character, but only one element
  • [..] Matches any character, can match any number of elements, must be used in conjunction with *when representing a class
  • [+] must follow the class name, such as Horseman+, representing the class itself and all classes that inherit or extend the specified class

2. Logical Operators

An expression can be made up of several tangent functions through logical operations

  • And operations, intersecting or writing as and

For example, execution (* chop (..)) & target (Horseman) represents the chop method of Horseman and its subclasses

  • || or operation, any expression is true, or it can be written as or

For example, execution(* chop(..)) || args(String) denotes a method named chop or a method with a String parameter

  • ! No operation, expression false results in true, or not

For example, execution (* chop (..)) and! Args (String) represent a method named chop but cannot be a method with only one String parameter

  • execution() method matches pattern string

Represents all target class method connection points that satisfy a matching pattern.For example, execution(* save(..)) represents the save() method in all target classes.

Since the minimum granularity of Spring slices is at the method level, execution expressions can be used to explicitly specify method return types, class names, method names, parameter names, and other method-related components, and in Spring, most business scenarios requiring AOP only need to reach the method level, so execution expressions are most widely used.The following is the syntax of the execution expression

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

Execution (<modifier> <return type> <class path> <method name> (<parameter list>) <exception mode>)

  • modifiers-pattern: Method visibility, such as public, protected;
  • ret-type-pattern: The return value type of the method, such as int, void, and so on;
  • declaring-type-pattern: The full path name of the class in which the method resides, such as com.spring.Aspect;
  • name-pattern: Method name type, such as buisinessService();
  • param-pattern: The parameter type of the method, such as java.lang.String;
  • throws-pattern: An exception type thrown by a method, such as java.lang.Exception;

7. Tangent function

  • @annotation(annotation-type) method annotation class name

    The following example shows how to match labels using the com.leone.aop.AopTest annotation:

    @annotation(com.leone.aop.AopTest)

  • args(param-pattern) method into the parameter tangent function

    The following example shows how to match all methods that have only one parameter and the parameter type is java.lang.String:

    args(java.lang.String)

  • The @args(annotation-type) method is incorporated into the class annotation tangent function

    The following example shows how to match a class labeled with the com.leone.aop.AopTest annotation as a parameter:

    @args(com.leone.aop.AopTest)

  • within(declaring-type-pattern) class name matching tangent function

    A within expression can only be specified at the class level, as shown in the following example matching all methods in com.leone.aop.UserService:

    within(com.leone.aop.UserService)

  • @within(annotation-type) class annotation matching tangent function

    The following example shows the classes that match the annotation using the org.springframework.web.bind.annotation.RestController annotation:

    @within(org.springframework.web.bind.annotation.RestController)

  • target(declaring-type-pattern) class name tangent function

    The following example shows matching all methods in com.leone.aop.UserService:

    target(com.leone.aop.UserService)

  • this

8. Actual warfare of spring-boot-aop

  • Configure Face Class to Implement Proxy

1. Add the tangent class to the IOC container using the @Component annotation on the class
2. Use the @Aspect annotation on the class to make it a tangent class

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

/**
 * Describe a facet class
 *
 * @author leone
 * @since 2018-06-21
 **/
@Slf4j
@Aspect
@Component
public class AopConfig {

    /**
     * 1.wildcard
     * [*]  Matches any character, but only one element
     * <p>
     * [..] Matches any character, can match any number of elements, must be used in conjunction with *when representing a class
     * <p>
     * [+]  Must follow the class name, such as Horseman+, representing the class itself and all classes that inherit or extend the specified class
     * <p>
     * Tangent expression is divided into modifier return type package path method name parameter
     * <p>
     * 2.Tangent expression
     * <p>
     * 3.Logical operators
     * An expression can be made up of several tangent functions through logical operations
     * ** && Intersect with operation, or write as and
     * <p>
     * For example, execution (* chop (.)) & & target (Horseman) represents the chop method of Horseman and its subclasses
     * <p>
     * ** || Or operation, any expression is true, or it can be written as or
     * <p>
     * For example, execution (* chop (.)) || args (String) denotes a method named chop or a method with a String parameter
     * <p>
     * ** ! Non-operation, expression false results in true or not
     * <p>
     * For example, execution (* chop (..)) and! Args (String) represent a method named chop but cannot be a method with only one String parameter
     */
    @Pointcut("execution(* com.leone.boot.aop.service.*.*(..))")
    public void pointCut() {
    }

    /**
     * Wrap notifications are performed at the beginning and end of the target
     *
     * @param point
     * @return
     */
    @Around(value = "pointCut()")
    public Object around(ProceedingJoinPoint point) {
        long start = System.currentTimeMillis();
        String methodName = point.getSignature().getName();
        log.info("around method name: {} params: {}", methodName, Arrays.asList(point.getArgs()));
        try {
            log.info("around end time: {}", (System.currentTimeMillis() - start) + " ms!");
            return point.proceed();
        } catch (Throwable e) {
            log.error("message: {}", e.getMessage());
        }
        return null;
    }

    /**
     * Pre-notification executes before target
     *
     * @param joinPoint
     */
    // @Before("@annotation(com.leone.boot.aop.anno.AopBefore)")
    // @Before("within(com.leone.boot.aop.controller.*)")
    // @Before("@within(org.springframework.web.bind.annotation.RestController)")
    // @Before("target(com.leone.boot.aop.controller.UserController)")
    @Before("@target(com.leone.boot.aop.anno.ClassAop) && @annotation(com.leone.boot.aop.anno.AopBefore)")
    public void beforeMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        log.info("before inform method name: {} param: {}", methodName, args);
    }

    /**
     * Post Notification Executes After target
     *
     * @param joinPoint
     */
    @After("@args(org.springframework.stereotype.Component) && 
            execution(* com.leone.boot.aop.controller.*.*(..))")
    public void afterMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        log.info("after inform method name : {} param: {}", methodName, args);
    }

    /**
     * Post-return executes after target returns
     *
     * @param joinPoint
     * @param result
     */
    @AfterReturning(value = "within(com.leone.boot.aop.controller.*)", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        log.info("afterReturning inform method name: {} return value: {}", methodName, result);
    }

    /**
     * Post-exception notification executes after a target exception
     *
     * @param joinPoint
     * @param ex
     */
    @AfterThrowing(value = "args(com.leone.boot.common.entity.User) && 
            execution(* com.leone.boot.aop.controller.*.*(..))", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Exception ex) {
        String methodName = joinPoint.getSignature().getName();
        log.info("afterThrowing inform method name: {} exceptions: {}" + methodName, ex);
    }
}

Test class:

import com.leone.boot.aop.anno.AopBefore;
import com.leone.boot.aop.anno.ClassAop;
import com.leone.boot.aop.interf.UserService;
import com.leone.boot.common.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author leone
 * @since 2018-06-21
 **/
@Slf4j
@ClassAop
@RestController
@RequestMapping("/api")
public class UserController {

    private UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }


    @AopBefore
    @RequestMapping(value = "/user/{userId}", method = RequestMethod.GET)
    public User findOne(@PathVariable Long userId) {
        return userService.findOne(userId);
    }

    @AopBefore
    @RequestMapping("/user")
    public User save(User user) {
        return user;
    }
}

Posted by PRSBOY on Wed, 18 Dec 2019 20:50:06 -0800