[Java design pattern · structured & AOP] Proxy Pattern & Spring AOP application

Structural patterns focus on how to organize existing classes or objects together to form a more powerful structure.

1, Overview

Proxy Pattern: provide a proxy live placeholder for an object, and the proxy object controls access to the original object.

2, Structure

  • Subject (abstract subject role):
    It declares the common interface between the real topic and the proxy topic. In this way, the proxy topic can be used wherever the real topic is used. The client usually needs to program for the abstract topic role.
  • Proxy (proxy subject role):
    It contains a reference to the real subject object, so that the real object can be operated. The proxy subject provides the same interface as the real subject, controls and restricts the use of the real object, and can perform other operations before and after the subject operation.
  • RealSubject:
    The real object represented by the proxy role is defined, and the real business operation is realized in the real subject role. The client can call the operation defined in the real role through the proxy subject role

3, Realize

Let's take the landlords and agents in the housing rental problem as an example:

Abstract subject role: IRent lease interface: define lease related behavior specifications

/* IRent Lease interface: defines the lease related code of conduct */
public interface IRent {
    void rent();	//lease
}

Real theme role: HouseKeeping: Landlord

/* HouseKeeping: Landlord class */
public class HouseKeeping implements IRent {
    /**
     * Core business method: renting houses
     */
    @Override
    public void rent() {
        System.out.println("I have a suite for 1500 a month");
    }
}

1. Static proxy

Agent subject role: HouseAgency rental agent class

/* HouseAgency Rental agents */
public class HouseAgency implements IRent {
    //Real subject object
    private IRent rent;
    
    /* Unique constructor: initialization */
    public HouseAgency(IRent rent) {
        this.rent = rent;
    }

    /**
     * Core business method: rental housing
     */
    @Override
    public void rent() {
        before();
        Object result = method.invoke(this.real, args);
        after();
    }
	
	/**
     * Method to process before calling business method
     */
    public void before() {
        System.out.println("Deposit in advance");
    }

    /**
     * Method to process after calling business method
     */
    public void after() {
        System.out.println("Property fee and water and electricity fee will be charged for check-in...");
    }
}

Test code:
Unit tests using JUnit:

@Test
public void test() {
	/* Create proxy object */
    IRent rent = new HouseAgency(new HouseKeeping());
    /* By proxy object */
    rent.rent();
}

Test results:

2. JDK dynamic agent

Starting from JDK 1.3, the Java language provides support for dynamic agents. Some classes under the java.lang.reflect package are required:

Proxy class:

Proxy class provides methods for creating dynamic proxy classes and instance objects. It is the parent class for creating dynamic proxy classes. Its most common methods are as follows:

/* It is used to return a proxy Class of Class type, provide the Class loader in the parameter, and specify the interface array of the proxy */
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)

/* Used to return a dynamically created proxy class instance:
   The first parameter, loader, represents the class loader of the proxy class
   The second parameter interfaces represents the list of interfaces implemented by the proxy class
   The third parameter h represents the assigned call handler class
 */
public static Object newProxyInstance(ClassLoader loader, Class<?>... interfaces, InvocationHandler h)

InvocationHandler interface:

The implementation interface of the proxy handler, which is the public parent class of the invocation handler of the proxy instance. Each instance of the proxy class can provide a related specific invocation handler (subclass of InvocationHandler interface)

Its core method is as follows:

/* This method is used by the proxy to call the method of the proxy class instance. When the proxy implementation business method is called, this method will be called automatically
   The first parameter proxy represents an instance of the proxy class
   The second parameter, method, represents the method that requires a proxy
   The third parameter args represents the parameters of the proxy method
 */
public Object invork(Object proxy, Method method, Object[] args)

Implementation code (proxy subject role):

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

/* Agent subject role: implement InvocationHandler interface */
public class HouseProxy implements InvocationHandler {
	//Real theme role
    private Object real;
	
	/* Unique constructor: initialization */
    public HouseProxy(Object real) {
        this.real = real;
    }

	/**
     * Generate proxy object
     */
    public Object createProxy() {
        return Proxy.newProxyInstance(this.real.getClass().getClassLoader(), this.real.getClass().getInterfaces(), this);
    }

