Spring 面向切面(AOP)

Spring 支持面向切面编程。

一、面向切面(AOP)

编程思想 面向切面(AOP)

二、Spring 对 AOP 的支持

1. 名词解析

  • 横切关注点:希望切面实现的功能

    例如:日志记录、性能统计、安全检查、权限校验等

  • 切面

    例如:日志记录类、性能统计类、安全检查类、权限校验类等

  • 增强:切面的具体工作

    例如:日志记录类的打印日志方法、性能统计类的性能统计方法等

  • 目标:被切入的对象

  • 代理:被切入后的对象

  • 连接点:目标可以被切入的位置

    例如:数据库操作类实例的 add() 方法

  • 切入点:目标被切入的位置

2. 实现 AOP 的方式

  • 实现增强接口
  • 自定义类
  • 注解

三、实现增强接口

1. 说明

增强接口自带了待实现的方法,并指定了该增强方法的切入时机。

2. 步骤

  • 确保导入 aspectjweaver 依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>版本号</version>
    </dependency>
  • 在 Spring 配置文件中添加 aop 命名空间

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <?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">

    ···

    </beans>
  • 编写增强接口的实现类,注册为 Bean

  • 配置 aop

    • 配置切入点

      1
      2
      3
      4
      5
      <aop:config>

      <aop:pointcut id="切入点名" expression="execution表达式"/>

      </aop:config>
    • 配置增强

      1
      2
      3
      4
      5
      <aop:config>

      <aop:advisor advice-ref="非业务功能Bean名" pointcut-ref="切入点名"/>

      </aop:config>

3. 切入点的配置

切入点通过 execution 表达式配置,具体如下:

1
execution(修饰符 返回值类型 包名.类名.方法名(参数) throws 异常)
  • 修饰符(一般省略)

  • 返回值类型(不能省略)

    * 表示任意类型的返回值

  • 包名类名

  • 方法名(不能省略)

    • xxx:指定方法
    • xxx*:指定前缀的方法
    • *xxx:指定后缀的方法
    • *:任意方法
  • 参数

    • ():无参
    • (int, String):指定参数
    • (..):任意参数
  • throws 异常(一般省略)

4. 增强接口

  • 增强接口中定义了用于编写增强的方法

  • 方法自带了参数,可以从中获取信息

    方法、参数、业务功能类示例、返回结果、异常

  • 不同的接口将会在不同的时机被执行

    • MethodBeforeAdvice:在目标方法执行前会被执行
    • AfterReturningAdvice:在目标方法执行后会被执行
    • ThrowsAdvice:在目标方法抛出异常时会被执行
    • MethodInterceptor:在目标方法执行前后会被执行
    • IntroductionInterceptor:为目标对象添加新的属性和方法
1
2
3
4
5
6
7
8
public class 非义务功能类 implements MethodBeforeAdvice {

@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
···做些什么···
}

}

5. 示例

UserService 接口:

1
2
3
4
5
6
7
8
9
public interface UserService {
void add();

void del();

void update();

void query();
}

UserService 接口实现类 UserServiceImpl:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("...add...");
}

@Override
public void del() {
System.out.println("...del...");
}

@Override
public void update() {
System.out.println("...update...");
}

@Override
public void query() {
System.out.println("...query...");
}
}

