jdk动态代理

动态代理

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、生成的Proxy对象是怎样调用执行者的invoke函数的

这个地方通过这段代码将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("写文件错误");
 }

反编译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。

这就是我们经常看到的,为什么代理对象调用方法的时候会首先调用代理对象的Invoke()方法,然后再去调用目标对象的方法。

3.1、注意

注意看动态代理生成的类,可以看到实现的是接口,而不是具体的类类型,这也是为什么只能够使用接口来进行接口对应的数据类型。

这个点需要注意就行了。

4、看动态代理生成对应的文件

public class UserManagerTest {
    public static void main(String[] args) {
        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", UserManagerImpl.class.getInterfaces());
        String path = "D:\\project\\java-basic-test\\jdk-proxy\\src\\com\\guang\\proxy\\demothree\\impl\\UserManagerImpl.class";
        try(FileOutputStream fos = new FileOutputStream(path)) {
            fos.write(classFile);
            fos.flush();
            System.out.println("代理类class文件写入成功");
        } catch (Exception e) {
            System.out.println("写文件错误");
        }
    }
}   

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

https://www.zhihu.com/question/41988550/answer/732712752
posted @ 2022-01-28 11:24  写的代码很烂  阅读(88)  评论(0编辑  收藏  举报