猿蜕变14——一文搞懂AOP的套路

时间:2022-07-22
本文章向大家介绍猿蜕变14——一文搞懂AOP的套路,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

看过之前的蜕变系列文章,相信你对mybatis有了应用方面的认识。但是这些要完成你的蜕变还不够,考虑到大家的基础知识,我们继续回到spring的话题上来,我们一起聊一聊AOP。

AOP是Aspect OrientedProgramming的缩写,中文译名,面向切面编程。我们曾经为了解决字符编码问题,编写了Filter对所有请求的字符编码进行了统一的字符处理。我们配置了Filter之后,请求要先经过Filter之后,才会交给servlet进行处理。Filter中实现的代码,直接强行插入到了Servlet的代码之前。相对于Servlet来说,Filter像一把砍刀一样,把原本正常执行的请求流程横切了,只不过,这个切入点,是固定在了在执行Servlet代码之前。这种切面是静态的。AOP其实是一种编程思想:在不修改源代码的情况下,将实现了某方面功能的代码切入到原有程序的指定位置的一种思想。

当然,我这样讲可能太抽象了,一般情况下,很多其它的教材一般是AOP的从目的出发的:在实际开发中,我们需要添加一些和业务无关的代码,比如打日志,提交数据库事务等等。这些操作和业务无关,代码写法又比较固定,我们可以抽取出来,放到一个统一的地方去处理…诚然,是可以将和业务无关的代码抽取出来,实现非业务功能和业务逻辑的分离,让大家更加专注于业务逻辑,但是不要忘了,AOP可不仅仅能干这个噢。至于能干点儿啥新鲜的咱们后面再说。

下面这张图很形象的说明了AOP

提到AOP,那么就不得不搞懂几个概念。

1) Aspect :切面,既然是面向切面编程,没有切面还面向啥呢?什么是切面,切面是一个方面,代表需要解决的一个方面的问题,比如日志打印、比如事务管理、比如异常处理等等。

2)Join point :连接点,也就是横向切入的位置,程序执行的某个位置;

3) Pointcut :切点,符合切点规则的连接点集合(一个切点包含一个或多个连接点),也就代码真正需要被切入执行的地方;

4) Advice :增强,也就是一段代码,面向切面不是要解决某个方面的问题吗?解决问题是需要编写代码的,Advice就是代码的地方,也就是某个连接点,需要执行的具体操作(在Spring中分为: Before advice , After returning advice , After throwing advice ,After (finally) advice , Around advice );

5)Introduction:引介,一种特殊的增强方式,给一个类增加属性和方法,让原有的类具备增加新的功能和特性。

6)Weaving:织入,织入是指将增强设置到连接点的具体过程。

7)aop代理(AOP proxy)

spring中的aop代理有两种:jdk自带的动态代理和CGLIB代理。

上面说了一大堆,我们看点简单的原理性质的东西,——如何才能在不修改源代码的基础上实现新的功能呢?我们带着例子一步一步来看,我们有一个HelloService还有一个实现类HelloServiceImpl:

package com.pz.study.frame.aop.service;
 
public interface HelloService {
      
       public void sayHello();
}
package com.pz.study.frame.aop.service.impl;
 
import com.pz.study.frame.aop.service.HelloService;
 
public class HelloServiceImpl implements HelloService {
 
       @Override
       public void sayHello() {
              System.out.println("sayHello");
 
       }
 
}

还有一个测试用例

package com.pz.study.frame.aop.test;
 
import org.junit.Test;
 
import com.pz.study.frame.aop.service.HelloService;
import com.pz.study.frame.aop.service.impl.HelloServiceImpl;
 
public class TestDemo {
      
       @Test
       public voidtestSayHello(){
             
              HelloService helloService = new HelloServiceImpl();
             
              helloService.sayHello();
             
             
       }
 
}

我们知道,HelloServiceImpl的sayHello方法会输出”sayHello”。我们有没有什么办法在不修改HelloServiceImpl代码的情况下,让sayHello的方法做一点别的事情呢?别想了,直接看例子:

编写代码:

package com.pz.study.frame.aop.invocation;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
 
public class HelloInvocationHandler implements InvocationHandler {
      
    private Object target;
   
    public HelloInvocationHandler(Object target) {
        this.target = target;
    }
 
       @Override
       public Object invoke(Object proxy, Method method,Object[] args)
                     throws Throwable {
              method.invoke(target, args);
              System.out.println("hello world");
             
              return null;
       }
 
}

编写测试用例

@Test
       public void testSayHello2(){
             
              HelloInvocationHandler handler = new HelloInvocationHandler( new HelloServiceImpl() );
             
              HelloService helloService =(HelloService)Proxy.newProxyInstance(HelloService.class.getClassLoader(),  newClass<?>[]{HelloService.class},handler);
             
              helloService.sayHello();
       }

运行测试用例控制台输出:

sayHello

hello world

这是什么鬼?没有修改HelloServiceImpl的情况下,调用了helloService.sayHello()方法,结果多输出了一行hello world!

这就是JDK的动态代理,动态代理?那什么是动态代理?动态代理是一种机制:在代码的运行时期,使用Proxy给某个接口创建一个代理对象。接口将方法委托给代理对象执行,JDK提供的动态代理是针对接口的,只能做接口增强的事情,而不能给某个类做增强的事情。要实现动态代理,需要以下步骤:

1.定义一个类实现java.lang.relect.invocation接口重写invoke方法,实现调用接口的具体逻辑。

这里要特别说一下,method.invoke方法会调用目标对象的方法(接口实现类),这个方法要不要调用根据具体的需求来看,不是一定要调用的。把一件自己要做的事情交给别人来完成,那个别人就是你的代理人,怎么做是代理人的事 情,结果怎么样也是他的事情。

2.使用Proxy.newProxyInstance来创建代理对象需要3个参数,ClassLoader,接口数组,需要处理接口方法调用的InvocationHandler对象。

3.将Proxy.newProxyInstance返回的参数强制转换为接口类型。

已经忘记猿思考系列5——一文明白java和微商那点儿事儿,的同学,好好看看,懂了这些再看下一章节spring 的AOP的用法,喝水一样简单。