什么是代理模式
我们现在有这样一个场景。有一个简单的手机类,只能打电话
public class Phone {
public void call() {
Sy("打电话");
}
}
现在我们要改需求了,我们想要手机在打电话的时候可以开启录音
public class Phone {
public void call() {
Sy("开启了录音...");
Sy("打电话");
}
}
但是追求优雅的程序员是不会这么写的,修改源代码不就破坏了面向对象的开闭原则了么。那么我们创建一个子类去继承Phone
public class RecordPhone extends Phone {
public void call() {
Sy("开启了录音...");
Sy("打电话");
}
}
这样我们也不用修改源代码了,如果需要可录音的电话,直接使用RecordPhone就可以了。受到上面的启发,我们继续改进
抽象出一个Phone接口
public interface IPhone {
void call();
}
实现这个接口
public class Phone implements IPhone {
public void call() {
Sy("打电话");
}
}
我们再创建一个代理类也实现IPhone接口
public class PhoneProxy implements IPhone {
private IPhone phone;
public PhoneProxy(){
= new Phone();
}
@Override
public void call() {
Sy("开启了录音...");
();
}
}
我们直接调用这个代理类
public class Test {
public static void main(String[] args) {
PhoneProxy proxy = new PhoneProxy();
();
}
}
结果
开启了录音...
打电话
上面就是使用了代理模式,我们抽象出接口让程序更具备扩展性。
但有个问题如果手机的游戏方法也需要增加录音功能,我们需要在代理类中重写游戏方法增加录音功能,这个还好办,毕竟是同一个类。如果不是同一个类呢,如果微信类,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 {
Sy("JDK动态代理,监听开始...");
Object invoke = me(target, args);
Sy("JDK动态代理,监听结束...");
return invoke;
}
public Object getJdkProxy(Object targetObject) {
= targetObject;
//实例化
return Proxy.newProxyInstance().getClassLoader(),().getInterfaces(),this);
}
}
测试
@org.junit.Test
public void testJdkProxy() {
JdkProxy jdkProxy = new JdkProxy();
UserService userService = (UserService) jdkProxy.getJdkProxy(new UserServiceImpl());
u("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)的区别。当调用u("xxx"),实际上是进入了JdkProxy的invoke方法。
Object invoke = me(target, args);
就是执行的userService本身的addUser()方法,我们在me(target, args)前后进行方法增强。
jdkProxy.getJdkProxy()可以塞入其他的类,从而获得对应类的代理类。这样就避免了静态代理的弊端:每个类都要写死对应的代理类。
jdkProxy.getJdkProxy()方法返回代码
().getClassLoader(),().getInterfaces(),this);
参数中有().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 > 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 (loader, interfaces); //缓存
}
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
在缓存的get()方法中有段代码
Object subKey = Objec(key, parameter));
()实际是调用的ProxyClassFactory的apply方法。ProxyClassFactory是Proxy的内部类。apply方法就是生成代理类的方法。
生成代理类的时序图如下
CGLIB动态代理
代码
public class CglibProxy implements MethodInterceptor {
private Object target;
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Sy("CGLIB动态代理,监听开始...");
Object invoke = me(target, objects);
Sy("CGLIB动态代理,监听结束...");
return invoke;
}
public Object getCglibProxy(Object target) {
= target;
Enhancer enhancer = new Enhancer();
//指定父类
en());
en(this);
Object result = en();
return result;
}
}
测试
@org.junit.Test
public void testCglibProxy() {
CglibProxy cglibProxy = new CglibProxy();
UserService service = (UserService) cglibProxy.getCglibProxy(new UserServiceImpl());
("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表示要触发父类的方法对象;
”
最后,我们发现生成代理类的方法在En()中。
protected Object nextInstance(Object instance) {
EnhancerFactoryData data = (EnhancerFactoryData) instance;
if (classOnly) {
return da;
}
Class[] argumentTypes = ;
Object[] arguments = ;
if (argumentTypes == null) {
argumentTypes = Con;
arguments = null;
}
return da(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以后,两者都优化的很不错,不要再纠结使用哪种性能更好了。