    /**
     * The method call of the agent to the agent class instance. When the agent implementation business method is called, the method is automatically called
     * @param proxy     Represents an instance of a proxy class
     * @param method    Represents a method that requires a proxy
     * @param args      Represents the parameters of the proxy method
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(this.real, args);
        after();
        return result;
    }

    /**
     * Method to process before calling business method
     */
    public void before() {
        System.out.println("Deposit in advance");
    }

    /**
     * Method to process after calling business method
     */
    public void after() {
        System.out.println("Property fee and water and electricity fee will be charged for check-in...");
    }
}

Test code:
Unit tests using JUnit:

@Test
public void test(){
	/* Generate proxy object */
    IRent rent = (IRent) new HouseProxy(new HouseKeeping()).createProxy();
    /* Call business methods through proxy objects */
    rent.rent();
}

Test results:

3. CGLib agent

CGLib (Code Generation Library), a powerful, high-performance and high-quality code generation class library.
It can extend Java classes and implement Java interfaces at run time. Hibernate uses it to realize the dynamic generation of PO bytecode. CGlib is stronger than Java's java.lang.reflect.Proxy class in that it can take over not only the methods of interface classes, but also the methods of ordinary classes. CGlib inherits the class to be dynamically proxied, rewrites the method of the parent class, and realizes AOP aspect oriented programming.

Differences between JDK dynamic agent and CGLIB agent:

  • JDK dynamic proxy can only generate proxy for classes that implement interfaces, not for classes.
  • CGLIB implements proxy for classes. It mainly generates a subclass of the specified class and overrides the methods in it.

Speed comparison between the two:

  • JDK dynamic proxy is interface oriented. When creating proxy implementation classes, it is faster than CGLib
  • CGLib dynamic proxy is implemented by inheriting the proxy class at the bottom of bytecode (the proxy class cannot be modified by final keyword), and the running speed is faster than JDK dynamic proxy

Maven introduces dependencies:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

The core interface of CGLib is the MethodInterceptor interface under the net.sf.cglib.proxy package, which inherits from the Callback interface:

Core approach:

/** The method call of the agent to the agent class instance. When the agent implementation business method is called, the method is automatically called
  * @param obj		Represents this class
  * @param method	Represents a method that requires a proxy
  * @param args		Represents the parameters of the proxy method
  * @param proxy	Delegate to parent class
  */
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable 

Implementation code:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

/* Agent subject role: implement MethodInterceptor interface */
public class CGLibProxy implements MethodInterceptor {
	//Real theme role
    private Object real;
	
	/* Unique constructor: initialization */
    public CGLibProxy(Object real) {
        this.real = real;
    }

    /**
     * Create proxy object
     * @return
     */
    public Object createProxy() {
        Enhancer e = new Enhancer();
        e.setSuperclass(this.real.getClass());
        e.setCallback(this);
        return e.create();
    }

	/** The method call of the agent to the agent class instance. When the agent implementation business method is called, the method is automatically called
	  * @param obj		Represents this class
	  * @param method	Represents a method that requires a proxy
	  * @param args		Represents the parameters of the proxy method
	  * @param proxy	Delegate to parent class
	  */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        before();
        Object result = proxy.invokeSuper(obj,args);
        after();
        return result;
    }
    /**
     * Method to process before calling business method
     */
    public void before() {
        System.out.println("Deposit in advance");
    }

    /**
     * Method to process after calling business method
     */
    public void after() {
        System.out.println("Property fee and water and electricity fee will be charged for check-in...");
    }
}

Test code:

@Test
public void test(){
	/* Generate proxy object */
	HouseKeeping h = (HouseKeeping) new CGLibProxy(new HouseKeeping()).createProxy();
	/* Call business methods through proxy objects */
	h.rent();
}

Test results:

4, Characteristics

☯ advantage

  • It can coordinate the caller and the callee, and reduce the coupling degree of the system to a certain extent
  • It can be programmed for abstract subject roles, and the replacement of agent classes is flexible, easy to expand and in line with the opening and closing principle

☯ shortcoming

  • The presence of proxy objects may slow down the processing of requests

