抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

摘要:本文学习了Spring面向切面的使用方式。

环境

Windows 10 企业版 LTSC 21H2
Java 1.8
Tomcat 8.5.50
Maven 3.6.3
Spring 5.2.25.RELEASE

1 依赖

添加依赖:

pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- Spring Context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.25.RELEASE</version>
</dependency>
<!-- Spring AOP -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.25.RELEASE</version>
</dependency>
<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>

2 配置方式

2.1 XML配置

在XML配置文件中使用aop:config标签配置切面,需要在XML文件中添加aop命名空间。

创建实体类:

java
1
2
3
4
5
6
7
8
public class User {
private Integer id;
private String name;
public void info() {
System.out.println("id=" + id + ", name=" + name);
}
// ...
}

创建切面类:

java
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
public class DemoAspect {
// 定义前置通知
public void before() {
System.out.println("执行前置通知before()方法");
}
// 定义后置通知
public void after() {
System.out.println("执行后置通知after()方法");
}
// 定义返回通知,result为返回值
public void afterReturning(Object result) {
System.out.println("执行返回通知afterReturning()方法");
}
// 定义异常通知,exception为异常
public void afterThrowing(Throwable exception) {
System.out.println("执行异常通知afterThrowing()方法");
}
// 定义环绕通知,joinPoint为连接点,代理执行业务方法
public Object around(ProceedingJoinPoint joinPoint) {
Object result = null;
try {
System.out.println("执行环绕通知around()方法的before前置通知");
// 执行业务方法
result = joinPoint.proceed(joinPoint.getArgs());
System.out.println("执行环绕通知around()方法的afterReturning返回通知");
} catch (Throwable e) {
System.out.println("执行环绕通知around()方法的afterThrowing异常通知");
throw new RuntimeException(e);
} finally {
System.out.println("执行环绕通知around()方法的after后置通知");
}
return result;
}
}

创建配置文件:

spring.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
<?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">
<!-- 创建User对象 -->
<bean id="user" class="com.example.bean.User"/>

<!-- 创建DemoAspect对象 -->
<bean id="demoAspect" class="com.example.aop.DemoAspect"/>

<!-- 配置切面,需要在XML文件中添加aop命名空间 -->
<aop:config>
<!-- 配置切点,使用切点表达式 -->
<aop:pointcut expression="execution(* *.*(..))" id="pointcut"/>
<!-- 配置切面类和优先级 -->
<aop:aspect ref="demoAspect" order="1">
<!-- 配置前置通知对应的方法和切点 -->
<aop:before method="before" pointcut-ref="pointcut"/>
<!-- 配置后置通知对应的方法和切点 -->
<aop:after method="after" pointcut-ref="pointcut"/>
<!-- 配置返回通知对应的方法和切点,返回值通过returning属性指定 -->
<aop:after-returning method="afterReturning" returning="result" pointcut-ref="pointcut"/>
<!-- 配置异常通知对应的方法和切点,异常通过throwing属性指定 -->
<aop:after-throwing method="afterThrowing" throwing="exception" pointcut-ref="pointcut"/>
<!-- 配置环绕通知对应的方法和切点 -->
<aop:around method="around" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>

创建启动类:

java
1
2
3
4
5
6
7
8
9
10
11
12
public class DemoApplication {
public static void main(String[] args) {
// 加载配置文件
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
// 获取User对象
User user = context.getBean(User.class);
// 使用User对象
user.info();
// 关闭容器
context.close();
}
}

执行结果:

log
1
2
3
4
5
6
7
执行前置通知before()方法
执行环绕通知around()方法的before前置通知
id=null, name=null
执行环绕通知around()方法的afterReturning返回通知
执行环绕通知around()方法的after后置通知
执行返回通知afterReturning()方法
执行后置通知after()方法

2.2 半注解配置

通过注解简化XML配置。

在XML配置文件中使用aop:aspectj-autoproxy标签启用切面代理,将标注了切面注解的类自动生效并拦截目标方法。

修改实体类:

java
1
2
3
4
5
6
7
8
9
@Component
public class User {
private Integer id;
private String name;
public void info() {
System.out.println("id=" + id + ", name=" + name);
}
// ...
}

修改切面类:

