动态代理详细说明

动态代理

0、代理模型的作用

代理模式是一种设计模式,解决的问题是:在直接访问对象调用目标方法时带来的问题

代理模式是为了帮助目标对象调用目标方法增强一些自己不关心的事,比如日志代理,在目标对象调用目标方法前后加一些日志;再者就是比如说事务,主体功能没有改变,只是说开启事务、提交事务(回滚事务),但是主体功能是没有改变的。

概念介绍

被代理对象:真实发挥作用的对象;

代理对象:增强后的对象;

对于调用者来说,是不知道被代理对象的存在的;但是背后确实是被代理对象是在发挥作用。

流程图

可能被代理对象的功能是可以实现的,但是我们有时候会考虑到对其进行增强的方式来对其进行解决;但是有时候我们也不需要来对其进行增强。

本质上是我们需要根据自己的需求实现,来对其采用特殊处理。

为了保持行为的一致性,代理类和目标类会实现相同的接口,所以在访问者看来两者没有丝毫的区别。

通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。

按照代理的创建时期,代理类可以分为两种: 静态代理动态代理

静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的.class文件就已经存在了。

动态代理:在程序运行时运用反射机制动态创建而成。

1、静态代理

直接看一个对应的例子说明:

public interface UserManager {
    void addUser(String userId, String userName);
    void delUser(String userId);
    String findUser(String userId);
    void modifyUser(String userId, String userName);
}

创建其中一个实现类来对其进行实现:

public class UserManagerImpl implements UserManager {

    @Override
    public void addUser(String userId, String userName) {
        System.out.println("UserManagerImpl.addUser");
    }

    @Override
    public void delUser(String userId) {
        System.out.println("UserManagerImpl.delUser");
    }

    @Override
    public String findUser(String userId) {
        System.out.println("UserManagerImpl.findUser");
        return "张三";
    }

    @Override
    public void modifyUser(String userId, String userName) {
        System.out.println("UserManagerImpl.modifyUser");
    }
}

创建静态代理类:

public class UserManagerImplProxy implements UserManager {
  
    // 目标对象  
    private UserManager userManager;

    // 通过构造方法传入目标对象  
    public UserManagerImplProxy(UserManager userManager){
        this.userManager=userManager;  
    }

    @Override  
    public void addUser(String userId, String userName) {  
        try{  
                //添加打印日志的功能  
                //开始添加用户  
                System.out.println("start-->addUser()");  
                userManager.addUser(userId, userName);  
                //添加用户成功  
                System.out.println("success-->addUser()");  
            }catch(Exception e){  
                //添加用户失败  
                System.out.println("error-->addUser()");  
            }  
    }  
  
    @Override  
    public void delUser(String userId) {  
        userManager.delUser(userId);  
    }  
  
    @Override  
    public String findUser(String userId) {  
        userManager.findUser(userId);  
        return "张三";  
    }  
  
    @Override  
    public void modifyUser(String userId, String userName) {  
        userManager.modifyUser(userId,userName);  
    }  
  
} 

测试:

public class TestStaticProxy {
    public static void main(String[] args) {
        UserManagerImpl userManager = new UserManagerImpl();
        UserManagerImplProxy userManagerImplProxy = new UserManagerImplProxy(userManager);
        userManagerImplProxy.addUser("111","lig");
    }
}

静态代理类优缺点总结

优点:

代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可(解耦合),对于如上的客户端代码,newUserManagerImpl()可以应用工厂将它隐藏,如上只是举个例子而已。

缺点:

1)代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

2)代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。上面代码是只为UserManager类的访问提供了代理,但是如果还要为其他类如Department类提供代理的话,就需要我们再次添加代理Department的代理类。

举例说明:代理可以对实现类进行统一的管理,如在调用具体实现类之前,需要打印日志等信息,这样我们只需要添加一个代理类,在代理类中添加打印日志的功能,然后调用实现类,这样就避免了修改具体实现类。满足我们所说的开闭原则。但是如果想让每个实现类都添加打印日志的功能的话,就需要添加多个代理类,以及代理类中各个方法都需要添加打印日志功能(如上的代理方法中删除,修改,以及查询都需要添加上打印日志的功能)

即静态代理类只能为特定的接口(Service)服务。如想要为多个接口服务则需要建立很多个代理类。

