Spring 04 AOP
1、代理设计模式
概念
GoF95 一共定义了23种设计模式,代理设计模式是非常重要的模式之一
作用:对目标对象访问进行控制,在访问目标对象的前后进行功能的扩展
可以进行功能扩展的技术:
过滤器,拦截器,代理 等
静态代理
只能代理一种类型的对象,代理类需要自定义开发
动态代理
可以代理所有的类型对象,代理类由代理工具类动态生成
JDK动态代理
⑴ 基于接口进行代理
⑵ 目标对象必须实现相关的接口,才能使用这种代理方式
⑶ 代理类和目标类实现了相同的接口
⑷ 使用InvocationHandler【调用处理器】
Cglib/Javassist
⑴ 基于继承方式进行代理
⑵ 代理类是目标类的子类
代理流程
使用代理前:
A(客户端) → D(目标程序【业务代码 + 非业务代码(日志、事务、校验、权限 等)】)
使用代理后:
A(客户端) → B(代理对象) → C(调用处理器【非业务代码(日志、事务、权限、校验 等)】) → D(目标程序【业务代码】)
代理可以解决的问题
日志打印,事务处理,权限控制,数据校验 等
2、代理设计模式案例
【目标接口】
public interface Calc {
int add(int a, int b);
int sub(int a, int b);
int mul(int a, int b);
int div(int a, int b);
}
【目标类】
public class SimpleCalc implements Calc {
@Override
public int add(int a, int b) {
int result = a + b;
System.out.println("方法内部打印:" + result);
return result;
}
@Override
public int sub(int a, int b) {
int result = a - b;
System.out.println("方法内部打印:" + result);
return result;
}
@Override
public int mul(int a, int b) {
int result = a * b;
System.out.println("方法内部打印:" + result);
return result;
}
@Override
public int div(int a, int b) {
int result = a / b;
System.out.println("方法内部打印:" + result);
return result;
}
}
【生成代理对象的类】
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class CalcProxy {
// 目标对象
private Object targetObj;
public CalcProxy(Object targetObj) {
this.targetObj = targetObj;
}
/**
* 生成代理对象
*
* @return 代理对象
*/
public Object getProxyObject() {
/**
* ClassLoader loader 类加载器。用于加载代理类到JVM中运行。一般传递目标对象的类加载器
*
* Class<?>[] interfaces 接口类型数组。JDK动态代理,是基于接口的。
* 目标类和代理类实现共同的接口,需要通过目标对象来获取接口类型
*
* InvocationHandler h 调用处理器。代理类是在内存中动态生成的。无法将扩展代码写在代理类中。
* 所以需要通过InvocationHandler接口,来编写实现类,完成功能的扩展
*/
return Proxy.newProxyInstance(targetObj.getClass().getClassLoader(), targetObj.getClass()
.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object returnVal = null;
try {
System.out.println("【" + method.getName() + "方法开始】【参数:"
+ (args == null ? "" : Arrays.asList(args)) + "】");
returnVal = method.invoke(targetObj, args);
System.out.println("【" + method.getName() + "方法返回】【返回值:】" + returnVal);
} catch (Exception e) {
e.printStackTrace();
System.out.println("【" + method.getName() + "方法出异常了】【异常信息:"
+ e.getCause().getMessage() + "】");
} finally {
System.out.println("【" + method.getName() + "方法结束】");
}
return returnVal;
}
});
}
}
【测试类】
public class TestCalc {
@Test
public void testSimpleCalc() {
Calc calc = new SimpleCalc();
calc.add(10, 5);
calc.sub(10, 5);
calc.mul(10, 5);
calc.div(10, 5);
}
@Test
public void testLogCalc() {
Calc calc = new LogCalc();
calc.add(10, 5);
System.out.println("-----------------------------");
calc.sub(10, 5);
System.out.println("-----------------------------");
calc.mul(10, 5);
System.out.println("-----------------------------");
calc.div(10, 5);
}
@Test
// 测试代理
public void testCalcProxy() {
Calc calc = new SimpleCalc();
Calc calcProxy = (Calc) new CalcProxy(calc).getProxyObject();
// class com.sun.proxy.$Proxy2
System.out.println(calcProxy.getClass());
calcProxy.add(10, 5);
System.out.println("-----------------------------");
calcProxy.sub(10, 5);
System.out.println("-----------------------------");
calcProxy.mul(10, 5);
System.out.println("-----------------------------");
calcProxy.div(10, 5);
}
@Test
// 测试异常
public void testException() {
Calc calc = new SimpleCalc();
Calc calcProxy = (Calc) new CalcProxy(calc).getProxyObject();
calcProxy.div(10, 0);
}
}
3、AOP概述
概念
Aspect-Oriented Programming 面向切面编程
横切关注点
表示解决程序中什么样的问题
例如:解决日志打印,数据校验,权限控制,事务处理 等
切面类
将横切关注点代码模块化,形成一个一个独立的组件程序,称为切面类
切面类中定义多个通知方法,通知方法中编写的就是非业务代码(包括日志打印,事务处理,权限控制,数据校验 等)
通知
将横切关注点的代码封装到一个一个的方法中,在执行目标过程中,进行动态的执行
通知的5种方式
⑴ 前置通知(@Before)
在目标方法执行前,执行扩展
⑵ 方法返回通知(@AfterReturning)
在目标方法执行后,返回结果时,执行扩展
⑶ 异常通知(@AfterThrowing)
在目标方法抛出异常时,执行扩展
⑷ 后置通知【最终通知】(@After)
目标方法不管是否存在异常,都必须执行扩展。即一定会被执行的扩展
⑸ 环绕通知(@Around)
相当于以上4种通知的各种组合,功能更强大。但使用较少
连接点
通知方法需要执行的位置
在Spring的AOP中,连接点就是目标对象的方法
切入点
使用切入点表达式,来查找连接点,被查找到的连接点,统一称为切入点
切入点就是连接点的集合
代理对象
切面类的通知(方法)是被代理对象调用执行的
Spring采用两种代理方式
⑴ 默认采用JDK动态代理,只要目标对象有接口,就采用JDK动态代理
⑵ 目标对象没有接口,就采用Cglib动态代理
目标对象
该对象用于完成具体业务逻辑代码
4、AOP的具体实现
步骤
⑴ 导入jar包
除了:
commons-logging-1.1.3.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
必须的jar包
spring-aop-4.0.0.RELEASE.jar
用注解方式声明bean
还需:
spring-aspects-4.0.0.RELEASE.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
⑵ 在核心配置文件中配置
① 先增加context和aop名称空间
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
会在xsi:schemaLocation 中增加:
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="要扫描的包及其子包" />
<!-- 启用AOP功能:扫描AOP注解 -->
<aop:aspectj-autoproxy />
⑶ 定义目标类
@Component // 声明组件对象
public class ?? {
}
⑷ 定义切面类
@Aspect // 声明这是一个切面类
@Component // 声明对象
public class ?? {
}
⑸ 在切面类中定义通知(方法)
// 该方法是一个前置通知
@Before(value = "execution(切入点表达式)")
public void ?? {
}
// 该方法是一个方法返回通知
@AfterReturning(value = "execution(切入点表达式)")
public void ?? {
}
// 该方法是一个异常通知
@AfterThrowing(value = "execution(切入点表达式)")
public void ?? {
}
// 该方法是一个后置通知
@After(value = "execution(切入点表达式)")
public void ?? {
}
示例(测试JDK动态代理)
【核心配置文件】
<context:component-scan base-package="???.???.???" />
<aop:aspectj-autoproxy />
【目标接口】
public interface Calc {
int add(int a, int b);
int sub(int a, int b);
int mul(int a, int b);
int div(int a, int b);
}
【目标类】
@Component
public class SimpleCalc implements Calc {
@Override
public int add(int a, int b) {
int result = a + b;
System.out.println("方法内部打印:" + result);
return result;
}
@Override
public int sub(int a, int b) {
int result = a - b;
System.out.println("方法内部打印:" + result);
return result;
}
@Override
public int mul(int a, int b) {
int result = a * b;
System.out.println("方法内部打印:" + result);
return result;
}
@Override
public int div(int a, int b) {
int result = a / b;
System.out.println("方法内部打印:" + result);
return result;
}
}
【切面类】
@Aspect
@Component
public class LogAspect {
@Before(value = "execution(public int *..SimpleCalc.add(int, int))")
public void beforeLog() {
System.out.println("【add方法开始执行】");
}
}
【测试类】
private ApplicationContext ioc = new ClassPathXmlApplicationContext("???.xml");
@Test
// JDK动态代理(有接口)
public void testJDK() {
Calc calc = ioc.getBean(Calc.class);
// class com.sun.proxy.$Proxy8
// 包名[email protected] 【目标类有接口,采用JDK动态代理,代理类是目标类的兄弟类】
System.out.println(calc.getClass());
calc.add(10, 5);
System.out.println("-------------------");
calc.sub(10, 5);
System.out.println("-------------------");
calc.mul(10, 5);
System.out.println("-------------------");
calc.div(10, 5);
}
测试Cglib动态代理
【添加目标类】
@Component
public class SimpleCalc2 {
public int add(int a, int b) {
int result = a + b;
System.out.println("方法内部打印:" + result);
return result;
}
public int sub(int a, int b) {
int result = a - b;
System.out.println("方法内部打印:" + result);
return result;
}
public int mul(int a, int b) {
int result = a * b;
System.out.println("方法内部打印:" + result);
return result;
}
public int div(int a, int b) {
int result = a / b;
System.out.println("方法内部打印:" + result);
return result;
}
}
【添加切面类】
@Aspect
@Component
public class LogAspect2 {
@Before(value = "execution(public int *..SimpleCalc2.add(int, int))")
public void beforeLog() {
System.out.println("【add方法开始执行】");
}
}
【添加测试】
@Test
// Cglib动态代理(没有接口)
public void testCglib() {
SimpleCalc2 simpleCalc2 = (SimpleCalc2) ioc.getBean("simpleCalc2");
// class com.test.spring.calc.impl.SimpleCalc2$$EnhancerByCGLIB$$8008b12c 【目标对象没有接口,采用Cglib动态代理,代理类是目标类的子类】
System.out.println(simpleCalc2.getClass());
simpleCalc2.add(10, 5);
System.out.println("-------------------");
simpleCalc2.sub(10, 5);
System.out.println("-------------------");
simpleCalc2.mul(10, 5);
System.out.println("-------------------");
simpleCalc2.div(10, 5);
}
5、切入点表达式
作用
用于查找连接点
语法
execution([修饰符][返回值类型][全类名][方法名][形参类型列表])
execution(方法签名)
在AspectJ中,切入点表达式可以通过 “&&”、“||”、“!”等操作符结合起来
语法:
execution(方法签名) && execution(方法签名)
execution(方法签名) || execution(方法签名)
!execution(方法签名)
Tips:
⑴ [返回值类型]可以用* 来匹配
⑵ [修饰符][返回值类型]可以用* 来匹配
⑶ [全类名]可以用* 来匹配
⑷ [全类名]可以用 ??.??..??来匹配
⑸ [全类名][方法名]可以用. 来匹配
⑹ [方法名]可以用??* 来匹配
⑺ [形参类型]可以用.. 来匹配
⑻ [形参类型]可以用??, ..来匹配
⑼ 最模糊查询 execution(* .(..))
示例
【目标类】
com.test.spring.MyClass
有一个方法:
public String method(String a, int b) {}
package com.test.spring;
import org.springframework.stereotype.Component;
@Component
public class MyClass {
public String method(String a, int b) {
return "";
}
}
【切面类】
com.test.spring.MyAspect
有一个方法:
public void test() {}
package com.test.spring;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MyAspect {
@Before(value = "execution(方法签名)")
public void test() {
}
}
【切入点表达式】
⑴ 最精确匹配
execution(public String com.test.spring.MyClass.method(String, int))
⑵ 模糊修饰符
execution(* com.test.spring.MyClass.method(String, int))
⑶ 模糊包
execution(public String com..MyClass.method(String, int))
⑷ 模糊全类名和方法名
execution(public String *.*(String, int))
⑸ 模糊参数列表
execution(public String com.test.spring.MyClass.method(..))
⑹ 最模糊查询,匹配所有方法【通用】
execution(* *.*(..))
6、通知
JoinPoint
JoinPoint放在通知(方法)的形参位置,以便调用
一般用来获取方法和参数列表
// 获取方法签名
Signature signature = joinPoint.getSignature();
// 获取方法名
String methodName = signature.getName();
// 获取参数列表
Object[] args = joinPoint.getArgs();
Tips:【将参数列表转换为String类型】
Arrays.asList(args).toString();
使用Arrays工具类,将Object[] 数组转换为ArrayList集合,再调用ArrayList集合的重写过的toString 方法,即可
注意:在转换将数组转集合前,需要判断数组是否为空,可以使用三元运算符
示例:
(null == args)? “” : Arrays.asList(args).toString()
ProceedingJoinPoint
ProceedingJoinPoint是JoinPoint的子接口
所以它除了可以获取方法名,参数列表外,还可以
执行目标方法:
public Object proceed() throws Throwable;
该方法有Object类型的返回值,即目标方法的返回值
前置通知
@Before(value = "execution(* *.*(..))")
public void beforeLog(JoinPoint joinPoint) throws Throwable {
// 获取方法签名
Signature signature = joinPoint.getSignature();
// 获取方法名
String methodName = signature.getName();
// 获取参数列表
Object[] args = joinPoint.getArgs();
System.out.println(methodName + "方法开始,参数:" + (null == args ? "" : Arrays.asList(args)));
}
方法返回通知
可以在通知的形参位置,添加一个Object类型的参数,用于接收目标方法的返回值
同时需要给@AfterReturning注解,添加一个returning的属性,这个String类型的值就是定义的参数的名字
注意:一定要让returning的value值和通知的Object参数的名相同,否则报错:
java.lang.IllegalStateException: Returning argument name ‘???’ was not bound in advice arguments
@AfterReturning(value = "execution(* *.*(..))", returning = "result")
public void returnLog(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println(methodName + "方法返回,返回值:" + result);
}
异常通知
可以在通知的形参位置,添加一个Throwable或Exception类型的参数,用于接收目标方法的返回值
同时需要给@AfterThrowing注解,添加一个throwing的属性,这个String类型的值就是定义的参数的名字
注意:一定要让throwing的value值和通知的Throwable或Exception参数的名相同,否则报错:
Caused by: java.lang.IllegalStateException: Throwing argument name ‘???’ was not bound in advice arguments
Tips:可以通过具体的Exception,来指定出现特定的异常时,执行通知(方法)
@AfterThrowing(value = "execution(* *.*(..))", throwing = "e")
public void throwLog(JoinPoint joinPoint, Throwable e) {
String methodName = joinPoint.getSignature().getName();
System.out.println(methodName + "方法出异常了,异常信息:" + e.getMessage());
}
可以获取任意类型的异常对象
@AfterThrowing(value = "execution(* *.*(..))", throwing = "e")
public void throwLog(JoinPoint joinPoint, NullPointerException e) {
String methodName = joinPoint.getSignature().getName();
System.out.println(methodName + "方法出异常了,异常信息:" + e.getMessage());
}
只能获取空指针异常对象
后置通知
@After(value = "execution(* *.*(..))")
public void afterLog(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println(methodName + "方法执行完毕");
}
环绕通知
注意:
⑴ 环绕通知(方法)的返回值为Object类型,用于返回目标方法的返回值
⑵ 形参须为ProceedingJoinPoint,以便调用目标方法joinPoint.proceed(); 如果执行目标方法,则无法获取目标方法的返回值【null】
⑶ 在catch中,一定要将异常对象抛出,否则其他切面将无法获取异常
@Around(value = "execution(* *.*(..))")
public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
// 目标方法返回值
Object returnVal = null;
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
try {
System.out.println("[" + methodName + "方法开始][参数:"
+ (null == args ? "" : Arrays.asList(args)) + "]");
// 执行目标方法,并接收返回值
returnVal = joinPoint.proceed();
System.out.println("[" + methodName + "方法返回][返回值:" + returnVal + "]");
} catch (Exception e) {
System.out.println("[" + methodName + "方法出异常了][异常信息:" + e.getMessage() + "]");
// 如果是JUnit测试,则打印堆栈信息
// e.printStackTrace();
// 一定要抛出异常,否则等同于在这里处理了
throw e;
} finally {
System.out.println("[" + methodName + "方法执行完毕]");
}
return returnVal;
}
7、重用切入点表达式
概念
当一些通知(方法)的切入点相同时,可以定义一个公共的切入点表达式,以便让这些通知(方法)来调用
需要使用@Pointcut注解,其value属性值就是切入点表达式
步骤
⑴ 在切面类中定义一个无返回值,无参数,无方法体的方法
⑵ 在本类通知中,通过方法名() 引用即可
⑶ 在其他类的通知中,通过全类名.方法名() 引用即可
注意:该方法的访问修饰符,决定了其能否被其他的切面类引用
示例
【切面类 一】
package com.test.spring;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MyAspect {
@Pointcut(value = "execution(* com.test.spring.目标类.*(..))")
public void myPointcut() {}
@Before(value = "myPointcut()")
public void before() {
System.out.println("==========MyAspect==========");
}
}
【切面类 二】
package com.test.spring;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MyAspect2 {
@Before(value = "com.test.spring.MyAspect.myPointcut()")
public void before() {
System.out.println("----------MyAspect2----------");
}
}
8、设置切面类的优先级
实现方法
当存在多个切面类,以及相同的多个通知(前置或后置等),可以给切面类设置优先级,来控制通知的执行顺序
通过给切面类添加@Order 注解来实现,其value属性值,是int类型,值越小,优先级越高。可以是负数
示例
【目标类】
package com.test.spring.target;
import org.springframework.stereotype.Component;
@Component
public class TargetObject {
public String targetMethod(Integer i, String str) {
System.out.println("方法内部执行");
return str;
}
}
【切面类 一】
package com.test.spring.aspect;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Aspect
// 设置优先级
@Order(value = 2)
@Component
public class LogAspect {
@Pointcut(value = "execution(* com.test.spring.target.TargetObject.targetMethod(Integer, String))")
public void myPointcut() {
}
@Before(value = "myPointcut()")
public void beforeLog() {
System.out.println("[LogAspect]【目标方法执行前】");
}
@After(value = "myPointcut()")
public void afterLog() {
System.out.println("[LogAspect]【目标方法执行完毕】");
}
}
【切面类 二】
package com.test.spring.aspect;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Aspect
// 设置优先级
@Order(value = 1)
@Component
public class LogAspect2 {
@Before(value = "com.test.spring.aspect.LogAspect.myPointcut()")
public void beforeLog() {
System.out.println("----------[LogAspect2]目标方法执行前----------");
}
@After(value = "com.test.spring.aspect.LogAspect.myPointcut()")
public void afterLog() {
System.out.println("----------[LogAspect2]目标方法执行完毕----------");
}
}
【测试类】
@Test
public void test() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("???.xml");
TargetObject to = ioc.getBean(TargetObject.class);
to.targetMethod(1, "字符串");
}
【执行结果】
----------[LogAspect2]目标方法执行前----------
[LogAspect]【目标方法执行前】
方法内部执行
[LogAspect]【目标方法执行完毕】
----------[LogAspect2]目标方法执行完毕----------
9、基于XML的AOP配置
相关的8个标签
<aop:config><aop:config>
用于声明切面类对象和目标类对象的关系。在其里面声明切面类及其通知
<aop:aspect id="切面类对象的id" ref="引用的切面类的id" order="切面类的通知执行的优先级别"></aop:aspect>
声明切面对象
<aop:pointcut expression="execution(方法签名)" id="切入点表达式的id" />
用于声明切入点表达式。提供id,以便其他切面类来引用
<aop:before method="对应切面类的通知(方法)" pointcut-ref="所引用的切入点表达式的id" />
用于声明前置通知
<aop:after-returning method="对应切面类的通知(方法)" returning="通知(方法)里的Object的参数的名字【用于接收目标方法的返回值】" pointcut-ref="所引用的切入点表达式的id" />
用于声明方法返回通知
<aop:after-throwing method="对应切面类的通知(方法)" throwing="通知(方法)里的Throwable类型或Exception类型及其子类型的参数的名字【用于接收目标方法的异常对象】" pointcut-ref="所引用的切入点表达式的id" />
用于声明异常通知
<aop:after method="对应切面类的通知(方法)" pointcut-ref="所引用的切入点表达式的id" />
用于声明后置通知
<aop:around method="对应切面类的通知(方法)" pointcut-ref="所引用的切入点表达式的id" />
用于声明环绕通知
具体步骤
⑴ 通过bean标签,声明目标类和切面类
⑵ 通过aop:config 标签,来声明切面类对象和目标类对象之间的关系
⑶ 通过aop:aspect 标签,来声明切面类
⑷ 通过aop:pointcut 标签,来声明切入点表达式
⑸ 通过aop:before、app:after 等标签,来声明对应的通知(方法)。如果通知(方法)有接收目标对象的返回值或异常对象,则还需配置对应的属性【returning或throwing】
使用示例
【核心配置文件】
<?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.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!-- 声明目标类对象 -->
<bean id="targetObject" class="com.test.spring.target.TargetObjectImpl" />
<!-- 声明切面类对象 -->
<bean id="logAspect" class="com.test.spring.aspect.LogAspect" />
<bean id="logAspect2" class="com.test.spring.aspect.LogAspect2" />
<!-- 声明切面类对象和目标类对象之间的关系 -->
<aop:config>
<!-- 声明全局的切入点表达式,可以被任何的切面类使用 -->
<aop:pointcut expression="execution(* *.*(..))" id="pointcutId" />
<!-- 声明切面类 -->
<!-- 切面类的优先级别为2 -->
<aop:aspect id="logAspectId" ref="logAspect" order="2">
<!-- 声明局部的切入点表达式,只能被当前的切面类使用 -->
<aop:pointcut expression="execution(* com.test.spring.target.TargetObjectImpl.*(..))" id="logAspectPointcutId" />
<!-- 声明前置通知 -->
<aop:before method="beforeLog" pointcut-ref="logAspectPointcutId" />
<!-- 声明方法返回通知 -->
<aop:after-returning method="returningLog" returning="result" pointcut-ref="logAspectPointcutId" />
<!-- 声明异常通知 -->
<aop:after-throwing method="throwingLog" throwing="e" pointcut-ref="logAspectPointcutId" />
<!-- 声明后置通知 -->
<aop:after method="afterLog" pointcut-ref="logAspectPointcutId" />
<!-- 声明环绕通知 -->
<aop:around method="aroundLog" pointcut-ref="logAspectPointcutId" />
</aop:aspect>
<!-- 切面类的优先级别为1 -->
<aop:aspect id="logAspectId2" ref="logAspect2" order="1">
<aop:pointcut expression="execution(* com.test.spring.target.TargetObjectImpl.*(..))" id="logAspect2PointcutId" />
<aop:before method="beforeLog" pointcut-ref="logAspect2PointcutId" />
<aop:after method="afterLog" pointcut-ref="logAspect2PointcutId" />
</aop:aspect>
</aop:config>
</beans>
【目标接口】
package com.test.spring.target;
public interface TargetObject {
int add(int a, int b);
int div(int a, int b);
}
【目标类】
package com.test.spring.target;
public class TargetObjectImpl implements TargetObject {
@Override
public int add(int a, int b) {
int result = a + b;
System.out.println("方法内部打印:" + result);
return result;
}
@Override
public int div(int a, int b) {
int result = a / b;
System.out.println("方法内部打印:" + result);
return result;
}
}
【切面类 一】
package com.test.spring.aspect;
import java.util.Arrays;
import org.aspectj.lang.ProceedingJoinPoint;
public class LogAspect {
public void beforeLog() {
System.out.println("【方法执行前】");
}
public void returningLog(Object result) {
System.out.println("【方法返回】【返回值:" + result + "】");
}
public void throwingLog(Throwable e) {
System.out.println("【方法出异常了】【异常信息:" + e.getMessage() + "】");
}
public void afterLog() {
System.out.println("【方法结束了】");
}
public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
Object returnVal = null;
try {
System.out.println("[" + methodName + "方法开始][参数:"
+ (null == args ? "" : Arrays.asList(args)) + "]");
returnVal = joinPoint.proceed();
System.out.println("[" + methodName + "方法返回][返回值:" + returnVal + "]");
} catch (Exception e) {
e.printStackTrace();
System.out.println("[" + methodName + "方法出异常了][异常信息:" + e.getMessage() + "]");
throw e;
} finally {
System.out.println("[" + methodName + "方法执行完毕]");
}
return returnVal;
}
}
【切面类 二】
package com.test.spring.aspect;
public class LogAspect2 {
public void beforeLog() {
System.out.println("----------方法开始前----------");
}
public void afterLog() {
System.out.println("----------方法执行完毕----------");
}
}
【测试类】
@Test
public void test() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("???.xml");
TargetObject to = ioc.getBean(TargetObject.class);
to.add(10, 5);
System.out.println("========================================");
to.div(10, 5);
}
【执行结果】
----------方法开始前----------
【方法执行前】
[add方法开始][参数:[10, 5]]
方法内部打印:15
[add方法返回][返回值:15]
[add方法执行完毕]
【方法结束了】
【方法返回】【返回值:15】
----------方法执行完毕----------
========================================
----------方法开始前----------
【方法执行前】
[div方法开始][参数:[10, 5]]
方法内部打印:2
[div方法返回][返回值:2]
[div方法执行完毕]
【方法结束了】
【方法返回】【返回值:2】
----------方法执行完毕----------
推荐阅读
-
详解Spring框架下向异步线程传递HttpServletRequest参数的坑
-
Spring Boot 整合mybatis 与 swagger2
-
Spring Boot 2.X优雅的解决跨域问题
-
Spring Boot使用RestTemplate消费REST服务的几个问题记录
-
Spring Boot使用过滤器和拦截器分别实现REST接口简易安全认证示例代码详解
-
Spring中@Async注解执行异步任务的方法
-
详解spring boot应用启动原理分析
-
详解在Spring中如何使用AspectJ来实现AOP
-
深入Spring Boot之ClassLoader的继承关系和影响
-
Spring Boot中自动化配置的利弊以及解决方法