动态代理在Java中有着广泛的应用,比如Spring AOP、Hibernate数据查询、测试框架的后端mock、RPC远程调用、Java注解对象获取、日志、用户鉴权、全局性异常处理、性能监控,甚至事务处理等。本文主要介绍Java中几种常见的动态代理方式:
一、JDK的动态代理
JDK提供的动态代理功能只能针对(一个或多个)接口提供代理。使用步骤如下:
(1) 新建一个接口
public interface ISubject {
public void print();
}
(2) 为接口创建一个实现类
public class SubjectImpl implements ISubject {
@Override
public void print() {
System.out.println("hello world!");
}
}
(3) 创建拦截类,实现java.lang.reflect.InvocationHandler接口
public class MyInvokeHandler implements InvocationHandler {
private Object instance;
public MyInvokeHandler(Object o) {
this.instance=o;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
this.before(method);
Object result=method.invoke(this.instance, args);
this.after(method);
return result;
}
private void before(Method method) {
System.out.println("run before method:"+method.getName());
}
private void after(Method method) {
System.out.println("run after method:"+method.getName());
}
}
(4) 使用Proxy.newProxyInstance()函数生成代理对象
public class JdkProxyTester {
public static void main(String[] args) {
ISubject subject=new SubjectImpl();
InvocationHandler handler=new MyInvokeHandler(subject);
ClassLoader classLoader = ISubject.class.getClassLoader();
Class<?>[] interfaces = { ISubject.class };
ISubject proxy = (ISubject) Proxy.newProxyInstance(classLoader,interfaces,handler);
proxy.print();
}
}
二、CGlib的动态代理
CGlib也可以实现动态代理,但与JDK的不同之处是: JDK只能为接口的实现类提供代理,而CGlib可以为类的实现类提供代理,原理上使用字节码技术,不能对 final类进行继承。
(1) 添加pom依赖
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
(2) 新建一个需要被代理的类
public class MySubject {
private String name;
public MySubject(String name) {
this.name=name;
}
public void print() {
System.out.println("hello world!,name=" + this.name);
}
}
(3) 创建拦截类,实现net.sf.cglib.proxy.MethodInterceptor接口
public class MyInvokeHandler implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
this.before(method);
Object result = proxy.invokeSuper(obj, args);
this.after(method);
return result;
}
private void before(Method method) {
System.out.println("run before method:" + method.getName());
}
private void after(Method method) {
System.out.println("run after method:" + method.getName());
}
}
(4) 使用net.sf.cglib.proxy.Enhancer的create()方法生成代理对象
public class CglibTester {
public static void main(String[] args) {
MethodInterceptor handler = new MyInvokeHandler();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(MySubject.class);
enhancer.setCallback(handler);
Class<?>[] argsType = new Class<?>[] { String.class };
Object[] argsValue = new Object[] { "aa" };
MySubject proxy= (MySubject)enhancer.create(argsType, argsValue);
proxy.print();
}
}
三、JAVAssist的动态代理
Java代码编译完生成.class文件,就JVM(准确说是JIT)会解释执行这些字节码(转换为机器码并执行),由于字节码的解释执行是在运行时进行的,而Javassist正式再运行时改字节码来实现的。它同样可以对类进行代理。
(1) 添加pom依赖
<!-- https://mvnrepository.com/artifact/javassist/javassist -->
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.12.1.GA</version>
</dependency>
(2) 新建一个需要被代理的类
public class MySubject {
private String name;
public MySubject(String name) {
this.name=name;
}
public void print() {
System.out.println("hello world!,name=" + this.name);
}
}
(3) 创建拦截类,实现javassist.util.proxy.MethodHandler接口
public class MyMethodHandler implements MethodHandler {
@Override
public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
this.before(thisMethod);
// thisMethod为被代理方法 ,proceed为代理方法, self为代理实例 ,args为方法参数
Object result = proceed.invoke(self, args);
this.after(thisMethod);
return result;
}
private void before(Method method) {
System.out.println("run before method:" + method.getName());
}
private void after(Method method) {
System.out.println("run after method:" + method.getName());
}
}
(4) 使用javassist.util.proxy.ProxyFactory的createClass()方法生成代理对象
public class JavassistTester {
public static void main(String[] args) throws Exception {
ProxyFactory factory = new ProxyFactory();
factory.setSuperclass(MySubject.class);
factory.setFilter(new MethodFilter() {
@Override
public boolean isHandled(Method m) {
return m.getName().equals("print");
}
});
Class<?> clazz = factory.createClass();
Class<?>[] argsType = new Class<?>[] { String.class };
Object[] argsValue = new Object[] { "aa" };
Constructor<?> constructor=clazz.getConstructor(argsType);
MySubject proxy = (MySubject) constructor.newInstance(argsValue);
proxy.print();
}
}
四、Java动态代理实现的原理
Java动态代理实现的原理:在编译期或运行期间操作修改java的字节码。
操作java字节码的工具有两个比较流行,一个是ASM,一个是Javassit 。
- ASM
直接操作字节码指令,执行效率高,要是使用者掌握Java类字节码文件格式及指令,对使用者的要求比较高。
官网地址:https://asm.ow2.io/index.html
- Javassit
提供了更高级的API,执行效率相对较差,但无需掌握字节码指令的知识,对使用者要求较低。
官网地址:http://www.javassist.org/
应用层面来讲一般使用建议优先选择Javassit,如果后续发现Javassit 成为了整个应用的效率瓶颈的话可以再考虑ASM。
更多Java字节码操作开源框架介绍请参考:
https://www.cnblogs.com/zj2lzh/p/3844681.html