上面的两个缺点非常致命,所以开发中都不会来使用这两个代理类来做操作

2、动态代理

根据如上的介绍,你会发现每个代理类只能为一个接口服务,这样程序开发中必然会产生许多的代理类。所以我们就会想办法可以通过一个代理类完成全部的代理功能,那么我们就需要用动态代理。在上面的示例中,一个代理只能代理一种类型,而且是在编译器就已经确定被代理的对象。而动态代理是在运行时,通过反射机制实现动态代理,并且能够代理各种类型的对象。在Java中要想实现动态代理机制,需要java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy 类的支持:

java.lang.reflect.InvocationHandler接口的定义如下:

//Object proxy:被代理的对象,这个都不来进行使用  
//Method method:要调用的方法  
//Object[] args:方法调用时所需要参数  
public interface InvocationHandler {  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;  
} 

java.lang.reflect.Proxy类的定义如下:

//CLassLoader loader:类的加载器  
//Class<?> interfaces:得到全部的接口  
//InvocationHandler h:得到InvocationHandler接口的子类的实例  
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException 

对应的代理实例如下所示:

接口类:

public interface UserManager {
    void addUser(String userId, String userName);
    void delUser(String userId);
    String findUser(String userId);
    void modifyUser(String userId, String userName);
}

实现类:

public class UserManagerImpl implements UserManager {
  
    @Override  
    public void addUser(String userId, String userName) {  
        System.out.println("UserManagerImpl.addUser");  
    }  
  
    @Override  
    public void delUser(String userId) {  
        System.out.println("UserManagerImpl.delUser");  
    }  
  
    @Override  
    public String findUser(String userId) {  
        System.out.println("UserManagerImpl.findUser");  
        return "张三";  
    }  
  
    @Override  
    public void modifyUser(String userId, String userName) {  
        System.out.println("UserManagerImpl.modifyUser");  
  
    }  
  
} 

对应的代理类:

//动态代理类只能代理接口(不支持抽象类),代理类都需要实现InvocationHandler类,实现invoke方法。
// 该invoke方法就是调用被代理接口的所有方法时需要调用的,该invoke方法返回的值是被代理接口的一个实现类
public class LogHandlerProxy implements InvocationHandler {
  
    // 目标对象  
    private Object targetObject;

    // 绑定关系,也就是关联到哪个接口(与具体的实现类绑定)的哪些方法将被调用时,执行invoke方法。
    public UserManager bind(Object targetObject){
        this.targetObject=targetObject;  
        // 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
        // 第一个参数指定产生代理对象的类加载器,需要将其指定为和目标对象同一个类加载器
        // 第二个参数要实现和目标对象一样的接口,所以只需要拿到目标对象的实现接口
        // 第三个参数表明这些被拦截的方法在被拦截时需要执行哪个InvocationHandler的invoke方法
        // 根据传入的目标返回一个代理对象
        return (UserManager) Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
                targetObject.getClass().getInterfaces(),this);  
    }  
    @Override  
    //关联的这个实现类的方法被调用时将被执行  
    /*InvocationHandler接口的方法,proxy表示代理,method表示原对象被调用的方法,args表示方法的参数*/  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("start-->>");  
        for(int i=0;i<args.length;i++){  
            System.out.println(args[i]);  
        }  
        Object ret=null;  
        try{  
            /*原对象方法调用前处理日志信息*/  
            System.out.println("satrt-->>");
            //调用目标方法  
            ret=method.invoke(targetObject, args);  
            /*原对象方法调用后处理日志信息*/  
            System.out.println("success-->>");  
        }catch(Exception e){  
            e.printStackTrace();  
            System.out.println("error-->>");  
            throw e;  
        }  
        return ret;  
    }  
  
}

测试类:

public class UserManagerTest {
    public static void main(String[] args) {
        LogHandlerProxy logHandlerProxy = new LogHandlerProxy();
        UserManager userManager = logHandlerProxy.bind(new UserManagerImpl());
        userManager.addUser("111","lig");
        System.out.println("=====================");
        userManager.delUser("666");
    }
}

