About proxy pattern and spring AOP

Posted by lol on Thu, 02 Sep 2021 21:10:19 +0200

Proxy mode (proxy)

Agent mode is a structural design mode. It provides an agent (agent class) for other objects (delegate class) to control access to the original object, and allows some processing before and after submitting the request to the object, such as preprocessing the message for the delegate class, filtering and forwarding the message, and subsequent processing after the message is executed by the delegate class.

Static: the programmer creates an agent class or a specific tool automatically generates the source code and compiles it. The. Class file of the agent class already exists before the program runs.
Dynamic: it is created dynamically by using reflection mechanism when the program is running.

Static proxy

Class diagram

Case code

  1. Define an interface
public interface ServiceInterface {
	void operation();
}
  1. Create a concrete class implementation interface
public class ServiceImpl implements ServiceInterface {   
    @Override  
    public void operation() {  
        System.out.println("Perform business operations.....");  
    }  
} 
  1. Create a proxy class and implement the interface (the proxy object should implement the same interface as the target object)
public class ServiceProxy implements ServiceInterface {  
  
    // Target object  
    private ServiceInterface service;  
    // Pass in the target object through the construction method  
    public Proxy(ServiceInterface service){  
        this.service = service;  
    }  
    
  	@Override
	public void operation() {
		System.out.println("Before business operation execution....");
		service.operation();
		System.out.println("After the business operation is executed....");
	}
}  
  1. test
public class Client {
	public static void main(String[] args) {
		//Create a proxy interface
		ServiceInterface service = new ServiceImpl();
		//Create a proxy object and aggregate the proxied object
		ServiceProxy serviceProxy = new ServiceProxy(service);
		//Call proxy method
		serviceProxy.operation();
	}
}

Advantages and disadvantages of static agent

advantage:
(1) You can extend the function of the target object without modifying the function of the target object (the advantage of proxy mode).
(2) The static agent generates class bytecode files during compilation, which can be used directly and with high efficiency.

Disadvantages:
(1) Because the proxy class and the delegate class implement the same interface, once the method is added to the interface, both the target object and the proxy object must be maintained.
(2) Proxy objects only serve one type of objects. If you want to serve multiple types of objects, you must proxy for each object. At this time, you need to create a static proxy class for each object (that is, the static proxy class can only serve one interface. If you want to serve multiple interfaces, you need to establish many proxy classes). Class explosion will occur when the project scale is large.

Solution to the disadvantage: introduce dynamic agent.

Dynamic agent

Dynamic agents can be implemented in two ways:
1.jdk dynamic agent
2.cgilb dynamic agent

jdk dynamic agent

The dynamic proxy mechanism of jdk can only proxy classes that implement interfaces, so it is called interface proxy( The proxy object does not need to implement the interface, but the target object must implement the interface)

Related classes: Proxy class and InvocationHandler class under java.lang.reflect package. InvocationHandler is an interface that can define crosscutting logic by implementing the interface, and call the code of the target class through the reflection mechanism to dynamically weave the crosscutting logic and business logic together.

/*
Returns an instance of the proxy class of the specified interface, and the method call is dispatched to the specified call handler( (return proxy object)
     loader: A ClassLoader object that defines which ClassLoader object loads the generated proxy object
     interfaces: An array of Interface objects indicates what set of interfaces I will provide to the object I need to proxy. If I provide a set of interfaces to it, the proxy object claims to implement the Interface (polymorphic), so that I can call the methods in this set of interfaces
     h: An InvocationHandler interface that represents the interface implemented by the calling handler of the proxy instance. Each proxy instance has an associated invocation handler. When a method is called on a proxy instance, the method call is encoded and assigned to the invoke method of its invocation handler (a subclass passed into the InvocationHandler interface)
*/
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
Class diagram

Code case

1. Delegate class (interface and implementation class)

//Interface
public interface ITeacherDao {
	void teach(); // Training Methods 
	void sayHello(String name);
}
//Implementation class
public class TeacherDao implements ITeacherDao {
	@Override
	public void teach() {
		System.out.println(" The teacher is teaching.... ");
	}
	@Override
	public void sayHello(String name) {
		System.out.println("hello " + name);
	}
}

2. Agent factory

public class ProxyFactory {
    //Maintain a target Object, Object
	private Object obj;
    //constructor 
	public ProxyFactory(Object obj) {
		this.obj = obj;
	}
    //Generate a proxy object for the target object
	public Object getProxyInstance() {
		//explain
		//1. Classloader: Specifies the class loader used by the current target object, and the method to obtain the loader is fixed
		//2. Class<?>[]  Interfaces: the interface type implemented by the target object. Use generic methods to confirm the type
		//3. InvocationHandler h: event processing. When the method of the target object is executed, the event handler method will be triggered and the current execution will be executed
        //The target object method of is passed in as a parameter
		return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), 
			new InvocationHandler() {
				@Override
				public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
					System.out.println("jdk The agency started");
					//Method of calling target object using reflection mechanism
					Object returnVal = method.invoke(obj, args);
					System.out.println("jdk Agent end");
					return returnVal;
				}
			});
	}
}

