什么是代理
代理是很常见的一个词,可以作为功能性描述,比如代理服务器,代理对象,甚至代理(proxy)本身也是一个名词。在大部分情况,我所理解的代理都是由于它本身所代表的代理特性,或者说是符合了代理的模式。
代理模式简单来说,即不暴露核心的情况的使用某个功能,这里的不暴露,有可能是不想暴露(AOP之我要加料),也有可能是无法暴露(RMI)。代理类负责为委托类处理消息。
静态代理
代理中最简单的就是静态代理。看看下面这段例子,有一个服务类(委托类)A,我们不想它被其他代码直接调用,那么我们提供一个代理类B,它提供了所有A的服务,只是它不是A。(这有什么意义?后面就知道了)。
这是我们的服务类,它只有一个很基本的功能,但实际上在使用中,它应该是那些真正实现了你的关键逻辑的地方。
package proxy.staticproxy;
/**
* Created by xiang on 15/10/9.
*/
public class HideService {
public String getSecret() {
return "Secret";
}
}
这是代理类,它继承了服务类,这样子它可以被转型为它的父类,使用者仍然能正常使用。
package proxy.staticproxy;
/**
* Created by xiang on 15/10/9.
*/
public class HideServiceProxy extends HideService{
private HideService delegate;
public HideServiceProxy(HideService delegate) {
this.delegate = delegate;
}
@Override
public String getSecret() {
return "Proxyed " + this.delegate.getSecret();
}
public static void main(String[] args) {
//Pretend this is the container that compose beans
HideService service = new HideService();
HideService proxyBean = new HideServiceProxy(service);
//And the proxy bean will be given when requiring the service bean.
System.out.println(proxyBean.getSecret());
}
}
Output
Proxyed Secret
细心的人会发现,用继承的话,使用者仍然能发现它不是他想要的类,因为instanceOf可以很容易的找出来它的真正class对象是什么。不用担心,那只是我偷懒不想多写一个接口,实际上静态代理更推荐接口,在DIP实践广泛的今天,接口才是主流。当使用接口时,静态代理类的代码并不会有太多改变,只是从继承变成了实现接口,内部的delegate变量的类型同时也变成接口类。而使用者在使用接口进行调用时,代理类同样也是接口的实现,没有任何问题。
为什么这种形式叫静态代理或者说什么是静态?静态是指运行前就确定了,代理在编译期就已经生成。这个代理就是这个样子,代码都已经写好。这样当然很好,代码清晰可读,一看就知道这里有个代理。缺点也很明显,首先,写它占用了时间,如果需要实现代理的类很多,一个个是写不过来的。其次,如果更改了接口,除了更改服务实现类以外,还得去更新这些代理类,维护上容易出纰漏。
动态代理
想要不自己费事写代理类,就需要动态代理。动态代理怎么动态呢?第一,代理类的创建是动态的,不需要手工的定义一个代理类;第二,对于代理方法的处理也是动态的,不需要为被代理类的每个方法都做一个代理实现。
在动态代理这里,针对接口代理和针对实体类的代理就有点不一样了,背后所需要的技术不一样导致了使用上的不一样。我们先来看JDK支持的动态代理,它适用于接口编程模式。也就是说被代理的类必须是实现了某个接口,代理类将基于此接口进行动态创建。
JDK Dynamic Proxy
还是先上例子
这是接口
public interface TestService {
public void say();
}
然后是实现类
public class TestServiceImpl implements TestService {
private String name;
public TestServiceImpl(String name) {
this.name = name;
}
public void say() {
System.out.println(name);
}
}
最后是我们的动态代理
public class JDKDynamicProxy implements InvocationHandler{
private Object delegate;
public JDKDynamicProxy(Object delegate) {
this.delegate = delegate;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("In proxy");
Object result = method.invoke(delegate, args);
System.out.println("Exit proxy");
return result;
}
public Object getProxy (ClassLoader cl, Class clasz) {
return Proxy.newProxyInstance(cl, new Class[]{clasz}, this);
}
public static void main(String[] args) {
TestService ts = new TestServiceImpl("service bean instance");
if (ts instanceof TestService) {
System.out.println("Yep, i'm the one you need");
}
TestService proxy = (TestService)new JDKDynamicProxy(ts).getProxy(TestService.class.getClassLoader(), TestService.class);
proxy.say();
if (proxy instanceof TestService) {
System.out.println("Proxy is also seen as the bean");
}
}
}
Output
Yep, i'm the one you need
In proxy
service bean instance
Exit proxy
Proxy is also seen as the bean
我们的代理类并没有去实现那个接口,相反,它根本连哪个接口要代理都不关心,它只关心一点,就是当我拦截了方法请求,即进入代理时,该做点啥?这就是invoke方法关心的,也是我们要实现的。而我们除了这点,还必须关心另一点,就是对于动态代理类,生成代理实例的时候,归根结底还是需要一个委托对象的,不然它又不是神,怎么知道到底跑哪个业务逻辑呢?在我们这个例子里,是通过构造函数扔进去的,即生成InvocationHandler实例时,需要提供一个委托对象作为参数,基于这个handler,我们通过newProxyInstance方法构造出一个动态代理对象。
我们该好奇的是,newProxyInstance做了啥事?拿出来的到底是什么东西,如何实现代理的?我们先进这个方法看看,关键的应该就是下面三行
//获取动态代理类(一个新的类?)
Class<?> cl = getProxyClass0(loader, intfs);
//获取该类的构造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
//通过构造器生成新的代理对象并返回,构造器的参数就是invocationhandler实例
return cons.newInstance(new Object[]{h});
再看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);
}
注释写的非常清楚,所有创建过的动态代理class都会存在一个cache里面,如果有的话,凭借特有的loader和接口就可以找到,找不到的话就通过ProxyClassFactory创建一个。
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
String proxyName = proxyPkg + proxyClassNamePrefix + num;
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
generateProxyClass看不到源码,不过可以分析一个创建出来的动态类有什么特点。下面就是反编译了上面使用的动态代理类后得到的代码。
public final class $Proxy100 extends Proxy implements TestService {
//四个method类型的静态变量,用来做invocation时候的参数
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
//构造函数接受一个invocation handler,传给父类的构造器。在Proxy类里将这个值赋予h变量
public $Proxy100(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
//这就是接口定义方法的实现,可以看到将调用转到了传进来的invocation handler的invoke方法上
public final void say() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("proxy.dynamicproxy.TestService").getMethod("say", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
从上面的代码,可以创建出来的代理类有几个特点
- 基于Proxy这个父类,代理类的名字是$ProxyN,N为数字。
- 对所有的接口类方法调用,都转交给invocation handler处理,所被调用的method对象和参数也一并传入。
- invocation handler里通过invoke方法和反射机制,实现了添加额外统一处理逻辑和对原实现方法的调用。
CGLIB
当委托类没有实现接口时,就无法使用JDK动态代理,这时候我们想想前面静态代理那个例子,就会发现其实子类也是一个可行的方案。当然,我们了解前面JDK动态代理的原理是通过反射,那么动态的继承了委托类的代理类该怎么创建?我们使用CGLIB这个工具。
public class ServiceWithDefaultConstructor {
public void say() {
System.out.println("In real service");
}
}
在JDK动态代理中有invocation handler,在CGLIB中也有类似的东西,就是MethodInterceptor。在新创建的代理类中,对所有方法的调用,也会交给它来处理。
private static class MethodInterceptorImpl implements MethodInterceptor {
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("In proxy");
Object result = methodProxy.invokeSuper(proxy, args);
System.out.println("In proxy");
return result;
}
}
做好了代理逻辑,就可以去调用生成代理类了。
public static void proxyServiceWithDefaultConstructor() {
Enhancer en = new Enhancer();
en.setSuperclass(ServiceWithDefaultConstructor.class);
en.setCallback(new MethodInterceptorImpl());
ServiceWithDefaultConstructor test = (ServiceWithDefaultConstructor)en.create();
test.say();
}
输出结果如下:
In proxy
In real service
In proxy
是不是很简单?代理对象都要从enhancer里面获取,所以我们首先要告诉enhancer,它这个是要负责创建哪个委托类的代理,然后把MethodInterceptor对象设置成callback,这样创建出来的代理类上,方法调用的实现都会去找这个MethodInterceptor。最后让enhancer给你一个实例,就可以用了。
这里也与JDK动态代理有个明显不同的地方,就是关于委托类的实例。在JDK动态代理中,我们需要在Invocation handler里面保留一个委托类对象,因此在方法调用进来后,除了代理的逻辑,还能够调用到真正的服务实现。而在CGLIB中,我们发现MethodInterceptor根本没关心这事。为什么呢?因为在JDK动态代理时,一切都是基于接口去代理的,它无法知道你到底是要代理哪个实现了接口的委托类,所以必须由调用者去传入委托对象。而CGLIB则是基于继承,它要代理的一定就是要继承的那个类的实例,所以如果CGLIB能够直接创建一个委托类的对象,那就一切完美了。在这个例子里是不是这样?我们继续验证下去……
没有无参构造器的类 是的,如果没用无参构造器,我们也不提供参数,怎么办?
public class ServiceWithoutDefaultConstructor {
private String name;
public ServiceWithoutDefaultConstructor(String name) {
this.name = name;
}
public void say() {
System.out.println("In real service");
}
public void getName() {
System.out.println(this.name);
}
}
然后试一下CGLIB来创建
public static void proxyServiceWithoutDefaultConstructor() {
Enhancer en = new Enhancer();
en.setSuperclass(ServiceWithoutDefaultConstructor.class);
en.setCallback(new MethodInterceptorImpl());
ServiceWithoutDefaultConstructor test = (ServiceWithoutDefaultConstructor)en.create();
test.say();
}
Error:
Exception in thread "main" java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
果然失败了,错误信息告诉我们是因为父类需要参数来构造,而我们没提供任何参数。那么,如何提供参数给CGLIB呢?在用 enhancer 创建代理对象的时候,提供了参数的入口,只要稍微改一下就可以了。
public static void proxyServiceWithoutDefaultConstructor() {
Enhancer en = new Enhancer();
en.setSuperclass(ServiceWithoutDefaultConstructor.class);
en.setCallback(new MethodInterceptorImpl());
ServiceWithoutDefaultConstructor test =
(ServiceWithoutDefaultConstructor)en.create(new Class[] {String.class}, new Object[] {"constructor parameteres"});
test.say();
}
总结
- 静态代理更偏向设计模式的演练,在实际使用中会显得繁琐不通用。
- 动态代理有很强的实用性,代码层面上可以集中在代理逻辑上的编写。
- jdk 动态代理适用于接口,cglib 适用于实体类。使用上 cglib 更直接,但是 jdk 动态代理与Spring 等框架鼓励的通过接口解耦的编程方式也合作的很好。