这里有三个对象:

  1. UserManagerImpl对象 , 我们称之为被代理对象
  2. LogHandlerProxy对象,我们称之为执行者对象
  3. Proxy对象 (通过在ProviderHandler bind方法中使用Proxy.newProxyInstance生成的对象) 我们称之为代理对象

这三个对象是什么关系呢?

Proxy是真正的代理类,UserManagerImpl是被代理类,LogHandlerProxy是执行方法增强的执行者。

我们是为了增强UserManagerImpl(被代理对象)的addUser方法,就Proxy对象来代理被代理对象的执行,Proxy不亲自来做这件事,而是交给执行者对象ProviderHandler 来实现增加的目录,执行调用前的限流校验。

实际怎么实现的呢?

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        //对 Invocationhandler做判空处理
        Objects.requireNonNull(h);
        //复制[IProvider接口]
        final Class<?>[] intfs = interfaces.clone();

       //根据IProvider的类加载器IProvider接口生成了Proxy类,关键:根据类加载器和接口对象在JVM缓存中生成一个类对象
        Class<?> cl = getProxyClass0(loader, intfs);
        //获取构造器
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        //保存InvocationHandler的引用
        final InvocationHandler ih = h;
        //通过构造器实例化Proxy代理对象
        return cons.newInstance(new Object[]{h});
    }

3、动态代理原理

1、猜想动态代理过程

其实我们也可以手动模拟一下执行过程,

接口:

public interface AService{
  Object doService(Object args);
}

实现类:

public class A implements AService{
  
  Object doService(Object args){
    Object result = doHanler(args);
    return result;
  }
  
}

但是现在想要在A对象执行doService方法前执行参数打印,在A对象执行doService方法后修改返回值。

public class AProxy implements AService{
  
  private AService realObject
    
    public AProxy(AService realObject){
    	this.realObject = realObject;
  	}
  
  Object doService(Object args){
    loggger.info("参数值是:{}",args);
    Object result = realObject.doService(args);
    result = modifyData(result);
    return result;
  }
  
}

我们如果在代理类中知道了真实操作之后,按照上面的操作来执行是可以行的。

但是如果这样子来写的话,跟静态代理是没有任何区别的。

那么既然我们知道静态代理的缺点之后,我们可以来概括一下这个类。

那么我们用我们自己的想法来确定一下动态代理类的结构

1、首先开发者定义的接口和方法是固定的,那么代理类可以直接继承并实现方法;

2、如果在代理类中来使用目标对象呢?多态特性,因为目标类一定是接口的实现类,所以在代理类中利用接口来接收一个目标对象是可行的;

3、关键就是如何代理类的方法中来执行开发者操作?如下所示:

  Object doService(Object args){
    loggger.info("参数值是:{}",args);
    Object result = realObject.doService(args);
    result = modifyData(result);
    return result;
  }

用目标类来执行目标方法,执行前、执行后以及执行出现异常该如何来进行处理呢?

如果是代理类,那么无法获取得到对应的逻辑!!!!这里才是最大的问题!

但是如果我们规定,一个接口让动态代理类来实现:

public interface H{
  Object before(Object args);
  Object after(Object args);
}

那么在代理类中这么写:

  Object doService(Object args){
    Object beforeResult = h.before(args);
    Object result = realObject.doService(args);
    Object afterResult = h.after(args);
    return result;
  }

也就是说这个时候,将具体的实现逻辑交给开发者来定义并实现,而开发者只需要执行H接口和目标类对象即可。

public class A implements H{
  
  Object before(Object args){
    loggger.info("参数值是:{}",args);
    return null;
  }
  
  Object after(Object args){
    Object result = modifyData(result);
    return result;
  }
  
}

那么一个完成的动态代理类我们就可以手动的来创建出来了。

所以其实动态代理也就是按照这个逻辑来进行实现的而已。

我为什么来写这篇博客呢,因为我在这里已经无法深刻体会到这里的精髓,终于是想明白了精髓的地方来做一个记录。

2、验证猜想

写出具体类:

public interface AService {
    Object doService(Object args);
}

public class A implements AService {
    @Override
    public Object doService(Object args) {
        System.out.println("目标方法执行");
        Object result = doHanler(args);
        return result;
    }

    private Object doHanler(Object args) {
        int result = (int) (Math.random() * 100);
        return result;
    }
}

程序员手动定义代理逻辑:

