Java设计模式-代理模式

代理模式

使用代理模式创建代表(representative)对象,让代理对象控制某对象的访问,被代理的对象可以是远程的对象,创建开销大的对象或需要安全控制的对象。——[Head First 设计模式]

简单的讲就是 :为服务对象提供代理,通过代理控制对服务对象的访问范围

  • 生活中的场景
  • 代理模式的好处
  • 技术上的应用
  • 分类
  • 静态代理
  • JDK动态代理
  • Cglib动态代理
  • 小结

生活中代理模式的场景

设计模式-代理模式

思考一下,其实在我们生活中不乏出现代理模式的场景:

  1. 滴滴出行;出门搭车并不需要直接联系某个司机,而是通过滴滴下单,滴滴就会帮我们将乘车需求通知到附近的司机;
  2. 淘宝购物;相信大多数人会在淘宝或其他电商平台上购物,而作为用户,我们可以通过淘宝就可以了解到商品的具体详情,而不需要到线下店了解;
  3. 链家租房;生活中找租房通常会选择中介代理,比如链家,这样一来,用户不需要四处奔波就可以通过代理获取到租房信息;

为什么需要代理呢?因为代理最大的好处就是方便,上面例子,体现代理的第一个作用:服务透明,屏蔽具体实现,用户无法感知真正的服务提供方;但是通过代理就可以实现自己的需求;

代理模式的好处

  1. 扩展,逻辑解耦,屏蔽真实实现,更换被代理对象,使用无感知;
  2. 保护,非直接调用,只提供本该提供的特定服务,防止越界访问;
  3. 简化,职责专一,被代理者只负责业务逻辑,不关心其他事项;

保护的理解:比如链家,只提供房东的房屋信息,对于房东的其他信息,客户无从知道,从而起到了保护作用
简化的理解:比如滴滴,对于司机来说,只负责正真的运输,不关心开车以外的事情,比如,找客源,讲价钱等等,简化了整个流程。

技术上的应用

代理模式最典型的的应用就是AOP;讲到AOP,相信大家脑海里,都会浮现出很多关键字:面向切面编程、静态代理、JDK动态代理,CGLIB;

AOP(Aspect Orient Programming),面向切面编程,用于在系统各个模块中的业务流程中织入通用的处理逻辑,比如事务管理、操作日志、缓存、异常处理等等。

有关AOP的介绍后续会用心的篇章来讲解,本文主要讲解代理的实现。

分类

从功能的角度划分,可以分为:远程代理、虚拟代理、保护代理、智能引用代理;
按实现的角度划分,则有静态代理和动态代理;动态代理还可以分为JDK动态代理和CGLIB动态代理

这里写图片描述

在了解具体实现之前,先详细看一下上图;
首先是Service提供了服务接口,ServiceImpl和Proxy都必须实现Service接口;通过实现同个接口,使得上层调用可以像ServiceImpl一样使用Proxy的服务;
Proxy持有Service的引用,以便后续将请求转发给ServiceImpl;
当然除了接口,基于继承的方式也是可以实现代理模式,比如Cglib动态代理;
了解以上内容,已经大概了解了代理模式的具体实现。

静态代理

假设我们需要提供一个提供新增用户的服务接口给上层业务使用;
我们首先要先制定好接口UserService,将接口 addUser(String user) 开放给上层业务;

/**
 * 开放代理的接口
 */
public interface UserService {
    public void addUser(String user);
}

接下来,编写具体实现的服务类 UserServiceImpl.java

/**
 * 实际业务逻辑
 */
public class UserServiceImpl implements UserService{
    @Override
    public void addUser(String user) {
        System.out.println(String.format("add user:%s success", user));
    }
}

测试使用静态代理

/**
 * 代理模式测试类
 */
public class ProxyTest {
    /**
     * 测试静态代理
     */
    @Test
    public void testStaticProxy(){
        UserService userService = new UserServiceImpl();
        StaticProxy staticProxy = new StaticProxy(userService);
        staticProxy.addUser("xupeng.zhang");
    }
}

运行上述代码输出结果:
begin to execute static proxy to add user......
add user:xupeng.zhang success
execute static proxy to add user end. cost 0 ms**

采用静态代理,我们需要在代理类StaticProxy中传入UserService的引用;
同时,代理类需要同时实现UserService的接口;只是简单的将请求转发给UserServiceImpl对象;

/**
 * 静态代理类
 */
public class StaticProxy implements UserService{
    private UserService userService;

    public StaticProxy(UserService userService) {
        this.userService = userService;
    }

    @Override
    public void addUser(String user) {
        //统计耗时
        Long executeTime = System.currentTimeMillis();
        System.out.println("begin to execute static proxy to add user......");
        userService.addUser(user);
        System.out.println(String.format("execute static proxy to add user end. cost %s ms", System.currentTimeMillis() - executeTime));
    }
}

以上便是静态代理类的实现方式,比较简单;但是如果UserService新增一个接口,那么从UserService到Proxy,都要重新实现一下,而且统计耗时的代码逻辑每个方法都要写一份。
有没有什么办法可以减少这种重复性的工作呢?当然有,那就是接下来要讲的动态代理;

JDK动态代理

同样需要实现 UserService 接口,需要持有 UserService的引用;
所不同的是,需要JDK帮我们动态生成代理对象;

具体做法如下:

  1. 实现InvocationHandler接口,代理类对象的执行最后会转发到invoke方法;
  2. 通过JDK的动态代理获取UserService的代理对象;
/**
 * 实际执行处理类
 */
public class RealInvocationHandler implements InvocationHandler{
    private Object target;

    public RealInvocationHandler(Object target) {
        this.target = target;
    }

    /**
     * 最终代理对象会将请求转发到invoke方法执行
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Long executeTime = System.currentTimeMillis();
        System.out.println("begin to execute method in dynamic proxy...");
        Object result = method.invoke(target, args);
        System.out.println(String.format("execute method by dynamic proxy end, cost %s ms", System.currentTimeMillis() - executeTime));
        return result;
    }

    /**
     * 基于反射实现动态代理对象生成
     * @param <T>
     * @return
     */
    public <T> T getProxy(){
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), this);
    }
}

测试使用JDK动态代理

/**
 * 代理模式测试类
 */
public class ProxyTest {
    /**
     * 测试JDK动态代理
     */
    @Test
    public void testJDKDynamicProxy(){
        UserService userService = new UserServiceImpl();
        RealInvocationHandler realInvocationHandler = new RealInvocationHandler(userService);
        UserService userProxy = realInvocationHandler.getProxy();
        userProxy.addUser("xupeng.zhang");
    }
}

运行上述代码输出结果:
begin to execute method in dynamic proxy...
add user:xupeng.zhang success
execute method by dynamic proxy end, cost 0 ms
Process finished with exit code 0**

上述代码中,我们并没有手写过代理类,JDK基于反射动态帮我们生成了代理类:

/**
     * 基于反射实现动态代理对象生成
     * @param <T>
     * @return
     */
    public <T> T getProxy(){
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), this);//this即 RealInvocationHandler对象
    }

如果进去newProxyInstance看源码的话,无非就以下三句关键代码:


 1. Class<?> cl = getProxyClass0(loader, intfs); //获取代理类对象
 2. final Constructor<?> cons = cl.getConstructor(constructorParams);//获取代理类的构造方法
 3. return cons.newInstance(new Object[]{h});//构造方法生成代理对象并返回

其中:
loader,指定代理对象的类加载器;
intfs 即 interfaces,代理对象需要实现的接口,可以同时指定多个接口;
h 即 RealInvocationHandler对象,方法调用的实际处理者,代理对象的方法调用都会转发到invoke方法。

继续查看代理类的相关信息,你会发现JDK的动态代理居然如此奇妙:

/**
 * 代理模式测试类
 */
public class ProxyTest {
    @Test
    public void testGetJDKProxyClassInfo(){
        UserService userService = new UserServiceImpl();
        RealInvocationHandler realInvocationHandler = new RealInvocationHandler(userService);
        UserService userProxy = realInvocationHandler.getProxy();
        System.out.println(userProxy.getClass().getName());
        System.out.println(userProxy.getClass().getSuperclass());
        System.out.println(userProxy.getClass().getInterfaces()[0].getName());
        ProxyGeneratorUtils.saveProxyClass("D:/JdkProxy.class", userProxy.getClass());
    }
}

运行上述代码输出结果:
com.sun.proxy.$Proxy2
class java.lang.reflect.Proxy
com.orig.design.proxy.UserService**

代理类的类型是com.sun.proxy.$Proxy2;且继承自Proxy.java
此外还实现了UserService接口;
而我们将UserSercieProxy.class的字节码文件反编译会发现以下关键代码:

public final void addUser(String var1) throws  {
     try {
         super.h.invoke(this, m3, new Object[]{var1});
     } catch (RuntimeException | Error var3) {
         throw var3;
     } catch (Throwable var4) {
         throw new UndeclaredThrowableException(var4);
     }
 }

最终addUser会将请求转发给 RealInvocationHandler的 invoke方法执行;invoke方法调用method.invoke执行服务类对应的方法;

CGLIB动态代理

