Spring只支持XML方式而没有实现注解的方式(也叫AspectJ方式)的AOP,所以要使用@Aspect注解,只能引入AspectJ相关的 jar 包 aopalliance-1.0.jar 和 aspectjweaver.jar,这个坑把我给坑惨了。
使用步骤如下:
1、引入相关jar包
2、Spring的配置文件 applicationContext.xml 中引入context、aop对应的命名空间;配置自动扫描的包,同时使切面类中相关方法中的注解生效,需自动地为匹配到的方法所在的类生成代理对象。
<?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/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
-
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
-
<!-- 配置自动扫描的包 -->
-
<context:component-scan base-package="com.qcc.beans.aop"></context:component-scan>
-
<!-- 自动为切面方法中匹配的方法所在的类生成代理对象。 -->
-
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
-
</beans>
注意: <aop:aspectj-autoproxy />有一个proxy-target-class属性,
默认为 false,表示使用jdk动态代理织入增强,
当配为<aop:aspectj-autoproxy poxy-target-class="true"/>时,表示使用CGLib动态代理技术织入增强。
不过即使proxy-target-class设置为 false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。
3、创建简单计算器的接口ArithmeticCalculator.java及实现类ArithmeticCalculatorImpl.java
-
package com.qcc.beans.aop;
-
public interface ArithmeticCalculator {
-
int add(int i, int j);
-
int sub(int i, int j);
-
int mul(int i, int j);
-
int div(int i, int j);
-
}
-
package com.qcc.beans.aop;
-
import org.springframework.stereotype.Component;
-
//将实现类加入Spring的IOC容器进行管理
-
@Component("arithmeticCalculator")
-
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
-
@Override
-
public int add(int i, int j) {
-
int result = i + j;
-
return result;
-
}
-
@Override
-
public int sub(int i, int j) {
-
int result = i - j;
-
return result;
-
}
-
@Override
-
public int mul(int i, int j) {
-
int result = i * j;
-
return result;
-
}
-
@Override
-
public int div(int i, int j) {
-
int result = i / j;
-
return result;
-
}
-
}
4、现在想在实现类中的每个方法执行前、后、以及是否发生异常等信息打印出来,需要把日志信息抽取出来,写到对应的切面的类中 LoggingAspect.java 中 。
要想把一个类变成切面类,需要两步,
① 在类上使用 @Component 注解 把切面类加入到IOC容器中
② 在类上使用 @Aspect 注解 使之成为切面类
下面直接上完整代码,用@Aspect注解方式来实现前置通知、返回通知、后置通知、异常通知、环绕通知。
-
package com.qcc.beans.aop;
-
import java.util.Arrays;
-
import org.aspectj.lang.JoinPoint;
-
import org.aspectj.lang.ProceedingJoinPoint;
-
import org.aspectj.lang.annotation.After;
-
import org.aspectj.lang.annotation.AfterReturning;
-
import org.aspectj.lang.annotation.AfterThrowing;
-
import org.aspectj.lang.annotation.Around;
-
import org.aspectj.lang.annotation.Aspect;
-
import org.aspectj.lang.annotation.Before;
-
import org.springframework.stereotype.Component;
-
/**
-
* 日志切面
-
*
-
* @author XiaoYe
-
* @date 2017年1月1日 下午4:14:34
-
*/
-
@Component
-
@Aspect
-
public class LoggingAspect {
-
/**
-
* 前置通知:目标方法执行之前执行以下方法体的内容
-
* @param jp
-
*/
-
@Before("execution(* com.qcc.beans.aop.*.*(..))")
-
public void beforeMethod(JoinPoint jp){
-
String methodName = jp.getSignature().getName();
-
System.out.println("【前置通知】the method 【" + methodName + "】 begins with " + Arrays.asList(jp.getArgs()));
-
}
-
/**
-
* 返回通知:目标方法正常执行完毕时执行以下代码
-
* @param jp
-
* @param result
-
*/
-
@AfterReturning(value="execution(* com.qcc.beans.aop.*.*(..))",returning="result")
-
public void afterReturningMethod(JoinPoint jp, Object result){
-
String methodName = jp.getSignature().getName();
-
System.out.println("【返回通知】the method 【" + methodName + "】 ends with 【" + result + "】");
-
}
-
/**
-
* 后置通知:目标方法执行之后执行以下方法体的内容,不管是否发生异常。
-
* @param jp
-
*/
-
@After("execution(* com.qcc.beans.aop.*.*(..))")
-
public void afterMethod(JoinPoint jp){
-
System.out.println("【后置通知】this is a afterMethod advice...");
-
}
-
/**
-
* 异常通知:目标方法发生异常的时候执行以下代码
-
*/
-
@AfterThrowing(value="execution(* com.qcc.beans.aop.*.*(..))",throwing="e")
-
public void afterThorwingMethod(JoinPoint jp, NullPointerException e){
-
String methodName = jp.getSignature().getName();
-
System.out.println("【异常通知】the method 【" + methodName + "】 occurs exception: " + e);
-
}
-
// /**
-
// * 环绕通知:目标方法执行前后分别执行一些代码,发生异常的时候执行另外一些代码
-
// * @return
-
// */
-
// @Around(value="execution(* com.qcc.beans.aop.*.*(..))")
-
// public Object aroundMethod(ProceedingJoinPoint jp){
-
// String methodName = jp.getSignature().getName();
-
// Object result = null;
-
// try {
-
// System.out.println("【环绕通知中的--->前置通知】:the method 【" + methodName + "】 begins with " + Arrays.asList(jp.getArgs()));
-
// //执行目标方法
-
// result = jp.proceed();
-
// System.out.println("【环绕通知中的--->返回通知】:the method 【" + methodName + "】 ends with " + result);
-
// } catch (Throwable e) {
-
// System.out.println("【环绕通知中的--->异常通知】:the method 【" + methodName + "】 occurs exception " + e);
-
// }
-
//
-
// System.out.println("【环绕通知中的--->后置通知】:-----------------end.----------------------");
-
// return result;
-
// }
-
}
5、编写Main方法进行测试
-
package com.qcc.beans.aop;
-
import org.springframework.context.ApplicationContext;
-
import org.springframework.context.support.ClassPathXmlApplicationContext;
-
public class Main {
-
public static void main(String[] args) {
-
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
-
ArithmeticCalculator arithmeticCalculator = (ArithmeticCalculator) ctx.getBean("arithmeticCalculator");
-
System.out.println(arithmeticCalculator.getClass());
-
int result = arithmeticCalculator.add(3, 5);
-
System.out.println("result: " + result);
-
result = arithmeticCalculator.div(5, 0);
-
System.out.println("result: " + result);
-
}
-
}
运行结果:
-
class com.sun.proxy.$Proxy10
-
【前置通知】the method 【add】 begins with [3, 5]
-
【后置通知】this is a afterMethod advice...
-
【返回通知】the method 【add】 ends with 【8】
-
result: 8
-
【前置通知】the method 【div】 begins with [5, 0]
-
【后置通知】this is a afterMethod advice...
-
Exception in thread "main" java.lang.ArithmeticException: / by zero
-
at com.qcc.beans.aop.ArithmeticCalculatorImpl.div(ArithmeticCalculatorImpl.java:28)
-
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
-
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
-
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
-
at java.lang.reflect.Method.invoke(Method.java:606)
-
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317)
-
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
-
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
-
at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:52)
-
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
-
at org.springframework.aop.aspectj.AspectJAfterAdvice.invoke(AspectJAfterAdvice.java:43)
-
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
-
at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:52)
-
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
-
at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:58)
-
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
-
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
-
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
-
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
-
at com.sun.proxy.$Proxy10.div(Unknown Source)
-
at com.qcc.beans.aop.Main.main(Main.java:15)
把其它代码都注释掉,把环绕通知的方法释放出来,测试结果如下:
-
【环绕通知中的--->前置通知】:the method 【add】 begins with [3, 5]
-
【环绕通知中的--->返回通知】:the method 【add】 ends with 8
-
【环绕通知中的--->后置通知】:-----------------end.----------------------
-
result: 8
-
【环绕通知中的--->前置通知】:the method 【div】 begins with [5, 0]
-
【环绕通知中的--->异常通知】:the method 【div】 occurs exception java.lang.ArithmeticException: / by zero
-
【环绕通知中的--->后置通知】:-----------------end.----------------------
-
Exception in thread "main" org.springframework.aop.AopInvocationException: Null return value from advice does not match primitive return type for: public abstract int com.qcc.beans.aop.ArithmeticCalculator.div(int,int)
-
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:219)
-
at com.sun.proxy.$Proxy7.div(Unknown Source)
-
at com.qcc.beans.aop.Main.main(Main.java:15)
从以上发现,返回通知和异常通知不会同时出现;不管是否发生异常,后置通知都会正常打印。