《Proxy系列专题》:代理模式(静态、JDK、CGLib)


《Proxy系列专题》:代理模式(静态、JDK、CGLib)使用

  现象:在如今互联网时代,项目的复杂度不断的提升,有些场景下需要一定的设计优化来支撑业务的扩展,如为了不改动原始类,但需要对其做相应事件扩展,例如:日志,事物,功能增强等。

  思想:想办法用一个B类代表另一个A类的功能,不改变其A类本质。

  结果:代理模式出现,这只是一个思想,实现的方式有很多种,如:静态代理、JDK动态代理、CGLib动态代理 等等其他的。

 使用方式

  本文主要写代理模式主要的几种代码的使用,其原理会接下来的文章种输出。

  主要的代理方式

  1. 静态代理(类似工厂)
  2. 动态代理
    • JDK动态代理(修改字节码JVM + 接口 + 反射)
    • CGLib动态代理(修改字节码ASM + 继承 + 调用父类super)

 静态代理

  静态代理主要过程(整了一个外套--代理类)

  1. 在代理类中实现目标类相同的接口,并设置接口类型变量属性
  2. 将目标类实例作为参数传入赋值,如构造器方式,set方式等等
  3. 调用时其实是代理类的方法调用了相同目标类的方法,中转一下,在调用目标类方法的前后做一些处理

  优点:代理类在编译期生成,效率高。

  缺点:代理类会很多,后期维护复杂。

代码逻辑

  接口类UserService

public interface UserService {
    String getUserName(String username);
}

  接口实现类UserServiceImpl

public class UserServiceImpl implements UserService{

    @Override
    public String getUserName(String username) {
        System.err.println(username);
        return username;
    }
}

  代理类UserStaticProxy

public class UserStaticProxy implements UserService{

    private UserService userService;

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

    @Override
    public String getUserName(String username) {
        System.err.println("开始------");
        String userName = userService.getUserName(username);
        System.err.println("结束------");
        return userName;
    }
}

  测试类StaticProxyTest

public class StaticProxyTest {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        userService.getUserName("test");
        System.err.println("_______________________________________________");
        UserStaticProxy userStaticProxy = new UserStaticProxy(new UserServiceImpl());
        userStaticProxy.getUserName("lisi");
    }
}

 

 JDK动态代理

问题:为什么会出现动态代理呢,静态代理哪里不好了

回答:若是又出现一个类型需要代理,需要再次写一个代理类去维护包装,或者类型中新增一个方法功能代理类也需要跟着维护,复杂过于耦合。所以就有想法如何解决这样的尴尬困境。

  这时候出现了JDK动态代理,完美解决了新增类型和新增方法的一些尴尬问题。主要将静态代理中代理类涉及到目标类有关的以其他种方式体现,变得可动态。

  主要流程:

  1. 从静态代理中对目标类方法处理事件,进行了封装,变成了InvocationHandler调用处理器作为参数形式传递,可以代理工具类内部实现,也可调用逻辑内部匿名实现
  2. 传入目标类实例,进行相应的处理(提取有效信息进行相应的字节码封装)
  3. 代理工具类生成目标类的代理类,此时代理类实例在内存中,其内部与静态代理很相似。

代码逻辑

  接口类SanGuoService

public interface SanGuoService {
    void warfare(String username);
}

  目标类 LiuBeiServiceImpl 实现 SanGuoService

public class LiuBeiServiceImpl implements SanGuoService{
    @Override
    public void warfare(String username) {
        System.err.println("LiuBei-warfare:"+username);
    }
}

  代理工具 JDKSimpleDynamicProxy 可实现 InvocationHandler 调用处理器,也可以逻辑内部匿名实现当作参数传递

  主要为了生成代理类

public class JDKSimpleDynamicProxy implements InvocationHandler {

