05 Java的动态代理知识点

代理基础知识

内容来源于代理模式

一 代理模式介绍

1- 1 代理模式的实例

需求:通过代理类来节约资源占用

package proxy;
interface Image {
    void display();
}
/*被代理对象*/
class RealImage implements Image {
    private String fileName;
    public RealImage(String fileName){
        this.fileName = fileName;
        loadFromDisk(fileName);
    }
    @Override
    public void display() {
        System.out.println("Displaying " + fileName);
    }
    private void loadFromDisk(String fileName){
        System.out.println("Loading " + fileName);
    }
}
/*代理类实现被代理类的接口*/
class ProxyImage implements Image{
    private RealImage realImage;
    private String fileName;
    public ProxyImage(String fileName){
        this.fileName = fileName;
    }

    @Override
    public void display() {
        if(realImage == null){
            realImage = new RealImage(fileName);
        }
        realImage.display();
    }
}

public class Test1 {
    public static void main(String[] args) {
        /*这里代理类扩展了被代理类的display方法,使得图像在需要显示的时候采取new被代理对象,
          避免被代理对象在不需要时对资源的占用 */
        Image image = new ProxyImage("test_10mb.jpg");
        image.display();        // 图像第一次需要才从磁盘加载
        image.display();        // 图像后续显示无需加载
    }
}

问题:代理类的实现流程?

step1:确定被代理类(委托类)的接口

step2:代理类具体实现时需保证两点

  • 实现与被代理类相同的接口(或者直接继承委托类)
  • 方法的实现需要调用被代理类对应的方法并根据需求进行扩展

结合上图可以看到 ProxyImage实现了与RealImage相同的接口,并调用RealImage对象的方法实现对应方法的扩展。

总结事实上,真正的业务功能还是由目标类来实现,代理类只是用于扩展、增强目标类的行为。例如,在项目开发中我们没有加入缓冲、日志这些功能而后期需要加入,我们就可以使用代理来实现,而没有必要去直接修改已经封装好的目标类。

代理模式的开闭原则:对原有类的修改进行闭合,对原有类的扩展进行开放。

1-2 代理模式的注意点

问题:为什么需要代理类?

主要原因:直接访问对象不方便或者存在问题

被代理对象常见的特点:
1)要访问的对象在远程的机器上
2)对象创建开销很大(有需要时才创建,比如1-1的实例)
3)需要安全控制
4)进程外的对象的访问进程内部的对象

问题:代理模式的优缺点于与使用场景?

优点: 1、职责清晰。 2、高扩展性。 3、智能化。
缺点: 
1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 
2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

使用场景:按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。

问题:代理模式与装饰器模式以及适配器模式的区别?

模式名称 特点 实例
代理模式 代理模式不能改变所代理类的接口,代理模式用于加以控制 spring的AOP和mybatis中的Mapper接口开发
装饰器模式 装饰器模式是为了增强功能 Collections synchronized系列集合类型
适配器模式 适配器模式需要考虑改变所考虑对象的接口 SpringMVC中的适配器HandlerAdatper。

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

适配器模式(Adapter Pattern)将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

Spring中用到的设计模式有

1.简单工厂(非23种设计模式中的一种)
2.工厂方法
3.单例模式
4.适配器模式
5.装饰器模式
6.代理模式
7.观察者模式
8.策略模式
9.模版方法模式

Spring 中经典的 9 种设计模式

Spring框架中设计模式的运用

二 静态/动态代理

1-1 静态/动态代理的概述

静态代理缺点

当项目中被代理的类的数目比较多的时候,会出现:

1)由于代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,代理类的数量会很多,比较麻烦

2)被代理的类的接口被修改或者扩展,所有代理类也需要重新实现,不好维护。


静态代理与动态代理区别

1)对于静态代理,每个代理类的创建需要指定被代理对象,代理class需要人工创建。

