Spring框架(8) —— 面向切面编程(AOP)

简介

  • Spring 框架的关键组件是 面向切面的编程(AOP,Aspect Oriented Programming),面向切面的编程需要把程序逻辑分解成不同的部分称为所谓的连接点。跨一个应用程序的多个点的功能被称为切入点,切入点在概念上独立于应用程序的业务逻辑。有各种各样的常见的很好的方面的例子,如日志记录、审计、声明式事务、安全性和缓存等。
  • 面向对象编程(OOP,Object Oriented Programming) 中,关键单元模块度是类,而在 AOP 中单元模块度是方面。依赖注入帮助你对应用程序对象相互解耦和 AOP 可以帮助你从它们所影响的对象中对切入点解耦。AOP 是像编程语言的触发物,如 Perl,.NET,Java 或者其他。
  • Spring AOP 模块提供拦截器来拦截一个应用程序,例如,当执行一个方法时,你可以在方法执行之前或之后添加额外的功能。

AOP

术语

  • 在我们开始使用 AOP 工作之前,让我们熟悉一下 AOP 概念和术语。这些术语并不特定于 Spring,而是与 AOP 有关的。
描述
Aspect(切面) 由一系列切点、增强和引入组成的模块对象
Join point(连接点) 程序执行期的一个
Pointcut(切入点) 一组通过表达式从连接点中被筛选出来的
Advice(通知) 切面在特定接入点的执行动作
Introduction(引入) 为某个type声明额外的方法和字段。
Target object(目标对象) 一个被一个或者多个切面所通知的被代理对象
Weaving(织入) 把切面连接到其它的对象上,并创建目标对象。

通知

  • Spring AOP 模块可以使用下面提到的五种通知工作:
通知 描述
前置通知(before) 在切入点方法执行之前,执行通知。
后置通知(after-returning) 在切入点方法执行之后,当方法正常执行时,执行通知。
异常通知(after-throwing) 在切入点方法执行之后,当方法抛出异常时,执行通知。
最终通知(after) 在切入点方法执行之后,不考虑其结果,执行通知。
环绕通知(around) 在建议方法调用之前和之后,执行通知。

目录结构

  • src

    • main

      • java
        • anno
          • Student.java(目标类)
          • Logger.java(通知类)
        • xml
          • Student.java(目标类)
          • Logger.java(通知类)
      • resources
        • anno
          • Beans.xml(Spring配置文件)
    • test

      • anno
        • SpringTest.java(测试类)
      • xml
        • SpringTest.java(测试类)

基于xml

Student.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
package OtherAdvice.xml;


public class Student {

/* 成员变量 */
private Integer age;
private String name;

/** 抛出异常 */
public void printThrowException(){
System.out.println("出现异常...");
throw new IllegalArgumentException();
}

/* 设值函数 */
public Integer getAge() {
System.out.println(" age:"+age);
return age;
}

public void setAge(Integer age) {
this.age = age;
}

public String getName() {
System.out.println(" name:"+name);
return name;
}

public void setName(String name) {
this.name = name;
}
}

Logger.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
package OtherAdvice.xml;


public class Logger {

/** 前置通知 */
public void beforeAdvice(){
System.out.println("------");
System.out.println("~前置通知");
}
/** 后置通知 */
public void afterReturningAdvice(Object retVal){
System.out.println("~后置通知:" + retVal.toString() );
}
/** 异常通知 */
public void afterThrowingAdvice(IllegalArgumentException ex){
System.out.println("~异常通知: " + ex.toString());
}
/** 最终通知 */
public void afterAdvice(){
System.out.println("~最终通知");
}

}

Beans.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
34
<?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-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">

<!-- 通知类 -->
<bean id="logging" class="OtherAdvice.xml.Logger"/>

<!-- 目标类 -->
<bean id="student" class="OtherAdvice.xml.Student">
<property name="name" value="cat"/>
<property name="age" value="1"/>
</bean>

<!-- Spring AOP框架 -->
<aop:config>
<!-- 切面(Aspect) -->
<aop:aspect id="log" ref="logging">
<!-- 切入点(Pointcut) -->
<aop:pointcut id="all" expression="execution(* OtherAdvice.xml.*.*(..))"/>
<!-- 通知(Advice) -->
<aop:before method="beforeAdvice" pointcut-ref="all" />
<aop:after-returning method="afterReturningAdvice" returning="retVal" pointcut-ref="all"/>
<aop:after-throwing method="afterThrowingAdvice" throwing="ex" pointcut-ref="all"/>
<aop:after method="afterAdvice" pointcut-ref="all"/>
</aop:aspect>
</aop:config>

