谈谈我对Spring AOP的理解

认识切面

Spring AOP是Spring最核心的功能之一,AOP中最关键的词也就是Aspect——切面。我刚开始接触这个词的时候Java编程水平还不高(当然,现在也不高0。0),只觉面向对象的意义还没有弄清楚,又出来一个面向切面,直呼难哉。

​如今再次学习到此处,发现所谓切面只是场景,本质还是对象之间的操作,只不过是用动态代理的方式,对原有方法的特定位置(如方法执行前,执行后,异常后等)进行增加一些操作,而增加的这些操作大多是重复的,所以为了减少冗余的代码,我们可以利用Spring的AOP把业务功能的一些重复代码进行抽取,放入切面,再对原有业务的执行进行一种监控。

​我们在编写单元测试的时候,如果是DAO的一些操作,一般都是拿到数据源,进行操作,提交事务,释放资源。

//伪代码
public class UnitTest{
    @Test
    public void testAdd(){
        //获取连接、数据源
        
        try{
            //数据库操作
            //...
            
            //事务提交
        }catch{
            //回滚事务
        }finally{
            //释放资源
        }
    }
    @Test
    public void testAdd(){
        //重复的代码又来一遍
    }
    @Test
    public void testUpdate(){
        //重复的代码又来一遍
    }
}

​真正有意义的代码有可能只是一次很简单的增、删、改操作,但还是不得不按照这样的流程编码,有经验的码农就会这样写:

//伪代码
public class UnitTest{
    //把数据源提取到成员变量
    @Before
    public void SetUp(){
        //获取连接、数据源
    }
    @Test
    public void testAdd(){
        try{
            //数据库操作
            //...
            
            //事务提交
        }catch{
            //回滚事务
        }
    }
    @Test
    public void testAdd(){
        //重复的代码又来一遍
    }
    @Test
    public void testUpdate(){
        //重复的代码又来一遍
    }
    @After
    public void close(){
        //释放资源。
    }
}

我们发现,这样编写单元测试,我们的每一个测试单元都可以省只做一次头(获取连接)和尾(关闭资源)的操作。但是每次还是需要写事务提交,事务回滚,那咋办呢?这个时候,AOP应运而生了…

切面怎么用?

知道了场景,我们来说一说切面怎么用,也就是它在代码中的体现是什么样的。

还是先从伪代码说起吧,首先声明一个切面,其实就是一个类:

public class 切面{
    //定义前置操作,我被管理的方法执行之前要先干什么?,其实就是在这再定义一个方法,一般叫before
    
    //方法正常运行结束后,执行什么操作?再定义一个方法,一般叫afterReturning
    
    //方法出现了指定异常,执行什么操作?还是先定义一个方法,一般叫afterThrowing
    
    //方法无论是异常还是正常,我都要关闭资源啊,再来个方法,叫after
    
    //之前的方法内容过时了,我想改动一下,但没办法修改之前的方法了,怎么办?我在这里再写一个方法,把之前的方法包裹起来,控制原有方法的执行,在原有方法的基础上进行操作就行了,这个方法比较特殊,他有参数,也有原方法执行的代码,我们暂且叫他around。
}

现在切面有了,比如我的切面方法这样定义:

  • before:获取连接
  • afterReturing: 提交事务
  • afterThrowing:回滚事务
  • after:关闭连接
  • around:你先去凉快会儿。一会再说你。

这个时候要写dao的类了,其实一般我们都会用切面去切service层,我一开始举例用了dao,就暂且继续用dao吧。

public class 被切的类{
    //数据库增操作  addXXX方法
    //数据库删操作  deleteXXX方法
    //数据库改操作  updateXXX方法
}

现在问题来了,怎么把这两个关联起来?怎么让切面切进去?

一般有两种方式,XML和注解都可以。这里用伪xml举例。

我是前边一堆的约束文件...

把定义的切面类也放到容器中,这时他有了一个可爱的id。

告诉spring这里有个切面,他的id是XXX。
	告诉spring这个切面的before方法,作用到哪些类的哪些方法上。spring就会自动在那些方法执行前,执行before了。
	同理,指定afterReturning的作用载体
	...
这样一个切面就配置好了。

PS:在指定方法载体的时候,可以是项目中的任意方法,一般是整个service层,就好像service层被这个切面监视了,无论你要干嘛,都要经过切面,看上去这个切面的类把整个service层的类横切了一刀,被切的类都会受到这个切面的影响。当然,要监视哪些方法,哪些方法不需要增强,也是可以通过切面过滤的。

这样,一个切面就写完了,当你再次执行dao的测试的时候,你只需要写业务代码就行了。当然,这里又涉及到切面中的事务所用的数据源要与dao用的数据源是同一个,这个问题的解决可以用threadlocal,也可以进行传参,不过这个问题不在本篇文章讨论范围,大家知道有这回事需要注意就行了。

然后接下来开始用真代码写一个模拟的例子。为了简便,就是模拟真实操作改成控制台输出,代表方法执行了即可。

