Home  >  Article  >  Java  >  How to implement JDK dynamic proxy in Java

How to implement JDK dynamic proxy in Java

WBOY
WBOYforward
2023-04-30 08:49:06996browse

Concept

Agent: In order to control the A object, a new B object is created, and the B object performs all operations of the A object instead, which is called an agent. The establishment of an agency system involves three participating roles: the real object (A), the proxy object (B), and the client.

The proxy object (B) plays an intermediary role, connecting the real object (A) and the client. If further expanded, the proxy object can implement more complex logic, such as access control for the real object.

Case

Requirements: The employee business layer interface requires admin permission to call save, and calling list does not require permission. An exception will be thrown when calling without permission.

Static proxy

/**
 * 代理接口
 */
public interface IEmployeeService {
    void save();
 
    void list();
}
/**
 * 真实对象
 */
public class EmployeeServiceImpl implements IEmployeeService {
    @Override
    public void save() {
        System.out.println("EmployeeServiceImpl-正常的save....");
    }
    @Override
    public void list() {
        System.out.println("EmployeeServiceImpl-正常的list....");
    }
}
/**
 * 模拟当前登录用户对象
 */
public class SessionHolder {
    private static String currentUser;
    public static String  getCurrentUser(){
        return currentUser;
    }
    public static void   setCurrentUser(String currentUser){
        SessionHolder.currentUser = currentUser;
    }
}
/**
 * 代理对象
 */
public class EmployeeProxy implements IEmployeeService {
    //真实对象
    private EmployeeServiceImpl employeeService;
    public EmployeeProxy(EmployeeServiceImpl employeeService){
        this.employeeService = employeeService;
    }
    @Override
    public void save() {
        //权限判断
        if("admin".equals(SessionHolder.getCurrentUser())){
            employeeService.save();
        }else{
            throw new RuntimeException("当前非admin用户,不能执行save操作");
        }
    }
    @Override
    public void list() {
        employeeService.list();
    }
}
public class App {
    public static void main(String[] args) {
        System.out.println("----------------真实对象--------------------");
        EmployeeServiceImpl employeeService = new EmployeeServiceImpl();
        employeeService.list();
        employeeService.save();
        System.out.println("----------------代理对象--------------------");
        SessionHolder.setCurrentUser("dafei");  //设置权限(当前登录用户)
        EmployeeProxy employeeProxy = new EmployeeProxy(employeeService);
        employeeProxy.list();
        employeeProxy.save();
    }
}
----------------真实对象--------------------
EmployeeServiceImpl-正常的list....
EmployeeServiceImpl-正常的save....
----------------代理对象--------------------
EmployeeServiceImpl-正常的list....
Exception in thread "main" java.lang.RuntimeException: 当前非admin用户,不能执行save操作
	at com.langfeiyes.pattern.proxy.demo.EmployeeProxy.save(EmployeeProxy.java:20)
	at com.langfeiyes.pattern.proxy.demo.App.main(App.java:16)

When calling directly using the real object EmployeeServiceImpl, both list and save can be accessed directly, but it does not meet the admin permission restrictions on the requirements. If you use the proxy object EmployeeProxy, you can complete the requirements.

Complete the proxy logic by directly creating a new class of proxy objects. This method is called the static proxy mode.

JDK dynamic proxy mode

The commonly used dynamic proxy modes in Java include JDK dynamic proxy and cglib dynamic proxy. Here we focus on JDK dynamic proxy

Still the original demand , the previous IEmployeeService EmployeeServiceImpl SessionHolder has not changed, a new JDK proxy controller-EmployeeInvocationHandler

/**
 * jdk动态代理控制类,由它牵头代理类获取,代理方法的执行
 */
public class EmployeeInvocationHandler  implements InvocationHandler {
    //真实对象-EmployeeServiceImpl
    private Object target;
    public EmployeeInvocationHandler(Object target){
        this.target = target;
    }
    //获取jvm在内存中生成代理对象
    public Object getProxy(){
        return  Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this);
    }
    //代理对象控制执行方法
    //参数1:代理对象
    //参数2:真实对象的方法(使用方式得到方法对象)
    //参数3:真实对象方法参数列表
    //此处是代理对象对外暴露的可编辑的方法处理场所,代理对象每调用一个次方法,就会执行一次invoke
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String name = method.getName();
        if("save".equals(name) && !"admin".equals(SessionHolder.getCurrentUser())){
            throw new RuntimeException("当前非admin用户,不能执行save操作");
        }
        return method.invoke(target, args);
    }
}

The test App class is slightly changed:

public class App {
    public static void main(String[] args) {
        System.out.println("----------------真实对象--------------------");
        EmployeeServiceImpl employeeService = new EmployeeServiceImpl();
        employeeService.list();
        employeeService.save();
 
        System.out.println("----------------代理对象--------------------");
        SessionHolder.setCurrentUser("dafei");
        EmployeeInvocationHandler handler = 
            new EmployeeInvocationHandler(employeeService);
        IEmployeeService proxy = (IEmployeeService) handler.getProxy();
        proxy.list();
        proxy.save();
 
    }
}

The above code can be the same The difference between implementing requirements and static proxy is that less proxy objects are created. There is a question at this point. No proxy object is created. Why can the proxy class call be implemented? ?

Principle Analysis

Let’s first conclude with the underlying implementation principle of JDK dynamic proxy: using interface implementation, at runtime, a class is dynamically constructed in memory, and then compiled and executed. This class is one-time use. When the JVM is stopped, the proxy class disappears.

Participating roles To understand the principle of JDK dynamic proxy, you must first understand the classes involved in JDK dynamic proxy

How to implement JDK dynamic proxy in Java

InvocationHandler : Real object method invocation processor, built-in invoke method, its function: customize proxy logic for the real object

EmployeeInvocationHandler: Employee service real object method invocation processor, this type has 3 uses: 1>Set the real object

     //真实对象-EmployeeServiceImpl
    private Object target;
    public EmployeeInvocationHandler(Object target){
        this.target = target;
    }

2>Customize the proxy method implementation logic

Added permission verification logic for the real object save method

    //代理对象控制执行方法
    //参数1:代理对象
    //参数2:真实对象的方法(使用方式得到方法对象)
    //参数3:真实对象方法参数列表
    //此处是代理对象对外暴露的可编辑的方法处理场所,代理对象每调用一个次方法,就会执行一次invoke
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String name = method.getName();
        if("save".equals(name) && !"admin".equals(SessionHolder.getCurrentUser())){
            throw new RuntimeException("当前非admin用户,不能执行save操作");
        }
        return method.invoke(target, args);
    }

3>Return After the proxy object

method is executed, a proxy class named: $ProxyX (where X is the serial number, generally defaults to 0) is returned. This proxy class is dynamically constructed by the JDK.

    //获取jvm在内存中生成代理对象
    public Object getProxy(){
        return  Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this);
    }

Proxy: Dynamic proxy control class is the parent class of the $ProxyX class dynamically generated by JDK. Its functions are as follows:
1> Build a proxy object by calling the ProxyBuilder class builder method Class

private static Constructor<?> getProxyConstructor(Class<?> caller,
                                                      ClassLoader loader,
                                                      Class<?>... interfaces){
            return proxyCache.sub(intf).computeIfAbsent(
                loader,
                (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
            );
}

2> Return an instance of the $ProxyX class through the newProxyInstance method

   public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) {
    //...
   }

$Proxy0: When the App class is running, the proxy class dynamically built by the JDK is inherited to Proxy class

public class App {
    public static void main(String[] args) {
        //System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
        System.out.println("----------------真实对象--------------------");
        EmployeeServiceImpl employeeService = new EmployeeServiceImpl();
        employeeService.list();
        employeeService.save();
        System.out.println("----------------代理对象--------------------");
        SessionHolder.setCurrentUser("dafei");
        EmployeeInvocationHandler handler = 
                     new EmployeeInvocationHandler(employeeService);
        IEmployeeService proxy = (IEmployeeService) handler.getProxy();
        proxy.list();
        proxy.save();
 
    }
}

By default, the JVM does not save dynamically created proxy class bytecode objects. You can configure the proxy parameters in the main method to retain the bytecode

//JDK8之前
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//JDK8之后
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");

After execution, A proxy class bytecode object will be generated in the project root directory.

How to implement JDK dynamic proxy in Java

In order to facilitate interpretation, after removing some unnecessary methods

$Proxy0 class

public class $Proxy0 extends Proxy implements IEmployeeService {
    private static Method m4;
    private static Method m3;
    static {
        try {
            m4 = Class.forName("com.langfeiyes.proxy.demo.IEmployeeService")
                 .getMethod("save");
            m3 = Class.forName("com.langfeiyes.proxy.demo.IEmployeeService")
                 .getMethod("list");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public $Proxy0(InvocationHandler var1) throws Throwable {
        super(var1);
    }
    public final void save() throws Throwable {
        super.h.invoke(this, m4, (Object[])null);
    }
 
    public final void list() throws  Throwable{
        super.h.invoke(this, m3, (Object[])null);
    }
}

Looking from the source code, the characteristics of $Proxy0 are:

  • 1>Inherits the Proxy class and implements the IEmployeeService interface

  • 2> Reflect the save and list methods of the IEmployeeService interface through static blocks to obtain their method objects Method

  • 3> To call the parent class constructor, you need to pass in the InvocationHandler parameter

  • 4> Rewriting the save list method of the IEmployeeService interface relies on the h attribute.invoke method of the parent class Proxy

The truth is revealed

The following picture shows all the classes participating in dynamic proxy:

How to implement JDK dynamic proxy in Java

The following picture is the operation sequence diagram of the above picture, just follow it

How to implement JDK dynamic proxy in Java

The above is the detailed content of How to implement JDK dynamic proxy in Java. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:yisu.com. If there is any infringement, please contact admin@php.cn delete