2)对于动态代理, 在运行时动态的创建代理类能够代理各种类型的对象,动态代理克服了静态代理的缺点

  • 动态代理不需要人工创建代理类
1)目标类增多,代理类不会变的多
2)目标类接的接口被修改,也不会影响代理类

补充:代理对象的创建

静态代理的代理对象创建

1) 程序员编写静态代理class文件并编译。
2)使用构造函数创建类的实例对象。

动态代理的代理对象创建

对于通过Java反射实现的动态代理,其实现如下:
    1)动态代理对象的class文件是运行时JDK生成的,class在加载到内存之后就删除了,所以看不到类文件
    注意:JDK会在磁盘文件生成class文件,类加载完成后会自动删除!!!!!!!!!
    2)使用构造函数创建类的实例对象。

1-2 动态代理的两种实现

1)jdk动态代理:使用java反射包中的类(InvocationHandler,Method,Proxy)和被代理的类所实现的接口实现动态代理功能。
注意:被代理类没有所实现的接口,则无法采用jdk实现动态代理,考虑使用cglib实现动态代理
2)cglib(code generation library)动态代理:其原理是生成目标类的子类,在子类中重写父类同名方法并增强功能,这个子类就是代理对象,因此使用cglib必须要求目标类能够被继承。
注意:cglib经常被应用于框架中,比如spring等框架,cglib代理效率要高于jdk.

补充知识点:Java中哪些类不能被继承?

一、没有被声明为public的类,只能在自已的包内被其他类继承,在包外不能被其他包里的类继承
二、使用了final关键字修饰的类不能被继承,被final修饰的方法不可被重写。
三、如果一个类只有私有的构造函数,它不能被继承

Javafinal的详细用法

1-3 使用Java的反射包实现动态代理

相关的反射类:

Class Proxy

Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.

Class Constructor

Constructor provides information about, and access to, a single constructor for a class.
Constructor permits widening conversions to occur when matching the actual parameters to newInstance() with the underlying constructor's formal parameters, but throws an IllegalArgumentException if a narrowing conversion would occur.

简单的实例

import java.lang.reflect.*;

interface ArbitrationStep {
    void prepareApp();
    void collectEvi();
    void debate();
}
// 被代理对象
class XiaoWen implements ArbitrationStep {
    @Override
    public void prepareApp() {
        System.out.println("准备仲裁申请!");
    }
    @Override
    public void collectEvi() {
        System.out.println("收集证据!");
    }
    @Override
    public void debate() {
        System.out.println("开庭答辩!");
    }
}



public class Test2 {
    public static Object getProxy(Object target) throws NoSuchMethodException, IllegalAccessException,         InvocationTargetException, InstantiationException {
        /*step1:传入2个参数到Proxy.getProxyClass: 1)被代理类的类加载器   2) 被代理类的接口*/
        Class<?> proxyClazz = Proxy.getProxyClass(target.getClass().getClassLoader(), target.getClass().getInterfaces());
        /*step2:获得代理类的构造函数*/
        Constructor<?> constructor = proxyClazz.getConstructor(InvocationHandler.class);
        /*step3:获得代理类对象*/
        Object targetProxy = constructor.newInstance((InvocationHandler) new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object invoke = method.invoke(target, args);
                System.out.println("代理类增强方法");
                return invoke;
            }
        });
        return targetProxy;
    }

    /*通过Proxy.newProxyInstance直接取代上面的三个步骤从而生成代理对象*/
    public static Object getProxyByProxyMethod(Object target) {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object invoke = method.invoke(target, args);
                System.out.println("代理对象增强方法");
                return invoke;
            }
        });
    }

    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
        XiaoWen xiaoWen = new XiaoWen();
        ArbitrationStep lawyerProxy = (ArbitrationStep) getProxy(xiaoWen);
        // 通过代理对象调用方法
        System.out.println("-------第一步-------");
        lawyerProxy.prepareApp();
        System.out.println("-------第二步-------");
        lawyerProxy.collectEvi();
        System.out.println("-------第三步-------");
        lawyerProxy.debate();
        System.out.println();
        System.out.println("仲裁结束");
        System.out.println("代理类的父类:"+lawyerProxy.getClass().getSuperclass());
    }
}

