Spring AOP learning notes

Posted by bengaltgrs on Sun, 12 Dec 2021 10:08:42 +0100

AOP

agent

In some cases, a customer cannot or does not want to directly access another object. At this time, it needs to find an intermediary to help complete a task. This intermediary is the proxy object. For example, you don't have to go to the railway station to buy train tickets. You can buy them through 12306 website or go to the train ticket agency. Another example is to find a girlfriend, a nanny, a job, etc. can be completed by looking for an intermediary.

Agent pattern in design pattern http://c.biancheng.net/view/1359.html

JDK dynamic agent

The agent needs to be an interface or an implemented interface, and what needs to be enhanced is the methods defined in the interface. Use the static method newproxyinstance (classloader, loader, class <? > [] interfaces, invocationhandler) of the Proxy class to dynamically generate the Proxy object

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.function.BiConsumer;

public class ProxyTest {

    public static void main(String[] args) {
    	// BiCosumer is used to define a functional variable, which is passed into the proxy object to dynamically specify the enhancement method
        BiConsumer before = (x, y) -> {
            String msg = ((Method) x).getName() + " Before method call" + y;
            System.out.println(msg);
        };
        BiConsumer after = (x, y) -> {
            String msg = ((Method) x).getName() + " After the method is called, the result is:" + y;
            System.out.println(msg);
        };
		// Get proxy object
        UserService us = (UserService) getProxy(new UserServiceImpl(), before,after);
		// Executing methods using proxy objects 
        us.addUser();
        us.addUser();
    }
	
	// Encapsulate the method of obtaining dynamic proxy object
    public static Object getProxy(Object target, BiConsumer beforeMethod, BiConsumer afterMethod) {
    	//The parameters to be passed in are: the class loader of the target object, the class type of the interface of the target object, and the calling handler
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
        	// The call handler is an interface in which the invoke method needs to be implemented
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
				// The method to be executed before the target method is executed is the enhanced method
                beforeMethod.accept(method, args);
				// Real execution target method
                Object result = method.invoke(target, args);
				// The method to be executed after the target method is executed is the enhanced method
                afterMethod.accept(method, result);

                return result;
            }
        });
    }
}

CGlib proxy mode

The proxy object is a subclass of the target object Step: first create the Enhancer object, set its parent class as the target object, and then set the callback method, that is, the wrapping method of the target method.

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

import java.lang.reflect.Method;

public class CglibProxyTest {

    public static void main(String[] args) {
        UserService us = (UserService) getProxy(new UserServiceImpl());
        us.addUser();

    }

    public  static Object  getProxy(Object target){
        Enhancer enhancer = new Enhancer();
        // Set the target object as the parent of the proxy object
        enhancer.setSuperclass(target.getClass());
        // Sets the wrap method when the target method is executed
        enhancer.setCallback(
        		// You need to pass in a variable of Callback type. At present, the teacher is talking about using this interface (it inherits Callback)
                new MethodInterceptor() {
                    @Override
                    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                        System.out.println(method.getName()+" Method was called");
                        Object result = method.invoke(target, objects);
                        System.out.println(method.getName()+" Method call completed");
                        return result;
                    }
                }
        );
        
        return enhancer.create();
    }
}

AOP annotation configuration method

In the software industry, AOP is the abbreviation of Aspect Oriented Programming, which means: Aspect Oriented Programming, a technology to realize the unified maintenance of program functions through precompiled mode and dynamic agent during operation. AOP is the continuation of OOP, a hot spot in software development, an important content in Spring framework, and a derivative paradigm of functional programming. AOP can isolate each part of business logic, reduce the coupling between each part of business logic, improve the reusability of program, and improve the efficiency of development.

Summary: AOP in Spring uses proxy objects to extend methods without modifying the source code

Conceptual literacy

