Java proxy mode (static proxy, JDK dynamic proxy, CGLIB)

From Xiao Zhang’s blog

proxy mode

The proxy pattern is one of the common design patterns. In Java, we usually access the services we need by new an object and then calling its corresponding method. The proxy mode accesses services by creating a proxy class. The proxy class usually holds a delegate class object. The proxy class does not implement the real service itself, but provides it by calling the relevant methods of the delegate class object. service, so in fact we are still calling the service of the delegate class, but there is a proxy class in between. This is beneficial. We can add the operations we need before or after accessing the service. For example, in Spring's aspect-oriented programming, we can perform some operations before the pointcut and perform some operations after the pointcut. This entry point is a method. The class where these methods are located must have been proxied, and some other operations have been performed during the proxy process.

image-1667465129301
Image source: Dahua Design Pattern

static proxy

Simple code implementation

When using a static proxy, you need to define an interface or a parent class. The proxy object and the proxy object implement the same interface or inherit the same parent class. Let me give you an example of a tenant renting a house through an agency.
(1) First create a person interface, which is the public interface between tenants and intermediaries. This interface has a rentHouse() method.

public interface Person {
    
    
	//租房
	public void rentHouse();
}

(2) Create the tenant Renter class and implement the above interface

public class Renter implements Person{
    
    

	@Override
	public void rentHouse() {
    
    
		System.out.println("租客租房成功!");
		
	}
}

(3) Create the intermediary class RenterProxy, which also implements the Person interface, but also holds a tenant class object

public class RenterProxy implements Person{
    
    
	private Person renter;
	public RenterProxy(Person renter){
    
    
		this.renter = renter;
	}
	@Override
	public void rentHouse() {
    
    
		System.out.println("中介找房东租房,转租给租客!");
		renter.rentHouse();
		System.out.println("中介给租客钥匙,租客入住!");
		
	}

}

(4) Create a new test class test

public class StaticProxyTest {
    
    

	public static void main(String[] args) {
    
    
		Person renter = new Renter();
		RenterProxy proxy = new RenterProxy(renter);
		proxy.rentHouse();
	}

}

operation result:

中介找房东租房,转租给租客!
租客租房成功!
中介给租客钥匙,租客入住!

dynamic proxy

The proxy class created when the program is running is called a dynamic proxy. In a static proxy, the proxy class (RenterProxy) has been defined by itself and has been compiled before the program is run. Dynamic proxies are dynamically generated at runtime based on our "instructions" in Java code. The advantage of dynamic proxy over static proxy is that it can easily manage all functions of the proxy class in a unified way. If we want to add a method before each proxy method, if there are many proxy methods, we need to add a method to each proxy method. It’s very troublesome to have to write it all over again. Dynamic proxies are not required.

Code

A Proxy class and an InvocationHandler interface are provided under the java.lang.reflect package of java . Through this class and this interface, JDK dynamic proxy classes and dynamic proxy objects can be generated.
(1) Same as static proxy, first define a person interface

public interface Person {
    
    

	//租房
	public void rentHouse();
}

(2) Create the proxied class

public class Renter implements Person{
    
    

	@Override
	public void rentHouse() {
    
    
		System.out.println("租客租房成功!");
		
	}

}

(3) Create the RenterInvocationHandler class. This class implements the InvocationHandler interface and holds an object of the proxy class. There is an invoke method in the InvocationHandler. All methods that execute the proxy object will be replaced by executing the invoke method. Then execute the proxy class method in the invoke method through reflection. During the proxy process, you can perform your own operations before or after executing the methods of the proxy class. This is the main principle of spring aop.

public class RenterInvocationHandler<T> implements InvocationHandler{
    
    
	//被代理类的对象
	private T target;
	
	public RenterInvocationHandler(T target){
    
    
		this.target = target;
	}

	/**
     * proxy:代表动态代理对象
     * method:代表正在执行的方法
     * args:代表调用目标方法时传入的实参
     */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
    
    
		//代理过程中插入其他操作
		System.out.println("租客和中介交流");
		Object result = method.invoke(target, args);
		return result;
	}

}

(4) Create dynamic proxy objects

public class ProxyTest {
    
    

	public static void main(String[] args) {
    
    

		//创建被代理的实例对象
		Person renter = new Renter();
		//创建InvocationHandler对象
		InvocationHandler renterHandler = new RenterInvocationHandler<Person>(renter);
		
		
		//创建代理对象,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
		Person renterProxy = (Person)Proxy.newProxyInstance(Person.class.getClassLoader(),new Class<?>[]{
    
    Person.class}, renterHandler);
		renterProxy.rentHouse();
		
	}

}

Results of the:

租客和中介交流
租客租房成功!

Principle analysis:

Click to view the source code of the newProxyInstance method of the Proxy class

 public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
    
    
        if (h == null) {
    
    
            throw new NullPointerException();
        }

        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
    
    
            checkProxyAccess(Reflection.getCallerClass(), loader, interfaces);
        }

        /*
         * Look up or generate the designated proxy class.
         */
         //生成动态代理类
        Class<?> cl = getProxyClass0(loader, interfaces);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
    
    
            //获取构造器参数是InvocationHandler类,constructorParams是Proxy的静态成员变量
            // private final static Class[] constructorParams ={ InvocationHandler.class };
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
    
    
                // create proxy instance with doPrivilege as the proxy class may
                // implement non-public interfaces that requires a special permission
                return AccessController.doPrivileged(new PrivilegedAction<Object>() {
    
    
                    public Object run() {
    
    
                        return newInstance(cons, ih);
                    }
                });
            } else {
    
    
                return newInstance(cons, ih);
            }
        } catch (NoSuchMethodException e) {
    
    
            throw new InternalError(e.toString());
        }
    }

    private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
    
    
        try {
    
    
           //使用构造器创建一个代理类实例对象
            return cons.newInstance(new Object[] {
    
    h} );
        } catch (IllegalAccessException | InstantiationException e) {
    
    
            throw new InternalError(e.toString());
        } catch (InvocationTargetException e) {
    
    
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
    
    
                throw (RuntimeException) t;
            } else {
    
    
                throw new InternalError(t.toString());
            }
        }
    }


