【AOP系列】自己动手实现一个JDK的Proxy类(四)
思路:
1.通过反射拿到一个类的Class对象。然后调用getMethods()方法。
2.拿到要代理类的方法后,可以得到原方法的所有信息,如(方法访问标识符,方法返回值,方法名称,方法参数)
3.可以使用字符串的形式拼接出原来的方法,并在方法前和方法后加入自己的代码逻辑。
4.把拼接好的代理类写入到一个Java文件中。这样我们就生成了代理的类。(有点像代码生成器)
5.编译这个.java的文件。并生成对应的class文件。
6.使用类加载器加载生成的class类。并返回。此时的对象,在拼接的时候已经对原有方法进行了增强。
实现
InvocationHandler.java
1 | public interface InvocationHandler { |
注意,我们写的这个接口和JDK的区别,少了第一个参数Object target
Log.java
1 | public class Log implements InvocationHandler { |
写一个类实现上面的接口,并在invoke前添加输出语句。
由于自己写Proxy类的实现,需生成byte[]加载到内存,需要自己定义ClassLoder,so
MyClassLoader.java
1 | public class MyClassLoader extends ClassLoader { |
最后是最重要的Proxy类
1 | public class Proxy { |
OK写完了。代码有点长。写的有点乱了。没有整理,把很多东西都放在一个方法里了。
其实不难发现这里的实现方式,和JDK的还是有一些不一样的。
JDK是直接根据class文件的格式直接构造class类型的byte[],
由于Class文件结构较为复杂,
而这里则是先用字符串拼接一个Java文件,
再用JavaCompiler去编译这个java文件。生成一个class文件。
再通过MyClassLoader把这个生成好的class文件Load到内存。得到Class对象。
虽然实现方式不同,但是最终目的效果是差不多的。
运行结果
连ArrarList都能被代理了???
不是说JDK的Proxy代理只能代理接口吗。为啥ArrayList都能被代理了。
思考。
为啥$Proxy0一定要继承Proxy呢?
在代理类中,与Proxy有关的只有Proxy的变量protected InvocationHandler h;这个InvocationHandler实例的引用,在调用接口方法时实际调用的是super.h.invoke(this, method, args), 如果仅仅为了这个变量,完全可以通过别的方法传过去。
既然我不知道为啥一定要继承这个Proxy。那我自己实现时我就不继承Proxy了。这样我生成的代理类就可以继承其他类了。既然可以继承其他类。那是不是就实现了对类的代理?
注意这两行代码。
1 | String relation = (clazz.isInterface() ? " implements " : " extends ") + interfaceName; |
className我依然模仿了JDK的命名方式$Proxy+递增序号,而继承关系则是,根据接口的类型不同,选择是实现,还是继承。这样我的Proxy就可以 支持普通类的代理了。虽然看上去,上面已经实现了对List接口和ArrayList类的动态代理。但是真的就没问题吗?真的就可以代理所有的接口和类了吗?
注意Proxy类中的这一行代码
1 | String relation = (clazz.isInterface() ? " implements " : " extends ") + interfaceName; |
如果是接口用implements 否则是类则extends,下面我们使用上面的Proxy尝试去代理Integer类。
尝试代理Integer类。
他说Integer是被final修饰过的。就是说被final定义的类是无法代理的;
一个错误的思路
其实我们可以换个思路。既然不能被继承,那我们就不继承,只把原来类中的方法重新实现一遍不就好了吗?
生成代理类的时候直接不继承,而是把Integer类的所有方法实现一遍。反正都是动态生成的。
好的,那我们试试。把
1 | String relation = (clazz.isInterface() ? " implements " : " extends ") + interfaceName; |
注释掉,换成
1 | String relation = clazz.isInterface() ? " implements " + interfaceName : " "; |
这样的语义是:如果是接口的话就实现。如果不是接口的话,就不做任何处理,只是生成对应一模一样的方法。
再次运行
现在清楚了,虽然生成了一模一样的类,但是现在就不算代理了,因为两个类已经没啥联系了,所以生成的Proxy类当然不能转换成Integer了
思考
JDK为啥不能实现普通类的动态代理?
因为生成的代理类继承Proxy,(这是网上很多人给的答案)
但是$Proxy0为啥要继承Proxy呢?
或许$Proxy0也可以不继承Proxy其实也是不影响的。(学术浅薄,仅仅猜测)。-_-~
不好意思,大佬们。在此对这个问题提出质疑。
jdk的动态代理及为什么需要接口 作者:XyGoodCode
或许是JDK的设计者们是这样考虑的??
因为无法知道被代理的类是否被final修饰了。如果被final修饰了,就实现不了代理。
而接口是不能被final关键字修饰的。
注意 接口中成员变量已默认是final了,而接口的定义和方法的定义是不能被final修饰的。
参考文章
Java 动态代理机制分析及扩展 作者:王忠平和何平
JDK 动态代理分析 作者:沉木
1 | { |