0%

Spring AOP(面向切面编程)

Spring AOP(面向切面编程)

1 概念

1.1 什么是AOP

  • AOP,即面向切面编程,可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高开发的效率
  • 通俗描述:不通过修改源代码的方式,在主干功能里面添加新功能

2 原理

2.1 动态代理

2.1.1 JDK动态代理

  • 有接口的情况,使用JDK动态代理,创建接口实现类的代理对象

2.1.1.1 Proxy类

  • Proxy类提供了newProxyInstance方法用于创建代理对象,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Returns an instance of a proxy class for the specified interfaces
* that dispatches method invocations to the specified invocation
* handler.

* @param loader the class loader to define the proxy class
* @param interfaces the list of interfaces for the proxy class
* to implement
* @param h the invocation handler to dispatch method invocations to
* @return a proxy instance with the specified invocation handler of a
* proxy class that is defined by the specified class loader
* and that implements the specified interfaces

*/
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler invocationHandler );

2.1.1.2 创建方式

  • 假设有一接口,
1
2
3
public interface UserDao {
public int add(int a, int b);
}
  • 这个接口有一实现类,
1
2
3
4
5
6
public class UserDaoImpl implements UserDao{
@Override
public int add(int a, int b) {
return a + b;
}
}
  • 实现调用处理器,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class UserDaoProxy implements InvocationHandler {
private Object object;

public UserDaoProxy(Object object) {
this.object = object;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before invoke");
Object result= method.invoke(object, args);
System.out.println("after invoke");
return result;
}
}
  • 创建代理对象,
1
2
3
4
5
UserDao userDao = new UserDaoImpl();
Class[] interfaces = {UserDao.class};
UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(UserDao.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
int result = userDaoProxy.add(1, 2);
System.out.println("result: " + result);

2.1.2 CGLIB动态代理

  • 没有接口的情况,使用CGLIB动态代理,创建当前类子类的代理对象

3 AOP术语

3.1 连接点

  • 类里面可以被增强的方法称为连接点

3.2 切入点

  • 实际被增强的方法称为切入点

3.3 通知(增强)

  • 实际增强的逻辑部分称为通知(增强)
  • 通知有5种类型:

(1)前置通知

(2)后置通知

(3)环绕通知

(4)异常通知

(5)最终通知

3.4 切面

  • 切面,是把通知应用到切入点的过程

4 实现AOP操作

4.1 AspectJ

  • AspectJ不是Spring的组成部分,是独立的AOP框架,一般将AspectJ和Spring搭配使用进行AOP操作
  • 基于AspectJ实现AOP操作:

(1)基于xml配置文件实现

(2)基于注解方式实现

4.2 导入依赖

  • 使用Maven配置项目依赖,
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
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.8</version>
</dependency>

<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2</version>
</dependency>

4.3 切入点表达式

4.3.1 作用

  • 让Spring框架知道对哪个类里的哪个方法进行增强

4.3.2 语法结构

  • execution([权限修饰符][返回类型][类全路径][方法名称][参数列表])
  • 权限修饰符、返回类型可用“*”通配
  • 类的名称可用“*”通配
  • 方法名称可用“*”通配
  • 举例:

(1)* com.company.dao.BookDao.add(..)

(2)* com.company.dao.BookDao.*(..)

(3)* com.company.dao..(..)

4.4 AspectJ注解

4.4.1 创建被增强类,在类里面定义方法

1
2
3
4
5
6
@Component
public class User {
public void add() {
System.out.println("User add......");
}
}

4.4.2 创建增强类(编写增强逻辑)

  • 在增强类里面创建方法,让不同的方法代表不同的通知类型
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
@Component
public class UserProxy {
public void before() {
System.out.println("before......");
}

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

public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("around before......");

proceedingJoinPoint.proceed();

System.out.println("around after......");
}

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

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

4.4.3 在Spring配置文件中开启组件扫描

1
2
3
4
5
6
7
8
9
10
11
<?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.lnhoo"></context:component-scan>

4.4.4 在增强类上面注解Aspect

1
2
3
4
5
@Component
@Aspect
public class UserProxy {
......
}

4.4.5 在Spring配置文件中开启AspectJ自动代理

1
2
<!--    开启AspectJ自动代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

4.4.6 配置不同类型的通知

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
@Component
@Aspect
public class UserProxy {
// 前置通知
@Before(value = "execution(* com.lnhoo.User.add())")
public void before() {
System.out.println("before......");
}

// 最终通知
@After(value = "execution(* com.lnhoo.User.add())")
public void after() {
System.out.println("after......");
}

// 环绕通知
@Around(value = "execution(* com.lnhoo.User.add())")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("around before......");

proceedingJoinPoint.proceed();

System.out.println("around after......");
}

// 后置通知
@AfterReturning(value = "execution(* com.lnhoo.User.add())")
public void afterReturning() {
System.out.println("after returning......");
}

// 异常通知
@AfterThrowing(value = "execution(* com.lnhoo.User.add())")
public void afterThrowing() {
System.out.println("after throwing......");
}
}
  • @Before,前置通知,在被增强的方法前执行
  • @AfterReturning,后置通知,在被增强的方法正常返回后执行(不包括抛异常的情况)
  • @Around,环绕通知,在被增强方法的前、后执行
  • @After,最终通知,无论如何都在被增强方法之后执行
  • @AfterThrowing,异常通知,只有在被增强方法抛出异常后执行

4.4.7 抽取公共切入点

  • 使用Pointcut注解抽取公共切入点,
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
@Component
@Aspect
public class UserProxy {

@Pointcut(value = "execution(* com.lnhoo.User.add())")
private void methodExtending() {

}

@Before(value = "methodExtending()")
public void before() {
System.out.println("before......");
}

@After(value = "methodExtending()")
public void after() {
System.out.println("after......");
}

@Around(value = "methodExtending()")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("around before......");

proceedingJoinPoint.proceed();

System.out.println("around after......");
}

@AfterReturning(value = "methodExtending()")
public void afterReturning() {
System.out.println("after returning......");
}

@AfterThrowing(value = "methodExtending()")
public void afterThrowing() {
System.out.println("after throwing......");
}
}

4.4.8 设置增强类优先级

  • 在增强类上面添加注解@Order(数字类型值),数字类型值越小优先级越高