Class<?> cl = getProxyClass0(loader, interfaces); This line of code generates a proxy class, which is cached in the java virtual machine. You can print it out with the following code.

        byte[] classByte = ProxyGenerator.generateProxyClass("$Proxy0",
				new Class<?>[] { Person.class });
		String path = "D:/Eclipse/Workspace/Demo/bin/$Proxy0.class";
		try (FileOutputStream fos = new FileOutputStream(path)) {
			fos.write(classByte);
			fos.flush();
		} catch (Exception e) {
			e.printStackTrace();
		}

Decompile the class file:

public final class $Proxy0 extends Proxy
  implements Person
{
    
    
  private static Method m1;
  private static Method m3;
  private static Method m0;
  private static Method m2;
//通过带InvocationHandler参数的构造器生成代理类对象时,将我们写的RenterInvocationHandler对象传进来
//父类持有:protected InvocationHandler h
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
    
    
    super(paramInvocationHandler);
  }

  public final boolean equals(Object paramObject)
    throws 
  {
    
    
    try
    {
    
    
      return ((Boolean)this.h.invoke(this, m1, new Object[] {
    
     paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
    
    
      throw localError;
    }
    catch (Throwable localThrowable)
    {
    
    
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final void rentHouse()
    throws 
  {
    
    
    try
    {
    
    
    //执行代理类的rentHouse方法时,实际上是执行InvocationHandler类的invoke方法,也就是我们自己写的
    //RenterInvocationHandler类的invoke方法,在此方法中我们通过method.invoke(target, args)反射,再
    //执行具体类的rentHouse方法      
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
    
    
      throw localError;
    }
    catch (Throwable localThrowable)
    {
    
    
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final int hashCode()
    throws 
  {
    
    
    try
    {
    
    
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
    
    
      throw localError;
    }
    catch (Throwable localThrowable)
    {
    
    
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final String toString()
    throws 
  {
    
    
    try
    {
    
    
      return (String)this.h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
    
    
      throw localError;
    }
    catch (Throwable localThrowable)
    {
    
    
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  static
  {
    
    
    try
    {
    
    
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
    
     
      Class.forName("java.lang.Object") });
      //获取到了rentHouse方法的方法名
      m3 = Class.forName("test.father.Person").getMethod("rentHouse", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
    
    
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
    
    
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

Java automatically generates a $Proxy0 proxy class. This class is in memory, so you can obtain the constructor of this class through reflection, and then create a proxy class instance object. Analyzing the source code of this class, we can know that the general process when calling the rentHouse method of the proxy class object is: calling the invoke method of the RenterInvocationHandler class, and the invoke method of the RenterInvocationHandler class uses reflection to call the rentHouse method of the proxy class.
RenterInvocationHandler can be regarded as an intermediate class, which holds the proxy object and converts external calls to invoke into calls to the proxy object. The proxy class achieves the purpose of calling the method of the proxy class by holding the intermediate class and calling the invoke method of the intermediate class.

CGLIBAgent

Dynamic proxy requires the proxied class to implement the interface. If the proxied class does not implement the interface, how to implement dynamic proxy in this way? At this time you need to use CGLib. This proxy method is called CGlib proxy.
The Cglib agent is also called a subclass agent. It builds a subclass in memory and uses method interception technology in the subclass to intercept all parent class method calls, and then adds the operations it needs. Because inheritance is used, final classes cannot be proxied.

Code

Import the cglib jar package. In addition, Spring's core package already includes Cglib functions. You can also import spring-core-5.3.20.jar.

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.3.20</version>
        </dependency>

(1) Create the proxy class UserService

public class UserService {
    
    
	
	public void getName(){
    
    
		System.out.println("张三!");
	}

}


Cannot create proxy factory class ProxyFactory for final (2)

public class ProxyFactory<T> implements MethodInterceptor {
    
    

	private T target;

	public ProxyFactory(T target) {
    
    
		this.target = target;
	}

	// 创建代理对象

	public Object getProxyInstance() {
    
    

		// 1.cglib工具类
		Enhancer en = new Enhancer();
		// 2.设置父类
		en.setSuperclass(this.target.getClass());
		// 3.设置回调函数
		en.setCallback(this);

		return en.create();
	}

   //拦截方法
	@Override
	public Object intercept(Object obj, Method method, Object[] args,
			MethodProxy methodProxy) throws Throwable {
    
    
		System.out.println("开始事务...");

		// 执行目标对象的方法
		Object result = method.invoke(target, args);

		System.out.println("提交事务...");
		return result;
	}

}

Dynamic proxy and CGLib dynamic proxy are both the basis for implementing Spring AOP. If the target object added to the container has an implemented interface, use a dynamic proxy. If the target object does not implement an interface, use a Cglib proxy.

Guess you like

Origin blog.csdn.net/qq_49619863/article/details/127836252