5, Dynamic agent application: AOP

AOP (aspect oriented programming). AOP includes aspect, advice and join point. The implementation method is to add notification to the agent of the target object before and after the join point to complete the unified aspect 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.

AOP application technology:

  1. Using dynamic agent technology, intercepted messages are decorated to replace the execution of the original object (real subject role) behavior
  2. The static weaving method is adopted to weave the relevant code during compilation

6, Spring AOP

Spring dynamic proxy mechanism:
Spring provides two methods to generate proxy objects by default: JDK Proxy + CGLib. The specific method depends on the situation. The default strategy is as follows:

  • The target class is the interface, using JDK dynamic proxy
  • The target object does not implement the interface and uses CGLIB proxy

Spring provides configuration parameters to force the use of CGLIB technology, as follows:

<aop:config proxy-target-class="true" />

CGLIB uses the generated proxy subclass to implement the proxy. Proxy target class indicates that the attribute value determines that the proxy based on the interface / class is created. Proxy target class = "true" indicates that CGLIB technology is forced to implement AOP. If < AOP: config / > is filled in to configure the default, the proxy is selected according to the Spring default policy.

Related terms:

  • Advice: contains crosscutting behavior that needs to be used for multiple application objects
  • Join Point: all points where notifications can be applied during program execution
  • PointCut: defines when to cut in and which connection points will be notified
  • Aspect: a combination of notification and pointcut
  • Introduction: allows you to add new properties and methods to existing classes
  • Weaving: the process of applying the aspect to the target object and creating a new proxy object. It is divided into compilation weaving, class loading weaving and runtime weaving

Spring Boot uses AOP

Before using AOP in SpringBoot, first introduce related dependencies:
Introducing dependencies using Maven:

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

We write a small demo to test AOP:

List of core code structure:

Write and slice the Controller:
IDEA has high support for AOP, and the cut methods will be marked with special icons:

Write Controller code:

package com.ljw.aop.controller;		//Package path
//Omit imports

@RestController
@RequestMapping("/api/aop")
public class AopController {

    @GetMapping("/hello")
    public String hello(){
        System.out.println("hello");
        return "hello";
    }
}

Define tangent point:

Pointcut is defined by @ pointcut annotation and pointcut expression. The @ pointcut annotation can define reusable pointcuts in one aspect.

The minimum strength of Spring aspect can reach the method level. The use of execution expression requires the return type, class name, method name, parameter name and other related information of the method. This is the most widely used method:

Define notification:

Five types of notifications (including IDEA support icons):

  • Pre (@ Before): execute notification Before the target method call

  • Post (@ After): execute the notification After the target method is called

  • Surround (@ Around): execution notification before and after the target method call

  • Return (@ AfterReturning): the notification is executed after the target method is successfully executed

  • Exception (@ AfterThrowing): execute notification after the target method throws an exception

Write Advice Code:

package com.ljw.aop.advice;		//Package path
//Omit imports

@Aspect
@Component
public class AopAdvice {

    /**
     * Define tangent point
     */
    @Pointcut("execution (* com.ljw.aop.controller.*.*(..))")
    public void point() {

    }

    /**
     * Before advice 
     */
    @Before("point()")
    public void before() {
        System.out.println("before");
    }

    /**
     * Post notification
     */
    @After("point()")
    public void after() {
        System.out.println("after");
    }

    /**
     * Around Advice 
     * @param proceedingJoinPoint   breakthrough point
     */
    @Around("point()")
    public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
        System.out.println("around-before");    //Before execution
        try {
            proceedingJoinPoint.proceed();      //When executing
        } catch (Throwable t) {
            t.printStackTrace();
        }
        System.out.println("around-after");     //After execution
    }
    
    /**
     * Method execution completion notification
     */
    @AfterReturning("point()")
    public void returnAdvice() {
        System.out.println("finish");
    }
	
	/**
     * Method throws an exception
     */
    @AfterThrowing("point()")
    public void throwAdvice() {
        System.out.println("Exception!!!");
    }
}

Access path:

Access succeeded:

Post visit results:

Tags: Java AOP

Posted on Wed, 29 Sep 2021 17:36:01 -0400 by Killswitch