    private Object object;
    //创建代理类实例
    public <T> T getNewProxy(T t){
        object = t;
        return (T) Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.err.println("------开始----");
        Object invoke = method.invoke(object, args);
        System.err.println("------结束----");
        return invoke;
    }
}

  JDK动态代理测试类 JDKSimpleDynamicProxyTest

public class JDKSimpleDynamicProxyTest {
    public static void main(String[] args) {
        JDKSimpleDynamicProxy jdkSimpleDynamicProxy = new JDKSimpleDynamicProxy();
        LiuBeiServiceImpl liuBeiService = new LiuBeiServiceImpl();
        SanGuoService sanGuoService = jdkSimpleDynamicProxy.getNewProxy(liuBeiService);
        sanGuoService.warfare("刘备");
    }
}

 

 

CGLib动态代理  

问题:有了JDK动态代理那么方便好用高扩展,为什么又要搞出一个CGLib动态代理呢。

回答:当一个类型没有接口,则不能使用JDK动态代理了,除非给硬塞一个接口,很滑稽,那怎么办呢!所有设计了一个可以对目标类直接代理的方式,直接继承,CGLib动态代理。

  其实CGLib和JDK动态代理使用流程上还是很相似的,只是内部逻辑实现不一样,若封装的好是看不出来的。

  主要流程:

  1. 首先明确需要对目标类方法处理要做的事,构造出MethodInterceptor实现,可以代理工具类内部实现,也可以外部业务逻辑自行实现。
  2. 将目标类实例传入代理工具类中,进行相应参数拼装,底层内部根据参数信息逻辑拼装字节码。
  3. 代理工具类生成目标类的代理类,此时代理类实例在内存中,代理类内部逻辑是以super形式调用父类方法

代码逻辑

  目标类:XiangYuServiceImpl 没有接口类

public class XiangYuServiceImpl {

    public void warfare(String username) {
        System.err.println("XiangYu-warfare:"+username);
    }
}

  代理工具类:CGLibDynamicProxy  这类的MethodInterceptor上面说过可以这里实现,也可以业务逻辑自己实现参数传入,因为内部只需要一个参数实例

public class CGLibDynamicProxy implements MethodInterceptor {

    public <T>  T create(Class<T> classN){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(classN);
        enhancer.setCallback(this);
        return (T)enhancer.create();
    }

    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.err.println(proxy.getClass().getName());
        System.err.println("1111111");
        Object invoke = methodProxy.invokeSuper(proxy, args);
        System.err.println("22222222");
        return invoke;
    }
}

  测试类:CGLibDynamicProxyTest

public class CGLibDynamicProxyTest {
    public static void main(String[] args) {
        CGLibDynamicProxy cgLibDynamicProxy = new CGLibDynamicProxy();
        XiangYuServiceImpl xiangYuService = cgLibDynamicProxy.create(XiangYuServiceImpl.class);
        xiangYuService.warfare("guanglin");
    }
}

代理模式使用总结

  其实这三种代理模式都是想尽办法创建一个代理类调用目标类来实现代理。

  动态代理的应用:Spring AOP、RPC调用、Hibernate的懒加载 等待

三种方式的区别

代理模式代理类创建时机创建方式代理类行为原理调用处理器效率方面

性能方面

静态代理编译时期手动创建实现接口直接调用目标类高效 高效
JDK动态代理运行时基于JVM对字节码的操作实现实现接口反射调用InvocationHandler

JDK1.6/1.7上的对比 

  • 类的创建速度:JDK快于CGLIB
  • 执行速度:JDK慢于CGLIB,大概慢2倍的关系

JDK1.8上的对比

  • 类的创建速度:JDK快于CGLIB
  • 执行速度:JDK快于CGLIB,经过努力,JDK1.8作了性能上的优化,速度明显比1.7提升了很多 
CGLib动态代理运行时基于ASM对字节码的操作继承目标类super调用父类MethodInterceptor
  • 分享:
评论
还没有评论
    发表评论 说点什么