Spring AOP (aspect-oriented programming) principle and proxy mode - example demonstration

 1. AOP introduction and application scenarios

Spring Chinese Documentation (springdoc.cn)

Spring | Home official website

1. Introduction to AOP (Why does AOP appear?)       

        Java is an object-oriented (OOP) language, but it has some drawbacks. Although using OOP can achieve code reuse through composition or inheritance. But when we need to introduce multiple objects that do not have an inheritance relationship (generally referring to two different classes, they do not inherit from the same parent class or interface. They cannot share properties and methods through inheritance.) Introduce a When using common behaviors, such as logging, authorization verification, transactions, etc., the same code will still be scattered into various methods. If you want to turn off a certain function or modify it, you must modify all related methods, which is not easy to maintain and has a lot of repeated code. So the emergence of AOP made up for this deficiency of OOP.

         AOP is an important feature provided by the Spring framework. Unlike OOP (object-oriented programming), AOP advocates horizontal isolation of the same business logic in the program, and extracts repeated business logic into an independent module. The runtime dynamically weaves the code logic into the method of the target object, realizes decoupling from the business logic and eliminates duplicate code, and improves the maintainability and scalability of the code.

2. AOP application scenarios

This is the description of the application scenario in the official document:

Authentication permission; Caching cache; Context passing content delivery;

Error handling error handling; Lazy loading lazy loading; Debugging debugging;

Logging log, tracing, profiling and monitoring record tracking optimization calibration;

Performance optimization performance optimization; Persistence persistence; Resource pooling resource pool

Synchronization synchronization; Transactions affairs

Two, AOP principle

 The execution process is

1. In Spring, creating a Bean instance starts with the getBean() method. After the instance is created, the Spring container will match the class name of the target class according to the AOP configuration to see if the class name of the target class meets the aspect rules. If the aspect rules are satisfied, ProxyFactory will be called to create a proxy Bean and cached in the IoC container. Different proxy strategies are automatically selected according to the target object. If the target class implements the interface, Spring will select JDK Proxy by default. If the target class does not implement the interface, Spring will select Cglib Proxy by default. Of course, we can also force the use of Cglib Proxy through configuration

2. When the user invokes a method of the target object, it will be intercepted by an object called AopProxy. Spring encapsulates all the invocation strategies into this object. It implements the InvocationHandler interface by default, which is the outer layer of the invocation proxy object. blocker. In the invoke() method of this interface, the proceed() method of MethodInvocation will be triggered. In this method, interceptor chains that meet all AOP interception rules are executed in order. 

3. Each element in the Spring AOP interceptor chain is named MethodInterceptor, which is actually the Advice notification in the aspect configuration. This callback notification can be simply understood as a method in the newly generated proxy bean. That is, what we often call the woven code fragments, these woven code fragments will be executed at this stage.

4. The MethodInterceptor interface also has an invoke() method. In the MethodInterceptor's invoke() method, the call to the method of the target object is triggered, that is, the method of the target object is called by reflection.

3. What is the proxy mode

        When talking about proxy mode, it can be explained by two common examples: buying train tickets and shortcuts in Windows.

  1. Buying a train ticket: Let's say you need to buy a train ticket, but you don't want to go to the train station to buy it yourself. Then you can find an agent and ask him to help you through the ticketing process. The agent is responsible for queuing, seat selection, payment, etc., and finally hands over the ticket to you. In this example, the agent acts as an intermediary between you and the train station, and you complete the ticket purchase process through the agent, while also avoiding the hassle of going to the train station in person and queuing.

  2. Shortcuts in Windows: In Windows, you can use a shortcut on the desktop to access a program or file. The shortcut is actually a proxy, which refers to the location information of the target program or file, and provides a convenient entry, so that you can quickly access the target content by clicking the shortcut. The shortcut hides the specific implementation details of the underlying layer and simplifies the user's operation process.

Here is the sample code for buying train tickets:

// 创建一个抽象主题接口
interface Ticket {
    void purchase();
}

// 创建真实主题类,即需要购买火车票的对象
class TrainTicket implements Ticket {
    public void purchase() {
        System.out.println("购买火车票");
    }
}

// 创建代理类,即代理对象
class TicketProxy implements Ticket {
    private TrainTicket ticket;

    public void purchase() {
        if (ticket == null) {
            ticket = new TrainTicket();
        }
        // 通过代理对象调用真实对象的方法
        prePurchase();
        ticket.purchase();
        postPurchase();
    }

    private void prePurchase() {
        System.out.println("代理人处理排队、选座位等操作");
    }

    private void postPurchase() {
        System.out.println("代理人将车票交给顾客");
    }
}

