动态代理
静态代理不适用的场景
当项目中有大量的类和方法需要同类型的代理时,静态代理就不太适用了,因为你不可能为每个类编写一个对应的代理类。
有时候无法提前获知要代理的类有哪些,例如第三方框架Spring,要为你类做代理,只能在运行时动态感知要代理的类。因此诞生了动态代理来解决这些问题,实现动态代理有JDK自带和Cglib两种方式,下面先分析JDK动态代理。
JDK动态代理
JDK动态代理主要用到Proxy类和InvocationHandler接口。Proxy用于生成代理类实例,方法如下:
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
ClassLoader为生成代理类的加载器
interfaces为此代理类要实现的接口
每一个代理类都要绑定一个InvocationHandler,调用代理类的方法,都会回调InvocationHandler.invoke。
这个方法的工作是:
- 在运行时动态生成一个新类ProxyClass作为代理类,其实现了interfaces接口,有一个InvocationHandler成员变量,体现了“动态”;
- 创建代理类对象返回;
下面简单介绍下生成的代理类的样子,以TestInterface和TestInterfaceImpl为例
public interface TestInterface{
void method1();
}
public class TestInterfaceImpl implements TestInterface{
void method1() {
System.out.printfln("method1 invoke");
}
}
调用Proxy.newProxyInstance(TestInterface.class.getClassLoader(),new Class[]{TestInterface.class},handler)生成的代理类大致如下:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
//public final的,继承Proxy,实现你传入的接口
public final class TestProxy extends Proxy
implements TestInterface
{
//private static 的Method属性,对应所有方法
private static Method m1;
private static Method m3;
private static Method m0;
private static Method m2;
//唯一的构造方法,需要一个InvocationHandler接口传入
public TestProxy(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
//代理的方法,回调传入的InvocationHandler的invoke方法
public final void method1()
throws
{
try
{
this.h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
static
{
try
{//每一个属性所代表的Method都是与上面加入代理方法列表时与固定类绑定的,这是class文件中的格式,方法要与固定的类绑定
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m3 = Class.forName("TestInterface").getMethod("method1", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
从上面的代码我们可以看到,代理类实现的method1方法回调了InvocationHandler的invoce,也印证了之前说的对代理类方法的调用都会委派给对应的InvocationHandler。
Each proxy instance has an associated invocation handler.
* When a method is invoked on a proxy instance, the method
* invocation is encoded and dispatched to the {@code invoke}
* method of its invocation handler.
实现InvocationHandler接口,来自定义具体的业务逻辑。InvocationHandler就一个方法
invoke(Object proxy, Method method, Object[] args)
其中proxy是代理类实例,
method是调用方法,
args是调用参数。
现在假设我们的功能是在每个类方法调用之前打印时间戳日志,那么定义自己的TimestampLogInvocationHandler:
class TimestampLogInvocationHandler implements InvocationHandler {
private Object target;//被代理对象
TimestampLogInvocationHandler(Object target) {
this.target = target;
}
@Override
Object invoke(Object proxy, Method method, Object[] args) {
logger.info("当前时间:"+new Date());
return method.invoke(target,args);
}
}
//测试一下:
class MainTest {
public static void main(String[] args) {
TestInterface interface = new TestInterfaceImpl();//被代理对象
TimestampInvocationHandler tih = new TimestampInvocationHandler(interface);//调用处理器
//代理类实例
TestInterface myProxy = (TestInterface)Proxy.newProxyInstance(TestInterface.class.getClassLoader(),new Class[]{TestInterface.class},tih);
//调用代理类方法
myProxy.method1();
}
}
将会输出:
当前时间:1020021212
method1
可以看到调用都被委派到了InvocationHandler上。
之后若有其他类也需要打印时间戳,可复用此InvocationHandler创建自己的代理类实例,无需再次开发。以上就是JDK的动态代理,下面看下Cglib动态代理。
Cgllib动态代理
JDK动态代理是基于接口的,不适用于类,同时效率也不高;因此诞生了CGLib框架,它底层采用asm技术实现动态代理,并且可以高效地为类做代理。Cglib的原理是使用Enhancer设置被代理类型,设置回调方法MethodInterceptor(类似于InvocationHandler),以继承的方式创建动态代理类。Enhancer的常用方法:
void setSuperclass(java.lang.Class superclass)//设置被代理的类型
void setCallback(Callback callback) //设置回调接口
Object create()//创建代理实例,该实例的类型为superclass的子类
在setCallback中定义了回调接口,调用被代理的方法都会委派到回调接口。最通用的回调接口是MethodInterceptor,它与InvocationHandler类似只有一个方法:
/**
* All generated proxied methods call this method instead of the original method.
* The original method may either be invoked by normal reflection using the Method object,
* or by using the MethodProxy (faster).
* @param obj "this", the enhanced object
* @param method intercepted Method
* @param args argument array; primitive types are wrapped
* @param proxy used to invoke super (non-intercepted method); may be called
* as many times as needed
* @throws Throwable any exception may be thrown; if so, super method will not be invoked
* @return any value compatible with the signature of the proxied method. Method returning void will ignore this value.
* @see MethodProxy
*/
public Object intercept(Object object, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable
object是代理实例,method是被拦截的方法,可以通过proxy.invokeSuper(obj, args)调用object的父类的相同方法。
下面看一个例子:
class Cglib{//目标类
public void cglib(){
System.out.println("CGLIB");
}
}
class HelloProxy implements MethodInterceptor{//回调方法
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throwsThrowable {
System.out.println("Hello");
Object object = proxy.invokeSuper(obj, args);
System.out.println("Powerful!");
return object;
}
}
public class TestMain {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Cglib.class);
enhancer.setCallback(new HelloProxy());
Cglib cglibProxy = (Cglib)enhancer.create();
cglibProxy.cglib();
}
}
输出内容:
Hello
CGLIB
Powerful!
你可能会说,标准的代理模式中,代理类不是要持有被代理类的引用吗,乍看起来CGLib动态代理没有目标类的引用,其实仔细想一下,CGLib生成的代理类是继承自目标类的,在创建子类对象之前一定要先创建父类对象,所以在代理对象中一定会有目标对象。