增强接口实现类 Log:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Log implements MethodBeforeAdvice {

/**
*
* @param method 服务对象要执行的方法
* @param args 参数
* @param target 服务对象
* @throws Throwable
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("------ 前置日志 ------");
assert target != null;
System.out.println(target.getClass().getName() + "的" + method.getName() + "方法被执行了");
System.out.println("共有参数如下:");
for (Object arg : args) {
System.out.println(arg);
}
}
}

applicationContext.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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-->
<bean id="userService" class="service.UserServiceImpl"/>

<bean id="log" class="log.Log"/>

<!--配置aop-->
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* service.UserService.*(..))"/>

<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>

</aop:config>

</beans>

测试代码:

1
2
3
4
5
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userService");
userService.add();
}

运行结果:

四、自定义类

1. 说明

如果使用自定义类,需要在类中编写方法,在配置时指定该方法,并指定该方法的切入时机。

2. 步骤

  • 确保导入 aspectjweaver 依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>版本号</version>
    </dependency>
  • 在 Spring 配置文件中添加 aop 命名空间

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <?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">

    ···

    </beans>
  • 编写自定义类,注册为 Bean

  • 在 Spring 配置文件中配置 aop

    • 配置切入点

      1
      2
      3
      4
      5
      <aop:config>

      <aop:pointcut id="切入点名" expression="execution表达式"/>

      </aop:config>
    • 配置切入

      1
      2
      3
      4
      5
      6
      7
      8
      9
      <aop:config>

      <aop:aspect ref="log">

      <aop:时机 method="方法名" pointcut-ref="切入点名"/>

      </aop:aspect>

      </aop:config>

3. aop:时机

  • aop:before:在目标方法执行前会被执行

  • aop:after-returning:在目标方法正常执行后会被执行

  • aop:after-throwing:在目标方法抛出异常后会被执行

  • aop:after:在目标方法执行后会被执行

    无论目标方法执行正常还是抛出异常

  • aop:around:在目标方法执行前后会被执行

  • aop:declare-parents:为目标对象添加新的属性和方法

4. 示例

UserService 接口:

1
2
3
4
5
6
7
8
9
public interface UserService {
void add();

void del();

void update();

void query();
}

UserService 接口实现类 UserServiceImpl:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("...add...");
}

@Override
public void del() {
System.out.println("...del...");
}

@Override
public void update() {
System.out.println("...update...");
}

@Override
public void query() {
System.out.println("...query...");
}
}

切面类 Log:

1
2
3
4
5
6
7
8
9
public class Log{
public void before() {
System.out.println("------ before ------");
}

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

applicationContext.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
<?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-->
<bean id="userService" class="service.UserServiceImpl"/>

<bean id="log" class="log.Log"/>

<!--配置aop-->
<aop:config>
<!--切入点-->
<aop:pointcut id="pointcut" expression="execution(* service.UserService.*(..))"/>

<aop:aspect ref="log">

<aop:before method="before" pointcut-ref="pointcut"/>

<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>

</aop:config>

</beans>

测试代码:

1
2
3
4
5
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userService");
userService.add();
}

运行结果:

五、注解

1. 步骤

  • 确保导入 aspectjweaver 依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>版本号</version>
    </dependency>
  • 在 Spring 配置文件中添加 aop 命名空间

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <?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">

    ···

    </beans>
  • 在 Spring 配置文件中开启 @AspectJ 支持

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <?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">

    <aop:aspectj-autoproxy/>

    </beans>
  • 编写切面类

  • 通过注解配置 aop

    • 通过 @Aspect,将切面类标记为切面:

      1
      2
      3
      4
      5
      6
      @Aspect
      public class 类名{

      ···

      }
    • 通过 @时机(execution表达式),将方法标记为增强

2. @时机(execution表达式)

  • @Before(“execution表达式”):在目标方法执行前会被执行

  • @AfterReturning(“execution表达式”):在目标方法正常执行后会被执行

  • @AfterThrowing(“execution表达式”):在目标方法抛出异常后会被执行

  • @After(“execution表达式”):在目标方法执行后会被执行

    无论目标方法执行正常还是抛出异常

  • @Around(“execution表达式”):在目标方法执行前后会被执行

3. 示例

UserService 接口:

1
2
3
4
5
6
7
8
9
public interface UserService {
void add();

void del();

void update();

void query();
}

UserService 接口实现类 UserServiceImpl:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("...add...");
}

@Override
public void del() {
System.out.println("...del...");
}

@Override
public void update() {
System.out.println("...update...");
}

@Override
public void query() {
System.out.println("...query...");
}
}

切面类 Log:

1
2
3
4
5
6
7
8
9
10
11
12
@Aspect
public class Log{
@Before("execution(* service.UserService.*(..))")
public void before() {
System.out.println("------ before ------");
}

@After("execution(* service.UserService.*(..))")
public void after() {
System.out.println("------ after ------");
}
}

applicationContext.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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-->
<bean id="userService" class="service.UserServiceImpl"/>

<bean id="log" class="log.Log"/>

<!--开启aop注解支持-->
<aop:aspectj-autoproxy/>

</beans>

测试代码:

1
2
3
4
5
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userService");
userService.add();
}

运行结果:

参考