终于开始切面编程

简单搭建一下环境:

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!--spring myaop-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

建一个切面类,就叫 MyAspect.java

package pers.buyusan.myaop;

/**
 * @author buyusan
 * @Description
 * @date 2019-07-16 22:14
 **/
public class MyAspect {
    //模拟拿到连接
    public void beforeDao(){
        System.out.println("资源的获取,拿到连接...");
    }
    //模拟事务提交
    public void commitDao(){
        System.out.println("提交事务...");
    }

    //事务回滚
    public void rollbackDao(){
        System.out.println("事务回滚了..");
    }

    //关闭资源
    public void closeDao(){
        System.out.println("关闭资源...");
    }
}

创建一个被监视的类:MyService.java

package pers.buyusan.service;

/**
 * @author buyusan
 * @Description
 * @date 2019-07-16 22:18
 **/
public class MyService{
    public void addData(){
        System.out.println("添加数据...");
    }
    public void updateData(){
        System.out.println("更新数据...");
    }
    public void deleteData(){
        System.out.println("删除数据...");
    }
}

配置Spring文件:ApplicationContext.xml

<bean id="myAspect" class="pers.buyusan.myaop.MyAspect"/>
<bean id="myService" class="pers.buyusan.service.MyService"/>
<aop:config>
    <aop:aspect ref="myAspect">
        <aop:before method="beforeDao" 
                    pointcut="execution(* pers.buyusan.service.MyService.*(..))"/>
        <aop:after-returning method="commitDao" 
                             pointcut="execution(* pers.buyusan.service.MyService.*(..))"/>
        <aop:after-throwing method="rollbackDao" 
                            pointcut="execution(* pers.buyusan.service.MyService.*(..))"/>
        <aop:after method="closeDao" 
                   pointcut="execution(void *.buyusan.service.MyService.*(..))"/>
    </aop:aspect>
</aop:config>

来个测试类:

public class TestAop {
    @Test
    public void testAdd(){
        //加载容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        MyService myService = ac.getBean("myService", MyService.class);
        //随便测一个方法
        myService.updateData();
    }
}

结果:

在这里插入图片描述
如果异常了呢?手动添加一个异常:
在这里插入图片描述
结果:
在这里插入图片描述

提高一下逼格

程序已经写完了,我们可以看到切面编程并不难理解,但以上是用我自己的理解的语言去描述的,官方的定义直接拿过来是很难接受和理解的,现在我们在理解的情况下,去和官方定义的术语进行匹配。

  • Aspect:一般翻译叫做切面,这个没什么争议,我们的MyAspect类就是一个切面,如果想让Spring容器认识这个切面,需要在容器中进行配置。
  • join point:正常翻译叫做连接点,就是需要被监视的点就叫连接点。连接点可以包括多个方法。
  • point cut:也叫切入点,或者切点,就是具体到被拦截的方法,我们例子中的XXXData方法就是切点,意为spring通过这个方法切进来进行干涉,spring中切点就是连接点。因为切点指方法,连接点更加广义,但是spring中只支持方法作为连接点。
  • Advice:通知,也叫增强,具体指的就是拦截到方法后做的具体的操作,通知共五种:
    • before:作用在被增强方法之前。一般翻译成:前置通知。
    • afterReturning:作用在被增强方法正常结束之后。有人翻译成返回通知,有的人翻译成后置通知。
    • afterThrowing:作用在被增强方法出现异常后。有人翻译成异常通知、或者方法异常通知。
    • after:作用在方法结束后,不管是否正常结束。有人翻译成后置通知,有人翻译成最终通知。
    • around:作用在方法之上,对被增强的方法再次进行包裹,替代被增强的方法。环绕通知。
  • Weaving:织入,代表被增强方法被监视到后进入切面的流程中的过程。
  • Introduction:叫引介或者引入,是一种特殊的通知,可以动态增加方法或者field(其实我也不太懂)
  • Proxy:代理,切点织入切面后,底层生成的是一个代理方法,其实真正执行的就是这个代理。

总结和注意

  1. 对于通知和一些术语的翻译,各个版本参差不齐,最直接的方式是直接用英语原文,以免产生歧义。
  2. 环绕通知没有进行演示,有兴趣可自行加入代码查看效果。
  3. 切面的存在是需要Spring支持的,如果连接点不在容器中,是无法进入切面的。
  4. 连接点所在的类在底层会被动态代理生成新的代理对象,Spring根据有无接口来决定使用jdk代理还是cglib代理。
  5. xml方式,after通知会在最后执行,而注解方式下,after会先执行,然后再执行afterReturning或者afterThrowing,所以我认为把after叫做最终通知不合理,因为它没有在最后执行。叫做后置通知与before对应更加亲民一些。
  6. before可以对方法的参数进行先一步的处理。
  7. afterReturning可以处理方法的返回值,所以我认为它被称作返回通知更合理一些。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值