AOP在Spring中的应用

AOP(面向切面编程)

  在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

  AOP是OOP的扩展和延伸,并不能取代OOP,AOP为OOP不能解决的问题提供了很好的方式。

  例如,我们现在需要在持久层的某个save方法前面加一个权限校验的方法。

  • 按照正常的想法,我们会在dao类上添加一个check权限的方法然后在调用save方法之前调用check方法,但是问题来了,当出现成千上百的保存方法的时候,我们就需要在成百上千的类中添加check校验方法,这样就很麻烦

  • 使用纵向继承的方式,我们可以编写一个BaseDao类,这个类中我们编写一个check校验方法,当我们某各类需要加入权限校验方法的时候,我们只需要继承BaseDao类并调用BaseDao类中的check方法就行了。但是,使用继承的方式虽然说比上面的方法好一些,但是也并不尽人意,比如当我们这个权限校验方法不需要的时候,我们必须删除这些extends和子类调用BaseDao的check方法。AOP为我们解决这类问题提供了很好的方案。

  • AOP(横向抽取),其实AOP的原理就是使用了Java中的代理模式,具体可以参考这篇文章。这时候我们在把save方法抽取出来,我们使用Proxy模式增强save方法。

AOP的好处和Spring AOP底层实现

  对程序进行曾倩,不修改源码的情况下,AOP可以进行权限校验,日志记录,性能监控,事务控制等功能。

  Spring AOP底层使用动态代理模式,在被代理类实现某种接口的时候使用JDK动态代理,在被代理类没有实现某个接口的时候使用cglib动态代理。

AOP相关术语

  首先我们先列出需要实现AOP的代码类

1
2
3
4
5
6
public class UserDao{
public void save(){}
public void find(){}
public void update(){}
public void delete(){}
}

  我们对照上面代码看

  • JoinPoint:连接点,可以被拦截到的点。这里四个crud方法都是可以进行方法增强的,所以这四个方法都可以被称为JoinPoint

  • PointCut:切入点,真正被拦截的点,如果这四个方法中我们只对save方法进行了增强,那么save方法就是PointCut。

  • Advice:通知增强,假如我们现在对save方法进行权限校验,那么这个check方法就称为通知或者增强,而且这是方法层面的增强

  • Introduction:引介,类方面的增强

  • Target:被增强的对象,如果现在我们对UserDao这个类增强,那么UserDao就是Target

  • Waving:织入,它是一个过程,指的是我们将通知(Advice)应用到目标(Target)过程,将全县校验的方法的代码应用到UserDao的save方法上的过程。

  • Proxy:代理对象,一个类被AOP织入增强之后就产生了一个结果代理类

  • Aspect:切面,是切入点和通知的结合。

Spring AOP的入门开发

AOP的配置

1
2
3
4
5
6
7
8
9
<aop:config>
<aop:pointcut expression="execution(表达式)" id="切入点的id可以随便取,下面要对应"/>
<!--这里配置切面类-->
<!--前提是切面类需要交给Spring管理-->
<aop:aspect ref="某个切面类">
<!--这里配置通知增强方法,这里是前置增强-->
<aop:before method="切面类里面的增强方法" pointcut-ref="切入点的id"/>
</aop:aspect>
</aop:config>

通知的类型

  • 前置通知 目标方法执行前进行操作(权限校验..)

    1
    <aop:before method="xxx" pointcut-ref="xxx">
  • 后置通知 目标方法执行之后进行操作(日志..),可以获得被增强方法返回值

    1
    2
    <aop:after-returning method="xxx" pointcut-ref="xxx" returning="returning对应的名字">
    <!--后置增强方法里面传入 Object 与returning对应的名字-->
  • 环绕通知 目标方法执行之后之前进行操作,可以控制方法是否执行

1
<aop:round method="around" pointcut-ref="切入点">
1
2
3
4
5
6
7
8
9
10
11
//环绕增强方法实例
//返回值必须是Object around(ProceedingJoinPoint joinPoint) throws Throwable{

}
public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("环绕前");
//执行切入点的方法
Object obj =joinPoint.proceed();
System.out.println("环绕后");
return obj;
}
  • 异常抛出通知 抛出异常的时候进行的操作(事物回滚),可以获取异常信息
    1
    <aop:after-throwing method="afterThrowing" pointcut-ref="切入点" throwing="给异常取个名字">
1
2
3
4
//增强方法
public void afterThrowing(Throwable 异常的名字){
System.out.println(异常名字.getMessage());
}
  • 最终通知 类似于finally,不管有没有异常都会执行的操作
    1
    <aop:after method="xxx" pointcut-ref="xxx">

切入点表达式

  • 基于execution的函数完成

  • 语法

    • [访问修饰符] 方法返回值 包名.类名.方法名(参数)

      execution( 方法修饰符 方法返回值 方法所属类 匹配方法名 ( 方法中的形参表 ) 方法申明抛出的异常 )

    • public void com.lgq.UserDao.save(..) 两个点代表任意参数

    • public void com.lgq.UserDao.save(*,Integer)

      “*”:代表一个任意类型的参数

    • public void com.lgq.UserDao.save(); ()匹配一个无参方法

    • 具体还有

使用注解进行AOP开发

  • 使用注解开发上面的XML配置的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
* 切面类:注解的切面类
* @author jt
*/
//配置切面
@Aspect
public class MyAspectAnno {
// 切入点注解
//后面的方法随便定义,在增强方法中要对应
@Pointcut(value="execution(* com.itheima.spring.demo1.OrderDao.find(..))")
private void pointcut1(){}

@Pointcut(value="execution(* com.itheima.spring.demo1.OrderDao.save(..))")
private void pointcut2(){}

@Pointcut(value="execution(* com.itheima.spring.demo1.OrderDao.update(..))")
private void pointcut3(){}

@Pointcut(value="execution(* com.itheima.spring.demo1.OrderDao.delete(..))")
private void pointcut4(){}

//前值增强
@Before(value="MyAspectAnno.pointcut2()")
public void before(){
System.out.println("前置增强===========");
}

// 后置通知:
@AfterReturning(value="MyAspectAnno.pointcut4()",returning="result")
public void afterReturning(Object result){
System.out.println("后置增强==========="+result);
}

// 环绕通知:
@Around(value="MyAspectAnno.pointcut3()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("环绕前增强==========");
Object obj = joinPoint.proceed();
System.out.println("环绕后增强==========");
return obj;
}

// 异常抛出通知:
@AfterThrowing(value="MyAspectAnno.pointcut1()",throwing="e")
public void afterThrowing(Throwable e){
System.out.println("异常抛出增强========="+e.getMessage());
}

// 最终通知
@After(value="MyAspectAnno.pointcut1()")
public void after(){
System.out.println("最终增强============");
}
}
-------------本文结束感谢阅读-------------