建议阅读 关于 Spring AOP (AspectJ) 你该知晓的一切,总结的很好。本文代码部分参考了这篇文章。
示例1:使用XML配置切面
项目结构
.
├── build.gradle
└── src
├── main
├── java
│ └── demo
│ ├── CustomInterrupter.java
│ ├── Main.java
│ └── SayHello.java
└── resources
└── beans.xml
build.gradle
plugins {
id 'java'
}
group 'com.example'
version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
dependencies {
implementation group: 'org.springframework', name: 'spring-core', version: '5.0.6.RELEASE'
implementation group: 'org.springframework', name: 'spring-context', version: '5.0.6.RELEASE'
implementation group: 'org.springframework', name: 'spring-aop', version: '5.0.6.RELEASE'
implementation group: 'org.aspectj', name: 'aspectjweaver', version: '1.9.1'
testImplementation group: 'junit', name: 'junit', version: '4.12'
}
SayHello.java
编写类SayHello:
package demo;
public class SayHello {
public String sayHello(String msg) {
System.out.println("正在执行sayHello,收到msg:"+msg);
return "Hello " + msg;
}
}
CustomInterrupter.java
编写切面类:
package demo;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class CustomInterrupter {
public void before(JoinPoint joinPoint){
System.out.println("我是前置通知");
}
public void afterReturning(JoinPoint joinPoint,Object returnVal){
System.out.println("我是后置通知...");
}
public void after(JoinPoint joinPoint) {
System.out.println("最终通知....");
}
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("我是环绕通知前....");
//执行目标函数
Object obj= (Object) joinPoint.proceed();
System.out.println("我是环绕通知后....");
return obj;
}
public void afterThrowing(Throwable throwable){
System.out.println("异常通知:"+ throwable.getMessage());
}
}
beans.xml
在 resources 目录添加beans.xml
:
<?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"
xmlns:context="http://www.springframework.org/schema/context"
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="demo"/>
<bean id="sayHello" class="demo.SayHello"/>
<bean id="customInterrupter" class="demo.CustomInterrupter"/>
<aop:config>
<!--定义切点(切入位置)-->
<aop:pointcut id="pointcut" expression="execution(* demo.SayHello.sayHello(..))" />
<aop:aspect ref="customInterrupter" order="0">
<aop:before method="before" pointcut-ref="pointcut" />
<aop:after method="after" pointcut-ref="pointcut" />
<aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="returnVal" />
<aop:around method="around" pointcut-ref="pointcut" />
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="throwable"/>
</aop:aspect>
</aop:config>
</beans>
Main.java
Main类:
package demo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
SayHello sayHello = (SayHello) ctx.getBean("sayHello");
String result = sayHello.sayHello("World");
System.out.println("结果:" + result);
}
}
执行结果
执行 Main类,输出:
我是前置通知
我是环绕通知前....
正在执行sayHello,收到msg:World
我是环绕通知后....
我是后置通知...
最终通知....
结果:Hello World
另外,环绕通知是可以拦截并修改返回值的。例如把 around 函数改成:
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("我是环绕通知前....");
//执行目标函数
Object obj= (Object) joinPoint.proceed();
System.out.println("我是环绕通知后....");
return "拦截返回值"; // 这个返回值类型必须和原函数类型一致,或者可转换,否则会抛出异常
}
执行Main类,将输出:
我是前置通知
我是环绕通知前....
正在执行sayHello,收到msg:World
我是环绕通知后....
我是后置通知...
最终通知....
结果:拦截返回值
示例2:使用注解配置切面
代码类似示例1。
项目结构
.
├── build.gradle
└── src
├── main
├── java
│ └── demo
│ ├── CustomInterrupter.java
│ ├── Main.java
│ ├── Main2.java
│ └── SayHello.java
└── resources
└── beans.xml
beans.xml
将beans.xml
改为:
<?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"
xmlns:context="http://www.springframework.org/schema/context"
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="demo"/>
<aop:aspectj-autoproxy />
<bean id="sayHello" class="demo.SayHello"/>
<bean id="customInterrupter" class="demo.CustomInterrupter"/>
</beans>
CustomInterrupter.java
CustomInterrupter 类中加上注解:
package demo;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
@Aspect
@Order(0) // 值越小,优先级越高
public class CustomInterrupter {
// 定义切点(切入位置)
@Pointcut("execution(* demo.SayHello.*(..))")
private void pointcut(){}
@Before("pointcut()")
public void before(JoinPoint joinPoint){
System.out.println("我是前置通知");
}
@AfterReturning(value="pointcut()", returning = "returnVal") // 别忘了returning参数
public void afterReturning(JoinPoint joinPoint, Object returnVal){
System.out.println("我是后置通知...,收到的returnVal: " + returnVal);
}
@After("pointcut()")
public void after(JoinPoint joinPoint) {
System.out.println("最终通知....");
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("我是环绕通知前....");
//执行目标函数
Object obj= (Object) joinPoint.proceed();
System.out.println("我是环绕通知后....");
return obj;
}
@AfterThrowing(value="pointcut()", throwing="throwable") // 别忘了throwing参数
public void afterThrowing(JoinPoint joinPoint, Throwable throwable){
System.out.println("异常通知:"+ throwable.getMessage());
}
}
把
@After("pointcut()")
改成@After("execution(* demo.SayHello.*(..))")
也可以。
执行结果
运行 Main 类,输出:
我是环绕通知前....
我是前置通知
正在执行sayHello,收到msg:World
我是环绕通知后....
最终通知....
我是后置通知...,收到的returnVal: Hello World
结果:Hello World
对于后置通知:
@AfterReturning(value="pointcut()", returning = "returnVal")
public void afterReturning(JoinPoint joinPoint, Object returnVal){
System.out.println("我是后置通知...,收到的returnVal: " + returnVal);
}
如果不关心返回值,也可以改成:
@AfterReturning(value="pointcut()")
public void afterReturning(JoinPoint joinPoint){
System.out.println("我是后置通知...");
}
示例3:处理异常
在示例2中,下面这个切面函数并没有用到:
@AfterThrowing(value="pointcut()", throwing="throwable") // 别忘了throwing参数
public void afterThrowing(JoinPoint joinPoint, Throwable throwable){
System.out.println("异常通知:"+ throwable.getMessage());
}
我们看下,它是在什么位置被触发的。
首先,在 SayHello 类中增加函数:
public String sayHello2(String msg) throws Exception {
System.out.println("正在执行sayHello,收到msg:"+msg);
if (true) {
throw new Exception("出现错误");
}
return "Hello " + msg;
}
编写 Main2 类:
package demo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main2 {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
SayHello sayHello = (SayHello) ctx.getBean("sayHello");
String result = sayHello.sayHello2("World");
System.out.println("结果:" + result);
}
}
运行后输出:
我是环绕通知前....
我是前置通知
正在执行sayHello,收到msg:World
最终通知....
异常通知:出现错误
Exception in thread "main" java.lang.Exception: 出现错误
at demo.SayHello.sayHello2(SayHello.java:13)
at demo.SayHello$$FastClassBySpringCGLIB$$b5a76f72.invoke(<generated>)