专栏首页码云大作战Spring知识点(五)代理模式

Spring知识点(五)代理模式

一、静态代理

· 一个简单的静态代理的示例

(1)定义一个用户接口,接口上有用户登录和用户退出方法。

public interface UserService {
    boolean login() throws InterruptedException;
    boolean quit() throws InterruptedException;
}

(2)用户接口的实现类。

public class UserServiceImpl implements UserService {

    @Override
    public boolean login() throws InterruptedException {
        System.out.println("用户登录开始======");
        Thread.sleep(1000);
        System.out.println("用户登录结束======");
        return true;
    }

    @Override
    public boolean quit() throws InterruptedException {
        System.out.println("用户退出开始======");
        Thread.sleep(500);
        System.out.println("用户退出结束======");
        return true;
    }
}

(3)使用代理方法,来记录方法执行的时间。

public class UserProxyService implements UserService {

    private UserService userService;
    /**
     * 需要保持原来类的引用,来执行原来类的方法
     */
    public UserProxyService(UserService userService) {
        this.userService = userService;
    }
    
    @Override
    public boolean login() throws InterruptedException {
        long startTime = System.currentTimeMillis();
        userService.login();
        long endTime = System.currentTimeMillis();
        System.out.println("登录接口执行时间:" + (endTime - startTime) + "毫秒");
        return true;
    }

    @Override
    public boolean quit() throws InterruptedException {
        long startTime = System.currentTimeMillis();
        userService.quit();
        long endTime = System.currentTimeMillis();
        System.out.println("退出接口执行时间:" + (endTime - startTime) + "毫秒");
        return true;
    }
}

(4)测试类。

public static void main(String[] args) throws InterruptedException {
    UserProxyService userProxyService = new UserProxyService(new UserServiceImpl());
    userProxyService.login();
    userProxyService.quit();
}

(5)输出结果。

· 静态代理结论

使用代理模式的目的是为了将原来类生成一个代理类,由代理类来执行原来类的一些增强方法,但是也不影响原来类中方法的执行。

上述例子中,原来类被代理后变成了一个代理类UserProxyService,执行了类的增强方法即打印方法的执行时间,也不影响原来类中的登录和退出方法的执行。

好处在于,将这些打印方法执行时间的这些与业务代码无关的方法抽离出来达到解耦的目的。

而使用静态代理会发现,需要为每一个类都声明一个代理类,当需要被代理的类过多时,会发现静态代理类也会非常的庞大。因此我们有了静态代理的思想,需要进一步的使用动态代理。

二、CGLIB动态代理

· CGLIB动态代理-代理类示例

(1)在原来的userSerivce基础上,对代理类作出改变。

声明CGLIBProxy作为基础类的代理类,并实现了CGLIB的MethodInterceptor拦截方法。

在拦截方法(intercept)中,进行代理方法的增强,即打印基础方法的运行时间。

在代理类中,还写了一个getProxy方法,将基础类通过二进制增强类Enhancer创建为代理对象。

public class CGLIBProxy implements MethodInterceptor {

