代理设计模式(JDK与CGLIB)

来源:互联网 发布:程序员要学什么 编辑:程序博客网 时间:2024/06/17 08:40

  • 静态代理
  • 动态代理
    • 1JDK动态代理
    • 2CGLIB动态代理

  • 代理模式的定义

    • 给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问
    • 客户不直接操控原对象,而是通过代理对象间接地操控原对象
  • 代理模式UML图:

图片名称

其中:

  • RealSubject 委托对象,Proxy 是代理对象
  • Subject 是委托对象和代理对象都共同实现的接口
  • Request() 是委托对象和代理对象共同拥有的方法

我们为什么要用代理模式呢?换句话说,代理模式的好处是什么?

  • 好处一:我们可以隐藏委托类的实现
  • 好处二:可以使客户端与委托类实现解耦。即在不修改委托类的情况下实现额外的处理。

代理的实现分为

  • 静态代理代理类是在编译时就实现好的。也就是说 Java 编译完成后代理类是一个实际的 class 文件。
  • 动态代理代理类是在运行时生成的。也就是说 Java 编译完之后并没有实际的 class 文件,而是在运行时通过Java的反射机制,动态生成类的字节码,并加载到JVM中。

静态代理

看懂了上面的UML图,其实静态代理不难实现。直接先给出代码:

  • 公共接口
public interface Subject{    void request();}
  • 委托类
public class RealSubject implements Subject{    public void request(){        System.out.println("realSubject request.");    }}
  • 代理类:
public class Proxy implements Subject {    private Subject subject;    public Proxy(Subject subject) {        this.subject = subject;    }    public void request() {        System.out.println("BeforeProcess")        subject.request();        System.out.println("AfterProcess");    }}
  • 客户端:
public class Client{    public static void main(String args[]){        RealSubject realSubject = new RealSubject();        Proxy p = new Proxy(realSubject);        p.request();    }}
  • 输出:
BeforeProcessrealSubject requestAfterProcess

可以看出,静态代理通过聚合来实现,只要让代理类持有一个委托类的引用即可。

静态代理的特点:

  • 优点:业务类只需要关注业务逻辑本身,保证了业务类的重用性。当然了,这是所以代理模式的共有优点。
  • 缺点:
    • 代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
    • 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

动态代理

(1)JDK动态代理:

Java实现JDK动态代理的大致步骤如下:

  • 首先定义一个委托类公共接口

  • 然后自定义一个调用处理器类(即实现 InvocationHandler 接口的类),这个类的目的是指定运行时将生成的代理类需要完成的具体任务(包括”before process”和”after process”),代理类调用任何方法都会经过这个调用处理器类。

  • 最后生成代理对象(当然也会生成代理类),需要为他指定(1)委托对象、(2)实现的一系列接口、(3)调用处理器类的实例。因此可以看出一个代理对象对应一个委托对象,对应一个调用处理器实例。

先用一个简单的例子实现Java实现JDK动态代理的整个过程:

  • 公共接口:
public interface Subject{    void request();}
  • 委托类:
public class RealSubject implements Subject{    public void request(){        System.out.println("realSubject request");    }}
  • 调用处理器类:
public class ProxyHandler implements InvocationHandler{    private Subject subject;    public ProxyHandler(Subject subject){        this.subject = subject;    }    @Override    public Object invoke(Object subject, Method method, Object[] args)            throws Throwable {        System.out.println("before process");        Object result = method.invoke(subject, args);        System.out.println("after process");        return result;    }}
  • 客户端:
