Spring核心之二:AOP(Aspect Oriented Programming) --- 面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。AOP是OOP的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生范型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
專業術語:
- Joinpoint(連接點): 所謂連接點是指那些被攔截到的點。在spring中,這些點指的是方法,因為spring只支持方法類型的連接點.
- Pointcut(切入點): 所謂切入點是指我們要對哪些Joinpoint進行攔截的定義.
- Advice(通知/增強): 所謂通知是指攔截到Joinpoint之后所要做的事情就是通知.通知分為前置通知,后置通知,異常通知,最終通知,環繞通知(切面要完成的功能)
- Introduction(引介): 引介是一種特殊的通知在不修改類代碼的前提下, Introduction可以在運行期為類動態地添加一些方法或Field.
- Target(目標對象): 代理的目標對象
- Weaving(織入): 是指把增強應用到目標對象來創建新的代理對象的過程.
spring采用動態代理織入,而AspectJ采用編譯期織入和類裝在期織入. - Proxy(代理): 一個類被AOP織入增強后,就產生一個結果代理類Aspect(切面): 是切入點和通知(引介)的結合
動態代理:AOP的核心是java的動態代理技術,比它更簡單。
看一個例子
一個類有幾個簡單的方法
1 package com.atguigu.spring.aop.helloword; 2 3 public class Calculator implements Calcul{ 4 5 public int add(int i,int j){ 6 return i+j; 7 }; 8 public int sub(int i,int j){ 9 return i-j; 10 }; 11 public int div(int i,int j){ 12 return i/j; 13 }; 14 public int mul(int i,int j){ 15 return i*j; 16 }; 17 }
如果要你給每個方法都加上日志,好在執行的時候知道調用了哪個方法、什么參數和什么結果,那就得這樣
1 package com.atguigu.spring.aop.helloword; 2 3 public class Calculator implements Calcul{ 4 5 public int add(int i,int j){ 6 System.out.println("The method add begins with: ["+i+","+j+"]"); 7 System.out.println("The method add ends with: "+(i+j)); 8 return i+j; 9 }; 10 public int sub(int i,int j){ 11 System.out.println("The method sub begins with: ["+i+","+j+"]"); 12 System.out.println("The method sub ends with: "+(i-j)); 13 return i-j; 14 }; 15 public int div(int i,int j){ 16 System.out.println("The method div begins with: ["+i+","+j+"]"); 17 System.out.println("The method div ends with: "+i/j); 18 return i/j; 19 }; 20 public int mul(int i,int j){ 21 System.out.println("The method mul begins with: ["+i+","+j+"]"); 22 System.out.println("The method mul ends with: "+i*j); 23 return i*j; 24 }; 25 }
看每一個方法的日志,都是很規范的,很整齊的。但就是太麻煩了,太重復了。而且要改日志的格式,那就得一個一個的改,這樣肯定不行。應該要把日志提取出來,讓它重復使用。思想如下:
1 package com.atguigu.spring.aop.helloword; 2 3 public int Allmethod(Method method,int j,int j){ 4 System.out.println("The method "+method+" begins with: ["+i+","+j+"]"); 5 int result=method(i.j); 6 System.out.println("The method "+method+" ends with: "result); 7 8 9 }
這樣,代碼就整潔多了,要改日志也只要改一次。其實這個例子不是很准確,好像是一種聚合式代理。學AOP,一定要先學動態代理
推薦一個視頻,講動態代理原理:http://www.imooc.com/learn/214
這個包含了學spring需要先學的各個知識:http://stamen.iteye.com/blog/1507535
AspectJ:java社區最完整最完善的AOP框架;
AspectJ支持的5種類型的通知注解:
@Before:前置通知,在方法執行之前執行
@After:后置通知,在方法執行之后執行
@AfterRunning:返回通知,在方法返回結果后執行
@AfterThrowing:異常通知,在方法拋出異常之后執行
@Around:環繞通知,圍繞着方法執行
Spring也有自身的AOP框架。可以使用基於AspectJ注解或基於XMl配置的AOP。
注解方式:
1、除spring基本jar包,額外需要加入jar包:aopalliance、aspectj.weaver、aop.RELRESE、aspects.RELEASE
2、配置自動掃描
1 <!-- 自動掃描,加入IOC容器 --> 2 <context:component-scan base-package="com.atguigu.spring.aop.impl"></context:component-scan> 3 4 <!-- 是Aspect注解起作用:自動為匹配的類生產代理對象 --> 5 <aop:aspect-autoproxy></aop:aspect-autoproxy>
3、創建接口和實現類
1 package com.atguigu.spring.aop.impl; 2 //接口 3 public interface Calculator { 4 5 public int add(int i,int j); 6 public int sub(int i,int j); 7 public int div(int i,int j); 8 public int mul(int i,int j); 9 }
1 package com.atguigu.spring.aop.impl; 2 //實現類 3 import org.springframework.stereotype.Component; 4 //加入注解 5 @Component 6 public class CalculatorImpl implements Calculator{ 7 8 public int add(int i,int j){ 9 return i+j; 10 }; 11 public int sub(int i,int j){ 12 return i-j; 13 }; 14 public int div(int i,int j){ 15 return i/j; 16 }; 17 public int mul(int i,int j){ 18 return i*j; 19 } 20 }
創建日志切面:
1 package com.atguigu.spring.aop.impl; 2 3 import org.springframework.stereotype.Component; 4 import org.springframework.test.context.transaction.BeforeTransaction; 5 6 //吧這個類申明為一個切面:要把該類放入到IOC容器、再聲明為一個切面 7 @Aspect 8 @Component 9 public class LogginfAspect { 10 //聲明該方法是一個前置通知,在目標方法開始前執行 11 //@Before("excution(修飾符 + 返回類型 + 類名,去參數名)") 12 @Before("excution(public int com.atguigu.spring.aop.impl.CalculatorImpl.add(int, int))"); 13 public void befored(){ 14 System.out.println("The method begins: "); 15 } 16 }
最后調用main方法
1 public class Main { 2 3 public static void main(String[] args) { 4 5 ApplicationContext ctx=new ClassPathXmlApplicationContext("application.xml"); 6 Calculator cal = (Calculator) ctx.getBean(Calculator.class); 7 int result=cal.add(3,6); 8 System.out.println("result: "+result); 9 } 10 11 }
結果:
若要獲取類名,參數等,在切面改就行:
1 //吧這個類申明為一個切面:要把該類放入到IOC容器、再聲明為一個切面 2 @Aspect 3 @Component 4 public class LogginfAspect { 5 //聲明該方法是一個前置通知,在目標方法開始前執行 6 //@Before("excution(public + 全類名,去參數名)") 7 @Before("excution(public int com.atguigu.spring.aop.impl.CalculatorImpl.add(int, int))"); 8 public void beforedMethod(JoinPoint joinPoint){ 9 String methodName=joinPoint.getSignature().getName(); 10 List<Object> args = Arrays.arrays(joinPoint.getArgs()) 11 System.out.println("The method "+ methodName +"begins: "+args); 12 } 13 }
結果:
@Before("excution(public int com.atguigu.spring.aop.impl.CalculatorImpl.add(int, int))");
這句話,只是在add方法才有用,變成所有方法:把add改成*:
@Before("excution(public int com.atguigu.spring.aop.impl.CalculatorImpl.*(int, int))");
同樣,還可以改成這樣
@Before("excution(* int com.atguigu.spring.aop.impl.CalculatorImpl.*(int, int))");
@Before("excution(* com.atguigu.spring.aop.impl.CalculatorImpl.*(int, int))");(表示前面兩個都是*)
注意:
對於后置通知
對於返回通知:
對於異常通知:
對於環繞通知,它的功能最強大,可以包含前面所以的通知,但並不意味着最常用
注意:如果有多個切面,有默認的先后執行順序。可以用@Order(num)定義優先級,num越小,優先級越高。
注意:使用切入點,后面的切面就可以使用這個切點,寫法更整潔。
如:前置通知寫成:@Before("declareJointPointExpression()");
后置通知寫成:@After("declareJointPointExpression()");
其他的同理。
再看一下筆記: