spring中用到代理的很多,比如上篇文章中的lookup-method和replaced-method,以及后面要学的aop、spring中的事务、spring中解析@configuration注解等等,这些都是依靠代理来实现的,所以现在来把代理学一下。
1,为什么要用代理
看一个案例
public interface Service {
void m1();
void m2();
}
public class Service1 implements Service {
@Override
public void m1() {
System.out.println("我是Service1中的m1方法!");
}
@Override
public void m2() {
System.out.println("我是Service1中的m2方法!");
}
}
public class Service2 implements Service{
@Override
public void m1() {
System.out.println("我是Service2中的m1方法!");
}
@Override
public void m2() {
System.out.println("我是Service2中的m2方法!");
}
}
测试输出
public class Main {
public static void main(String[] args) {
Service1 service1 = new Service1();
service1.m1();
service1.m2();
Service2 service2 = new Service2();
service2.m1();
service2.m2();
}
}
我是Service1中的m1方法!
我是Service1中的m2方法!
我是Service2中的m1方法!
我是Service2中的m2方法!
上面的代码很简单。现在我们需要调用IService接口中的任何方法的时候,需要记录方法的耗时。
Service接口有2个实现类Service1和Service2,我们可以在这两个类的所有方法中加上统计耗时的代码,但是如果Service接口有几十个实现,是不是要修改很多代码,所有被修改的方法需重新测试?是不是非常麻烦,不过上面这种修改代码的方式倒是可以解决问题,只是增加了很多工作量(编码 & 测试)
但是突然有一天需求又改了。 此时是不是又要去一个修改上面的代码?又要去测试?此时的系统是难以维护。
还有假如上面这些类都是第三方以jar包的方式提供给我们的,此时这些类都是class文件,此时我们无法去修改源码。
我们可以为Service接口创建一个代理类,通过这个代理类来间接访问IService接口的实现类,在这个代理类中去做耗时及或其他需求的代码。
public class ServiceProxy implements Service{
//被代理的对象
private Service target;
public ServiceProxy(Service target) {
this.target = target;
}
@Override
public void m1() {
long starTime = System.nanoTime();
this.target.m1();
long endTime = System.nanoTime();
System.out.println(this.target.getClass() + ".m1()方法耗时(纳秒):" + (endTime - starTime));
}
@Override
public void m2() {
long starTime = System.nanoTime();
this.target.m2();
long endTime = System.nanoTime();
System.out.println(this.target.getClass() + ".m1()方法耗时(纳秒):" + (endTime - starTime));
}
}
此时测试输出
public class Main {
public static void main(String[] args) {
Service1 service1 = new Service1();
Service2 service2 = new Service2();
Service proxy1 = new ServiceProxy(service1);
Service proxy2 = new ServiceProxy(service2);
proxy1.m1();
proxy1.m2();
proxy2.m1();
proxy2.m2();
}
}
我是Service1中的m1方法!
class com.chen.netty.ss.Service1.m1()方法耗时(纳秒):284900
我是Service1中的m2方法!
class com.chen.netty.ss.Service1.m1()方法耗时(纳秒):43200
我是Service2中的m1方法!
class com.chen.netty.ss.Service2.m1()方法耗时(纳秒):31000
我是Service2中的m2方法!
class com.chen.netty.ss.Service2.m1()方法耗时(纳秒):20000
上面实现中我们没有去修改Service1和Service2中的方法,只是给Service接口创建了一个代理类,通过代理类去访问目标对象,需要添加的一些共有的功能都放在代理中,当有其他需求的时候,我们只需修改ServiceProxy的代码,方便系统的扩展和测试。
假如现在我们需要给系统中所有接口都加上统计耗时的功能,若按照上面的方式,我们需要给每个接口创建一个代理类,此时代码量和测试的工作量也是巨大的,那么我们可以写一个通用的代理类。
2,jdk动态代理详解
jdk中为实现代理提供了支持,主要用到2个类:
java.lang.reflect.Proxy
java.lang.reflect.InvocationHandler
jdk自带的代理使用有个限制,只能为接口创建代理类,如果需要给具体的类创建代理类,需要用cglib
使用方式:
public class JdkProxy {
public static void main(String[] args) {
// 1. 创建代理类的处理器,执行被代理类的方法都会经过这个方法
InvocationHandler proxyHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("InvocationHandler#invoke:"+method.getName());
return null;
}
};
// 2. 创建代理实例,注意
Service proxyService = (Service) Proxy.newProxyInstance(Service.class.getClassLoader(), new Class[]{Service.class}, proxyHandler);
//3. 调用代理的方法
proxyService.m1();
proxyService.m2();
}
}
InvocationHandler#invoke:m1
InvocationHandler#invoke:m2
//newProxyInstance参数说明
ClassLoader loader 类加载器
Class<?>[] interfaces 需要代理的接口数组,你需要给哪些接口创建代理
InvocationHandler h 代理处理器,即你需要怎么实现代理
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
现在为上面的使用jdk动态代理创建代理
public class computeTimeInvocationHandler implements InvocationHandler {
private Object target;
public computeTimeInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long starTime = System.nanoTime();
Object invoke = method.invoke(target, args);
long endTime = System.nanoTime();
System.out.println(this.target.getClass() + method.getName() +"方法耗时(纳秒):" + (endTime - starTime));
return invoke;
}
public static <T> T createProxy(Object target, Class<T> targetInterface) {
if (!targetInterface.isInterface()) {
throw new IllegalStateException("targetInterface必须是接口类型!");
} else if (!targetInterface.isAssignableFrom(target.getClass())) {
throw new IllegalStateException("target必须是targetInterface接口的实现类!");
}
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new computeTimeInvocationHandler(target));
}
}
public class Main {
public static void main(String[] args) {
Service1 service1 = new Service1();
Service proxy = computeTimeInvocationHandler.createProxy(service1, Service.class);
proxy.m1();
proxy.m2();
}
}
我是Service1中的m1方法!
class com.chen.netty.ss.Service1m1方法耗时(纳秒):191900
我是Service1中的m2方法!
class com.chen.netty.ss.Service1m2方法耗时(纳秒):30900
当我们再为其他接口,也需要统计耗时的功能,我们无需去创建新的代理类即可实现同样的功能
如:为Service2创建
public class Main {
public static void main(String[] args) {
Service2 service2 = new Service2();
Service proxy = computeTimeInvocationHandler.createProxy(service2, Service.class);
proxy.m1();
proxy.m2();
}
}
我是Service2中的m1方法!
class com.chen.netty.ss.Service2m1方法耗时(纳秒):158300
我是Service2中的m2方法!
class com.chen.netty.ss.Service2m2方法耗时(纳秒):31100
小结:
- jdk中的Proxy只能为接口生成代理类,如果你想给某个类创建代理类,那么Proxy是不可以的,此时需要用到cglib了。
- 通过Proxy创建代理对象,当调用代理对象任意方法时候,会被InvocationHandler接口中的invoke方法进行处理,这个接口方法内容是最重要的。
3,cglib代理
jdk动态代理只能为接口创建代理,使用上有局限性。实际的场景中我们的类不一定有接口,此时如果我们想为普通的类也实现代理功能,我们就需要用到cglib来实现了。
cglib是一个强大、高性能的字节码生成库,它用于在运行时扩展Java类和实现接口;本质上它是通过动态的生成一个子类去覆盖所要代理的类(非final修饰的类和方法)。Enhancer可能是CGLIB中最常用的一个类,和jdk中的Proxy不同的是,Enhancer既能够代理普通的class,也能够代理接口。Enhancer创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的toString和hashCode方法)。Enhancer不能够拦截final方法,例如Object.getClass()方法,这是由于Java final方法语义决定的。同样,Enhancer也不能对final类进行代理操作。
spring已将第三方cglib jar包中所有的类集成到springjar包中,我们可以在spring依赖中直接使用。
使用:
public interface Service {
void m1();
void m2();
}
public class CglibMain {
public static void main(String[] args) {
//使用Enhancer来给某个类创建代理类,步骤
//1.创建Enhancer对象
Enhancer enhancer = new Enhancer();
//2.通过setSuperclass来设置父类型,即需要给哪个类创建代理类
enhancer.setSuperclass(Service1.class);
/*3.设置回调,需实现org.springframework.cglib.proxy.Callback接口,
此处我们使用的是org.springframework.cglib.proxy.MethodInterceptor,也是一个接口,实现了Callback接口,
当调用代理对象的任何方法的时候,都会被MethodInterceptor接口的invoke方法处理*/
enhancer.setCallback(new MethodInterceptor() {
/**
* 代理对象方法拦截器
* @param o 代理对象
* @param method 被代理的类的方法,即Service1中的方法
* @param objects 调用方法传递的参数
* @param methodProxy 方法代理对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("调用方法:" + method);
//可以调用MethodProxy的invokeSuper调用被代理类的方法
Object result = methodProxy.invokeSuper(o, objects);
//Object result = method.invoke(o,objects);
return result;
}
});
//4.获取代理对象,调用enhancer.create方法获取代理对象,这个方法返回的是Object类型的,所以需要强转一下
Service1 proxy = (Service1) enhancer.create();
//5.调用代理对象的方法
proxy.m1();
proxy.m2();
}
}
Object result = methodProxy.invokeSuper(o,objects);可以调用被代理类,也就是Service1类中的具体的方法,意思可以看出是调用父类,实际对某个类创建代理,cglib底层通过修改字节码的方式为Service1类创建了一个子类。
调用方法:public void com.chen.netty.ss.Service1.m1()
我是Service1中的m1方法!
调用方法:public void com.chen.netty.ss.Service1.m2()
我是Service1中的m2方法!
从输出中可以看出Service1中的2个方法都被MethodInterceptor中的invoke拦截处理了。
总结:
- Java动态代理只能够对接口进行代理,不能对普通的类进行代理(因为所有生成的代理类的父类为Proxy,Java类继承机制不允许多重继承);CGLIB能够代理普通类;
- Java动态代理使用Java原生的反射API进行操作,在生成类上比较高效;CGLIB使用ASM框架直接对字节码进行操作,在类的执行过程中比较高效