Spring之AOP概述

  1. AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论,是对传统 OOP(Object-Oriented Programming,面向对象编程)的补充。
  2. AOP编程操作的主要对象是切面(aspect),而切面模块化横切关注点
  3. 在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能应用在哪里,以什么方式应用,并且不必修改受影响的类。这样一来横切关注点就被模块化到特殊的类里——这样的类我们通常称之为“切面”。
  4. AOP的好处:
    1. 每个事物逻辑位于一个位置,代码不分散,便于维护和升级
    2. 业务模块更简洁,只包含核心业务代码

1. AOP专业术语

Image

1.1 横切关注点

从每个方法中抽取出来的同一类非核心业务。

1.2 切面(Aspect)

封装横切关注点信息的类,每个关注点体现为一个通知方法。

1.3 通知(Advice)

切面必须要完成的各个具体工作

1.4 目标(Target)

被通知的对象

1.5 代理(Proxy)

向目标对象应用通知之后创建的代理对象

1.6 连接点(Joinpoint)

横切关注点在程序代码中的具体体现,对应程序执行的某个特定位置。例如:类某个方法调用前、调用后、方法捕获到异常后等。

1.7 切入点(pointcut)

定位连接点的方式。每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物。如果把连接点看作数据库中的记录,那么切入点就是查询条件——AOP可以通过切入点定位到特定的连接点。切点通过org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。

2. AspectJ

2.1 AspectJ简介
  1. AspectJ是Java社区里最完整最流行的AOP框架。
  2. 在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP。
  3. Spring AOP旨在通过Spring IoC提供一个简单的AOP实现,以解决编码人员面临的最常出现的问题。这并不是完整的AOP解决方案,它只能用于Spring容器管理的beans。
  4. AspectJ是最原始的AOP实现技术,提供了完整的AOP解决方案。AspectJ更为健壮,相对于Spring AOP也显得更为复杂。值得注意的是,AspectJ能够被应用于所有的领域对象。
2.2 Spring中启用AspectJ注解支持

导入JAR包

  • 加强包
  • com.springsource.net.sf.cglib-2.2.0.jar
  • com.springsource.org.aopalliance-1.0.0.jar
  • com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
  • 基础包
  • spring-aop-4.0.0.RELEASE.jar
  • spring-aspects-4.0.0.RELEASE.jar

引入aop名称空间

img

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">

<context:component-scan base-package="cn.justweb"></context:component-scan>

<!-- 开启基于注解的AOP功能;aop名称空间-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

​ 当Spring IOC容器侦测到bean配置文件中的<aop:aspectj-autoproxy>元素时,会自动为与AspectJ切面匹配的bean创建代理。

2.3 用AspectJ注解声明切面
  1. 要在Spring中声明AspectJ切面,只需要在IOC容器中将切面声明为bean实例。
  2. 当在Spring IOC容器中初始化AspectJ切面之后,Spring IOC容器就会为那些与 AspectJ切面相匹配的bean创建代理。
  3. 在AspectJ注解中,切面只是一个带有@Aspect注解的Java类,它往往要包含很多通知。
  4. 通知是标注有某种注解的简单的Java方法。
  5. AspectJ支持5种类型的通知注解:
    1. @Before:前置通知,在方法执行之前执行
    2. @After:后置通知,在方法执行之后执行
    3. @AfterRunning:返回通知,在方法返回结果之后执行
    4. @AfterThrowing:异常通知,在方法抛出异常之后执行
    5. @Around:环绕通知,围绕着方法执行

3. AOP测试

3.1 接口部分

Calculator

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 虽然不通过@Component添加到ioc容器中,但是我们仍然使用接口来获取
* 接口不加载容器中:实际上可以加,因为加入了也不创建对象,只要这个组件是一个接口
* 相当于告诉Spring,ioc容器中可能有这种类型的组件
* @Date 2020/5/23 23:23
* @Version 10.21
* @Author DuanChaojie
*/
public interface Calculator {
int add(int m,int n);

int sub(int m,int n);
}
3.2 实现类

CalculatorImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* @Date 2020/5/23 23:24
* @Version 10.21
* @Author DuanChaojie
*/
@Service
public class CalculatorImpl implements Calculator {

public int add(int m, int n) {
int result = m + n;
return result;

}

public int sub(int m, int n) {
int result = m - n;
return result;
}
}
3.3 配置文件

applicationContext-aop.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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="cn.justweb.calculator"/>
<aop:aspectj-autoproxy />


</beans>
3.4 切面类

LogUtils

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
/**
* 如何将这个切面类中的这些方法(通知方法)动态地在目标方法运行的各个位置切入
* @Date 2020/5/24 10:51
* @Version 10.21
* @Author DuanChaojie
*/
@Aspect
@Component
public class LogUtils {

/**
* 告诉Spring每个方法都什么时候运行
* try {
* @Before
* method.invoke(obj,args)
* @After
* } catch (Exception e) {
* @AfterThrowing
* e.printStackTrace();
* } finally {
* @AfterReturning
* }
* 五个通知方法:
* @Before 在目标方法之前运行 前置通知
* @After 在目标方法结束之后运行 后置通知
* @AfterReturning 在目标方法正常返回之后 返回通知
* @AfterThrowing 在目标方法抛出异常之后执行 异常通知
* @Around 环绕通知
*/


// 想在执行目标方法之前运行,写切入点表达式
// execution(访问权限符 返回值类型 方法签名)
@Before("execution(public int cn.justweb.calculator.impl.CalculatorImpl.*(int,int))")
public static void logStart(){
System.out.println("[xxx]:方法执行了,其参数列表为xxx");
}

@After("execution(public int cn.justweb.calculator.impl.CalculatorImpl.*(int,int))")
public static void logEnd(){
System.out.println("[xxx]:方法正常执行完成了,其结果为xxx");
}
@AfterThrowing("execution(public int cn.justweb.calculator.impl.CalculatorImpl.*(int,int))")
public static void logError(){
System.out.println("[xxx]:方法执行出现了异常,原因为:xxx");
}

@AfterReturning("execution(public int cn.justweb.calculator.impl.CalculatorImpl.*(int,int))")
public static void logFinally(){
System.out.println("[xxx]:方法最终执行完成了,其结果为xxx");
}
}
3.5 最终测试
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
/**
* @Date 2020/5/24 10:39
* @Version 10.21
* @Author DuanChaojie
*/
public class TestCalculator {
/**
* 动态代理存在的问题:
* 1. 写起来很复杂,很难实现
* 2. jdk默认的动态代理,如果目标对象没有实现任何接口,是无法为他创建代理对象的
* 所以Spring实现了AOP功能,底层就是动态代理
*/
@Test
public void testCalculator() {

// 测试aop
ClassPathXmlApplicationContext ioc = new
ClassPathXmlApplicationContext("applicationContext.xml");

// 1. 从IOC容器中拿到目标对象,注意:如果想用类型,一定用他的接口类型,不用它本类
// 细节一:
//bean = cn.justweb.calculator.impl.CalculatorImpl@1aafa419
//bean.getClass() = class com.sun.proxy.$Proxy13
// AOP的底层就是动态代理,容器中保存的组件是他的代理对象:$Proxy13,当然不是本类的类型
// 通过接口拿目标对象,是jdk帮我们创建代理对象的
//Calculator bean = ioc.getBean(Calculator.class);
// 为什么接口没有添加到ioc容器中也能使用?详情见接口部分注解
//System.out.println("bean = " + bean);
//System.out.println("bean.getClass() = " + bean.getClass());
//bean.add(2,1);

// 去掉接口测试,也能通过实现类拿到
//CalculatorImpl bean = ioc.getBean(CalculatorImpl.class);
// 通过id拿到对象
CalculatorImpl bean =(CalculatorImpl) ioc.getBean("calculatorImpl");
bean.add(2,2);

// bean.getClass() = class cn.justweb.calculator.impl.CalculatorImpl$$EnhancerByCGLIB$$7305b6d6
// cglib帮我们创建好的代理对象
System.out.println("bean.getClass() = " + bean.getClass());
}
}