JDK生成的代理类已经继承了Proxy类,Java不支持多重继承,因此无法实现继承式的动态代理;这个时候就可以采用Cglib动态代理来实现;
原理上跟JDK动态代理其实大同小异:
cglib会动态生成代理类,它只关注自己的父类,通过继承获得父类非final的接口;之后将请求转发给MethodInterceptor的intercept方法去做实际处理;

具体做法如下:

  1. 实现MethodInterceptor接口,intercept方法用于后续实际请求处理;
  2. 通过Cglib的Enhancer对象构造代理对象,需要告知enhance继承的父类;

具体实现代码如下:

/**
 * cglib动态代理
 * 和JDK动态代理不同
 * 是基于继承实现
 */
public class RealInterceptor implements MethodInterceptor{
    private Enhancer enhancer = new Enhancer();

    /**
     * 具体执行的回调方法
     * @param o
     * @param method
     * @param args
     * @param methodProxy
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        Long currentTime = System.currentTimeMillis();
        System.out.println("begin to execute cglib proxy to add user");
        System.out.println(String.format("method: %s", method.getName()));
        Object result = methodProxy.invokeSuper(o, args);//注意这里是invokeSuper
        System.out.println(String.format("execute cglib proxy end, cost: %s ms", System.currentTimeMillis()-currentTime));
        return result;
    }

    /**
     * 设置代理的父类
     * 设置回调方法
     * @param tClass
     * @param <T>
     * @return
     */
    public <T> T newProxyInstance(Class<T> tClass){
        enhancer.setSuperclass(tClass);
        enhancer.setCallback(this);
        return (T) enhancer.create();
    }
}

运行上述代码输出结果:
begin to execute cglib proxy to add user
method: addUser
add user:xupeng.zhang success
execute cglib proxy end, cost: 22 ms**

通过上述结果可以看出,Cglib性能上相对于JDK动态代理稍差;同样的代理的实现,Cglib的实现比JDK动态代理的实现要耗时;

同样的,我们针对代理类的相关信息进行挖掘:

/**
 * 代理模式测试类
 */
public class ProxyTest {
	/**
     * 获取cglib代理类信息
     */
    @Test
    public void testGetCglibProxyClassInfo() throws Exception {
        RealInterceptor realInterceptor = new RealInterceptor();
        UserRoleService userRoleProxy = realInterceptor.newProxyInstance(UserRoleService.class);
        System.out.println(userRoleProxy.getClass().getName());
        System.out.println(userRoleProxy.getClass().getSuperclass());
        System.out.println(userRoleProxy.getClass().getInterfaces()[0].getName());
        ProxyGeneratorUtils.saveCglibProxyClass("D:/CglibProxy.class",realInterceptor.getEnhancer());
    }
}

运行上述代码输出结果:
com.orig.design.proxy.UserServiceImpl$$EnhancerByCGLIB$$591ab16e
class com.orig.design.proxy.UserServiceImpl
net.sf.cglib.proxy.Factory**

Cglib生成的代理类关键代码如下:

public class UserRoleService$$EnhancerByCGLIB$$8ba328ed extends UserRoleService implements Factory {
    .......省略部分代码
    public final void addUserRole(String var1, String var2) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (this.CGLIB$CALLBACK_0 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            var10000.intercept(this, CGLIB$addUserRole$0$Method, new Object[]{var1, var2}, CGLIB$addUserRole$0$Proxy);
        } else {
            super.addUserRole(var1, var2);
        }
    }
}

Cglib动态代理类直接继承了UserRoleService,并重写了addUserRole方法;
此外,对原有的方法做了增强处理,如果Enhancer有设置设置回调方法,则会执行对应的回调方法interceptor;

/**
     * 设置代理的父类
     * 设置回调方法
     * @param tClass
     * @param <T>
     * @return
     */
    public <T> T newProxyInstance(Class<T> tClass){
        enhancer.setSuperclass(tClass);//设置继承的父类
        enhancer.setCallback(this);//设置回调函数,入参是Callback类型
        return (T) enhancer.create();
    }

/**回调方法**/
public interface MethodInterceptor extends Callback {
    Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
}

小结

  1. 动态代理原理:(1)动态生成字节码;(2)通过反射调用真实实现类方法
  2. JDK原生动态代理基于接口实现;Cglib动态代理基于继承方式实现;
  3. 动态代理相对静态代理的好处在于灵活,强大,不需要对每个接口进行代理逻辑的开发;
  4. 当然,增加了代理,无形中会增加调用链,意味着降低处理速度;实现代理也需要额外的开发工作,增加了实现成本。

本文相关的代码Demo已经上传到github上:github


posted @ 2018-06-21 09:20  xupeng.zhang  阅读(208)  评论(0编辑  收藏  举报