public class Client{    public static void main(String[] args) {       //1.创建委托对象       RealSubject realSubject = new RealSubject();           //2.创建调用处理器对象       ProxyHandler handler = new ProxyHandler(realSubject);          //3.动态生成代理对象       Subject proxySubject = (Subject)Proxy.newProxyInstance(RealSubject.class.getClassLoader(),            RealSubject.class.getInterfaces(), handler);           //4.通过代理对象调用方法       proxySubject.request();       }}
  • 输出:
before processrealSubject requestafter process

Jdk 的 java.lang.reflect 包下的 Proxy 类,正是构造代理类的入口。他内部的newProxyInstance 就是创建代理对象的方法,源码如下:

public static Object newProxyInstance(ClassLoader loader,                                          Class<?>[] interfaces,                                          InvocationHandler h)        throws IllegalArgumentException    {        //如果h为空将抛出异常        Objects.requireNonNull(h);        //拷贝被代理类实现的一些接口,用于后面权限方面的一些检查        final Class<?>[] intfs = interfaces.clone();        final SecurityManager sm = System.getSecurityManager();        if (sm != null) {            //这里对某些安全权限进行检查,确保我们有权限对预期的被代理类进行代理            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);        }        //根据类加载器和接口创建代理类!        Class<?> cl = getProxyClass0(loader, intfs);        /* 使用指定的调用处理程序获取代理类的构造函数对象 */        try {            if (sm != null) {                checkNewProxyPermission(Reflection.getCallerClass(), cl);            }            //获得代理类的带参数的构造器            final Constructor<?> cons = cl.getConstructor(constructorParams);            final InvocationHandler ih = h;            // 假如代理类的构造函数是非共有的,就使用反射来set accessible            if (!Modifier.isPublic(cl.getModifiers())) {                AccessController.doPrivileged(new PrivilegedAction<Void>() {                    public Void run() {                        cons.setAccessible(true);                        return null;                    }                });            }            //根据代理类的构造函数来生成代理类的对象并返回            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,根据类加载器和接口创建代理类;
  • 2,获得代理类的带参数的构造函数;
  • 3,根据代理类的构造函数来生成代理类的对象(调用处理器实例为参数传入),并返回。

InvocationHandler 接口中有方法:

invoke(Object proxy, Method method, Object[] args)

这个函数是在代理对象调用任何一个方法时都会调用的,方法不同会导致第二个参数method不同,第一个参数是代理对象(表示哪个代理对象调用了method方法),第二个参数是 Method 对象(表示哪个方法被调用了),第三个参数是指定调用方法的参数。

总结一下使用JDK动态生成的代理类的特点

  • 继承 Proxy 类,并实现了在Proxy.newProxyInstance()中提供的接口数组。
  • 代理类是public final的。
  • 命名方式为$ProxyN,其中N会慢慢增加,一开始是 $Proxy1,接下来是$Proxy2。。。
  • 有一个参数为 InvocationHandler 的构造函数。这个从 Proxy.newProxyInstance() 函数内部的clazz.getConstructor(new Class[] { InvocationHandler.class }) 可以看出。
  • 动态代理类相对于静态代理类,当代理类的实现是有很多共性的(重复代码),动态代理的好处在于避免了这些重复代码,只需要关注操作。
  • Java 实现动态代理的缺点:因为 Java 的单继承特性(每个代理类都继承了 Proxy 类),只能针对接口创建代理类,不能针对类创建代理类。

(2)CGLIB动态代理:

上文提到,JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能使用JDK的动态代理了。cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。

下面我们来实现简单的CGLIB动态代理。

首先,我们需要使用maven引入cglib的依赖:

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

下面开始实现cglib动态代理:

  • 委托类:
public class RealSubject {    public void request() {        System.out.println("request.");    }}

注意,该委托类没有实现接口,所以不能、或者说无法使用JDK动态代理。

  • 实现MethodInterceptor接口的方法拦截器:
public class MyMethodInterceptor implements MethodInterceptor {    public Object intercept(Object objcet, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {        System.out.println("before...");        Object obj = methodProxy.invokeSuper(object, objects);        System.out.println("after...");        return obj;    }}
  • 客户端:
public class Client {    public static void main(String[] args) {        // 利用Enhancer类生成代理类        Enhancer enhancer = new Enhancer();        // 继承被代理类        enhancer.setSuperClass(RealSubject.class);        // 设置回调        enhancer.setCallBack(new MyMethodInterceptor());        // 生成代理对象        RealSubject real = (RealSubject)enhancer.create();        // 在调用代理类方法时,会被我们实现的拦截器拦截        real.request();    }}

整个CGLIB动态代理的具体实现步骤大概如下:

  • 1、通过Enhancer类生成代理类Class的二进制字节码,并通过Class.forName加载二进制字节码,生成Class对象;
  • 2、通过反射机制获取实例构造,并初始化代理类对象。
  • 3,代理类将委托类作为自己的父类并为其中的非final委托方法创建两个方法,一个是与委托方法签名相同的方法,它在方法中会通过super调用委托方法;另一个是代理类独有的方法。
  • 4,在代理方法中判断是否存在实现了MethodInterceptor接口的对象,若存在则将调用intercept方法对委托方法进行增强代理。

CGLIB动态代理的特点

  • 优点:可以在运行时对类或者是接口进行增强操作,委托类无需实现接口,这正好弥补了JDK动态代理的缺点。
  • 缺点:不能对final类以及final方法进行代理。

最后,相信大家对什么时候用JDK动态代理,什么时候用CGLIB动态代理,以及怎么用这两种代理模式,都已经了然了。

原创粉丝点击