一、什么是代理
代理,Proxy。意思就是你的事情不用自己去做,而是由别人去帮你处理。举例:今天下班要去接孩子,但是临时要加班,于是请了家里的专职司机去接。这个专职司机就是你的Proxy,在这里司机替你完成了接小孩的工作。这样的好处在哪呢?通过代码展现:
首先定义一个Person接口,接口里包含接小孩的的方法:
public interface Person {
void pickChild(String childName);
}
接着定义Person的实现类:
public class PersonImpl implements Person {
@Override
public void pickChild(String childName) {
System.out.println(childName+" is back home!");
}
}
现在如果想使得pickChild方法在前面和后面分别实现一些逻辑,例如分别在输出前面和后面打印“hello Dad!”和“I love you!”,应该怎么做呢?
最愚蠢的方法是,直接修改实现类里的方法,虽然我以前经常这么愚蠢。
public class PersonImpl implements Person {
@Override
public void pickChild(String childName) {
System.out.println("hello Dad!");
System.out.println(childName+" is back home!");
System.out.println("I love you!");
}
}
这种方法我们在对一个方法添加逻辑时经常使用,但是这种方法十分的不优雅,如果一个或者好几个实现类里面的方法都需要添加同样的逻辑呢,那么就要在每一个方法前后都加上这么两句。
代理模式就是在这个时候用的。我们写一个driverProxy类,写一个before方法和after方法专门负责添加这些功能。
public class DriverProxy implements Person {
@Override
public void pickChild(String childName) {
before();
System.out.println(childName+" is back home!");
after();
}
private void after() {
System.out.println("I love you!");
}
private void before() {
System.out.println("hello Dad!");
}
}
我们写一个main方法测试一下输出结果:
public class Output {
public static void main(String[] args) {
DriverProxy driverProxy = new DriverProxy();
driverProxy.pickChild("Tiger");
}
}
输出:
hello Dad!
Tiger is back home!
I love you!
这就是代理模式,但是这种代理模式每一个需要添加逻辑的接口都需要单独写一个代理类,这样在较大工程下面疯狂使用这种方法的结果就是,代码里到处都是XxxProxy,其实上面介绍的这种方式就是静态代理。而想要实现代码的精简、易重构,就需要用到动态代理模式。
二、动态代理(jdk动态代理和CGLib动态代理)
下面是使用jdk提供的动态代理方案写的DriverProxy:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class DriverProxy implements InvocationHandler {
private Object target;
public DriverProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target,args);
after();
return result;
}
private void after() {
System.out.println("I love you!");
}
private void before() {
System.out.println("hello Dad!");
}
}
动态代理通过反射方式可以动态的去调用需要添加逻辑的方法。而在main方法中想要使用动态代理的代码如下:
import java.lang.reflect.Proxy;
public class Output {
public static void main(String[] args) {
Person person = new PersonImpl();
DriverProxy driverProxy = new DriverProxy(person);
Person personProxy = (Person) Proxy.newProxyInstance(person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
driverProxy);
personProxy.pickChild("Tiger");
}
}
输出:
hello Dad!
Tiger is back home!
I love you!
jdk动态代理只需要传入需要被代理类的对象,然后调用Proxy类的工厂方法newProxyInstance去动态地创建一个代理类,最后调用代理类的方法便实现了“增强功能”。Proxy.newProxyInstance方法需要传入的参数有:
参数1:ClassLoader
参数2:该实现类的所有接口
参数3:动态代理对象
最后还要进行一次强制类型转换。
那么现在问题又来了,没有到处的XxxProxy了,又出来一大堆的Proxy.newProxyInstance了。所以需要对这个方法进行封装。在DriverProxy代理类中添加方法:
public <T> T getProxy(){
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
这样一来使用时就非常的简便了:
import java.lang.reflect.Proxy;
public class Output {
public static void main(String[] args) {
Person person = new PersonImpl();
DriverProxy driverProxy = new DriverProxy(person);
Person personProxy = driverProxy.getProxy();
personProxy.pickChild("Tiger");
}
}
使用了动态代理之后,无论有多少类多少方法需要增加逻辑,只需要在使用的时候将类对象传入得到代理对象,然后使用代理对象调用需要增强的方法即可。但是还存在着一个问题,jdk动态代理只能代理接口类,如果没有接口的类需要代理该怎么办。
接下来介绍CGlib代理:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class DriverProxy implements MethodInterceptor {
public <T> T getProxy(Class<T> cls){
return (T) Enhancer.create(cls, this);
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object result = methodProxy.invokeSuper(o,objects);
after();
return result;
}
private void after() {
System.out.println("I love you!");
}
private void before() {
System.out.println("hello Dad!");
}
}
使用CGlib动态代理需要导入四个jar包,见附件,亲测可用。
CGlib动态代理实现了MethodInterceptor,并且填充intercept方法,传入的参数中最后一个methodProxy,其实就是一个方法拦截器,因此CGlib可以增强非接口类的方法。调用methodProxy的invokeSuper方法,将被代理的对象以及方法参数args传入即可。
如何使用:
public class Output {
public static void main(String[] args) {
DriverProxy driverProxy = new DriverProxy();
Person personProxy = driverProxy.getProxy(PersonImpl.class);
personProxy.pickChild("Tiger");
}
}
动态代理就介绍完了。动态代理的主要作用是为已有方法增加逻辑,俗称“增强”。但是如果不使用代理模式的话,需要对每个方法进行修改,而使用静态代理的话,又需要写茫茫多个代理类,因此利用反射,通过一个动态代理类,即可实现对多个类的多个方法的增强。因此,我们通过上面的逐步学习可以看到,动态代理并不是反射,代理的主要任务是“增强”,而为了动态的“增强”,引入反射机制,使得代码可以尽可能精简。
三、jdk动态代理与CGlib动态代理的区别
jdk动态代理:
利用拦截器加上反射机制生成一个实现代理接口的匿名类。
CGlib动态代理:
利用ASM开源包,将被代理对象类的Class文件加载进来,通过修改其字节码来处理。
什么时候使用jdk动态代理,什么时候使用CGlib动态代理?
1、在Spring中,如果目标实现了接口,默认使用jdk动态代理。jdk动态代理只能针对接口类,非接口类无法代理。
2、若目标bean没有实现接口,则使用CGlib动态代理。CGlib的实现原理是针对被代理类生成一个子类,覆盖其中的方法并实现增强,但是采用的手段是继承。因此,被代理类中使用final声明的方法,无法使用CGlib动态代理,因为final声明的方法无法被继承。
CGlib动态代理要快?
CGlib使用ACM字节码生成框架,采用字节码技术生成代理类,在jdk6之前确实比jdk动态的代理效率要高。但是每一次jdk版本是升级,jdk动态代理的效率都得到提升,而CGlib逐渐有点跟不上步伐了。