代理一般分为静态代理和动态代理,静态代理在平时工作中还是用的比较少的,比较常用的是动态代理。代理模式简单理解就是,外部对象不去访问实际执行的类对象,而去访问他的代理,让代理去访问。例如,超市想要批发某种食品,超市老板不需要去到该食品的工厂商量购买,只需找到该食品的代理商,代理商会去完成这些事,而工厂只需要生产食品就好,这样就消除了超市老板和工厂之间的耦合度,只需要各自关心自己的事情就可以。
UML图如下
从程序的角度来看,由于是通过代理对象去调用实际对象的方法,那么代理对象就可以在实际对象方法调用前后完成一些操作,比如记录被调用的方法耗时。
一、静态代理
写个简单的例子来演示一下。
首先抽象出一个接口,意为“可移动”,仅有一个move方法,然后添加一个实现类Car。
public interface Movable {
void move();
}
public class Car implements Movable{
@Override
public void move() {
System.out.println("car is moving...");
try {
// 让汽车开一会
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
接着添加一个Time的代理,用于记录Movable对象的move方法耗时。
package com.xc.proxy.staticproxy;
public class TimeMovableProxy implements Movable{
private Movable movable;
public TimeMovableProxy(Movable movable) {
this.movable = movable;
}
@Override
public void move() {
System.out.println("start to record time");
long start = System.currentTimeMillis();
movable.move();
long end = System.currentTimeMillis();
System.out.println("cost time: " + (end - start) + "ms");
}
}
注意,如果这里的私有属性不使用Movable类型,而是用Car类型,那么他就只能记录Car类型的move方法耗时。使用Movable类型,就可以记录所有Movable实现类的耗时。
同时TimeMovableProxy类也实现了Movable接口,这说明他本身也可以被代理,即代理的代理。比如我们希望在记录时间之前先记录一下日志,那么可以再添加一个Log代理。Log代理的实现,其实跟Time代理是一样的,所以他也可以被代理。
public class LogMovableProxy implements Movable{
private Movable movable;
public LogMovableProxy(Movable movable) {
this.movable = movable;
}
@Override
public void move(){
System.out.println("start log...");
movable.move();
System.out.println("end log...");
}
}
测试一下
public class Main {
public static void main(String[] args) {
// 仅仅使用Time代理
// Movable movable = new TimeMovableProxy(new Car());
// 使用Time和Log代理,先代理Time再代理Log
// Movable movable = new TimeMovableProxy(new LogMovableProxy(new Car()));
// 使用Time和Log代理,先代理Log再代理Time
Movable movable = new LogMovableProxy(new TimeMovableProxy(new Car()));
movable.move();
}
}
二、动态代理
java中动态代理一般使用的是JDK动态代理或CGLIB动态代理,都是通过运行期间生成字节码文件来实现动态代理的目的。
JDK动态代理,优点:不需引入第三方jar包,且速度较快;缺点:只能代理接口或实现了接口的类。
CGLIB动态代理,优点:可以代理任何不是final的类;缺点:需要引入第三方jar包,速度稍慢。
下面,分别来使用这两种动态代理,同样还是使用Movable接口,和一个实现类Car。另外新建一个Train类不实现任何接口,但是也有一个同名的move方法。我们使用JDK动态代理Car,使用CGLIB来代理Train,都用来做计时操作。
public interface Movable {
void move();
}
public class Car implements Movable{
@Override
public void move() {
System.out.println("car is moving...");
}
}
public class Train {
public void move() {
System.out.println("train is moving...");
}
}
1.JDK动态代理
JDK动态代理需要实现InvocationHandler接口。
public class TimeInvocationHandler implements InvocationHandler {
private Object obj;
public TimeInvocationHandler(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("start to record time");
long start = System.currentTimeMillis();
Object o = method.invoke(obj, args);
long end = System.currentTimeMillis();
System.out.println("cost time: " + (end - start) + "ms");
return o;
}
}
2.CGLIB动态代理
首先需要引入CGLIB的包,如果使用的MAVEN的话就直接引入CGLIB的坐标就行。我这里没使用MAVEN就直接导入了两个jar包,cglib-3.1.jar 和 asm-4.2.jar。
CGLIB的方式需要实现MethodInterceptor接口。
public class TimeMethodInterceptor implements MethodInterceptor{
@Override
public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("start to record time");
long start = System.currentTimeMillis();
Object o = methodProxy.invokeSuper(obj, objects);
long end = System.currentTimeMillis();
System.out.println("cost time: " + (end - start) + "ms");
return o;
}
}
然后测试一下。
public class Main {
public static void main(String[] args) {
System.out.println("----------jdk proxy----------");
jdkProxy();
System.out.println("---------cglib proxy---------");
cglibProxy();
}
public static void jdkProxy() {
Movable m = (Movable)Proxy.newProxyInstance(Car.class.getClassLoader(), Car.class.getInterfaces(), new TimeInvocationHandler(new Car()));
m.move();
}
public static void cglibProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Train.class);
enhancer.setCallback(new TimeMethodInterceptor());
Train train = (Train)enhancer.create();
train.move();
}
}
注意,JDK动态代理的方式是使用 Proxy.newProxyInstance(Car.class.getClassLoader(), Car.class.getInterfaces(), new TimeInvocationHandler(new Car()));首先需要一个classLoader,然后是一个class数组,最后是一个InvocationHandler的实现。然后将返回值进行一个强制转换。由于JDK的限制,能用于动态代理的必须实现了接口或者本身是接口,所以这里只能强制转换为接口类型而不能转换为具体的类型,当然了也就只能调用接口定义的方法了。
从CGLIB的使用中可以看到一句enhancer.setSuperclass(Train.class);说明最终在生成字节码的时候,其实是生成了被代理类的一个子类,所以在调用enhancer.create()时可以强转为Train。这也说明了为什么使用CGLIB代理的类不能是final的,因为final修饰的类是无法被继承的。