java
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
@Component
@Aspect
@Order(1)
public class DemoAspect {
// 定义切点方法
@Pointcut(value = "execution(* *.*(..))")
public void pointcut() {
}
// 定义前置通知,指定切点方法
@Before(value = "pointcut()")
public void before() {
System.out.println("执行前置通知before()方法");
}
// 定义后置通知,指定切点方法
@After(value = "pointcut()")
public void after() {
System.out.println("执行后置通知after()方法");
}
// 定义返回通知,指定切点方法,result为返回值
@AfterReturning(value = "pointcut()", returning = "result")
public void afterReturning(Object result) {
System.out.println("执行返回通知afterReturning()方法");
}
// 定义异常通知,指定切点方法,exception为异常
@AfterThrowing(value = "pointcut()", throwing = "exception")
public void afterThrowing(Throwable exception) {
System.out.println("执行异常通知afterThrowing()方法");
}
// 定义环绕通知,指定切点方法,joinPoint为连接点,代理执行业务方法
@Around(value = "pointcut()")
public Object around(ProceedingJoinPoint joinPoint) {
Object result = null;
try {
System.out.println("执行环绕通知around()方法的before前置通知");
// 执行业务方法
result = joinPoint.proceed(joinPoint.getArgs());
System.out.println("执行环绕通知around()方法的afterReturning返回通知");
} catch (Throwable e) {
System.out.println("执行环绕通知around()方法的afterThrowing异常通知");
throw new RuntimeException(e);
} finally {
System.out.println("执行环绕通知around()方法的after后置通知");
}
return result;
}
}

修改配置文件:

spring.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 启用注解扫描 -->
<context:component-scan base-package="com.example"/>

<!-- 启用切面代理 -->
<aop:aspectj-autoproxy/>
</beans>

执行启动类:

java
1
2
3
4
5
6
7
8
9
10
11
12
public class DemoApplication {
public static void main(String[] args) {
// 加载配置文件
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
// 获取User对象
User user = context.getBean(User.class);
// 使用User对象
user.info();
// 关闭容器
context.close();
}
}

执行结果:

log
1
2
3
4
5
6
7
执行环绕通知around()方法的before前置通知
执行前置通知before()方法
id=null, name=null
执行返回通知afterReturning()方法
执行后置通知after()方法
执行环绕通知around()方法的afterReturning返回通知
执行环绕通知around()方法的after后置通知

2.3 全注解配置

使用基于注解的配置方式,代替XML配置,能够大大简化配置工作,这是最常用的实现方式。

使用@EnableAspectJAutoProxy注解可以启用切面代理,代替在XML配置文件中使用aop:aspectj-autoproxy标签。

创建配置类:

java
1
2
3
4
5
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.example")
public class DemoConfig {
}

修改启动类:

java
1
2
3
4
5
6
7
8
9
10
11
12
public class DemoApplication {
public static void main(String[] args) {
// 加载配置类
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DemoConfig.class);
// 获取User对象
User user = context.getBean(User.class);
// 使用User对象
user.info();
// 关闭容器
context.close();
}
}

执行结果:

log
1
2
3
4
5
6
7
执行环绕通知around()方法的before前置通知
执行前置通知before()方法
id=null, name=null
执行返回通知afterReturning()方法
执行后置通知after()方法
执行环绕通知around()方法的afterReturning返回通知
执行环绕通知around()方法的after后置通知

3 常用切点

3.1 方法执行切点

使用execution匹配符合切点表达式的方法。

语法:

txt
1
execution(权限修饰符 返回值类型 类名.方法名(参数列表) 异常)

说明:

  • 权限修饰符:可以省略,表示匹配所有。
  • 返回值类型:不可以省略,使用*表示匹配所有。
  • 类名:不可以省略,使用*表示匹配所有。
  • 方法名:不可以省略,使用*表示匹配所有。
  • 参数列表:不可以省略,使用..表示匹配所有。

示例:

  • execution(public * com.example.*.save*(..)):匹配com.example包下所有类中以save开头的public方法
  • execution(* *.*(..)):匹配所有方法

3.2 方法注解切点

使用@annotation匹配带有指定注解的方法。

示例:

  • @annotation(com.example.annotation.DemoAnnotation):匹配带有@DemoAnnotation注解的方法。
  • @annotation(com.example.annotation.DemoLog):匹配带有@DemoLog注解的方法。

3.3 类注解切点

使用@within匹配带有指定注解的类的所有方法。

示例:

  • @within(org.springframework.stereotype.Controller):匹配带有@Controller注解的类的所有方法。
  • @within(org.springframework.stereotype.Service):匹配带有@Service注解的类的所有方法。

3.4 组合切点

支持使用逻辑运算符组合匹配方法:

  • &&:与,同时满足两个切点表达式。
  • ||:或,满足任意一个切点表达式。
  • !:非,不满足切点表达式。

评论