Java 动态代理以及自定义注解
关于Java中的动态代理,是一种常用的设计模式,即代理模式,根据创建代理的方式,又可以分为静态代理和动态代理
代理模式
代理模式是常用的java设计模式,他的特征是代理类和委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等,代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联。代理类的对象本身并不会去实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务,在程序的编译过程中,我们访问实际的对象,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。在这里我们主要总结一下动态代理的实现。
为何使用代理模式?
如何去理解代理模式
这里我们举个栗子来描述一下简单的代理模式
这里有个打印机类
1 | public class Printer { |
如果说为了验证打印的资料是否正确,我却要在打印机打印之前加上一段记录日志
1 | public class Printer { |
感觉没有问题,但是注意我们修改了打印机的源代码,这里便破坏了面向对象的开闭原则,有可能会影响到其他功能,那应该去如何解决呢?
PS 开闭原则:软件中的对象(类, 模块, 函数等等)应该对于扩展是开放的,但是对于修改是封闭的
我可以新建一个类
1 | public class LogPrinter extends Printer { |
我这个类继承了打印机的类,重写了打印机print的方法,提供了记录日志的功能
我们还可以抽象出一个接口:
1 | public interface IPrinter { |
通过打印机去实现这个接口
1 | public class Printer implements IPrinter { |
创建打印机代理类也实现该接口,在构造函数中将打印机对象传进去,实现接口的打印方法时调用打印机对象的打印方法并在前面加上记录日志的功能:
1 | public class PrinterProxy implements IPrinter { |
以后我们就可以直接实例化PrinterProxy对象调用它的打印方法了,这就是静态代理模式,通过抽象出接口让程序的扩展性和灵活性更高了。
但是在一个服务中,类似日志记录,监控,安全处理的代码涉及到的业务和类很多,让他们都实现这样的静态代理显然是不现实的,于是我们的动态代理类便闪亮登场了。
要想不重复写记录日志的功能,针对每一个接口实现一个代理类的做法肯定不可行了,可不可以让这些代理类的对象自动生成呢?
Jdk提供了invocationHandler接口和Proxy类,借助这两个工具可以达到我们想要的效果。
1 | //Object proxy:被代理的对象 |
之类我们新创建一个动态代理对象
Proxy.newProxyInstance,非常重要的方法,我们来看一下如何实现的呢
1 | //CLassLoader loader:被代理对象的类加载器 |
接口IPrinter 和 该接口的实现类 Printer的代码同前。
实现一个类,该类用来创建代理对象,它实现了InvocationHandler接口
1 | public class ProxyHandler implements InvocationHandler { |
被代理的对象targetObject可以通过方法参数传进来:
1 | public Object newProxyInstance(Object targetObject){ |
1 | return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(),this); |
示例示范
show the code
在这里我们先创建一个自定义注解, @Target(ElementType.METHOD) 表明注解是使用在方法上的,@Retention(RetentionPolicy.RUNTIME) 代表是在运行时注解,这里就可以通过反射取到,如果使用@Documented标注了,在生成javadoc的时候就会把@Documented注解给显示出来。
1 | package Example.ProxyExample; |
当然再创建一个在类上面使用的
1 | package Example.ProxyExample; |
我们自定义个方法拦截器,invoke()里面便是对方法的增强,在这里通过反射来取到注解中的属性
1 | package Example.ProxyExample; |
创建一个动态代理的工厂类
1 | package Example.ProxyExample; |
接下来就是我们主要的main方法了
1 | package Example.ProxyExample; |
运行实例
1 | 现在进入代理对象进行对象增强了 |
参考应用资料: