当前位置: 主页 > JAVA语言

java动态代理详解-java动态代理

发布时间:2023-02-10 07:15   浏览次数:次   作者:佚名

目录

概念

代理:为了控制A对象,创建一个新的B对象,由B对象代替执行A对象的所有操作,称为代理。 代理系统的建立涉及三个参与角色:真实对象(A)、代理对象(B)和客户端。

代理对象(B)起中介作用,连接真实对象(A)和客户端。 如果进一步扩展,代理对象可以实现更复杂的逻辑,比如对真实对象的访问控制。

案子

需求:员工业务层接口调用save需要admin权限,调用list不需要权限,未经权限调用会抛出异常提示。

静态代理

/**
 * 代理接口
 */
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;
    }
}

/**
 * 代理对象

java 动态代理面试_java动态代理_java动态代理详解

*/ 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)

java动态代理详解_java 动态代理面试_java动态代理

at com.langfeiyes.pattern.proxy.demo.App.main(App.java:16)

使用真实对象EmployeeServiceImpl直接调用时,list和save都可以​​直接访问,但不满足admin权限限制要求。 如果使用代理对象EmployeeProxy,就可以实现需求。

代理逻辑是通过直接新建一类代理对象来完成的,称为静态代理模式。

JDK动态代理模式

Java中常用的动态代理模式有JDK动态代理和cglib动态代理。 这里重点介绍JDK动态代理

还是原来的需求,之前的IEmployeeService EmployeeServiceImpl SessionHolder没有变化,增加了一个新的JDK代理控制器-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);
    }
}

测试 App 类略有变化:

public class App {
    public static void main(String[] args) {
        System.out.println("----------------真实对象--------------------");
        EmployeeServiceImpl employeeService = new EmployeeServiceImpl();

java动态代理_java动态代理详解_java 动态代理面试

employeeService.list(); employeeService.save(); System.out.println("----------------代理对象--------------------"); SessionHolder.setCurrentUser("dafei"); EmployeeInvocationHandler handler = new EmployeeInvocationHandler(employeeService); IEmployeeService proxy = (IEmployeeService) handler.getProxy(); proxy.list(); proxy.save(); } }

上面的代码也能满足要求,和静态代理的区别是创建的代理对象少。 这时候就有一个疑惑点,没有创建代理对象,为什么代理类调用可以实现呢? ?

原理分析

先总结一下JDK动态代理的底层实现原理:使用接口实现方式在运行时在内存中动态构建一个类,然后编译执行。 这个类是一次性的,JVM停止,代理类消失。

参与角色 要了解JDK动态代理的原理,首先要了解JDK动态代理涉及的类

java动态代理详解_java 动态代理面试_java动态代理

InvocationHandler:真实对象方法调用处理器,内置invoke方法,其作用:自定义真实对象的代理逻辑

EmployeeInvocationHandler:Employee服务真实对象方法调用处理程序java动态代理详解,该类有3个用途:1>设置真实对象

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

2> 自定义代理方法实现逻辑

增加真实对象保存方法的权限验证逻辑

    //代理对象控制执行方法
    //参数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> 返回代理对象

java动态代理_java动态代理详解_java 动态代理面试

该方法执行后返回一个名为:$ProxyX(其中X为序列号,一般默认为0)的代理类,由JDK动态构造。

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

Proxy:动态代理控件类,是JDK动态生成的$ProxyX类的父类。 其功能如下:

1> 通过调用ProxyBuilder类构建器方法构建代理对象类

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> 通过newProxyInstance方法返回$ProxyX类的实例

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

$Proxy0:App类运行时,JDK动态构造的代理类,继承自Proxy类

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();

java 动态代理面试_java动态代理_java动态代理详解

} }

默认情况下,JVM 不保存动态创建的代理类字节码对象。 可以在main方法中配置代理参数,保持字节码

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

执行后会在项目根目录下生成代理类字节码对象。

java动态代理_java动态代理详解_java 动态代理面试

为了方便解释,去掉一些不需要的方法后

$Proxy0 类

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);
    }
}

从源码来看java动态代理详解,$Proxy0的特点:

真相大白

下图中所有参与动态代理的类:

java动态代理详解_java动态代理_java 动态代理面试

下图是上图的操作时序图,跟着走就行了

java 动态代理面试_java动态代理_java动态代理详解

至此,JDK动态代理就ok了。