5.1. AOP concept
Let's first define some core AOP concepts and terms. These terms are not Spring specific. Unfortunately, AOP terminology is not particularly intuitive. However, if Spring uses its own terminology, it will be more confusing.

  • Aspect: a module that spans concerns of multiple classes. Transaction management is a good example of crosscutting concerns in enterprise Java applications. In Spring AOP, aspect is implemented by using regular classes (pattern based methods) or regular classes annotated with @ aspect annotations (@ AspectJ style).
  • Join point: a point in the process of program execution, such as method execution or exception handling. In Spring AOP, a join point always represents the execution of a method.
  • Advice: action taken by advice at a specific connection point. Different types of advice include "around", "before" and "after" suggestions. (notification types will be discussed later.) Many AOP frameworks, including Spring, model notifications as interceptors and maintain a chain of interceptors around connection points.
  • Pointcut: predicate that matches the join point. Advice is associated with a pointcut expression and runs at any join point that matches the pointcut (for example, executing a method with a specific name). The concept of join points matched by pointcut expressions is the core of AOP. Spring uses AspectJ pointcut expression language by default.
  • Introduction: declare additional methods or fields on behalf of a type. Spring AOP allows you to introduce new interfaces (and corresponding implementations) to any proposed object. For example, you can use the introduction to make bean s implement the IsModified interface to simplify caching. (describes what is called an inter type declaration in the AspectJ community.)
  • Target object: an object suggested by one or more aspects. Also known as "suggested object". Since Spring AOP is implemented using a runtime proxy, this object is always a proxy object.
  • AOP proxy: an object created by the AOP framework to implement aspect contracts (recommended method execution, etc.). In the Spring Framework, AOP proxy is JDK dynamic proxy or CGLIB proxy.
  • Weaving: Associate facets with other application types or objects to create suggested objects. This can be done at compile time (for example, using the AspectJ compiler), load, or run time. Spring AOP, like other pure Java AOP frameworks, performs weaving at run time.

Annotation development:

prepare

First POM You need to add aop dependencies to the XML file

<!--introduce AOPjar Package file-->
<dependency>
     <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

5.4. @AspectJ support https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-ataspectj

  1. First, you need to add the @ EnableAspectJAutoProxy annotation in the configuration class to start the proxy function
@Configuration
@ComponentScan("com.jt")
@EnableAspectJAutoProxy // Add this annotation to enable facet support
class MySpringConfig{}

Aspect aspect

Create a facet processing class, use the @ Aspect annotation to identify this class as a facet, and add the @ Component annotation to declare that this class needs to be managed by the Spring container. After the facet class is ready, you can define pointcuts and notifications in this class

@Component
@Aspect
class PointClass {}

PointCut entry point

  • definition
    Pointcuts can determine the connection points we need, allowing us to control when notifications run. Spring AOP only supports method execution join points for spring beans, so you can think of pointcuts as the execution of methods on matching spring beans. A Pointcut declaration has two parts: a signature containing a name and any parameters, and a Pointcut expression that determines which method execution we are interested in. In the @ AspectJ annotation style of AOP, a Pointcut signature is provided by the conventional method definition, and the Pointcut expression is represented by the @ Pointcut annotation (the method as the Pointcut signature must have a void return type).
    @Pointcut("bean(beanId)") uses this form to specify that you want to add the current aspect for all methods of the bean to which beanId points

For the writing method of pointcut expression, see official document 5.4 3. Declare an entry point https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-pointcuts

// The parenthesis behind the annotation means to execute the methods in the section for the bean with beanId "userServiceImpl"
@Pointcut("bean(userServiceImpl)") //Pointcut expression
public void pointCutHandler() {} // Pointcut signature
  • winthin tag
    @Pointcut ("winthin (fully qualified pathname of class)"), specifying the specific single class to be intercepted
@Pointcut("winthin(com.jt.service.UserServiceImpl") //Pointcut expression
public void pointCutHandler() {} // Pointcut signature

@Pointcut("winthin(*)") uses wildcards
com.jt.service.* In this way, only the classes under the service package can be configured, and the classes in the sub package and grandpackage cannot be matched
com.jt.service..* This method can be used to configure all the following classes, such as service package, sub package, sub package, etc
com.*. service..* All sub packages under the com package are service, and the classes under the service package
com..*.service..*com package, all subclasses under all service sub packages

@Pointcut("winthin(com.jt.service.*") //
public void pointCutHandler() {} // Pointcut signature
  • The execution expression has fine granularity and can be matched according to the method signature. The format of the execution expression is as follows:

