SSM系列第3篇AOP面向切面

基本概念

AOP全称Aspect Oriented Programming(面向切面编程)它本质就是一个动态代理,让我们把一些常用功能如权限检查、日志、事务等,从每个业务方法中剥离出来,可以在不惊动原始设计的基础上进行功能增强。AOP可以通过编译期、类加载器、运行期等方式实现,Spring的AOP实现就是基于JVM的动态代理。由于JVM的动态代理要求必须实现接口,如果一个普通类没有业务接口,就需要通过CGLIB或者Javassist这些第三方库实现。

重要术语

  • 通知(Advice): AOP 框架中的增强处理,通知描述了切面何时执行以及如何执行增强处理,也就是我们上面编写的方法实现。
  • 连接点(join point): 连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出,实际上就是我们在方法执行前或是执行后需要做的内容。
  • 切点(PointCut): 可以插入增强处理的连接点,可以是方法执行之前也可以方法执行之后,还可以是抛出异常之类的。
  • 切面(Aspect): 切面是通知和切点的结合,我们之前在xml中定义的就是切面,包括很多信息。
  • 引入(Introduction):引入允许我们向现有的类添加新的方法或者属性。
  • 织入(Weaving): 将增强处理添加到目标对象中,并创建一个被增强的对象,我们之前都是在将我们的增强处理添加到目标对象,也就是织入
  • Interceptor:拦截器,是一种实现增强的方式;
  • Target Object:目标对象,即真正执行业务的核心逻辑对象;
  • AOP Proxy:AOP代理,是客户端持有的增强后的对象引用

核心概念

  • 连接点:程序执行过程中任意位置,粒度为执行方法、抛出异常、设置变量等
  • 切入点:匹配连接点的式子,一个切入点可以只描述一个具体方法,也可以匹配多个方法。切入点定义依托于一个不具有实际意义的方法进行,无参数、无返回值、方法体无实际逻辑
  • 通知: 在切入点处执行的操作,也就是共性功能,在springaop中功能最终以方法的形式呈现
  • 通知类:定义通知的类
  • 切面:描述通知与切入点的对应关系

通知类型

  • 前置通知(Before advice):在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。
  • 后置通知(After returning advice):在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
  • 异常通知(After throwing advice):在方法抛出异常退出时执行的通知。
  • 最终通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
  • 环绕通知(Around Advice):包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。

切入点表达式

可以使用通配符描述切入点,快速描述。切入点可以匹配未实现的接口。

  • *单独独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
  • ..多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
  • +专用于匹配子类类型

基本案例

这个案例使用了黑马的AOP入门案例,黑马并没有使用xml方式进行实现,这里进行了相关内容的补充。

xml方式

applicationContext.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 目标类 -->
<bean id="bookDao" class="aop.BookDao"/>
<!-- 切面 -->
<bean id="timeAdvice" class="aop.TimeAdvice"/>

<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="timeAdvice">
<!-- 前置通知 -->
<aop:before method="time" pointcut="execution(public void aop.BookDao.save())"/>
</aop:aspect>
</aop:config>
</beans>

BookDao.java

1
2
3
4
5
6
7
8
9
10
11
package aop;
public class BookDao {

public void save() {
System.out.println("BookDao---save()");
}

public void update() {
System.out.println("BookDao---update()");
}
}

TimeAdvice.java

1
2
3
4
5
6
7
8
9
10
11
12
13
package aop;

import java.text.SimpleDateFormat;
import java.util.Date;

public class TimeAdvice {

public void time() {
SimpleDateFormat formatter= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date(System.currentTimeMillis());
System.out.println(formatter.format(date));
}
}

TimeAdvice.java

1
2
3
4
5
6
7
8
9
10
11
12
13
package aop;

import java.text.SimpleDateFormat;
import java.util.Date;

public class TimeAdvice {

public void time() {
SimpleDateFormat formatter= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date(System.currentTimeMillis());
System.out.println(formatter.format(date));
}
}

BookMain.java

1
2
3
4
5
6
7
8
9
10
11
12
package aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class BookMain {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = context.getBean(BookDao.class);
bookDao.save();
}
}

注解方式

BookDao.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package aop;

import org.springframework.stereotype.Repository;

@Repository
public class BookDao {

public void save() {
System.out.println("BookDao---save()");
}

public void update() {
System.out.println("BookDao---update()");
}
}

BookConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
package aop;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan("aop")
@EnableAspectJAutoProxy
public class BookConfig {

}

TimeAdvice.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;
@Component
@Aspect
public class TimeAdvice {
@Pointcut("execution(public void aop.BookDao.save())")
private void pt(){}

@Before("pt()")
public void time() {
SimpleDateFormat formatter= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date(System.currentTimeMillis());
System.out.println(formatter.format(date));
}
}

BookMain.java

1
2
3
4
5
6
7
8
9
10
11
12
package aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class BookMain {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(BookConfig.class);
BookDao bookDao = context.getBean(BookDao.class);
bookDao.save();
}
}

通知使用

需要特别注意的是,环绕通知需要在方法参数上写上ProceedingJoinPoint,在方法体中使用ProceedingJoinPoint执行proceed方法。还可以通过ProceedingJoinPoint中的signature获取类的相关信息。通过JoinPoint里的args可以获取参数

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
<!-- 目标类 -->
<bean id="" class="">
<!-- configure properties of bean here as normal -->
</bean>

<!-- 切面 -->
<bean id="" class="">
<!-- configure properties of aspect here as normal -->
</bean>

<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="">
<!-- 配置切入点 -->
<aop:pointcut id="" expression="execution(* .*.*(..))"/>
<!-- 环绕通知 -->
<aop:around method="" pointcut-ref=""/>
<!-- 前置通知 -->
<aop:before method="" pointcut-ref=""/>
<!-- 后置通知;returning属性:用于设置后置通知的第二个参数的名称,类型是Object -->
<aop:after-returning method="" pointcut-ref="" returning=""/>
<!-- 异常通知:如果没有异常,将不会执行增强;throwing属性:用于设置通知第二个参数的的名称、类型-->
<aop:after-throwing method="" pointcut-ref="" throwing=""/>
<!-- 最终通知 -->
<aop:after method="" pointcut-ref=""/>
</aop:aspect>
</aop:config>

注解方式

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
55
56
57
58
59
60
61
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

/**
* @author pdai
*/
@EnableAspectJAutoProxy
@Component
@Aspect
public class UserAspect {

//这里是切入点
@Pointcut("execution(* tech.pdai.springframework.service.*.*(..))")
private void pointCutMethod() {

}


//环绕通知
@Around("pointCutMethod()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("-----------------------");
System.out.println("环绕通知: 进入方法");
Object o = pjp.proceed();
System.out.println("环绕通知: 退出方法");
return o;
}

//前置通知
@Before("pointCutMethod()")
public void doBefore() {
System.out.println("前置通知");
}


//后置通知
@AfterReturning(pointcut = "pointCutMethod()", returning = "result")
public void doAfterReturning(String result) {
System.out.println("后置通知, 返回值: " + result);
}

//异常通知
@AfterThrowing(pointcut = "pointCutMethod()", throwing = "e")
public void doAfterThrowing(Exception e) {
System.out.println("异常通知, 异常: " + e.getMessage());
}

//最终通知
@After("pointCutMethod()")
public void doAfter() {
System.out.println("最终通知");
}
}

SSM系列第3篇AOP面向切面
https://www.eldpepar.com/coding/13339/
作者
EldPepar
发布于
2022年11月13日
许可协议