基本概念
1.什么是代理?
在阐述动态代理之前,我们很有必要先来弄明白代理的概念。代理这个词本身并不是计算机专用术语,它是生活中一个常用的概念。这里引用维基百科上的一句话对代理进行定义:
A proxy is an agent or substitute authorized to act for another person or a document which authorizes the agent so to act.
意思是说:代理指的是一个代理人(或替代品),它被授权代表另外一个人(或文档)。
从这个简明扼要的定义中,可以看出代理的一些特性:
1.代理存在的意义就是代表另一个事物。
2.代理至少需要完成(或实现)它所代表的事物的功能。
JDK动态代理:
了解了动态代理的概念后,现在我们动手写个JDK动态代理的代码样例。直观的认识下JDK动态代理技术为我们的做了什么。上面说到了,
JDK动态代理主要依靠Proxy和InvocationHandler这两个类来生成动态代理类和类的实例。这两个类都在jdk的反射包java.lang.reflect下面。Proxy是个工具类,有了它就可以为接口生成动态代理类了。如果需要进一步生成代理类实例,需要注入InvocationHandler实例。这点我们上面解释过,因为代理类最终逻辑的实现是分派给InvocationHandler实例的invoke方法的。
JDK动态代理的实例:
闲话休絮,先开始我们的第一步,定义一个接口。这个接口里面定义一个方法helloWorld()。
public interface MyIntf {
void helloWorld();
}
第二步,编写一个我们自己的调用处理类,这个类需要实现InvocationHandler接口。
public class MyInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
return null;
}
}
InvocationHandler接口只有一个待实现的invoke方法。这个方法有三个参数,proxy表示动态代理类实例,method表示调用的方法,args表示调用方法的参数。在实际应用中,invoke方法就是我们实现业务逻辑的入口。这里我们的实现逻辑就一行代码,打印当前调用的方法(在实际应用中这么做是没有意义的,不过这里我们只想解释JDK动态代理的原理,所以越简单越清晰)。
第三步,直接使用Proxy提供的方法创建一个动态代理类实例。并调用代理类实例的helloWorld方法,检测运行结果。
public class ProxyTest {
public static void main(String[] args) {
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
MyIntf proxyObj = (MyIntf)Proxy.newProxyInstance(MyIntf.class.getClassLoader(),new Class[]{MyIntf.class},new MyInvocationHandler());
proxyObj.helloWorld();
}
}
第三行代码是设置系统属性,把生成的代理类写入到文件。这里再强调一下,JDK动态代理技术是在运行时直接生成类的字节码,并载入到虚拟机执行的。这里不存在class文件的,所以我们通过设置系统属性,把生成的字节码保存到文件,用于后面进一步分析。
第四行代码就是调用Proxy.newProxyInstance方法创建一个动态代理类实例,这个方法需要传入三个参数,第一个参数是类加载器,用于加载这个代理类。第二个参数是Class数组,里面存放的是待实现的接口信息。第三个参数是InvocationHandler实例。
第五行调用代理类的helloWorld方法,运行结果:
public abstract void com.tuniu.distribute.openapi.common.annotation.MyIntf.helloWorld()
cglib动态代理:
cglib (Code Generation Library)是一个开源项目!是一个强大的,高性能,高质量的Code生成类库,
它可以在运行期扩展Java类与实现Java接口。Hibernate用它来实现PO(Persistent Object 持久化对象)字节码的动态生成。
CGLIB是一个强大的高性能的代码生成包。它广泛的被许多AOP的框架使用,例如Spring AOP为他们提供
方法的interception(拦截)。CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。
除了CGLIB包,脚本语言例如Groovy和BeanShell,也是使用ASM来生成java的字节码。当然不鼓励直接使用ASM,
因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。
简单看下cglib的用法:cglib的Enhancer说起来神奇,用起来一页纸不到就讲完了。它的原理就是用Enhancer生成一个原有类的子类,并且设置好callback到proxy, 则原有类的每个方法调用都会转为调用实现了MethodInterceptor接口的proxy的intercept() 函数。
我之前写过一篇spring中用到cglib的文章,是关于@Configuration注解的,
地址如下:https://blog.csdn.net/weixin_43107805/article/details/102980058
CGLIB动态代理实例
实现一个业务类,注意,这个业务类并没有实现任何接口:
package com.lanhuigu.spring.proxy.cglib;
public class HelloService {
public HelloService() {
System.out.println("HelloService构造");
}
/**
* 该方法不能被子类覆盖,Cglib是无法代理final修饰的方法的
*/
final public String sayOthers(String name) {
System.out.println("HelloService:sayOthers>>"+name);
return null;
}
public void sayHello() {
System.out.println("HelloService:sayHello");
}
}
自定义MethodInterceptor:
package com.lanhuigu.spring.proxy.cglib;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 自定义MethodInterceptor
*/
public class MyMethodInterceptor implements MethodInterceptor{
/**
* sub:cglib生成的代理对象
* method:被代理对象方法
* objects:方法入参
* methodProxy: 代理方法
*/
@Override
public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("======插入前置通知======");
Object object = methodProxy.invokeSuper(sub, objects);
System.out.println("======插入后者通知======");
return object;
}
}
生成CGLIB代理对象调用目标方法:
package com.lanhuigu.spring.proxy.cglib;
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
public class Client {
public static void main(String[] args) {
// 代理类class文件存入本地磁盘方便我们反编译查看源码
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
// 通过CGLIB动态代理获取代理对象的过程
Enhancer enhancer = new Enhancer();
// 设置enhancer对象的父类
enhancer.setSuperclass(HelloService.class);
// 设置enhancer的回调对象
enhancer.setCallback(new MyMethodInterceptor());
// 创建代理对象
HelloService proxy= (HelloService)enhancer.create();
// 通过代理对象调用目标方法
proxy.sayHello();
}
}
运行结果:
总结:
JDK代理是不需要第三方库支持,只需要JDK环境就可以进行代理,使用条件:
1)实现InvocationHandler
2)使用Proxy.newProxyInstance产生代理对象
3)被代理的对象必须要实现接口
CGLib必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类,覆盖其中的方法,是一种继承但是针对接口编程的环境下推荐使用JDK的代理;