public class TargetHandler implements InvocationHandler {


    /**
     * 目标对象
     */
    private AService target;

    public TargetHandler(AService target) {
        this.target = target;
    }


    public AService getProxy(){
        return (AService) Proxy.newProxyInstance(TargetHandler.class.getClassLoader(),new Class[]{AService.class},this);
    }


    /**
     *
     * @param proxy 代理类
     * @param method 目标对象执行的方法
     * @param args 目标对象执行的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before(args);
        Object result = method.invoke(target,args);
        after(result);
        return result;
    }

    private void before(Object args){
        System.out.println(String.format("目标对象执行目标方法参数的值是:%s",args));
    }


    private void after(Object result){
        System.out.println(String.format("目标对象执行目标方法结果的值是:%s",result));
    }

}

做测试:

public class AProxyTest {
    public static void main(String[] args) {
        AService target = new A();

        System.out.println(String.format("-----------目标对象执行目标方法的效果----------"));
        target.doService(null);
        System.out.println("---------------------------------------");
        TargetHandler targetHandler = new TargetHandler(target);
        AService proxy = targetHandler.getProxy();
        System.out.println(String.format("-----------对象对象执行目标方法的效果----------"));
        proxy.doService(null);
    }
}

控制台打印效果:

-----------目标对象执行目标方法的效果----------
目标方法执行
---------------------------------------
-----------对象对象执行目标方法的效果----------
目标对象执行目标方法参数的值是:[Ljava.lang.Object;@266474c2
目标方法执行
目标对象执行目标方法结果的值是:14

那么通过下面代码将Proxy0的class字节码输出到文件。

byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", WeiboProvider.class.getInterfaces());
String path = "C:**/IdeaProjects/study/out/production/study/SimpleProxy.class";
try(FileOutputStream fos = new FileOutputStream(path)) {
    fos.write(classFile);
    fos.flush();
    System.out.println("代理类class文件写入成功");
   } catch (Exception e) {
     System.out.println("写文件错误");
 }

利用反编译工具Luyten来反编译代理对象,反编译Proxy0如下:

//Proxy0 是动态生成的类,继承自Proxy,实现了IProvider接口
public final class $Proxy0 extends Proxy implements IProvider {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

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

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String getData(String var1) throws  {
        try {
            // 这里来到了最本质的地方
            //m3就是IProvider 接口的getData方法 
            //super.h 是父类java.lang.reflect.Proxy的属性 InvocationHandler
            return (String)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final int hashCode() throws  {
        try {
            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            //m3就是IProvider 接口的getData方法
            m3 = Class.forName("aop.IProvider").getMethod("getData", new Class[]{Class.forName("java.lang.String")});
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

重点在 return (String)super.h.invoke(this, m3, new Object[]{var1});代码。

$Proxy0继承Proxy类,实现了IProvider接口,所以也有getData()函数,而getData函数调用的是执行者InvocationHandler的invoke方法,m3是通过反射拿到的Method对象,所以看getData调用invoke传递的。三个参数,第一个是Proxy对象,第二个是getData方法对象,第三个是参数。

总结一下:

  • 动态代理的本质就是,生成一个继承自Proxy,实现被代理接口(IProvider)的类 - Proxy0。
  • Proxy0 持有InvocationHandler实例,InvocationHandler 持有SimpleProvider实例。Proxy0调用接口 getData方法时,先传递给InvocationHandler,传递的时候调用invoke()方法,然后InvocationHandler再传递给SimpleProvider实例来反射调用其中的方法。

动态代理实际上就是帮我们在JVM内存中直接重新生成了代理类class和对应类对象,然后通过执行者InvocationHandler调用被代理对象SimpleProvider。

也就是说,我们在

    public AService getProxy(){
        return (AService) Proxy.newProxyInstance(TargetHandler.class.getClassLoader(),new Class[]{AService.class},this);
    }

已经告知了Proxy中的InvocationHandler是当前的InvocationHandler中的invoke方法。当代理类执行到目标方法的时候,就会执行到这个方法中来。

记录:Java中“装饰模式”和“代理模式”有啥区别?

https://www.zhihu.com/question/41988550/answer/732712752
posted @ 2022-02-08 15:37  写的代码很烂  阅读(72)  评论(0编辑  收藏  举报