// 客户端代码
public class ProxyPatternExample {
    public static void main(String[] args) {
        // 创建代理对象,并通过它来购买火车票
        Ticket ticketProxy = new TicketProxy();
        ticketProxy.purchase();
    }
}

        In the above example, Ticketit is an abstract subject interface that defines the method for purchasing tickets. TrainTicketIt is a real theme class, which represents the objects that really need to buy tickets. TicketProxyIt is a proxy class that implements Ticketthe interface. It maintains a TrainTicketreference to the object and purchaseperforms some additional operations before and after calling the method, such as queuing and ticket delivery. By creating a proxy object and calling its purchasemethods, we can indirectly let the proxy object complete the ticketing process and add some specific functionality to it without directly accessing the real subject object.

        The proxy mode is divided into two types, dynamic proxy and static proxy. They are all object-oriented proxy modes, and their functions are to act as an intermediary between the client and the target object, intercepting access to the target object, so as to achieve the purpose of enhancing the function of the target object or controlling its access rights.

The difference between the two is as follows:

        A static proxy is an agency relationship that has been determined at compile time, and both the proxy class and the target class already exist at compile time. In static proxies, proxy classes need to be created manually, and the relationship between proxy classes and target classes is fixed. Whenever a new interface or class needs to be proxied, a new proxy class needs to be manually written.

        The dynamic proxy is a proxy class that is dynamically generated at runtime. java.lang.reflect.ProxyThrough the classes and interfaces provided by Java java.lang.reflect.InvocationHandler, proxy objects can be dynamically generated at runtime. The dynamic proxy does not need to manually write the proxy class, it dynamically generates the proxy object according to the interface information at runtime. When using a dynamic proxy, the relationship between the proxy class and the target class is flexible. You only need to define a common interface, and InvocationHandlerhandle method calls through , and you can proxy multiple different interfaces or classes.

        The advantage of dynamic proxy is that it can reduce the amount of code and avoid writing a large number of repetitive proxy classes. At the same time, it can also implement more flexible proxy logic, which can dynamically modify proxy behavior at runtime.

        In general, both dynamic proxy and static proxy are the implementation of proxy mode. Static proxy is implemented by manually writing proxy classes, while dynamic proxy dynamically generates proxy classes at runtime through reflection mechanism.

Fourth, the proxy mechanism of Spring AOP

        Spring will generate a dynamic proxy object for the target object during runtime, and implement enhancements to the target object in the proxy object. The bottom layer of Spring AOP performs horizontal weaving for the target object (Target Bean) through the following two dynamic proxy mechanisms.

        In the configuration of Spring AOP, pointcuts and aspects can be declared through XML or annotations. The XML method needs to configure the entry point expression, the advice type (such as pre-advice, post-advice, etc.) and the reference of the aspect class; the annotation method specifies the entry point and advice type by adding annotations to the aspect class.

        Spring uses JDK dynamic proxy by default. When a proxy class is needed instead of a proxy interface, Spring will automatically switch to using CGLIB proxy. However, current projects are all interface-oriented programming, so JDK dynamic proxy is relatively more used.

What is the difference between JDK proxy and CGLIB proxy?

There is no need to create a proxy class, JDK dynamically creates it for us at runtime, and the JDK proxy is an interface. If the target class does not have an interface, use Cglib to generate a proxy, whether it is a JDK proxy or a Cglib proxy, essentially operates on bytecode.

proxy technology describe
JDK Dynamic Proxy The default dynamic proxy mode of Spring AOP, if the target object implements several interfaces, Spring uses the java.lang.reflect.Proxy class of JDK for proxy.
CGLIB dynamic proxy If the target object does not implement any interface, Spring uses the CGLIB library to generate a subclass of the target object to realize the proxy to the target object.

Note: Since methods marked as final cannot be overridden, such methods cannot be proxied through either the JDK dynamic proxy mechanism or the CGLIB dynamic proxy mechanism.

Interview: Let you implement a JDK to implement dynamic proxy? What is your train of thought?

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

// 定义接口
interface Hello {
    void sayHello();
}

// 实现 InvocationHandler 接口
class MyInvocationHandler implements InvocationHandler {
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在方法调用前后添加自定义逻辑
        System.out.println("Before method invocation");
        Object result = method.invoke(target, args);
        System.out.println("After method invocation");
        return result;
    }
}

public class DynamicProxyExample {
    public static void main(String[] args) {
        // 创建被代理对象
        Hello hello = new HelloImpl();

        // 创建 InvocationHandler 实例
        MyInvocationHandler handler = new MyInvocationHandler(hello);

        // 创建代理对象
        Hello proxyHello = (Hello) Proxy.newProxyInstance(
            hello.getClass().getClassLoader(),
            hello.getClass().getInterfaces(),
            handler
        );

        // 调用代理对象的方法
        proxyHello.sayHello();
    }
}

// 实现接口
class HelloImpl implements Hello {
    @Override
    public void sayHello() {
        System.out.println("Hello World!");
    }
}

