Spring--AOP 例子
先用代碼講一下什麽是傳統的AOP(面向切面編程)編程
需求:實現一個簡單的計算器,在每一步的運算前添加日誌。最傳統的方式如下:
Calculator.Java
[java] view plain copy print?
- package cn.limbo.spring.aop.calculator;
- /**
- * Created by Limbo on 16/7/14.
- */
- public interface Calculator {
- int add(int i , int j);
- int sub(int i , int j);
- int mul(int i , int j);
- int div(int i , int j);
- }
CalculatorImpl.java
[java] view plain copy print?
- package cn.limbo.spring.aop.calculator;
- /**
- * Created by Limbo on 16/7/14.
- */
- public class CalculatorImpl implements Calculator {
- @Override
- public int add(int i, int j) {
- System.out.println("The method add begin with [ "+ i +"," + j+" ]");
- System.out.println("The method add end with [ "+ i +"," + j+"]");
- return i + j;
- }
- @Override
- public int sub(int i, int j) {
- System.out.println("The method sub begin with [ "+ i +"," + j+" ]");
- System.out.println("The method sub end with [ "+ i +"," + j+" ]");
- return i - j;
- }
- @Override
- public int mul(int i, int j) {
- System.out.println("The method mul begin with [ "+ i +"," + j+" ]");
- System.out.println("The method mul end with [ "+ i +"," + j+" ]");
- return i * j;
- }
- @Override
- public int div(int i, int j) {
- System.out.println("The method div begin with [ "+ i +"," + j+" ]");
- System.out.println("The method div end with [ "+ i +"," + j+" ]");
- return i / j;
- }
- }
這樣就完成了需求,但是我們發現,倘若是要修改日誌的信息,那麽就需要在具體方法裏面改,這樣做很麻煩,而且把原本清爽的方法改的十分混亂,方法應該表現的是核心功能,而不是這些無關緊要的關註點,下面用原生的java的方式實現在執行方法時,動態添加輸出日誌方法
CalculatorImpl.java
[java] view plain copy print?
- package cn.limbo.spring.aop.calculator;
- /**
- * Created by Limbo on 16/7/14.
- */
- public class CalculatorImpl implements Calculator {
- @Override
- public int add(int i, int j) {
- return i + j;
- }
- @Override
- public int sub(int i, int j) {
- return i - j;
- }
- @Override
- public int mul(int i, int j) {
- return i * j;
- }
- @Override
- public int div(int i, int j) {
- return i / j;
- }
- }
CalculatorLoggingProxy.java
[java] view plain copy print?
- package cn.limbo.spring.aop.calculator;
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
- import java.util.Arrays;
- import java.util.Objects;
- /**
- * Created by Limbo on 16/7/14.
- */
- public class CalculatorLoggingProxy {
- //要代理的對象
- private Calculator target;
- public CalculatorLoggingProxy(Calculator target) {
- this.target = target;
- }
- public Calculator getLoggingProxy(){
- //代理對象由哪一個類加載器負責加載
- ClassLoader loader = target.getClass().getClassLoader();
- //代理對象的類型,即其中有哪些方法
- Class[] interfaces = new Class[]{Calculator.class};
- // 當調用代理對象其中的方法時,執行改代碼
- InvocationHandler handler = new InvocationHandler() {
- @Override
- /**
- * proxy:正在返回的那個對象的代理,一般情況下,在invoke方法中不使用該對象
- * method:正在被調用的方法
- * args:調用方法時,傳入的參數
- */
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- String methodName = method.getName();
- System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
- //日誌
- Object result = method.invoke(target,args);
- System.out.println("The method " + methodName + " ends with " + result);
- return result;
- }
- };
- Calculator proxy = (Calculator) Proxy.newProxyInstance(loader,interfaces,handler);
- return proxy;
- }
- }
Main.java
[java] view plain copy print?
- package cn.limbo.spring.aop.calculator;
- /**
- * Created by Limbo on 16/7/14.
- */
- public class Main {
- public static void main(String[] args) {
- Calculator calculator = new CalculatorImpl();
- Calculator proxy = new CalculatorLoggingProxy(calculator).getLoggingProxy();
- int result = proxy.add(1,2);
- System.out.println("--->" + result);
- result = proxy.sub(1,2);
- System.out.println("--->" + result);
- result = proxy.mul(3,2);
- System.out.println("--->" + result);
- result = proxy.div(14,2);
- System.out.println("--->" + result);
- }
- }
這樣寫雖然已經簡化了代碼,而且可以任意修改日誌信息代碼,但是寫起來還是很麻煩!!!
下面我們使用spring自帶的aop包實現但是要加入
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.8.5.RELEASE.jar
這兩個額外的包
下面看代碼,要點全部卸載代碼裏面了
Calculator.java
[java] view plain copy print?- package cn.limbo.spring.aop.impl;
- /**
- * Created by Limbo on 16/7/14.
- */
- public interface Calculator {
- int add(int i, int j);
- int sub(int i, int j);
- int mul(int i, int j);
- int div(int i, int j);
- }
CalculatorImpl.java
[java] view plain copy print?
- package cn.limbo.spring.aop.impl;
- import org.springframework.stereotype.Component;
- /**
- * Created by Limbo on 16/7/14.
- */
- @Component("calculatorImpl")
- public class CalculatorImpl implements Calculator {
- @Override
- public int add(int i, int j) {
- return i + j;
- }
- @Override
- public int sub(int i, int j) {
- return i - j;
- }
- @Override
- public int mul(int i, int j) {
- return i * j;
- }
- @Override
- public int div(int i, int j) {
- return i / j;
- }
- }
LoggingAspect.java
[java] view plain copy print?
- package cn.limbo.spring.aop.impl;
- import org.aspectj.lang.JoinPoint;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.*;
- import org.springframework.core.annotation.Order;
- import org.springframework.stereotype.Component;
- import java.util.Arrays;
- import java.util.List;
- import java.util.Objects;
- /**
- * Created by Limbo on 16/7/14.
- */
- //把這個類聲明為切面:需要該類放入IOC容器中,再聲明為一個切面
- @Order(0)//指定切面優先級,只越小優先級越高
- @Aspect
- @Component
- public class LoggingAspect {
- /**
- * 定義一個方法,用於聲明切入點表達式,一般的,該方法中再不需要添加其他代碼
- * 主要是為了重用路徑,[email protected]
- * 後面的其他通知直接使用方法名來引用當前的切入點表達式
- */
- @Pointcut("execution(* cn.limbo.spring.aop.impl.Calculator.*(..))")
- public void declareJointPointExpression()
- {
- }
- //聲明該方法是一個前置通知:在目標方法之前執行
- @Before("declareJointPointExpression()")
- // @Before("execution(* cn.limbo.spring.aop.impl.*.*(..))") 該包下任意返回值,任意類,任意方法,任意參數類型
- public void beforeMethod(JoinPoint joinPoint)
- {
- String methodName = joinPoint.getSignature().getName();
- List<Object> args = Arrays.asList(joinPoint.getArgs());
- System.out.println("The Method "+ methodName+" Begins With " + args);
- }
- //在目標方法執行之後執行,無論這個方法是否出錯
- //在後置通知中還不能訪問目標方法的返回值,只能通過返回通知訪問
- @After("execution(* cn.limbo.spring.aop.impl.Calculator.*(int,int))")
- public void afterMethod(JoinPoint joinPoint)
- {
- String methodName = joinPoint.getSignature().getName();
- System.out.println("The Method "+ methodName+" Ends ");
- }
- /**
- * 在方法正常結束後執行的代碼
- * 返回通知是可以訪問到方法的返回值
- */
- @AfterReturning(value = "execution(* cn.limbo.spring.aop.impl.Calculator.*(..))",returning = "result")
- public void afterReturning(JoinPoint joinPoint , Object result)
- {
- String methodName = joinPoint.getSignature().getName();
- System.out.println("The Method " + methodName + " Ends With " + result);
- }
- /**
- *在目標方法出現異常的時候執行代碼
- * 可以訪問異常對象,且可以指定出現特定異常時再執行通知
- */
- @AfterThrowing(value = "execution(* cn.limbo.spring.aop.impl.Calculator.*(..))",throwing = "ex")
- public void afterThrowing(JoinPoint joinPoint,Exception ex)//Exception 可以改成 NullPointerException等特定異常
- {
- String methodName = joinPoint.getSignature().getName();
- System.out.println("The Method " + methodName + " Occurs With " + ex);
- }
- /**
- * 環繞通知需要ProceedingJoinPoint 類型參數 功能最強大
- * 環繞通知類似於動態代理的全過程:ProceedingJoinPoint 類型的參數可以決定是否執行目標方法
- * 且環繞通知必須有返回值,返回值即為目標方法的返回值
- */
- @Around("execution(* cn.limbo.spring.aop.impl.Calculator.*(..))")
- public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint)
- {
- Object result =null;
- String methodName = proceedingJoinPoint.getSignature().getName();
- try {
- //前置通知
- System.out.println("The Method " + methodName + " Begins With " + Arrays.asList(proceedingJoinPoint.getArgs()));
- result = proceedingJoinPoint.proceed();
- // 返回通知
- System.out.println("The Method " + methodName + " Ends With " + result);
- } catch (Throwable throwable) {
- //異常通知
- throwable.printStackTrace();
- }
- System.out.println("The Method " + methodName + " Ends ");
- return result;
- }
- }
applicationContext.xml
[html] view plain copy print?
- <?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:context="http://www.springframework.org/schema/context"
- 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/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="cn.limbo.spring.aop.impl"></context:component-scan>
- <!--使Aspect註解起作用,自動為匹配的類生成代理對象-->
- <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
- </beans>
用xml來配置aop
application-config.xml
[html] view plain copy print?
- <?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:tx="http://www.springframework.org/schema/tx"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
- http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
- http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
- <bean id="userManager" class="com.tgb.aop.UserManagerImpl"/>
- <!--<bean id="aspcejHandler" class="com.tgb.aop.AspceJAdvice"/>-->
- <bean id="xmlHandler" class="com.tgb.aop.XMLAdvice" />
- <aop:config>
- <aop:aspect id="aspect" ref="xmlHandler">
- <aop:pointcut id="pointUserMgr" expression="execution(* com.tgb.aop.*.find*(..))"/>
- <aop:before method="doBefore" pointcut-ref="pointUserMgr"/>
- <aop:after method="doAfter" pointcut-ref="pointUserMgr"/>
- <aop:around method="doAround" pointcut-ref="pointUserMgr"/>
- <aop:after-returning method="doReturn" pointcut-ref="pointUserMgr"/>
- <aop:after-throwing method="doThrowing" throwing="ex" pointcut-ref="pointUserMgr"/>
- </aop:aspect>
- </aop:config>
- </beans>
一共有5類的通知,其中around最為強大,但是不一定最常用,aspect的底層實現都是通過代理來實現的,只能說這個輪子造的不錯
2016.12.09更新
最近在實際項目中配置aop發現了幾個問題,實際的項目配置的的模式是ssh即Spring4+SpringMVC+Hibernate4的模式。
基於註解的方式配置方式有些坑點:
1.發現SpringMVC中aop不起任何作用
經過排查和查找網上的資料,發現問題如下:
Spring MVC啟動時的配置文件,包含組件掃描、url映射以及設置freemarker參數,[email protected]設置?因為springmvc.xml與applicationContext.xml不是同時加載,如果不進行這樣的設置,那麽,[email protected],等到加載applicationContext.xml的時候,會因為容器已經存在Service類,使得cglib將不對Service進行代理,直接導致的結果就是在applicationContext 中的事務配置不起作用,發生異常時,無法對數據進行回滾。以上就是原因所在。
所以改進後的applicationContext.xml如下(只是更改自動掃描包的那個配置):
[html] view plain copy print?
- <context:component-scan base-package="cn.limbo">
- <!--不要將Controller掃進來,否則aop無法使用-->
- <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
- </context:component-scan>
spring-mvc.xml掃描包配置如下:
[html] view plain copy print?
- <context:component-scan base-package="cn.limbo">
- <!--不要將Service掃進來,否則aop無法使用-->
- <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service" />
- </context:component-scan>
這樣就可以成功解決ssh配置下不能使用aop的情況。看來掃描包的時候不能暴力直接全部掃描進來啊,真是成也掃描,敗也掃描,手動哭笑不得。
Spring--AOP 例子