代理模式
通过代理控制对对象的访问,可以详细控制访问某个(某类)对象的方法,在调用这个方法之前做前置处理,调用这个方法后做后置处理。
与装饰者模式的简单区别
两者都是对类的方法(能力进行拓展),但是:
- 装饰者模式强调的还是本身功能的增强。仍然和被装饰者做相同的事情,职责上并没有进行延伸。
- 代理模式强调的是代理和“中介”的能力。在代理中去做一些与本身业务不相关的事情而不对对象本身的功能做增强或改造,也可以对被代理对象进行控制或包装,避免暴露。
JDK动态代理
接口:
public interface Animal
{
void run();
void fly();
}
一个会飞的狗实现:
public class Dog implements Animal
{
@Override
void run(){
System.out.println("Dog run");
}
@Override
void fly(){
System.out.println("Dog fly");
}
}
为了记录狗的日常动作,需要一个记录仪。这项工作,狗自己是完成不了的。但是要理解,这个记录仪并不是一个代理类,因为它并不能进行run或者fly,无法提供服务,只能算是一个包装类。这个包装类是一个InvocationHandler。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DogProxy implements InvocationHandler
{
// 目标类,也就是被代理对象。包装类是持有这个被代理对象的
private Object target;
public void setTarget(Object target)
{
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
// 这里可以做控制 做记录等
System.out.println("开始" + method.getDeclaringClass().getName() + method.getName());
Object result = method.invoke(target, args);
System.out.println("结束" + method.getDeclaringClass().getName() + method.getName());
return result;
}
// 生成代理类
public Object CreatProxyedObj()
{
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
}
真正的代理类是CreatProxyedObj()返回的对象。它的意思就是,使用相同的classLoader,通过被包装类实现的相同的接口,来创建一个实现对象,而这个对象的方法invoke通过this来触发,this就是这个InvocationHandler。
可以看到,一般代理套路:
step1: new 被代理的对象A。
step2: new 一个InvocationHandler B,并持有被代理对象。重写invoke方法,本身的方法执行需要交给被代理对象去执行,并在invoke中添加自己想要的操作。
step3: 创建真正的代理对象 C,并转换为所实现的接口类型。Proxy.newProxyInstance()方法所生成的对象已经实现了目标接口。生成的类为 $Proxy+数字 的“新的类型”。
实际的内涵是这样的:C代理了InvocationHandler B,InvocationHandler B代理了我们的类A,两级代理。其实Proxy.newProxyInstance()的作用,就是把一个实际的内涵是这样的:C代理了InvocationHandler,“变成”一个实现A所实现的interface的新的对象,便于使用接口通用的方式进行调用。这里ABC都是一个新的对象了。
在最终的类C中,对于方法的调用,是一次调用的分发,实质都是通过InvocationHandler的invoke来调用的。
从原理可以看出,JDK动态代理是“对象”的代理。
CGLIB动态代理
JDK动态代理的方法是持有被代理的类,但是代理的方法并不是只能这样实现。CGLIB的思想是,通过“继承”可以继承父类所有的公开方法,然后可以重写这些方法,在重写时对这些方法增强。
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibProxy implements MethodInterceptor
{
// 根据一个类型产生代理类,此方法不要求一定放在MethodInterceptor中
public Object CreatProxyedObj(Class<?> clazz)
{
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable
{
// 这里是扩展内容
System.out.println("开始");
return arg3.invokeSuper(arg0, arg2);
}
}
CGLIB中只需要一个class就可以产生一个代理对象,不像jdk动态代理需要根据一个实现了接口的对象来生成一个代理对象。所以CGLIB可以理解为是“类”的代理。
cglib最终生成一个继承B(被代理的类型)的类型C(代理类),这个代理类持有一个MethodInterceptor,enhancer.setCallback(this);传入。 C重写所有B中的方法(方法名一致),然后在C中,构建名叫“CGLIB”+“ 父 类 方 法 名 父类方法名 父类方法名”的方法(下面叫cglib方法,所有非private的方法都会被构建),方法体里只有一句话super.方法名(),可以简单的认为保持了对父类方法的一个引用,方便调用。
这样的话,C中就有了重写方法、cglib方法、父类方法(不可见),还有一个统一的拦截方法(增强方法intercept)。其中重写方法和cglib方法肯定是有映射关系的。
C的重写方法是外界调用的入口(LSP原则),它调用MethodInterceptor的intercept方法,调用时会传递四个参数,第一个参数传递的是this,代表代理类本身,第二个参数标示拦截的方法,第三个参数是入参,第四个参数是cglib方法,intercept方法完成增强后,我们调用cglib方法间接调用父类方法完成整个方法链的调用。
这里有个疑问就是intercept的四个参数,为什么我们使用的是arg3而不是arg1?
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable
{
System.out.println("开始");
return arg3.invokeSuper(arg0, arg2);
}
因为如果我们通过反射 arg1.invoke(arg0, …)这种方式是无法调用到父类的方法的,子类有方法重写,隐藏了父类的方法,父类的方法已经不可见,如果硬调arg1.invoke(arg0, …)很明显会死循环。
所以调用的是cglib开头的方法,但是,我们使用arg3也不是简单的invoke,而是用的invokeSuper方法,这是因为cglib采用了fastclass机制,不仅巧妙的避开了调不到父类方法的问题,还加速了方法的调用。
fastclass基本原理是,给每个方法编号,通过编号找到方法执行避免了通过反射调用。
对比JDK动态代理,cglib依然需要一个第三者分发请求,只不过jdk动态代理分发给了目标对象,cglib最终分发给了自己,通过给method编号完成调用。cglib是继承的极致发挥,本身还是很简单的,只是fastclass需要另行理解。
此博文为记录+思考。原博文地址:地址