衆所周知,Spring的兩大核心機制爲:java
本篇博客就來細細品一下AOP的實現以及它的原理;web
AOP意味面向切面編程,咱們以前一直使用的是OOP(面向對象編程):spring
建立一個計算機接口Com,定義如下四個方法:編程
public interface Com { int add(int x, int y); int sub(int x, int y); int mul(int x, int y); int div(int x, int y); }
定義其實現類:ComImpl:app
public class ComImpl implements Com { public int add(int x, int y) { int result = x+y; return result; } public int sub(int x, int y) { int result = x-y; return result; } public int mul(int x, int y) { int result = x*y; return result; } public int div(int x, int y) { int result = x/y; return result; } }
測試類:框架
public class Test { public static void main(String[] args) { Com com = new ComImpl(); System.out.println(com.add(1,4)); } }
輸出:
5
15svg
這個例子很簡單,這裏不作描述了,接下來看新的需求 :測試
這個需求也很簡單,咱們能夠經過在ComImpl類中的四個方法中每一個方法都添加以下:this
public int add(int x, int y) { System.out.println("add方法的參數是:"+x+","+y); int result = x+y; System.out.println("add方法的結果爲:"+result); return result; }
其餘三個方法也相似,我就再也不寫其代碼,這種方法能完成業務需求,可是其弊端顯著:spa
那麼如何解決這個問題呢?
咱們能夠發現日誌打印的代碼基本都是一個格式的,咱們能不能將這些相同部分的代碼提取出來造成一個橫切面呢?而且將這個橫切面抽象成一個對象,將全部的打印日誌代碼寫到這個對象中,以實現業務和代碼的分離;
上面就是AOP的思想 ☝☝☝☝
靜態代理的要求:
缺點:實質上仍是比較繁雜,由於你仍是須要在代理類的每一個真實業務中添加本身的輔助業務,這樣仍是有許多重複的代碼,不便於擴展;
上面的思想咱們能夠用動態代理來實現;
對於ComImpl,咱們只保留其業務代碼(即最初的版本,不在其中添加日誌代碼);
建立MyInvocationHandler類,這個類實現InvocationHandler接口,成爲一個動態代理類:
public class MyInvocationHandler implements InvocationHandler { Object targetObj; //返回代理對象 public Object bind(Object targetObj) { this.targetObj = targetObj; return Proxy.newProxyInstance(this.targetObj.getClass().getClassLoader(), targetObj.getClass().getInterfaces(), this); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; //日誌業務 System.out.println(method.getName()+"的參數是:"+Arrays.toString(args)); //主業務 result = method.invoke(this.targetObj, args); //日誌業務 System.out.println(method.getName()+"的結果是:"+result); return result; } }
bind 方法是 MyInvocationHandler 類提供給外部調用的方法,傳入委託對象,bind 方法會返回一個代理對象,bind 方法完成了兩項工做:
以上所有是反射的知識點,invoke 方法:method 是描述委託對象全部方法的對象,agrs 是描述委託對象方法參數列表的對象。 method.invoke(this.obj,args) 是經過反射機制來調用委託對象的方法,即業務方法。 所以在 method.invoke(this.obj, args) 先後添加打印日誌信息,就等同於在委託對象的業務方法先後添加打印日誌信息,而且已經作到了分類,業務方法在委託對象中,打印日誌信息在代理對象中;
給出測試類:
public class Test { public static void main(String[] args) { //真實業務類 Com com = new ComImpl(); //代理類的對象(這個不叫代理對象) MyInvocationHandler myInvocationHandler = new MyInvocationHandler(); //根據代理類的對象獲取代理對象 Com com1 = (Com) myInvocationHandler.bind(com); com1.add(1,5); com1.sub(6, 3); } }
輸出:
add的參數是:[1, 5]
add的結果是:6
sub的參數是:[6, 3]
sub的結果是:3
從測試中咱們能夠看到已經達到了咱們的要求,業務和日誌都能正確實現,並且:
在上面的案例中,咱們用動態代理實現了AOP,可是在Spring中,咱們不須要建立MyInvocationHandler類,Spring已經對其完成了封裝,咱們只須要建立一個切面類,Spring底層會自動根據切面類以及目標類生成一個代理對象;
@Aspect @Component public class LoggerAspect { //int表明返回值類型,必須寫,後面是具體類名,*表明這個類的全部方法,..表明全部參數 @Before("execution(public int www.springAOP.ComImpl.*(..))") public void before(JoinPoint joinPoint) { String name = joinPoint.getSignature().getName(); String args = Arrays.toString(joinPoint.getArgs()); System.out.println("方法名爲:"+name+" 參數爲:"+args); } @After("execution(public int www.springAOP.ComImpl.*(..))") public void after(JoinPoint joinPoint) { System.out.println("方法結束"); } @AfterReturning(value = "execution(public int www.springAOP.ComImpl.*(..))", returning = "result") //注意上面的result必須與下面的形參名如出一轍,且必須有這個形參 public void afterReturn(JoinPoint joinPoint, Object result) { String name = joinPoint.getSignature().getName(); System.out.println(name+"方法結果爲:"+result); } @AfterThrowing(value = "execution(public int www.springAOP.ComImpl.*(..))", throwing = "ex") public void afterThrow(JoinPoint joinPoint, Exception ex) { String name = joinPoint.getSignature().getName(); System.out.println(name+"方法拋出異常:"+ex); } }
下面來分別解釋如下各個註解:
在applicationContext_AOP.xml中進行以下配置:
<context:component-scan base-package="www.springAOP"/> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
第一行就不用說了,自動掃描包,使用註解的時候都必須用到這一行;
第二行則是:
使Aspect註解生效,爲目標類自動生成代理對象;
Spring容器會結合切面類和目標類自動生成代理對象,Spring框架的底層就是經過動態代理的方式完成AOP;
目標類其實就是在那些execution表達式裏;
public class AOPTest { ApplicationContext applicationContext; @Before public void testInitial() { applicationContext = new ClassPathXmlApplicationContext ("applicationContext_AOP.xml"); } @Test public void testMethod() { //注意這裏返回的是代理類,這個代理類繼承了Com這個接口的 //這裏返回的不是ComImpl,因此前面的類型只能是Com Com com = (Com) applicationContext.getBean("comImpl"); com.div(2,1); System.out.println("---------------------------------"); com.add(2,5); System.out.println("---------------------------------"); com.div(2,0); } }
方法名爲:div 參數爲:[2, 1] 方法結束 div方法結果爲:2 --------------------------------- 方法名爲:add 參數爲:[2, 5] 方法結束 add方法結果爲:7 --------------------------------- 方法名爲:div 參數爲:[2, 0] 方法結束 div方法拋出異常:java.lang.ArithmeticException: / by zero java.lang.ArithmeticException: / by zero 。。。。。。(報錯信息一大堆)
注意:
在Spring AOP中,有以下幾個術語: