本文参考实验楼教程:https://www.shiyanlou.com/courses/578/learning/?id=1940
Spring Aop即 Aspect-Oriented Programming,面向切面编程。是用于处理系统中各个模块(不同方法)之间的交叉关注的问题。
简单地说,就是一个拦截器,拦截一些处理过程。
例如:当一个method被执行,Spring AOP能够劫持正在运行的method,在method执行前后加入一些额外的功能。(日志记录、性能统计、权限控制等)
实验环境:
JDK1.8
idea 开发环境
一、Spring AOP 支持4种类型的通知(advice)
- Before advice --- method 执行前通知
- After returning advice --- method 执行完毕或者返回一个结果后通知
- After throwing advice --- method 抛出异常后通知
- Around advice --- 环绕通知,结合了以上三种
下面来学习如何使用这四种通知。
以下是整个实验项目结构:
1、准备环境,创建一个maven工程SpringAop
1)、添加maven依赖,对应的pom.xml文件如下:
4.0.0
com.shiyanlou.spring
SpringAopTest
1.0-SNAPSHOT
SpringAopTest
http://www.example.com
UTF-8
1.7
1.7
5.1.1.RELEASE
junit
junit
4.11
test
org.springframework
spring-core
${spring.version}
org.springframework
spring-context
${spring.version}
org.aspectj
aspectjweaver
1.9.2
org.aspectj
aspectjtools
1.9.2
org.aspectj
aspectjrt
1.9.2
cglib
cglib
3.2.9
2)、创建类 CustomerService.java 如下:
package com.shiyanlou.spring.aop.advice;
import org.springframework.stereotype.Service;
/**
* Created by Administrator on 2019/10/30.
*/
public class CustomerService {
private String name;
private String url;
public CustomerService() {
}
public CustomerService(String name, String url) {
this.name = name;
this.url = url;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public void printName(){
System.out.println("Customer name: " + name);
}
public void printURL(){
System.out.println("Customer URL: " + url);
}
public void printThrowException(){
throw new IllegalArgumentException();
}
}
3)、在src/main/resources/下新建 Xml 配置文件 SpringAopAdvice.xml 如下:
4)、在com.shiyanlou.spring包下的App.java中编写如下:
package com.shiyanlou.spring;
import com.shiyanlou.spring.aop.advice.CustomerService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App
{
private static ApplicationContext context;
public static void main( String[] args ) {
context = new ClassPathXmlApplicationContext("SpringAopAdvice.xml");
CustomerService cust = (CustomerService) context.getBean("customerService");
System.out.println("*************************");
cust.printName();
System.out.println("*************************");
cust.printURL();
System.out.println("*************************");
try {
cust.printThrowException();
} catch (IllegalArgumentException e){
}
}
}
5)、运行App.java文件,结果如下:
*************************
Customer name: weihouye
*************************
Customer URL: www.weihouyeaiyangzhan.com
*************************
2、Before advice
1)、定义org.springframework.aop.MethodBeforeAdvice
的实现类WeiBeforeMethod
,重写before
方法,该方法将在被代理的类的方法之前运行。代码如下:
package com.shiyanlou.spring.aop.advice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
/**
* Created by Administrator on 2019/10/30.
*/
public class WeiBeforeMethod implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("WeiBeforeMethod: Before method hi wei!!!");
}
}
2)、在SpringAopAdvice.xml
配置文件中加入新的bean配置weiBeforeMethod
,然后创建一个代理bean(Proxy),命名为customerServiceProxy
。
代理bean中target
定义了被代理(被劫持)的类,拦截器interceptorNames
则定义了想要用哪个类(advice)劫持target
,拦截器可以有多个,所以写在
中。配置如下:
weiBeforeMethod
用 Spring proxy 之前,必须添加 CGLIB 类库,在之前的 pom.xml 文件中,已经添加到了其中,以下是 pom.xml 依赖:
cglib
cglib
3.2.9
3)、App.java
如下
package com.shiyanlou.spring;
import com.shiyanlou.spring.aop.advice.CustomerService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Hello world!
*
*/
public class App
{
private static ApplicationContext context;
public static void main( String[] args ) {
context = new ClassPathXmlApplicationContext("SpringAopAdvice.xml");
CustomerService cust = (CustomerService) context.getBean("customerServiceProxy");
System.out.println("*************************");
cust.printName();
System.out.println("*************************");
cust.printURL();
System.out.println("*************************");
try {
cust.printThrowException();
} catch (IllegalArgumentException e){
}
}
}
ps:注意 App.java中获取的是代理bean,即customerServiceProxy
,由代理bean去执行原来的功能。
4)、运行结果如下:
*************************
WeiBeforeMethod: Before method hi wei!!!
Customer name: weihouye
*************************
WeiBeforeMethod: Before method hi wei!!!
Customer URL: www.weihouyeaiyangzhan.com
*************************
WeiBeforeMethod: Before method hi wei!!!
可以看到,被代理类CustomerService
被WeiBeforeMethod
劫持,被代理类中的方法被执行前,均先执行WeiBeforeMethod
中的before
方法。
3、After Returning Advice
1)、创建一个org.springframework.aop.AfterReturningAdvice
接口的实现类WeiAfterMethod.java
,重写afterReturning
方法,用于劫持被代理类;被代理类中的方法执行完毕或者返回结果后,将执行WeiAfterMethod
中的afterReturning
方法,若中途异常退出,则不会执行该方法。
WeiAfterMethod.java
代码如下:
package com.shiyanlou.spring.aop.advice;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
/**
* Created by Administrator on 2019/10/30.
*/
public class WeiAfterMethod implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("WeiAfterMethod: After Method hi wei!!");
}
}
2)、修改bean配置xml文件 SpringAopAdvice.xml
,加入weiAfterMethod
配置,如下:
weiAfterMethod
3)、同样在App.java
中获取并运行代理bean,结果如下:
*************************
Customer name: weihouye
WeiAfterMethod: After Method hi wei!!
*************************
Customer URL: www.weihouyeaiyangzhan.com
WeiAfterMethod: After Method hi wei!!
*************************
&emps;可以看到,在CustomerService.java
中的每个方法执行完毕后,均会执行WeiAfterMethod.java
的afterReturning
方法,抛出异常除外。
4、Afetr Throwing Advice
1)、创建一个实现org.springframework.aop.ThrowsAdvice
接口的实现类WeiThrowExceptionMethod.java
,劫持IllegalArgumentException
异常,当被劫持的类抛出该异常时,将会执行afterThrowing
方法;代码如下:
package com.shiyanlou.spring.aop.advice;
import org.springframework.aop.ThrowsAdvice;
/**
* Created by Administrator on 2019/10/30.
*/
public class WeiThrowExceptionMethod implements ThrowsAdvice {
public void afterThrowing(Exception ex){
System.out.println("WeiThrowException : Throw exception hi wei!!");
}
}
2)、修改bean配置xml文件 SpringAopAdvice.xml
,加入weiThrowExceptionMethod
配置,如下:
weiThrowExceptionMethod
3)、同样在App.java
中获取并运行代理bean,结果如下:
*************************
Customer name: weihouye
*************************
Customer URL: www.weihouyeaiyangzhan.com
*************************
WeiThrowException : Throw exception hi wei!!
可以看到当执行到CustomerService.java
的printThrowException
方法时,我们抛出了IllegalArgumentException
异常,异常抛出后执行了拦截器WeiThrowExceptionMethod.java
的afterThrowing
方法。
5、Around advice
1)、结合上面3种形式的advice,创建一个org.aopalliance.intercept.MethodInterceptor
接口的实现类WeiAroundMethod.java
,重写public Object invoke(MethodInvocation invocation) throws Throwable
方法;
必须通过方法调用对象invocation.proceed()
来调用原来的被代理的方法,当然也可以不调用;代码如下,注意注释中包含十分重要的信息。
package com.shiyanlou.spring.aop.advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import java.util.Arrays;
/**
* Created by Administrator on 2019/10/30.
*/
public class WeiAroundMethod implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
//Method调用的处理
System.out.println("Method Name: " + invocation.getMethod().getName());
System.out.println("Method Arguments: " + Arrays.toString(invocation.getArguments()));
//相当于MethodBeforeAdvice
System.out.println("WeiBeforeMethod: Before method hi wei!!!");
try {
//调用原方法,即调用CustomerService中的方法
Object result = invocation.proceed();
//相当于AfterReturningAdvice
System.out.println("WeiAfterMethod: After Method hi wei!!");
return result;
} catch (IllegalArgumentException e) {
//相当于ThrowsAdvice
System.out.println("WeiThrowException : Throw exception hi wei!!");
throw e; //将异常抛出
}
}
}
2)、修改bean配置xml文件 SpringAopAdvice.xml
,加入weiAroundMethod
配置,如下:
weiAroundMethod
3)、运行App.java
结果如下:
*************************
Method Name: printName
Method Arguments: []
WeiBeforeMethod: Before method hi wei!!!
Customer name: weihouye
WeiAfterMethod: After Method hi wei!!
*************************
Method Name: printURL
Method Arguments: []
WeiBeforeMethod: Before method hi wei!!!
Customer URL: www.weihouyeaiyangzhan.com
WeiAfterMethod: After Method hi wei!!
*************************
Method Name: printThrowException
Method Arguments: []
WeiBeforeMethod: Before method hi wei!!!
WeiThrowException : Throw exception hi wei!!
可以看出Around advice 结合了前面三种advice。
6、改进
在上边的结果中,CustomerService.java
的全部方法均被拦截,下面展示如何使用Pointcuts
只拦截printName()
。
在Spring AOP中,有3个常用的名词,Advices、Pointcuts、Advisor,解释如下:
- Advices:表示一个方法执行前或者执行后的动作;
- Pointcuts:表示根据一个方法的名字或者正则表达式去拦截一个方法;
- Advisor:Advices和Pointcuts组成的独立的单元,且可以传给proxy factory对象。
我们可以用名字匹配法或者正则表达式去匹配要拦截的方法。
1)、Pointcuts ——Name match example (根据方法名匹配)
SpringAopAdvice.xml
中,创建一个NameMatchMethodPointcut
的 Bean,并将想要拦截的方法名printName
注入到属性mappedName
中。配置如下:
创建一个DefaultPointcutAdvisor
的 Advisor bean,将 Pointcut 和 Advice 注入到 Advisor中。如下:
更改代理customerServiceProxy
的interceptorNames
中的值,将上面的Advisor代替原来的weiAroundMethod
,所有的配置如下:
customerAdvisor
运行一下App.java
,发现只有个printName
方法被拦截了。结果输出如下:
*************************
Method Name: printName
Method Arguments: []
WeiBeforeMethod: Before method hi wei!!!
Customer name: weihouye
WeiAfterMethod: After Method hi wei!!
*************************
Customer URL: www.weihouyeaiyangzhan.com
*************************
以上 Poincut 和 Advisor可以一起配置,即不用单独配置 customerPointcut 和 customerAdvisor,只要在配置 customerAdvisor 时选择 NameMatchMethodPointcutAdvisor
,如下:
2)、Pointcuts ——Regular expression match example (根据正则表达式匹配)
可以使用正则表达式匹配拦截方法,Advisor 配置如下:
.*URL.*
运行结果:只有个printURL
方法被拦截
*************************
Customer name: weihouye
*************************
Method Name: printURL
Method Arguments: []
WeiBeforeMethod: Before method hi wei!!!
Customer URL: www.weihouyeaiyangzhan.com
WeiAfterMethod: After Method hi wei!!
*************************