前言: 早期学习了动态代理在实际开发中的使用场景和使用方法,我们也知道了最经典的mybatis的mapper就是采用动态代理来实现的,那么动态代理的背后是怎样的原理?为什么能实现动态代理?为什么动态代理只可以代理接口,而无法代理普通类?为什么动态代理需要传入类的classLoder和接口?带着这些疑问,我们来开启本期的主题:探究动态代理的内部原理。
本篇博客的目录
一:动态代理的基本使用方法
二:动态代理的内部运行过程
三:几个相关的问题
四:总结
一:动态代理的基本使用方法
1.1:简单例子
首先我们来模拟一个简单的动态代理的过程:某歌手去参加一个晚会,需要唱歌,他在演奏的过程中需要别人来报幕:演奏开始、演奏结束,每个歌手都遵循这样的过程,在歌手进行表演的过程,穿插着主持人的开场白和结语,我们来用代码模拟这个场景:
1.2:代理接口
首先我们来定义一个singer接口表示我们将要代理的接口:
public interface Singer {
/**
* 表演
* @param soonName
*/
public void perform(String soonName);
}
1.3:接口的具体实现类
public class Jay implements Singer {
public void perform(String soonName) {
System.out.println("接下来我为大家唱一首"+soonName);
}
}
1.4:辅助类,用来模拟注册人的前后台词
public class Presenter {
public void before(){
System.out.println("请开始你的表演!");
}
public void after(){
System.out.println("表演结束,大家鼓掌!");
}
}
1.5:具体的代理类
这里用proxy.newProxyInstance来创建一个代理类,传入原始类的类加载器和接口与接口InvocationHandler,同时插入Presenter类的before与after方法,用于前置和后置处理
public class SingerProxy {
private Presenter presenter;
public SingerProxy(Presenter presenter){
this.presenter = presenter;
}
/**
* 获取代理对象
* @return
*/
public Singer getProxy(){
final Singer jay = new Jay();
Singer singerProxy = (Singer)Proxy.newProxyInstance(jay.getClass().getClassLoader(), jay.getClass().getInterfaces(), new InvocationHandler() {」
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
presenter.before();
method.invoke(jay, args);
presenter.after();
return null;
}
});
return singerProxy;
}
}
1.6:测试类
public class Test {
public static void main(String[] args) {
SingerProxy singerProxy = new SingerProxy(new Presenter());
Singer proxy = singerProxy.getProxy();
proxy.perform("《夜曲》");
}
}
输出:
二:动态代理的内部探究
从上面的例子可以看出我们首先生成了一个代理类,然后用代理类来调用原始接口的方法,就可以实现我们的预设的逻辑,在原始接口的前后(或者出现异常的时候)插入我们想要的逻辑,那么究竟是为什么呢?
2.1:找到生成的代理类
我们如果需要打开生成的类,首先需要在测试类中添加这行代码,设置系统属性来保存生成的代理类的class文件:
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
2.2:singerProxy类
通过动态代理生成的代理类名为:$Proxy0.class然后通过intelj idea反编译之后源代码是这样的,这里主要看到有4个方法,method的m1\m2\m3\m0;分别由反射获取的equals()、toString()、perform()、hashcode()方法,同时代理类继承了proxy并且实现了原始Singer接口,重写了perform()方法,所以这就解释了为什么代理类可以调用perform()方法,在perform方法中,又调用了父类中的InvoationHander的invoke方法,并且传入原始接口中的方法,而invoke方法在我们在创建代理类的时候重写过,所以就会按照我们自定义的逻辑调用invoke方法,按照顺序执行