4种实例 advice aop_Java动态代理在Spring的应用:AOP编程与动态代理知识
关于代理模式的话题有很多,在开发中经常用到的应该是静态代理模式,能很好的去耦合。
动态代理是代理模式的另外一种实现。动态代理的区别在哪里?动态代理有什么好处?
今天我们来分析下这些问题。
回顾静态代理
之前我们分析过一次静态代理,
用代理模式优雅地写代码
一个典型的代理模式的 Proxy类像下面这样,
代理模式
RealSubject是需要被代理的对象。我们要在RealSubject之外增加一些处理,就增加一个Proxy类,实现Subject接口,在Proxy类里持有RealSubject的实例。
Client的请求全部打到Proxy实例上,由Proxy实例来控制是否将请求转发给RealSubject,或者做额外的处理。
代理模式的优点:对于外界来讲,完全无感知,耦合性低。
看一下实例代码:
这里对Integer类做了一层代理。这个类的作用在于,在compareTo请求之上增加了信息的打印输出。
在实际场景中,如果我们需要对多个方法都要做类似的处理,在每个地方都增加同样的代码,就显得有点不够优雅了。这时候,可以使用java的动态代理。
常用的动态代理有两种实现方式:JDK和CGLIBJDK动态代理
看名字就可以知道,这种动态代理是JDK本身就支持的,需要借助java.lang.reflect下的接口来实现。
创建代理对象
要想创建一个代理对象,需要使用Proxy类的newProxyInstance方法。
这个方法有三个参数:类加载器。作为Java安全模型的一部分,对于系统类和从因特网上下载下来的类,可以使用不同的类加载器。Class对象数组,每个元素都是需要实现的接口。调用处理器。
调用处理器
调用处理器是实现了InvocationHandler接口的类对象。在这个接口中只有一个方法:
动态代理类,需要实现此接口,在接口方法的实现里做代理逻辑的处理。
创建动态代理
创建一个动态代理对象的工作如下:获取 RealSubject上的所有接口列表;确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXXX ;
根据需要实现的接口信息,在代码中动态创建 该Proxy类的字节码;
将对应的字节码转换为对应的class 对象;创建InvocationHandler 实例handler,用来处理Proxy所有方法调用;
Proxy的class对象以创建的handler对象为参数,实例化一个proxy对象。
接下来我们看一段代码实现:
动态代理
前面提到,使用动态代理解决静态代理中重复代码的问题,其实就像是把全部需要代理执行的函数看成是一个可以动态执行的函数,把这个函数像针线一样,织入到需要执行的额外代码中间。如前面的日志输出,把函数织入到日志输出的代码中间。怎样能把函数动态执行?这就需要用到JAVA的反射技术了,这也是动态代理的关键。
JDK动态代理
知道了反射机制可以动态执行类对象,就容易理解动态代理了。在JDK中,已默认提供了动态代理的实现,它的关键点也是在于通过反射执行invoke来动态执行方法,主要实现流程如下:
- 实现InvocationHandler,由它来实现invoke方法,执行代理函数
- 使用Proxy类根据类的加载器及接口说明,创建代理类,同时关联委托类
- 使用代理类执行代理函数,则会调用invoke方法,完成代理
在示例代码中JdkLogProxyHandler类是日志输出代理类,代码如下:
public class ReflectionService { public void doSomething(){ System.out.println(" logging reflection service"); }}在客户端使用时,需要产生代理类,对的日志输出,执行如下(执行输出结果与静态代理功能一致):
//加载类Class> refClass = Class.forName("me.mason.demo.proxy.refrection.ReflectionService");//生成类对象Object refClassObject = refClass.getConstructor().newInstance();//调用类对象方法Method method = refClass.getDeclaredMethod("doSomething");method.invoke(refClassObject);这里把日志输出代理作为一类,把函数执行计时作为一类(JdkTimeProxyHandler),关注代理内容本身,而不是针对委托类的函数。这里的日志输出和函数执行计时,就是切面(后面会提到)。
可以比较一下,使用这种动态代理,与前面静态代理的区别:
- 代理不是固定在某个接口或固定的某个类,而在根据参数动态生成,不是固定(静态)的
- 在代理中无需针对接口的函数来一个一个实现,只需要针对代理的功能写一次即可
- 若有多个函数需要写日志输出,代理类无需再做修改,执行函数时会自动invoke来完成,就像把函数织入到代码中。这样就解决了前面静态代理的局限。
JDK动态代理与限制
JDK默认提供的动态代理机制使用起来很简单方便,但它也有相应的限制,就是只能动态代理实现了接口的类,如果类没有实现接口,只是单纯的一个类,则没有办法使用InvocationHandler的方式来动态代理了。此时,就需要用到CGLIB来代理。
CGLIB动态代理
CGLIB(Code Generator Library)是一个强大的、高性能的代码生成库。CGLIB代理主要通过对字节码的操作,为对象引入间接级别,以控制对象的访问。针对上面没有实现接口的类,CGLIB主要是通过继承来完成动态代理的。在使用方法上,主要也是有3个步骤:
- 实现MethodInterceptor接口,在intercept方法中实现代理内容(如日志输出)
- 使用Enhancer及委托类生成代理类
- 使用代理类执行函数,就会动态调用intercept方法的实现
如下所示是使用CGLIB来实现类的动态代理:
/** * 日志动态代理:cglib实现 **/public class CglibLogProxyInterceptor implements MethodInterceptor { @Override public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println(" cglib dynamic proxy log begin "); Object result = methodProxy.invokeSuper(object, args); System.out.println(" cglib dynamic proxy log begin "); return result; } /** * 动态创建代理 * * @param cls 委托类 * @return */ public static T createProxy(Class cls) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(cls); enhancer.setCallback(new CglibLogProxyInterceptor()); return (T) enhancer.create(); }}从上面代码可知道,代理类是通过Enhancer设置委托类为父类(setsuperclass),并把当前的intercept方法作为回调,以此创建代理类,在客户端执行代理时,则会执行回调,从而达到代理效果,客户端执行如下:
@Testvoid testLogProxy() { CglibService proxy = CglibLogProxyInterceptor.createProxy(CglibService.class); proxy.doAction1(); System.out.println("############"); proxy.doAction2();}动态代理在Spring的应用:AOP
前面提到JDK的默认动态代理和CGLIB动态代理,在Spring中,AOP(面向切面编程)就是使用这两个技术实现的(如果有实现接口的类使用JDK动态代理,没有实现接口的类则使用CGLIB)。具体到在Spring应用中,如何使用AOP进行切面编程,示例代码中使用springboot工程,模拟提供user的增删改查的REST接口,通过切面对所有Service类的函数统一进行日志输出。
AOP 概念
关于AOP的概念,从理解这两个问题开始,即代理发生在什么地方,以什么样的形式添加额外功能代码。
- 切面(Aspect):前面提到的日志输出代理和函数执行计时代理,它们其实都是与业务逻辑无关,只是在各个业务逻辑中都添加功能,这种代理就是切面。将横切关注点与业务逻辑分离的编程方式,每个横切关注点都集中在一个地方,而不是分散在多处代码中。
- 切点(PointCut):明确什么地方需要添加额外功能,这些地方有可能是一类函数(比如有多个函数都需要输出日志),因此需要使用一定的规则定义是哪一类函数。
- 连接点(JoinPoint):就是具体被拦截添加额外功能的地方,其实就是执行的某一个具体函数,是前面切点定义的其中一个函数。
- 通知(advice):明确以什么样的形式添加额外功能,可以在函数执行前(before),后(after),环绕(around),函数正常返回后通知(afterReturning)和异常返回后通知(afterThrowing)。
在AOP编程中,上面提到的概念,都有对应的注解进行使用,通过注解,就可以实现切面功能。
AOP编程
引入aop依赖
Springboot有提供aop的starter,添加以下依赖,即可使用AOP相关功能。
org.springframework.boot spring-boot-starter-aop定义切面、切点与通知
本示例的需求是对service包下所有类的全部函数统一进行日志输出。因此我们定义一个LogAopAspect作为这个日志输出功能的切面(使用注解@Aspect),使用@Pointcut来确定输出点的匹配规则是service这个包下所有类的全部函数。当真正某个函数执行时,通过动态代理执行通知(使用注解@Before、@After,@Around等)。具体的输出动作,也就是在这些通知里。
@Slf4j@Aspect@Componentpublic class LogAopAspect { /** * 切点:对service包中所有方法进行织入 */ @Pointcut("execution(* me.mason.demo.proxy.springaop.service.*.*(..))") private void allServiceMethodPointCut() {} @Before("allServiceMethodPointCut()") public void before() { log.info(" spring aop before log begin ");} @AfterReturning("allServiceMethodPointCut()") public void after() { log.info(" spring aop before log end ");} /** * 环绕通知,需要返回调用结果 */ @Around("allServiceMethodPointCut()") public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { log.info(" spring aop around log begin "); try { return proceedingJoinPoint.proceed(); } finally { log.info(" spring aop around log end "); } }}通过上面的类定义,即可完成动态代理,而不需要像上面的JDK和GCLIB那样自己实现接口来操作。
AOP的底层实现依然是使用JDK和CGLIB来实现动态代理的,若类有实现接口则使用JDK,没有则使用CGLIB。 Pointcut的定义规则是指示器+正则式,指示器有参数定义(agrs),执行方法(execution),指定对象(target),指定类型(within)及相应的注解(使用@开头)。正则式中*表示任何内容,(..)表示任意参数匹配。示例中execution(* me.mason.demo.proxy.springaop.service.*.*(..))表示对执行方法进行拦截,拦截的是me.mason.demo.proxy.springaop.service包下的所有类的所有函数,返回值不限,参数不限。 环绕通知(Around)需要有返回值来返回连接点执行后的结果。
总结
本文对JAVA的动态代理知识进行了梳理,先从代理模式说起,使用静态代理实现简单的外加功能,并通过示例实现JDK和CGLIB两种动态代理功能,最后结合springboot示例,使用AOP编程,实现对关心的类进行日志输出的切面功能。通过动态代理,我们可以把一些辅助性的功能抽取出来,在不修改业务逻辑的情况下,完成辅助功能的添加。所以当你需要添加新功能,又不想修改原代码的情况下,就用动态代理吧!
认真写文章,用心做分享。公众号:Java耕耘者 文章都会在里面更新,整理的资料也会放在里面。
总结
以上是生活随笔为你收集整理的4种实例 advice aop_Java动态代理在Spring的应用:AOP编程与动态代理知识的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: flink启动命令参数_Flink集群部
- 下一篇: java 定时删除_Java编写定时删除