执行结果

-------第一步-------
准备仲裁申请!
代理类增强方法
-------第二步-------
收集证据!
代理类增强方法
-------第三步-------
开庭答辩!
代理类增强方法

仲裁结束
代理类的父类:class java.lang.reflect.Proxy            // 返回的代理类必须继承反射包下的proxy类,因此必须有被代理类的接口才行

总结:只需要被代理类存在接口,并将其实例对象传入到方法中就可以获得对应的代理对象。

  • 这里的代理对象是通过JDK反射包的方法自动生成的,无需要实现。
  • 传入不同的target对象可以生成对应的代理对象,因此同种类型的被代理类只需要实现少量的代码就可以生成每个类的代理对象
public static Object getProxyByProxyMethod(Object target)
public static Object getProxy(Object target)

问题:为什么采用jdk反射机制创建代理必须通过接口实现?

原因:1)Java是单继承的 2)通过JDK反射机制获取的代理类都继承自java.lang.reflect.Proxy ,因此必须采用接口

class java.lang.reflect.Proxy 
// 返回的被代理类继承java.lang.reflect.Proxy,而Java又是单继承的,所以想要被代理对象与代理对象产生联系,就只能通过接口来实现了

1-3-1 newProxyInstance源码分析

public static Object getProxyByProxyMethod(Object target) {
    return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object invoke = method.invoke(target, args);  // 调用被代理对象的方法
            System.out.println("代理对象增强方法");
            return invoke;
        }
    });
}

源码

    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) throws                       IllegalArgumentException
    {   /*
    	    clone方法被对象调用,所以会复制对象。所谓的复制对象,首先要分配一个和源对象同样大小的空间,
    	    在这个空间中创建一个新的对象。那么在java语言中,有几种方式可以创建对象呢? 
    	    1) 使用new操作符创建一个对象 2)使用clone方法复制一个对象。
    	*/
        Objects.requireNonNull(h);
        final Class<?> [] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
        /*step1 调用getProxyClass0(loader, intfs)来获取的Class对象*/
        Class<?> cl = getProxyClass0(loader, intfs);      
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
            /*step2 获取对应的构造方法*/
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            /*step3 使用构造方法创建对应的实例对象*/
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

1-3-2 代理对象方法调用的流程分析

  • 上图中被代理类的接口的作为信息的提供者去创建代理对象
// 代理类的构造器的获取:传入了InvocationHandler
Constructor<?> constructor = proxyClazz.getConstructor(InvocationHandler.class);
// InvocationHandler是一个接口,内部只有一个方法invoke
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}
// 在通过constructor获取代理对象时, newInstance方法需要传入一个实现invocationHandler的class的实例
// 实例重写了invoke方法,就是通过它代理对象增强了被代理对象的方法
Object targetProxy = constructor.newInstance(new InvocationHandler() {
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         Object invoke = method.invoke(target, args);
         System.out.println("代理类增强方法");
         return invoke;
      }
   });
   return targetProxy;
}Copy

总结:调用代理对象的方法时,实际上调用的是InvocationHandler的invoke方法,这个方法内部不仅调用了被代理对象的方法,还可以增加其他功能

1-4 使用CGLib实现动态代理

引入cglib库:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

实例

package proxy1;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

class XiaoWen {
    public void prepareApp() {
        System.out.println("被代理类方法1");
    }

    public void collectEvi() {
        System.out.println("被代理类方法2");
    }

    public void debate() {
        System.out.println("被代理类方法3");
    }
}
// 实现拦截器接口,代理类的增强操作在此实现
class LawyerInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 前置增强
        System.out.println("代理类的功能增强,方法调用前");
        /*被代理类执行的方法(被增强的方法),这里是调用invokeSuper而不是invoke,否则死循环;
        * methodProxy.invokeSuper执行的是原始类的方法,method.invoke执行的是子类的方法;
        */
        Object invoke = methodProxy.invokeSuper(o, objects);
        // 后置增强
        System.out.println("代理类的功能增强,方法调用后");
        return invoke;
    }
}

public class Test3 {
    public static void main(String[] args) {
        // 创建增强器
        Enhancer enhancer = new Enhancer();
        // 传入被代理的Class对象(作为代理对象的父类)
        enhancer.setSuperclass(XiaoWen.class);
        // 设置回调函数,传入自定义的拦截器
        enhancer.setCallback(new LawyerInterceptor());
        // 获取代理对象
        XiaoWen proxy = (XiaoWen) enhancer.create();
        // 调用代理类的方法
        proxy.prepareApp();
        System.out.println();
        proxy.collectEvi();
        System.out.println();
        proxy.debate();

        System.out.println("代理类的父类:"+proxy.getClass().getSuperclass());

    }
}

执行结果

代理类的功能增强,方法调用前
被代理类方法1
代理类的功能增强,方法调用后

代理类的功能增强,方法调用前
被代理类方法2
代理类的功能增强,方法调用后

代理类的功能增强,方法调用前
被代理类方法3
代理类的功能增强,方法调用后
    
代理类的父类:class proxy1.XiaoWen

总结:可以看到采用cglib获取的代理类父类就是定义的被代理类

CGLib可以为没有实现接口的类创建代理类。其原理是CGLib底层使用了ASM框架,该框架可以通过修改字节码,来创建一个被代理类的子类,也就是代理类。并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。这种思想符合里氏替换原则,即子类可以扩展父类的功能,但是不能改变父类原有的功能。

1-4-1 cglib的代理流程

  • 生成的代理类继承被代理类。如果委托类被final修饰,那么它不可被继承,即不可被代理;同样,如果委托类中存在final修饰的方法,那么该方法也不可被代理
  • 代理类会为委托方法生成两个方法,一个是与委托方法签名相同的方法,它在方法中会通过super调用委托方法;另一个是代理类独有的方法
  • 当执行代理对象的方法时,会首先判断一下是否存在实现了MethodInterceptor接口的CGLIB$CALLBACK_0;,如果存在,则将调用MethodInterceptor中的intercept方法

intercept方法中,我们除了会调用委托方法,还会进行一些增强操作。在Spring AOP中,典型的应用场景就是在某些敏感方法执行前后进行操作日志记录

在CGLIB中,方法的调用并不是通过反射来完成的,而是直接对方法进行调用:通过FastClass机制对Class对象进行特别的处理,比如将会用数组保存method的引用,每次调用方法的时候都是通过一个index下标来保持对方法的引用

Fastclass机制

CGLIB采用了FastClass的机制来实现对被拦截方法的调用。FastClass机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法.

CGlib比JDK快?

  • 使用CGLiB实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类, 在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理, 因为CGLib原理是动态生成被代理类的子类。

  • 在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率。只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理,总之,每一次jdk版本升级,jdk代理效率都得到提升,而CGLIB代理消息确有点跟不上步伐

Spring如何选择用JDK还是CGLIB?

  • 当Bean实现接口时,Spring就会用JDK的动态代理。
  • 当Bean没有实现接口时,Spring使用CGlib实现。
  • 可以强制使用CGlib

参考链接

参考资料

Java-JDK动态代理(AOP)使用及实现原理分析

动态代理笔记

Java代理模式及其应用

Njima博客-代理模式

知乎关于动态代理的回答(部分回答涉及如何实现动态代理机制)

posted @ 2021-05-31 22:30  狗星  阅读(118)  评论(0编辑  收藏  举报
/* 返回顶部代码 */ TOP