内容简介:动态代理的作用其实就是在不修改原代码的前提下,对已有的方法进行增强。关键点:我们用一个很简单的例子来说明:
文档更新说明
-
2018年09月24日 v1.0 初稿
代理在生活中很常见,比如说婚介网站,其实就是找对象的代理;还有社保代理、人事代理;还有找黄牛抢票,其实也是一种代理;而这些代理,在 JAVA 中也是有对应实现的。
1、为什么要动态代理
动态代理的作用其实就是在不修改原代码的前提下,对已有的方法进行增强。
关键点:
- 不修改原来已有的代码(满足 设计模式 的要求)
- 对已有方法进行增强
2、举个栗子
我们用一个很简单的例子来说明: Hello
类,有一个 introduction
方法。
现在我们的需求就是不修改 Hello
类的 introduction
方法,在 introduction
之前先 sayHello
,在 introduction
之后再 sayGoodBye
3、实现方式
JAVA中,实现动态代理有两种方式,一种是JDK提供的,一种是第三方库 CgLib
提供的。特点如下:
CgLib
3.1、JDK动态代理
JDK动态代理需要实现接口,然后通过对接口方法的增强来实现动态代理
所以要使用JDK动态代理的话,我们首先要创建一个接口,并且被代理的方法要在这个接口里面
3.1.1、创建一个接口
我们创建一个接口如下:
Personal.java
public interface Personal {
/**
* 被代理的方法
*/
void introduction();
}
3.1.2、实现接口
创建接口实现类,并且完成 introduction
方法
PersonalImpl.java
public class PersonalImpl implements Personal {
@Override
public void introduction() {
System.out.println("我是程序员!");
}
}
3.1.3、创建代理类
JDK代理的关键就是这个代理类了,需要实现 InvocationHandler
在代理类中,所有方法的调用都好分发到 invoke
方法中。我们在 invoke
方法完成对方法的增强即可
JDKProxyFactory.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JDKProxyFactory<T> implements InvocationHandler {
/**
* 目标对象
*/
private T target;
/**
* 构造函数传入目标对象
*
* @param target 目标对象
*/
public JDKProxyFactory(T target) {
this.target = target;
}
/**
* 获取代理对象
*
* @return 获取代理
*/
public T getProxy() {
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 对方法增强
System.out.println("大家好!");
// 调用原方法
Object result = method.invoke(target, args);
// 方法增强
System.out.println("再见!");
return result;
}
}
就这样,JDK动态代理的代码就完成了,接下来写一份测试代码
3.1.4、编写测试代码
为了方便测试,我们编写一个 test
方法
同时为了查看class文件,还添加了一个 generatorClass
方法,这个方法可以将动态代理生成的 .class
输出到文件
ProxyTest.java
import org.junit.Test;
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
import java.io.IOException;
public class ProxyTest {
@Test
public void testJdkProxy() {
// 生成目标对象
Personal personal = new PersonalImpl();
// 获取代理对象
JDKProxyFactory<Personal> proxyFactory = new JDKProxyFactory<>(personal);
Personal proxy = proxyFactory.getProxy();
// 将proxy的class字节码输出到文件
generatorClass(proxy);
// 调用代理对象
proxy.introduction();
}
/**
* 将对象的class字节码输出到文件
*
* @param proxy 代理类
*/
private void generatorClass(Object proxy) {
FileOutputStream out = null;
try {
byte[] generateProxyClass = ProxyGenerator.generateProxyClass(proxy.getClass().getSimpleName(), new Class[]{proxy.getClass()});
out = new FileOutputStream(proxy.getClass().getSimpleName() + ".class");
out.write(generateProxyClass);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
// TODO Auto-generated catch block
}
}
}
}
}
3.1.5、查看运行结果
可以看到,运行 test
方法之后,控制台打印出如下:
大家好! 我是程序员! 再见!
我们在 introduction
方法前和后都成功增加了功能,让这个 程序员 的自我介绍瞬间变得更加有礼貌了。
3.1.6、探探动态代理的秘密
动态代理的代码并不多,那么JDK底层是怎么帮我们实现的呢?
在测试的时候我们将动态生成的代理类的 class
字节码输出到了文件,我们可以反编译看看。
结果有点长,就不全部贴出来了,不过我们可以看到,里面有一个 introduction
方法如下:
/**
* the invocation handler for this proxy instance.
* @serial
*/
protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
public final void introduction() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
原来,生成的代理对象里面,引用了我们的 InvocationHandler
,然后在将 introduction
方法里面调用了 InvocationHandler
的 introduction
,而 InvocationHandler
是由我们编写的代理类,在这里我们增加了 sayHello
和 sayGoodBye
操作,然后还调用了原对象的 introduction
方法,就这样完成了动态代理。
3.2、CgLib动态代理
CgLib
动态
3.2.1、创建被代理对象
由于 CgLib
不需要实现接口,所以我们不需要创建接口文件了(当然,你要有接口也没有问题)
直接创建目标类,实现 introduction
方法
PersonalImpl.java
public class PersonalImpl {
public void introduction() {
System.out.println("我是程序员!");
}
}
3.2.2、创建代理类
同样,我们也需要创建代理类,并且在这里实现增强的逻辑,这次我们不是实现 InvocationHandler
接口了,而是实现 CgLib
提供的接口 MethodInterceptor
,都是类似的, MethodInterceptor
中,全部方法调用都会交给 intercept
处理,我们在 intercept
添加处理逻辑即可。
CgLibProxyFactory.java
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CgLibProxyFactory<T> implements MethodInterceptor {
/**
* 获取代理对象
*
* @param tClass 被代理的目标对象
* @return 代理对象
*/
public T getProxyByCgLib(Class<T> tClass) {
// 创建增强器
Enhancer enhancer = new Enhancer();
// 设置需要增强的类的类对象
enhancer.setSuperclass(tClass);
// 设置回调函数
enhancer.setCallback(this);
// 获取增强之后的代理对象
return (T) enhancer.create();
}
/**
* 代理类方法调用回调
*
* @param obj 这是代理对象,也就是[目标对象]的子类
* @param method [目标对象]的方法
* @param args 参数
* @param proxy 代理对象的方法
* @return 返回结果,返回给调用者
* @throws Throwable
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("大家好!");
Object result = proxy.invokeSuper(obj, args);
System.out.println("再见!");
return result;
}
}
3.2.3、编写测试代码
在刚才的测试方法中,我们添加一个 cglib
的测试方法:
@Test
public void testCgLibProxy() {
// 生成被代理的目标对象
PersonalImpl personal = new PersonalImpl();
// 获取代理类
CgLibProxyFactory<PersonalImpl> proxyFactory = new CgLibProxyFactory<>();
PersonalImpl proxy = proxyFactory.getProxyByCgLib((Class<PersonalImpl>) personal.getClass());
// 将proxy的class字节码输出到文件
generatorClass(proxy);
// 调用代理对象
proxy.introduction();
}
3.2.4、查看运行结果
运行测试用例,可以看到跟JDK的实现一样的效果
大家好! 我是程序员! 再见!
3.2.5、探探动态代理的秘密
跟JDK的测试一样,我们也来看看生成的 class
文件
public final void introduction() throws {
try {
super.h.invoke(this, m7, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
可以发现,与JDK的动态代理并没有区别。
4、如何选择
既然有两种实现方式,那么到底应该怎么选择呢?
就两个原则:
-
目标类有接口实现的,
JDK和CgLib都可以选择,你开心就好 -
目标类没有实现任何接口,那只能用
CgLib了
5、后记
其实在第一次看到动态代理的时候,我就想不明白,我们都把目标类 new
出来了,为什么还要将目标类丢给代理类呢?为什么不直接调用目标类对应的方法呢?
后来才发现,原来我没搞清楚动态代理的使用场景,场景很清晰,就是:
- 不修改原来已有的代码(满足设计模式的要求)
- 对已有方法进行 增强
关键是 增强 ,代理类里面我们是可以添加很多处理逻辑的,从而实现增强效果。就像黄牛抢票比我们厉害些一样。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 动态代理三部曲(一):动态代理模式及实现原理
- 你必须会的 JDK 动态代理和 CGLIB 动态代理
- 彻底搞懂jdk动态代理并自己动手写一个动态代理
- Java 静态代理和动态代理的使用及原理解析
- 动态代理与RPC
- 动态代理+注解(DynamicProxyAndAnnotations)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Machine Learning
Kevin Murphy / The MIT Press / 2012-9-18 / USD 90.00
Today's Web-enabled deluge of electronic data calls for automated methods of data analysis. Machine learning provides these, developing methods that can automatically detect patterns in data and then ......一起来看看 《Machine Learning》 这本书的介绍吧!