</beans>

SpringTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package cn.water.test.OtherAdvice.xml;

import OtherAdvice.xml.Student;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class SpringTest {

@Test
public void test(){
/* 1、加载配置文件,初始化Bean对象 */
ApplicationContext app = new ClassPathXmlApplicationContext("OtherAdvice/xml/Beans.xml");
/* 2、获取Bean对象 */
Student student = app.getBean("student", Student.class);
/* 3、调用方法 */
student.getAge();
student.getName();
student.printThrowException();
}
}

基于注解

Student.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
package OtherAdvice.anno;


public class Student {

/* 成员变量 */
private Integer age;
private String name;

/** 抛出异常 */
public void printThrowException(){
System.out.println("出现异常...");
throw new IllegalArgumentException();
}

/* 设值函数 */
public Integer getAge() {
System.out.println(" age:"+age);
return age;
}

public void setAge(Integer age) {
this.age = age;
}

public String getName() {
System.out.println(" name:"+name);
return name;
}

public void setName(String name) {
this.name = name;
}
}

Logger.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
package OtherAdvice.anno;


import org.aspectj.lang.annotation.*;


@Aspect
public class Logger {

/** 切入点 */
@Pointcut(value = "execution(* OtherAdvice.anno.*.*(..))")
public void all(){};

/** 前置通知 */
@Before("all()")
public void beforeAdvice(){
System.out.println("------");
System.out.println("~前置通知");
}

/** 后置通知 */
@AfterReturning(pointcut = "all()", returning = "retVal")
public void afterReturningAdvice(Object retVal){
System.out.println("~后置通知:" + retVal.toString() );
}

/** 异常通知 */
@AfterThrowing(pointcut = "all()", throwing = "ex")
public void afterThrowingAdvice(IllegalArgumentException ex){
System.out.println("~异常通知: " + ex.toString());
}

/** 最终通知 */
@After("all()")
public void afterAdvice(){
System.out.println("~最终通知");
}

}

Beans.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-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">

<!-- 通知类 -->
<bean id="logging" class="OtherAdvice.anno.Logger"/>

<!-- 目标类 -->
<bean id="student" class="OtherAdvice.anno.Student">
<property name="name" value="cat"/>
<property name="age" value="1"/>
</bean>

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

</beans>

SpringTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package cn.water.test.OtherAdvice.anno;

import OtherAdvice.anno.Student;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class SpringTest {

@Test
public void test(){

/* 1、加载配置文件,初始化Bean对象 */
ApplicationContext app = new ClassPathXmlApplicationContext("OtherAdvice/anno/Beans.xml");
/* 2、获取Bean对象 */
Student student = app.getBean("student", Student.class);
/* 3、调用方法 */
student.getAge();
student.getName();
student.printThrowException();
}
}

四大通知

基于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
/** 环绕通知 */
public Object roundAdvice(ProceedingJoinPoint joinPoint){

Object result = null;
try {
/** 前置通知 */
System.out.println("------");
System.out.println("~前置通知");
/* 获取切入点方法的参数列表 */
Object[] args = joinPoint.getArgs();
/* 调用切入点方法,获取返回值 */
result = joinPoint.proceed(args);
/** 后置通知 */
System.out.println("~后置通知:" + result.toString());
/* 返回 */
return result;
} catch (Throwable throwable) {
/** 异常通知 */
System.out.println("~异常通知: " + throwable);
throw new RuntimeException();
} finally {
/** 最终通知 */
System.out.println("~最终通知");
}

}

配置文件

  • 添加AOP框架
    • aop:aspect:切面
      • aop:pointcut:切入点
        • aop:around:环绕方法
1
2
3
4
5
6
7
8
9
10
<!-- Spring AOP框架 -->
<aop:config>
<!-- 切面(Aspect) -->
<aop:aspect id="log" ref="logging">
<!-- 切入点(Pointcut) -->
<aop:pointcut id="all" expression="execution(* AroundAdvice.xml.*.*(..))"/>
<!-- 通知(Advice) -->
<aop:around method="roundAdvice" pointcut-ref="all"/>
</aop:aspect>
</aop:config>

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
------
~前置通知
age:1
~后置通知:1
~最终通知
------
~前置通知
name:cat
~后置通知:cat
~最终通知
------
~前置通知
出现异常...
~异常通知: java.lang.IllegalArgumentException
~最终通知

基于注解

通知类

  • 添加注解
    • @Aspect:切面
      • @Pointcut:切入点
        • @Before:前置方法
        • @AfterReturning:后置方法
        • @AfterThrowing:异常方法
        • @After:最终方法
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
@Aspect
public class Logger {

/** 切入点 */
@Pointcut(value = "execution(* OtherAdvice.anno.*.*(..))")
public void all(){};

/** 前置通知 */
@Before("all()")
public void beforeAdvice(){
System.out.println("------");
System.out.println("~前置通知");
}

/** 后置通知 */
@AfterReturning(pointcut = "all()", returning = "retVal")
public void afterReturningAdvice(Object retVal){
System.out.println("~后置通知:" + retVal.toString() );
}

/** 异常通知 */
@AfterThrowing(pointcut = "all()", throwing = "ex")
public void afterThrowingAdvice(IllegalArgumentException ex){
System.out.println("~异常通知: " + ex.toString());
}

/** 最终通知 */
@After("all()")
public void afterAdvice(){
System.out.println("~最终通知");
}

}

配置文件

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

运行结果

  • 运行顺序有误!!!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
------
~前置通知
age:1
~最终通知
~后置通知:1
------
~前置通知
name:cat
~最终通知
~后置通知:cat
------
~前置通知
出现异常...
~最终通知
~异常通知: java.lang.IllegalArgumentException

环绕通知

基于xml

通知类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/** 前置通知 */
public void beforeAdvice(){
System.out.println("------");
System.out.println("~前置通知");
}
/** 后置通知 */
public void afterReturningAdvice(Object retVal){
System.out.println("~后置通知:" + retVal.toString() );
}
/** 异常通知 */
public void afterThrowingAdvice(IllegalArgumentException ex){
System.out.println("~异常通知: " + ex.toString());
}
/** 最终通知 */
public void afterAdvice(){
System.out.println("~最终通知");
}

配置文件

  • 添加AOP框架
    • aop:aspect:切面
      • aop:pointcut:切入点
        • aop:before:前置方法
        • aop:afterReturning:后置方法
        • aop:afterThrowing:异常方法
        • aop:after:最终方法
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- Spring AOP框架 -->
<aop:config>
<!-- 切面(Aspect) -->
<aop:aspect id="log" ref="logging">
<!-- 切入点(Pointcut) -->
<aop:pointcut id="all" expression="execution(* OtherAdvice.xml.*.*(..))"/>
<!-- 通知(Advice) -->
<aop:before method="beforeAdvice" pointcut-ref="all" />
<aop:after-returning method="afterReturningAdvice" returning="retVal" pointcut-ref="all"/>
<aop:after-throwing method="afterThrowingAdvice" throwing="ex" pointcut-ref="all"/>
<aop:after method="afterAdvice" pointcut-ref="all"/>
</aop:aspect>
</aop:config>

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
------
~前置通知
age:1
~后置通知:1
~最终通知
------
~前置通知
name:cat
~后置通知:cat
~最终通知
------
~前置通知
出现异常...
~异常通知: java.lang.IllegalArgumentException
~最终通知

基于注解

通知类

  • 添加注解
    • @Aspect:切面
      • @Pointcut:切入点
        • @Before:前置方法
        • @AfterReturning:后置方法
        • @AfterThrowing:异常方法
        • @After:最终方法
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
@Aspect
public class Logger {

/** 切入点 */
@Pointcut(value = "execution(* AroundAdvice.anno.*.*(..))")
public void all(){};

/** 环绕通知 */
@Around("all()")
public Object roundAdvice(ProceedingJoinPoint joinPoint){
Object result = null;
try {
/** 前置通知 */
System.out.println("------");
System.out.println("~前置通知");
/* 获取切入点方法的参数列表 */
Object[] args = joinPoint.getArgs();
/* 调用切入点方法,获取返回值 */
result = joinPoint.proceed(args);
/** 后置通知 */
System.out.println("~后置通知:" + result.toString());
/* 返回 */
return result;
} catch (Throwable throwable) {
/** 异常通知 */
System.out.println("~异常通知: " + throwable);
throw new RuntimeException();
} finally {
/** 最终通知 */
System.out.println("~最终通知");
}

}

}

配置文件

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

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
------
~前置通知
age:1
~后置通知:1
~最终通知
------
~前置通知
name:cat
~后置通知:cat
~最终通知
------
~前置通知
出现异常...
~异常通知: java.lang.IllegalArgumentException
~最终通知
-------------本文结束-------------
Donate comment here