Expression structure
execution(modifiers-pattern? return-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
The non red part is optional

All parts except the returning type pattern (ret-type-pattern in the preceding snippet), the name pattern, and the parameters pattern are optional. The returning type pattern determines what the return type of the method must be in order for a join point to be matched. * is most frequently used as the returning type pattern. It matches any return type. A fully-qualified type name matches only when the method returns the given type. The name pattern matches the method name. You can use the * wildcard as all or part of a name pattern. If you specify a declaring type pattern, include a trailing . to join it to the name pattern component. The parameters pattern is slightly more complex: () matches a method that takes no parameters, whereas (...) matches any number (zero or more) of parameters. The () pattern matches a method that takes one parameter of any type. (,String) matches a method that takes two parameters. The first can be of any type, while the second must be a String. Consult the Language Semantics section of the AspectJ Programming Guide for more information.

expressionsignificance
execution(* com.jt.service.UserServiceImpl.adduser()Matches this method exactly, but does not limit its permissions
execution(* com.jt.service..*.*(..)Match com jt. All methods of all classes under the service package that contain arbitrary parameters and do not limit permissions
execution(public * *(..))Method to match all public permissions
execution(* set*(..))Method starting with set that matches any parameter list
execution(* com.xyz.service.AccountService.*(..))All methods of any parameter under the AccountService class or interface, with unlimited permissions
execution(* com.xyz.service.*.*(..))All methods of any parameter list of all first level subclasses under the service package
execution(* com.xyz.service..*.*(..))All methods of any parameter list of all subclasses (including sub packages) under the service package
execution(* *..tedu.*.add*(..))A method that matches any parameter list starting with add of all subclasses of tedu package under any package

  • annotation expression
    @annotation(org.springframework.transaction.annotation.Transactional) Any join point (method execution only in Spring AOP) where the executing method has an @Transactional annotation:

  • You can use & &, |, and combined pointcut expressions!. You can also reference pointcut expressions by name

@Pointcut("@annotation(com.jt.annotation.Animal) || execution(* *..tedu.*.add*(..))")
public void pointcut(){}
  • If the method is matched by annotation, and then the annotation is used again in the method, a formal parameter can be defined to receive this argument
@Around("@annotation(pri)")
public Object around1(ProceedingJoinPoint joinPoint,Pri pri) throws Throwable {
    String name = pri.name();
    System.out.println(name);
    return joinPoint.proceed();
}

Advise notification

  • The first type of notification, pre, post, after returning results and after throwing exceptions, will not affect the execution of the target method, so it is generally applied to the log system.
  1. @Before("pointCutHandler()") identifies the preceding method, that is, the method executed before the target method is executed. The pointCutHandler here refers to the signature of the pointcut in the current aspect class, or you can write the pointcut expression directly
    Note: if only the pointcut is defined but no notification is defined, the object obtained through the container is still the original object, otherwise the proxy object will be obtained
@Before("pointCutHandler()")
public void beforeMethod() {
    System.out.println("Target method pre execution point...");
}
  1. @After("pointCutHandler()") identifies the post method, that is, the method executed after the target method is executed. The pointCutHandler here is the same as above
@After("pointCutHandler()")
public void afterMethod() {
    System.out.println("Target method post execution point...");
}
  1. @After returning ("pointcuthandler()") method is the method executed after returning the result. Note that this method takes precedence over the @ After() annotation
@AfterReturning("pointCutHandler()")
public void afterReturningMethod() {
    System.out.println("Target method invoke Execution point after completion with results...");
}

@There is a returning attribute in the AfterReturning annotation, which can inject the return result of the target method into the formal parameters of the method

Returns:the name of the argument in the advice signature to bind the returned value to

@AfterReturning(value="@annotation(com.jt.annotation.Animal)",returning="haha")
public void afterReturningMethod(Object haha) {
    System.out.println("Target method invoke Execution point after completion with results...");
    System.out.println("Results returned by the target method:"+haha);
}
  1. @The AfterThrowing("pointCutHandler()") method is executed when an exception is encountered in the execution of the target method
@AfterThrowing("pointCutHandler()")
public void afterThrowingMethod() {
    System.out.println("Execution point when an exception is encountered during the execution of the target method...");
}

@There is also a throwing attribute in the AfterThrowing("pointCutHandler()") annotation, which is used to inject exception objects into formal parameters

Returns:the name of the argument in the advice signature to bind the thrown exception to

@AfterThrowing(value = "pointcut()",throwing = "myException")
public  void afterThrowing(Exception myException){
    System.out.println("Notification triggered after the pointcut throws an exception");
    System.out.println("Exception information:"+myException.getMessage());
    myException.printStackTrace();
}
  • The second type of notification has only one @ Around, which can control whether the target method is executed or not, that is, it can affect the process of business flow. Common applications include: permission verification, caching system and exception handling

@Around("pointCutHandler()") surround method. This method is special. It can receive a parameter, which is the proxy object after the target object binds all the notifications defined above. That is to say, surround programming is a layer outside after the proxy object is created and various notifications are bound.

@Around("pointCutHandler()")
public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("Wrap around pre execution point...");
    Object result = joinPoint.proceed();
    System.out.println("Wrap around post execution point...");
    return result;
}

@If Order() defines multiple surround notifications for the same pointcut, you can use the @ Order(1) annotation to specify the binding order. The smaller the value, the earlier the binding, and the binding takes precedence
It can act on classes, methods and properties

The trycatch in this figure may not be very appropriate for the execution timing of several notifications, which needs to be understood in combination with notes

@Around{
	Object result;
	aroundCode;// Wrap around the first half of the method code
	@Before(){}
	try{
		result = target.method();
		@AfterReturning(){}// If an exception is thrown during the execution of the method, this notification will not be executed
	}catch(Exception e){
		@AfterThrowing(){}// This notification is executed if an exception is thrown during method execution
		throw e;
	}finally{
		@After(){}// It is executed regardless of whether an exception occurs
	}
	aroundCode;// Wrap around the second half of the method. If an exception occurs, it will no longer be executed
	return result;
}

Overall code

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

public class AOPTest {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MySpringConfig.class);
        UserService userService = context.getBean(UserService.class);
        userService.addUser();
    }

}