3. Test

public class Client {
	public static void main(String[] args) {
		//Create target object
		ITeacherDao target = new TeacherDao();
		
		//Create a proxy object for the target object, which can be converted to ITeacherDao
		ITeacherDao proxyInstance = (ITeacherDao)new ProxyFactory(target).getProxyInstance();
	
		// proxyInstance=class com.sun.proxy.$Proxy0 dynamically generates proxy objects in memory
		System.out.println("proxyInstance=" + proxyInstance.getClass());
		
		//The method of the target object is called through the proxy object
		proxyInstance.teach();
		proxyInstance.sayHello(" tom ");
	}
}
/**
proxyInstance=class com.sun.proxy.$Proxy0
jdk The agency started
 The teacher is teaching 
jdk Agent end
jdk The agency started
hello  tom 
jdk Agent end
*/

cglib dynamic proxy

cglib proxy, also known as subclass proxy. Build a subclass object in memory to expand the function of the target object.

Generate a subclass of the specified target class and override the method implementation enhancement. However, because inheritance is adopted, the class modified by final cannot be proxied (if the method is final and private, cglib cannot be used).

maven dependency of cglib needs to be added (cglib is already included in spring):

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>
Class diagram

Case code

1. Entrustment

public class TeacherDao {
	public String teach() {
		System.out.println(" The teacher is teaching (I am a teacher) cglib Proxy, no need to implement interface)");
		return "hello";
	}
}

2. Agent factory

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

public class ProxyFactory implements MethodInterceptor {

	//Maintain a target object
	private Object target;
	
	//Constructor, passing in an object to be represented
	public ProxyFactory(Object target) {
		this.target = target;
	}

	//Get proxy object: it is the proxy object of target object
	public Object getProxyInstance() {
		//1. A class can be generated by the create() method of the Enhancer object to generate a proxy object
		Enhancer enhancer = new Enhancer();
		//2. Set the parent class (take the target class as its parent class)
		enhancer.setSuperclass(target.getClass());
		//3. Set callback function
		enhancer.setCallback(this);
		//4. Create a subclass object (i.e. proxy object) and return
		return enhancer.create();
		
	}
    
    /**
     * Interceptor
     *  1,Method call to target object
     *  2,Enhanced behavior
     */
	@Override
	public Object intercept(Object arg0, Method method, Object[] args, MethodProxy arg3) throws Throwable {
		System.out.println("Cglib proxy pattern ~~ start");
        // Call the method of the target Object (return Object)
		Object returnVal = method.invoke(target, args);
		System.out.println("Cglib proxy pattern ~~ Submit");
		return returnVal;
	}

}

3. Test

public class Client {
	public static void main(String[] args) {
		//Create target object
		TeacherDao target = new TeacherDao();
		//Gets the proxy object and passes the target object to the proxy object
		TeacherDao proxyInstance = (TeacherDao)new ProxyFactory(target).getProxyInstance();

		//Execute the method of the proxy object and trigger the concept method to call the target object
		String res = proxyInstance.teach();
		System.out.println("res=" + res);
        
        System.out.println("proxyInstance=" + proxyInstance.getClass());
	}
}
/**
Cglib Proxy mode ~ ~ start
 The teacher is teaching (I am a cglib agent and do not need to implement the interface)
Cglib Proxy mode ~ ~ submit
res=hello
proxyInstance=class com.hollay.TeacherDao$$EnhancerByCGLIB$$53d58614
*/

Differences between jdk agent and cglib agent

  • jdk dynamic proxy is based on interface and cglib dynamic proxy is based on inheritance
  • If the target object has interface implementation, select jdk proxy; If there is no interface implementation, choose cglib proxy
  • When the target object has an interface, the execution efficiency of jdk dynamic agent is higher than that of cglib
  • The class generated by jdk proxy is class com.sun.proxy.$Proxy0, and the class generated by cglib proxy is class com.hollay.TeacherDao$$EnhancerByCGLIB$d58614
  • JDK dynamic proxy is implemented through reflection mechanism, and CGLib dynamic proxy is implemented through bytecode underlying inheritance to proxy class

SpringAop

The bottom layer of AOP is realized through dynamic agent.

What kind of proxy does spring aop use

Spring AOP supports jdk and cglib proxy methods.

5.2.12 official releas documents:

5.3. AOP Proxies

Spring AOP defaults to using standard JDK dynamic proxies for AOP proxies. This enables any interface (or set of interfaces) to be proxied.

