Spring知识点(五)代理模式

2020-08-26 17:21:16 浏览数 (1)

一、静态代理

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

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

代码语言:javascript复制
public interface UserService {
    boolean login() throws InterruptedException;
    boolean quit() throws InterruptedException;
}

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

代码语言:javascript复制
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)使用代理方法,来记录方法执行的时间。

代码语言:javascript复制
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)测试类。

代码语言:javascript复制
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创建为代理对象。

代码语言:javascript复制
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类生成代理对象,然后执行代理对象的方法,观察是否有增强方法和基础方法被执行。

代码语言:javascript复制
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接口的代理对象,然后执行代理对象的方法。观察输出结果。

代码语言:javascript复制
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()方法也被重新覆盖,既执行了代理方法,又执行了基础方法。

代码语言:javascript复制
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();
    }
}
代码语言:javascript复制
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基础上,对代理类做出改变。

代码语言:javascript复制
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)测试类。

代码语言:javascript复制
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接口。

代码语言:javascript复制
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中的区别

代码语言:javascript复制
@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动态代理。

0 人点赞