面试必问的动态代理,我们来看看它们的源码

 

什么是代理模式

我们现在有这样一个场景。有一个简单的手机类,只能打电话

public class Phone {
  public void call() {
   System.out.println("打电话");
  }
}

现在我们要改需求了,我们想要手机在打电话的时候可以开启录音

public class Phone {
  public void call() {
    System.out.println("开启了录音...");
   System.out.println("打电话");
  }
}

但是追求优雅的程序员是不会这么写的,修改源代码不就破坏了面向对象的开闭原则了么。那么我们创建一个子类去继承Phone

public class RecordPhone extends Phone {
  public void call() {
    System.out.println("开启了录音...");
   System.out.println("打电话");
  }
}

这样我们也不用修改源代码了,如果需要可录音的电话,直接使用RecordPhone就可以了。受到上面的启发,我们继续改进

抽象出一个Phone接口

public interface IPhone {
    void call();
}

实现这个接口

public class Phone implements IPhone {
  public void call() {
   System.out.println("打电话");
  }
}

我们再创建一个代理类也实现IPhone接口

public class PhoneProxy implements IPhone {
    private IPhone phone;
    public PhoneProxy(){
        this.phone = new Phone();
    }
    @Override
    public void call() {
        System.out.println("开启了录音...");
        phone.call();
    }
}

我们直接调用这个代理类

public class Test {
    public static void main(String[] args) {
        PhoneProxy proxy = new PhoneProxy();
        proxy.call();
    }
}

结果

开启了录音...
打电话

上面就是使用了代理模式,我们抽象出接口让程序更具备扩展性。

但有个问题如果手机的游戏方法也需要增加录音功能,我们需要在代理类中重写游戏方法增加录音功能,这个还好办,毕竟是同一个类。如果不是同一个类呢,如果微信类,QQ类也需要增加录音功能,那岂不是还要写微信代理类,QQ代理类么。这样也太麻烦了。

因此我们需要一个动态的代理类,这个代理类并不是一开始就创建的,而是在调用的时候创建。

Java中的动态代理有动态代理和动态代理,这两种动态代理在Spring的代理模式中有用到。

Spring中这两种动态代理的区别为:

(1)当目标对象实现了接口,默认使用JDK动态代理,也可以强制使用CGLIB动态代理。

(2)当目标对象没有实现接口,必须使用CGLIB动态代理。

JDK动态代理

代码

public class JdkProxy  implements InvocationHandler {
    //需要代理的目标对象
    private Object target;
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("JDK动态代理,监听开始...");
        Object invoke = method.invoke(target, args);
        System.out.println("JDK动态代理,监听结束...");
        return invoke;
    }

    public Object getJdkProxy(Object targetObject) {
        this.target = targetObject;
        //实例化
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(),this);
    }
}

测试

@org.junit.Test
public void testJdkProxy() {
    JdkProxy jdkProxy = new JdkProxy();
    UserService userService = (UserService) jdkProxy.getJdkProxy(new UserServiceImpl());

    userService.addUser("lvshen","123456");

}
JDK动态代理,监听开始...
调用addUser()...
参数为:name[lvshen],password[123456]
JDK动态代理,监听结束...

核心代码在这里

UserService userService = (UserService) jdkProxy.getJdkProxy(new UserServiceImpl());

这里的userService实际上不是原本的userService,而是一个代理的userService。我们debug调试

如上图userService是代理方式生成的,userService1是自己new出来的。可以看到(1)和(2)的区别。当调用userService.addUser("xxx"),实际上是进入了JdkProxyinvoke方法。

Object invoke = method.invoke(target, args);

就是执行的userService本身的addUser()方法,我们在method.invoke(target, args)前后进行方法增强。

jdkProxy.getJdkProxy()可以塞入其他的类,从而获得对应类的代理类。这样就避免了静态代理的弊端:每个类都要写死对应的代理类。

jdkProxy.getJdkProxy()方法返回代码

publiProxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(),this);

参数中有targetObject.getClass().getInterfaces(),返回的代理对象需要获取到目标对象的接口,所以说JDK动态代理目标对象需要有接口才能生成代理对象。

继续跟代码newProxyInstance()

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h){
   ...
    Class<?> cl = getProxyClass0(loader, intfs);   //通过接口获取代理类
   ...
                                          
}

我们来看看getProxyClass0()方法,代理类是从缓存中获取的

private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);      //缓存
    }

proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

在缓存的get()方法中有段代码

 Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));

subKeyFactory.apply()实际是调用的ProxyClassFactoryapply方法。ProxyClassFactoryProxy的内部类。apply方法就是生成代理类的方法。

生成代理类的时序图如下

CGLIB动态代理

代码

public class CglibProxy implements MethodInterceptor {
    private Object target;
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("CGLIB动态代理,监听开始...");
        Object invoke = method.invoke(target, objects);
        System.out.println("CGLIB动态代理,监听结束...");
        return invoke;
    }

    public Object getCglibProxy(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        //指定父类
        enhancer.setSuperclass(target.getClass());

        enhancer.setCallback(this);
        Object result = enhancer.create();
        return result;
    }
}

测试

@org.junit.Test
    public void testCglibProxy() {
        CglibProxy cglibProxy = new CglibProxy();
        UserService service = (UserService) cglibProxy.getCglibProxy(new UserServiceImpl());
        service.addUser("zhouzhou","654321");
    }
CGLIB动态代理,监听开始...
调用addUser()...
参数为:name[zhouzhou],password[654321]
CGLIB动态代理,监听结束..

同样我们来看看这两种不同创建方式(通过代理创建,自己创建)。图上图,Enhancer类似JDK动态代理的Proxy

CGLIB动态代理需要实现MethodInterceptor接口。增强的方法就是写在intercept()中,这个方法有4个参数。

1)Object o表示增强的对象,即实现这个接口类的一个对象;

2)Method method表示要被拦截的方法;

3)Object[] objects表示要被拦截方法的参数;

4)MethodProxy methodProxy表示要触发父类的方法对象;

最后,我们发现生成代理类的方法在Enhancer.nextInstance()中。

protected Object nextInstance(Object instance) {
   EnhancerFactoryData data = (EnhancerFactoryData) instance;

   if (classOnly) {
      return data.generatedClass;
   }

   Class[] argumentTypes = this.argumentTypes;
   Object[] arguments = this.arguments;
   if (argumentTypes == null) {
      argumentTypes = Constants.EMPTY_CLASS_ARRAY;
      arguments = null;
   }
   return data.newInstance(argumentTypes, arguments, callbacks);
}

调用过程如下

生成代理类的时序图如下

最后总结,如果目标对象存在接口,可以通过JDK和CGLIB生成代理对象;如果目标对象没有接口,则只能通过CGLIB生成代理对象。

JDK生成的代理对象与目标对象平级;CGLIB生成的代理对象继承目标对象,并且使用CGLIB生成代理对象时,目标类不能是final修饰的。

关于性能,网上有相关的结论,在JDK1.8以前:

1、CGLIB所创建的动态代理对象在实际运行时候的性能要比JDK动态代理高不少,有研究表明,大概要高10倍;

2、但是CGLIB在创建对象的时候所花费的时间却比JDK动态代理要多很多,有研究表明,大概有8倍的差距;

3、因此,对于singleton的代理对象或者具有实例池的代理,因为无需频繁的创建代理对象,所以比较适合采用CGLIB动态代理,反正,则比较适用JDK动态代理。

然而,JDK1.8以后,两者都优化的很不错,不要再纠结使用哪种性能更好了。

 

往期推荐

 

 

 

 

 

扫码二维码,获取更多精彩。或微信搜Lvshen_9,可后台回复获取资料

1.回复"java" 获取java电子书;
2.回复"python"获取python电子书;
3.回复"算法"获取算法电子书;
4.回复"大数据"获取大数据电子书;
5.回复"spring"获取SpringBoot的学习视频。
6.回复"面试"获取一线大厂面试资料
7.回复"进阶之路"获取Java进阶之路的思维导图
8.回复"手册"获取阿里巴巴Java开发手册(嵩山终极版)
9.回复"总结"获取Java后端面试经验总结PDF版
10.回复"Redis"获取Redis命令手册,和Redis专项面试习题(PDF)


另:点击【我的福利】有更多惊喜哦。
 

 

文章已于

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值