Spring AOP can also use CGLIB proxies. This is necessary to proxy classes rather than interfaces. By default, CGLIB is used if a business object does not implement an interface. As it is good practice to program to interfaces rather than classes, business classes normally implement one or more business interfaces. It is possible to force the use of CGLIB, in those (hopefully rare) cases where you need to advise a method that is not declared on an interface or where you need to pass a proxied object to a method as a concrete type.

It is important to grasp the fact that Spring AOP is proxy-based. See Understanding AOP Proxies for a thorough examination of exactly what this implementation detail actually means.

5.8. Proxying Mechanisms

Spring AOP uses either JDK dynamic proxies or CGLIB to create the proxy for a given target object. JDK dynamic proxies are built into the JDK, whereas CGLIB is a common open-source class definition library (repackaged into ).spring-core

If the target object to be proxied implements at least one interface, a JDK dynamic proxy is used. All of the interfaces implemented by the target type are proxied. If the target object does not implement any interfaces, a CGLIB proxy is created.

If you want to force the use of CGLIB proxying (for example, to proxy every method defined for the target object, not only those implemented by its interfaces), you can do so. However, you should consider the following issues:

  • With CGLIB, methods cannot be advised, as they cannot be overridden in runtime-generated subclasses.final
  • As of Spring 4.0, the constructor of your proxied object is NOT called twice anymore, since the CGLIB proxy instance is created through Objenesis. Only if your JVM does not allow for constructor bypassing, you might see double invocations and corresponding debug log entries from Spring's AOP support.


Conclusion:

  1. proxyTargetClass is false by default, that is, jdk Proxy. If it is changed to true, it is cglib Proxy[ The Proxy object of the target is ture, which is the same type as the original object. It is implemented based on inheritance through cglib Proxy. If it is false, it is not of the same type as the original object, and instanceof is judged to be false. Through jdk Proxy, it is implemented based on the interface (the object generated by jdk dynamic Proxy belongs to Proxy type, belongs to the given interface type, but does not belong to the type of the object we want to Proxy)]

  2. If the object to be proxied is an implementation class, Spring will use jdk dynamic proxy to complete the operation (Spirng adopts jdk dynamic proxy implementation mechanism by default); If the object to be proxied is not an implementation class, Spring will force the use of cglib to implement dynamic proxy.

  3. We can force the use of cglib, specify proxy target class = "true" in xml or @ EnableAspectJAutoProxy(proxyTargetClass = true) based on the annotation

Five notification types

Use the @ AfterReturning annotation to specify the following two common attributes.

  1. pointcut/value: the functions of these two attributes are the same. They both belong to the entry expression corresponding to the specified entry point. Similarly, it can be either an existing pointcut or a pointcut expression can be defined directly. When the pointcut attribute value is specified, the value attribute value will be overwritten.

  2. returning: this attribute specifies a formal parameter name, which is used to indicate that a formal parameter with the same name can be defined in the Advice method, which can be used to access the return value of the target method. In addition, the type specified when defining the formal parameter (representing the return value of the target method) in the Advice method limits that the target method must return a value of the specified type or has no return value.

Get parameter value of custom annotation in AOP

package com.hollay.servicebase.annotation;

import java.lang.annotation.*;

/**
 * User defined annotation, used to mark aop tangent position
 */

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface MyUvLogAop {
    String value() default "";
}

 //Front desk login phone and password login
 @MyUvLogAop("login")
 @PostMapping("login")
 public R login(@RequestBody Member member){
    //Return the token value, which is generated using jwt
 	String token = memberService.login(member);
	return R.ok().data("token",token);
 }
package com.hollay.servicebase.aop;

import com.hollay.commonutils.JwtUtils;
import com.hollay.commonutils.R;
import com.hollay.servicebase.annotation.MyUvLogAop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * About website traffic statistics
 * UV: Number of independent visitors: UniqueVisitor, a computer client accessing the website is a visitor. The same client is calculated only once from 00:00 to 24:00.
 */

@Aspect
@Component
public class UvAspect {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    // The method to get the @ MyUvLogAop annotation is the pointcut
    @Pointcut("@annotation(com.hollay.servicebase.annotation.MyUvLogAop)")
    private void pointCut(){
    }

    @After("pointCut()")
    public void doAfter() {
        System.out.println("*****************aop Weave in******************");
    }

    /**
     * It is woven after the target method is completed normally
     * @param joinPoint
     * @param returnVal
     */
    @AfterReturning(returning = "returnVal", pointcut = "pointCut() && @annotation(myUvLogAop)")
    public void doAfterReturning(JoinPoint joinPoint, R returnVal, MyUvLogAop myUvLogAop) {
        String value = myUvLogAop.value();//Gets the parameter value of the custom annotation @ MyUvLogAop
        if ("login".equals(value)) {
            countLoginUser(returnVal);
        } else if ("play_video".equals(value)) {
            countPlayVideoNum();
        }
    }
}

Topics: Java Spring Design Pattern