How to implement dynamic proxy if CGLIB is used?

引入 CGLIB 的依赖。在 Maven 项目中,可以添加以下依赖项到 pom.xml 文件中:
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
创建一个普通的类作为目标类,该类不需要实现任何接口。例如:
public class TargetClass {
    public void doSomething() {
        System.out.println("Doing something...");
    }
}
创建一个实现了 MethodInterceptor 接口的类,该类负责处理方法的调用。在 intercept 方法中,可以编写逻辑来处理代理对象的操作。例如:
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before method invocation");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("After method invocation");
        return result;
    }
}
使用 CGLIB 创建代理对象。通过调用 Enhancer.create 方法,传入目标类的 Class 对象和 MethodInterceptor 对象,创建代理对象。例如:
import net.sf.cglib.proxy.Enhancer;

public class CglibDynamicProxyExample {
    public static void main(String[] args) {
        // 创建目标类的实例
        TargetClass target = new TargetClass();

        // 创建 MethodInterceptor 实例
        MyMethodInterceptor interceptor = new MyMethodInterceptor();

        // 使用 Enhancer 创建代理对象
        TargetClass proxy = (TargetClass) Enhancer.create(
            target.getClass(),
            interceptor
        );

        // 调用代理对象的方法
        proxy.doSomething();
    }
}
运行上述代码,会输出以下结果:
Before method invocation
Doing something...
After method invocation

CGLIB 可以在运行时生成目标类的子类,并通过拦截器拦截目标方法的调用。这样可以实现对目标方法的增强或拦截操作


5. Example - Realize AOP's recording of logs 

step:

First introduce aop dependency

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

Then follow the following three steps 

 


import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;

/**
 * 使用@Before在切入点开始处切入内容
 * 使用@After在切入点结尾处切入内容
 * 使用@AfterReturning在切入点return内容之后切入内容(可以用来对处理返回值做一些加工处理)
 * 使用@Around在切入点前后切入内容,并自己控制何时执行切入点自身的内容
 * 使用@AfterThrowing用来处理当切入内容部分抛出异常之后的处理逻辑
 */

@Aspect /**Description:  使之成为切面类*/
@Component /**Description: 把切面类加入到IOC容器中*/
public class AopLog {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    //线程局部的变量,解决多线程中相同变量的访问冲突问题。
    ThreadLocal<Long> startTime = new ThreadLocal<>();

    //定义切点 1
    @Pointcut("execution(public * com.example..*.*(..))")
    public void aopWebLog() {
    }
    //定义切点 2
    @Pointcut("execution(public * com.example..*.*(..))")
    public void myPointcut() {
    }

    @Before("aopWebLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        startTime.set(System.currentTimeMillis());
        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        // 记录下请求内容
        logger.info("URL : " + request.getRequestURL().toString());
        logger.info("HTTP方法 : " + request.getMethod());
        logger.info("IP地址 : " + request.getRemoteAddr());
        logger.info("类的方法 : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        //logger.info("参数 : " + Arrays.toString(joinPoint.getArgs()));
        logger.info("参数 : " + request.getQueryString());
    }

    @AfterReturning(pointcut = "aopWebLog()",returning = "retObject")
    public void doAfterReturning(Object retObject) throws Throwable {
        // 处理完请求,返回内容
        logger.info("应答值 : " + retObject);
        logger.info("费时: " + (System.currentTimeMillis() - startTime.get()));
    }

    //抛出异常后通知(After throwing advice) : 在方法抛出异常退出时执行的通知。
    @AfterThrowing(pointcut = "aopWebLog()", throwing = "ex")
    public void addAfterThrowingLogger(JoinPoint joinPoint, Exception ex) {
        logger.error("执行 " + " 异常", ex);
    }


    @Around("myPointcut()")
    public Object mylogger (ProceedingJoinPoint pjp) throws Throwable {
        String className = pjp.getTarget().getClass().toString();
        String methodName = pjp.getSignature().getName();
        Object[] arry = pjp.getArgs();
        ObjectMapper mapper = new ObjectMapper();
        logger.info("调用前:"+className+":"+methodName+"传递的参数为:"+mapper.writeValueAsString(arry));
        Object obj = pjp.proceed();
        logger.info("调用后"+className+":"+methodName+"返回值为:"+mapper.writeValueAsString(obj));
        return obj;
    }

}
@RestController
public class AopLogController {
//    @GetMapping("/aoptest")
//    public String aVoid(){
//        return "hello aop test";
//    }

    @GetMapping("/hello")
    public String hello (@RequestParam("name")String name,@RequestParam("age")String age){
        return "hello"+name+"age"+age;
    }
}

run:

 You can see that the log has been printed.

Guess you like

Origin blog.csdn.net/weixin_49171365/article/details/130912980