@Configuration
@ComponentScan("com.jt")
@EnableAspectJAutoProxy
class MySpringConfig{

}

interface UserService{
    void addUser();
}

@Service
class UserServiceImpl implements UserService{

    @Override
    public void addUser() {
        System.out.println("Added a user");
    }
}

@Component
@Aspect
class PointClass {

    @Pointcut("bean(userServiceImpl)")
    public void pointCutHandler() {

    }

    @Before("pointCutHandler()")
    public void beforeMethod() {
        System.out.println("Target method pre execution point...");
    }

    @After("pointCutHandler()")
    public void afterMethod() {
        System.out.println("Target method post execution point...");
    }

    @AfterReturning("pointCutHandler()")
    public void afterReturningMethod() {
        System.out.println("Target method invoke Execution point after completion with results...");
    }

    @AfterThrowing("pointCutHandler()")
    public void afterThrowingMethod() {
        System.out.println("Execution point when an exception is encountered during the execution of the target method...");
    }

    @Around("pointCutHandler()")
    public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("Wrap around pre execution point...");
        Object result = joinPoint.proceed();
        System.out.println("Wrap around post execution point...");
        return result;
    }
}

Common API s in notifications

  • The JoinPoint notification method can receive a JoinPoint type parameter, which is used to obtain the following values and apply them to @ Before, @ After
methodvalueexplain
getArgs()[Ljava.lang.Object;@18cc679eArray of type Object
getKind()method-execution
getSignature()void com.jt.service.UserServiceImpl.addUser()Method signature can be forcibly converted to MethodSignature, and then getMethod() can get the method object in the reflection
getSourceLocation()org.springframework.aop.aspectj. MethodInvocationProceedingJoinPoint$SourceLocationImpl@551a20d6
getStaticPart()execution(void com.jt.service.UserServiceImpl.addUser())
getTarget()com.jt.service.UserServiceImpl@578524c3Get the proxied object, which may return null
getThis()com.jt.service.UserServiceImpl@578524c3Gets the current proxy object
toLongString()execution(public void com.jt.service.UserServiceImpl.addUser())Pointcut expression
toShortString()execution(UserServiceImpl.addUser())Pointcut expression
toString()execution(void com.jt.service.UserServiceImpl.addUser())Pointcut expression

Topics: Back-end