1 前言
在 AspectJ動態代理實現AOP 中,筆者介紹了使用 AspectJ 動態代理實現 AOP,本文將介紹使用 xml 文件配置AOP。
在 xml 文件中,可以使用 aop:config 標籤配置 AOP,常用的屬性如下:
- aop:aspect:配置切面
- aop:pointcut:配置切入點
- aop:before:前置通知,作用於方法執行之前
- aop:after:後置通知,作用於方法的finally語句塊,即不管方法有沒有異常都會執行,通常用於關閉資源
- aop:after-returning:返回通知,作用於方法執行之後
- aop:after-throwing:異常通知,作用於方法拋出異常時
- aop:around:環繞通知
注意:要想讓 IOC 容器管理切面,還需要給切面和被代理類標註 @Component,其用法見通過註解配置bean
需要導入的包如下:
其中,com 開頭的包爲 AspectJ 動態代理核心包,下載如下:
com.springsource.net.sf.cglib-2.2.0、com.springsource.org.aopalliance-1.0.0、com.springsource.org.aspectj.weaver-1.7.2.RELEASE
2 案例
2.1 前置通知、後置通知、返回通知、異常通知
Comp.java
package com.compute;
public interface Comp {
public int add(int a,int b);
public int div(int a,int b);
}
CompImp.java
package com.compute;
import org.springframework.stereotype.Component;
@Component
public class CompImp implements Comp{
@Override
public int add(int a, int b) {
return a+b;
}
@Override
public int div(int a, int b) {
return a/b;
}
}
注意:CompImp 類前加 @Component 註解,是爲了將 bean 交給 IOC 容器管理
Logger.java
package com.compute;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.springframework.stereotype.Component;
@Component
public class Logger {
public void beforeMethod(JoinPoint joinPoint) {
Object[] args=joinPoint.getArgs();
String methodName=joinPoint.getSignature().getName();
System.out.println("前置通知:method:"+methodName+", args:"+Arrays.toString(args));
}
public void afterMethod() {
System.out.println("後置通知");
}
public void afterReturning(JoinPoint joinPoint,Object result) {
String methodName=joinPoint.getSignature().getName();
System.out.println("返回通知:method:"+methodName+", result:"+result);
}
public void afterThrowing(Exception e) {
System.out.println("異常通知:"+e);
}
}
注意:Logger 類前加 @Component 註解,是爲了將 bean 交給 IOC 容器管理
comp.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-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="com.compute"></context:component-scan>
<aop:config>
<aop:aspect ref="logger">
<aop:before method="beforeMethod" pointcut="execution(* com.compute.*.*(..))"/>
<aop:after method="afterMethod" pointcut="execution(* com.compute.*.*(..))"/>
<aop:after-returning method="afterReturning" pointcut="execution(* com.compute.*.*(..))" returning="result"/>
<aop:after-throwing method="afterThrowing" pointcut="execution(* com.compute.*.*(..))" throwing="e"/>
</aop:aspect>
</aop:config>
</beans>
注意:需要導入 context 和 aop 命名空間,context:component-scan 用於掃描組件,即對標有 @Compnent 的類生成 bean 交給 IOC 容器管理;aop:config 用於配置 AOP。
Test.java
package com.compute;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext ac=new ClassPathXmlApplicationContext("comp.xml");
Comp comp=ac.getBean("compImp",Comp.class);
System.out.println(comp.getClass().getName()); //打印代理類的類名
int result=comp.add(1,2);
System.out.println(result);
result=comp.div(2,0);
System.out.println(result);
}
}
運行結果:
com.sun.proxy.$Proxy7
前置通知:method:add, args:[1, 2]
後置通知
返回通知:method:add, result:3
3
前置通知:method:div, args:[2, 0]
後置通知
異常通知:java.lang.ArithmeticException: / by zero
拓展延伸
當有很多通知作用於同一切入點時,可以通過 aop:pointcut 指定公共切入點,如下:
comp.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-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="com.compute"></context:component-scan>
<aop:config>
<aop:aspect ref="logger">
<aop:pointcut expression="execution(* com.compute.*.*(..))" id="pcut"/>
<aop:before method="beforeMethod" pointcut-ref="pcut"/>
<aop:after method="afterMethod" pointcut-ref="pcut"/>
<aop:after-returning method="afterReturning" pointcut-ref="pcut" returning="result"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pcut" throwing="e"/>
</aop:aspect>
</aop:config>
</beans>
2.2 環繞通知
本節僅介紹 Logger.java 和 comp.xml,其他類見2.1節
Logger.java
package com.compute;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
@Component
public class Logger {
public Object aroundMethod(ProceedingJoinPoint joinPoint) {
Object result=null;
try {
System.out.println("前置通知");
result=joinPoint.proceed(); //執行方法
System.out.println("返回通知");
return result;
} catch (Throwable e) {
System.out.println("異常通知");
e.printStackTrace();
}finally {
System.out.println("後置通知");
}
return -1;
}
}
comp.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-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="com.compute"></context:component-scan>
<aop:config>
<aop:aspect ref="logger">
<aop:around method="aroundMethod" pointcut="execution(* com.compute.*.*(..))"/>
</aop:aspect>
</aop:config>
</beans>
運行結果:
com.sun.proxy.$Proxy7
前置通知
返回通知
後置通知
3
前置通知
異常通知
java.lang.ArithmeticException: / by zero
at com.compute.CompImp.div(CompImp.java:15)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
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.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:85)
at com.compute.Logger.aroundMethod(Logger.java:13)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:621)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:610)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:68)
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.$Proxy7.div(Unknown Source)
at com.compute.Test.main(Test.java:13)
後置通知
-1
2.3 切面優先級
當有多個切面時,可以通過 aop:aspect 標籤的 order 屬性定義切面的優先級。
本節僅介紹LoggerA.java、LoggerB.java、comp.xml 以及 Test.java,Comp.java、CompImp.java見2.1節。
LoggerA.java
package com.compute;
import org.springframework.stereotype.Component;
@Component
public class LoggerA {
public void beforeMethod() {
System.out.println("前置通知:LoggerA");
}
}
LoggerB.java
package com.compute;
import org.springframework.stereotype.Component;
@Component
public class LoggerB {
public void beforeMethod() {
System.out.println("前置通知:LoggerA");
}
}
comp.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-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="com.compute"></context:component-scan>
<aop:config>
<aop:aspect ref="loggerA" order="2">
<aop:before method="beforeMethod" pointcut="execution(* com.compute.*.*(..))"/>
</aop:aspect>
<aop:aspect ref="loggerB" order="1">
<aop:before method="beforeMethod" pointcut="execution(* com.compute.*.*(..))"/>
</aop:aspect>
</aop:config>
</beans>
Test.java
package com.compute;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext ac=new ClassPathXmlApplicationContext("comp.xml");
Comp comp=ac.getBean("compImp",Comp.class);
int result=comp.add(1,2);
System.out.println(result);
}
}
運行結果:
前置通知:LoggerB
前置通知:LoggerA
3