    /**
     * 获取真正的代理类
     */
    public <T> T getProxy(Class c) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(c);
        enhancer.setCallback(this);
        //代理类创建完毕
        return (T) enhancer.create();
    }

    /**
     * 使用了动态代理,即可以代理登录方法又可以代理退出方法,减少代理类
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object returnObject = methodProxy.invokeSuper(o, objects);
        long endTime = System.currentTimeMillis();
        System.out.println("方法名为:" + method.getName());
        System.out.println("方法执行时间" + (endTime - startTime) + "毫秒");
        return returnObject;
    }
}

(2)测试类。

先根据getProxy方法将UserServiceImpl类生成代理对象,然后执行代理对象的方法,观察是否有增强方法和基础方法被执行。

public static void main(String[] args) throws InterruptedException {
    CGLIBProxy cglibProxy = new CGLIBProxy();
    UserServiceImpl userService = cglibProxy.getProxy(UserServiceImpl.class);
    userService.login();
    userService.quit();
}

(3)输出结果。

输出结果如上图所示,即执行了基础方法,又执行了代理方法即增强方法。

并且此时的userSerivceImpl对象,变成了代理对象。

· CGLIB动态代理-代理接口示例

其余代码保持不变,测试类进行修改,获取代理对象为UserService接口的代理对象,然后执行代理对象的方法。观察输出结果。

public static void main(String[] args) throws InterruptedException {
    CGLIBProxy cglibProxy = new CGLIBProxy();
    UserService userService = cglibProxy.getProxy(UserService.class);
    userService.login();
    userService.quit();
}

上述结果展示,使用CGLIB动态代理来代理接口,执行invoke方法会报错。

· CGLIB动态代理类

将UserServiceImpl的代理类.class文件打印出来。可以看出实际上代理类是UserServiceImpl的子类。

public class UserServiceImpl

EnhancerByCGLIB

62d9c206

extends UserServiceImpl implements Factory {

login和quit()方法也被重新覆盖,既执行了代理方法,又执行了基础方法。

public final boolean login() throws InterruptedException {
    MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
    if (var10000 == null) {
        CGLIB$BIND_CALLBACKS(this);
        var10000 = this.CGLIB$CALLBACK_0;
    }

    if (var10000 != null) {
        Object var1 = var10000.intercept(this, CGLIB$login$0$Method, CGLIB$emptyArgs, CGLIB$login$0$Proxy);
        return var1 == null ? false : (Boolean)var1;
    } else {
        return super.login();
    }
}
public final boolean quit() throws InterruptedException {
    MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
    if (var10000 == null) {
        CGLIB$BIND_CALLBACKS(this);
        var10000 = this.CGLIB$CALLBACK_0;
    }

    if (var10000 != null) {
        Object var1 = var10000.intercept(this, CGLIB$quit$1$Method, CGLIB$emptyArgs, CGLIB$quit$1$Proxy);
        return var1 == null ? false : (Boolean)var1;
    } else {
        return super.quit();
    }
}

· CGLIB动态代理的原理是什么?

底层通过字节码动态生成代理类,代理类为被代理类的子类,并且代理类需要重写原来被代理类中不是被final修饰的方法。在动态代理实现中,会拦截所有需要被代理的方法,来执行代理方法和基础的方法。

· CGLIB动态代理总结

CGLIB动态代理可以代理接口的实现类,也可以代理普通的类(这里没做示范)作为代理对象。他不像JDK动态代理,必须代理实现了接口的类。

性能方面,CGLIB是使用字节码的技术来生代理类,因此比起JDK代理中的反射,性能更高。

spring的动态代理底层实现的其中一种就是CGLIB。

缺点:查看生成的代理类的class文件可以看出,代理类继承自被代理的普通类并且会重新需要被代理的方法,因此如果需要被代理的普通类如果无法被继承或方法无法被重写也就无法被代理,比如被final修饰的类或被final修饰的代理方法。

三、JDK动态代理

· JDK动态代理示例

(1)在原来的UserService和UserServiceImpl基础上,对代理类做出改变。

public class JDKProxy implements InvocationHandler {

    /**
     * 目标类对象
     */
    private Object target;
    public JDKProxy(Object target) {
        this.target = target;
    }

    public <T> T getProxy(Class cl) {
        return (T) Proxy.newProxyInstance
                (cl.getClassLoader(), new Class<?>[]{cl}, this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("执行方法:" + method.getName());
        long startTime = System.currentTimeMillis();
        Object invoke = method.invoke(target, args);
        long endTime = System.currentTimeMillis();
        System.out.println("执行时间:" + (endTime - startTime) + "毫秒");
        return invoke;
    }
}

声明JDKProxy作为基础类的代理类,并实现了InvocationHandler接口,在invoke中执行基础方法和代理的增强方法。

(2)测试类。

public static void main(String[] args) throws InterruptedException {

    JDKProxy jdkProxy = new JDKProxy(new UserServiceImpl());
    UserService userService = jdkProxy.getProxy(UserService.class);
    userService.login();
    userService.quit();
}

(3)运行结果。

并且此时的UserService对象变为了JDK代理对象。

· JDK动态代理类Class文件

CGLIB动态代理可以打印对应的class文件,JDK动态代理也是可以打印对应的class文,跟到代理类创建的方法底层后发现被写到了对应的磁盘路径下。

下图为UserService生成的代理对象,并且代理对象实现了UserService接口。

public final class $Proxy0 extends Proxy implements UserService {

下图为代理对象中的login和quit方法。

· JDK动态代理原理总结

JDK动态代理是利用了反射生成了一个代理类,代理类实现了InvokeHandler接口,使用invoke方法来执行代理方法和基础方法。

根据生成的代理类看出,JDK动态代理只能处理接口的实现类。

在spring aop中默认就使用了JDK动态代理。

· 性能方面(参考了网上的结论)

jdk8之前的动态代理,在调用次数少的情况下,JDK反射机制的动态代理性能高于CGLIB二进制技术的动态代理,在调用次数大的情况下,JDK动态代理的性能较低于CGLIB动态代理。

JDK8开始,JDK动态代理性能高于CGLIB动态代理。

四、JDK动态代理和CGLIB动态代理的区别

JDK动态代理,生成代理类的机制是使用了反射机制,并且代理类实现自被代理类,因此只能代理实现了接口的类。在spring aop中默认使用的是JDK动态代理。

CGLIB动态代理,生成代理类的机制是基于asm的字节码的机制生成的代理类,并且代理类是被代理类的子类,重写了被代理类的方法。虽然可以代理普通类和实现了接口的类,但是如果类被final修饰则不能进行代理,并且代理类生成时只能重写没被final修饰的方法。

· 在spring aop中的区别

@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
   if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
      Class<?> targetClass = config.getTargetClass();
      if (targetClass == null) {
         throw new AopConfigException("TargetSource cannot determine target class: " +
               "Either an interface or a target is required for proxy creation.");
      }
      if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
         return new JdkDynamicAopProxy(config);
      }
      return new ObjenesisCglibAopProxy(config);
   }
   else {
      return new JdkDynamicAopProxy(config);
   }
}

上图所示的代码其实就是spring aop中选择动态代理的核心方法。主要思想其实为,如果代理类为接口,就使用JDK动态代理。如果不为接口,就使用CGLIB动态代理。并且默认是使用JDK动态代理。

本文分享自微信公众号 - 码云大作战(gh_9b06dbcb85f3),作者:Y

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-08-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java虚拟机 - 超级详细的类加载说明

    java文件在编译时会被JVM编译成.class字节码文件,这篇主要讲解的是JVM如何将.class文件加载的加载过程。

    虞大大
  • HashMap源码分析 - JDK7和JDK8有什么区别

    前几天的文章中对JDK8的HashMap源码进行了分析,这篇文章是基于JDK8的基础上来分析下与JDK7的HashMap的区别。以下的源码主要为JDK7...

    虞大大
  • tomcat类加载机制了解一下

    tomcat类加载器设计结构如上图所示,上面三个Bootstrap加载器、Ext加载器、Application加载器是JVM加载器,下半部分的加载器才是...

    虞大大
  • 设计模式--Proxy模式

    Subject: 可以是接口,也可以是抽象类 Proxy: 内部含有对真实对象RealSubject的引用,负责对真实主题角色的调用,并在真实主题角色处理...

    河岸飞流
  • 所有和Java中代理有关的知识点都在这了。

    对于每一个Java开发来说,代理这个词或多或少都会听说过。你可能听到过的有代理模式、动态代理、反向代理等。那么,到底什么是代理,这么多代理又有什么区别呢。本文就...

    java思维导图
  • 2.3.1 理解动态代理 -《SSM深入解析与项目实战》

    Spring中AOP的拦截功能就是使用Java中的动态代理实现的。也就是在被代理类(方法)的基础上增加切面逻辑,生成代理类(方法)。切面的逻辑可以在目标类函数执...

    谙忆
  • 爬虫代理哪家强?十大付费代理详细对比评测出炉!

    前言 随着大数据时代的到来,爬虫已经成了获取数据的必不可少的方式,做过爬虫的想必都深有体会,爬取的时候莫名其妙 IP 就被网站封掉了,毕竟各大网站也不想自己的...

    崔庆才
  • 聊聊Java动态代理(上)

    前言 在之前的文章《聊聊设计模式之代理模式》中,笔者为大家介绍了代理模式,在这里简单回顾一下。代理模式的作用是提供一个代理来控制对一个对象的访问,因此我们可以...

    黄泽杰
  • 付费代理的使用

    崔庆才
  • 【Java入门提高篇】Day12 Java代理——Cglib动态代理

      今天来介绍另一种更为强大的代理——Cglib动态代理。   什么是Cglib动态代理?   我们先回顾一下上一篇的jdk动态代理,jdk动态代理是通过接口来...

    弗兰克的猫

扫码关注云+社区

领取腾讯云代金券