目錄html
AOP(Aspect Oriented Programming)面向切面編程,經過預編譯方式和運行期動態代理實現程序功能的橫向多模塊統一控制的一種技術。AOP是OOP的補充,是Spring框架中的一個重要內容。利用AOP能夠對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度下降,提升程序的可重用性,同時提升了開發的效率。AOP能夠分爲靜態織入與動態織入,靜態織入即在編譯前將需織入內容寫入目標模塊中,這樣成本很是高。動態織入則不須要改變目標模塊。Spring框架實現了AOP,使用註解配置完成AOP比使用XML配置要更加方便與直觀。上一篇隨筆中已經詳細講了代理模式。java
在講註解實現AOP功能前先用前面學習過的使用xml配置Spring AOP功能,這樣是爲了對比以便更好的理解。spring
1.一、新建一個Maven項目,添加引用,項目的pom.xml文件以下:express
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.zhangguo</groupId> <artifactId>Spring052</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>Spring052</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring.version>4.3.0.RELEASE</spring.version> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> <version>4.10</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.4</version> </dependency> </dependencies> </project>
1.二、建立要被代理的Math類,代碼以下:apache
package com.zhangguo.Spring052.aop01; /** * 被代理的目標類 */ public class Math{ //加 public int add(int n1,int n2){ int result=n1+n2; System.out.println(n1+"+"+n2+"="+result); return result; } //減 public int sub(int n1,int n2){ int result=n1-n2; System.out.println(n1+"-"+n2+"="+result); return result; } //乘 public int mut(int n1,int n2){ int result=n1*n2; System.out.println(n1+"X"+n2+"="+result); return result; } //除 public int div(int n1,int n2){ int result=n1/n2; System.out.println(n1+"/"+n2+"="+result); return result; } }
1.三、編輯AOP中須要使用到的通知類Advices.java代碼以下:編程
package com.zhangguo.Spring052.aop01; import org.aspectj.lang.JoinPoint; /** * 通知類,橫切邏輯 * */ public class Advices { public void before(JoinPoint jp){ System.out.println("----------前置通知----------"); System.out.println(jp.getSignature().getName()); } public void after(JoinPoint jp){ System.out.println("----------最終通知----------"); } }
1.四、配置容器初始化時須要的XML文件,aop01.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:p="http://www.springframework.org/schema/p" 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/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <!-- 被代理對象 --> <bean id="math" class="com.zhangguo.Spring052.aop01.Math"></bean> <!-- 通知 --> <bean id="advices" class="com.zhangguo.Spring052.aop01.Advices"></bean> <!-- aop配置 --> <aop:config proxy-target-class="true"> <!--切面 --> <aop:aspect ref="advices"> <!-- 切點 --> <aop:pointcut expression="execution(* com.zhangguo.Spring052.aop01.Math.*(..))" id="pointcut1"/> <!--鏈接通知方法與切點 --> <aop:before method="before" pointcut-ref="pointcut1"/> <aop:after method="after" pointcut-ref="pointcut1"/> </aop:aspect> </aop:config> </beans>
1.五、測試代碼Test.java以下:maven
package com.zhangguo.Spring052.aop01; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("aop01.xml"); Math math = ctx.getBean("math", Math.class); int n1 = 100, n2 = 5; math.add(n1, n2); math.sub(n1, n2); math.mut(n1, n2); math.div(n1, n2); } }
運行結果:函數
2.一、在上一個示例中修改被代理的類Math,爲了實現IOC掃描在Math類上註解了@Service並命名bean爲math。至關於上一個示例中在xml配置文件中增長了一個bean,<!-- 被代理對象 --><bean id="math" class="com.zhangguo.Spring052.aop01.Math"></bean>,Math類的代碼以下:學習
package com.zhangguo.Spring052.aop02; import org.springframework.stereotype.Service; /** * 被代理的目標類 */ @Service("math") public class Math{ //加 public int add(int n1,int n2){ int result=n1+n2; System.out.println(n1+"+"+n2+"="+result); return result; } //減 public int sub(int n1,int n2){ int result=n1-n2; System.out.println(n1+"-"+n2+"="+result); return result; } //乘 public int mut(int n1,int n2){ int result=n1*n2; System.out.println(n1+"X"+n2+"="+result); return result; } //除 public int div(int n1,int n2){ int result=n1/n2; System.out.println(n1+"/"+n2+"="+result); return result; } }
2.二、修改通知類Advices,代碼中有3個註解,@Component表示該類的實例會被Spring IOC容器管理;@Aspect表示聲明一個切面;@Before表示before爲前置通知,經過參數execution聲明一個切點,Advices.java代碼以下所示:
package com.zhangguo.Spring052.aop02; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; /** * 通知類,橫切邏輯 * */ @Component @Aspect public class Advices { @Before("execution(* com.zhangguo.Spring052.aop02.Math.*(..))") public void before(JoinPoint jp){ System.out.println("----------前置通知----------"); System.out.println(jp.getSignature().getName()); } @After("execution(* com.zhangguo.Spring052.aop02.Math.*(..))") public void after(JoinPoint jp){ System.out.println("----------最終通知----------"); } }
上面的代碼與下面的配置基本等同
<!-- 通知 --> <bean id="advices" class="com.zhangguo.Spring052.aop01.Advices"></bean> <!-- aop配置 --> <aop:config proxy-target-class="true"> <!--切面 --> <aop:aspect ref="advices"> <!-- 切點 --> <aop:pointcut expression="execution(* com.zhangguo.Spring052.aop01.Math.*(..))" id="pointcut1"/> <!--鏈接通知方法與切點 --> <aop:before method="before" pointcut-ref="pointcut1"/> <aop:after method="after" pointcut-ref="pointcut1"/> </aop:aspect> </aop:config>
2.三、新增配置文件aop02.xml,在配置IOC的基礎上增長了aop:aspectj-autoproxy節點,Spring框架會自動爲與AspectJ切面配置的Bean建立代理,proxy-target-class="true"屬性表示被代理的目標對象是一個類,而非實現了接口的類,主要是爲了選擇不一樣的代理方式。
<?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:p="http://www.springframework.org/schema/p" 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.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <context:component-scan base-package="com.zhangguo.Spring052.aop02"> </context:component-scan> <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy> </beans>
2.四、測試運行代碼Test.java以下:
package com.zhangguo.Spring052.aop02; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("aop02.xml"); Math math = ctx.getBean("math", Math.class); int n1 = 100, n2 = 5; math.add(n1, n2); math.sub(n1, n2); math.mut(n1, n2); math.div(n1, n2); } }
運行結果:
切點函數能夠定位到準確的橫切邏輯位置,在前面的示例中咱們只使用過execution(* com.zhangguo.Spring052.aop02.Math.*(..)),execution就是一個切點函數,但該函數只什麼方法一級,若是咱們要織入的範圍是類或某個註解則execution就不那麼好用了,其實一共有9個切點函數,有不一樣的針對性。
@AspectJ使用AspectJ專門的切點表達式描述切面,Spring所支持的AspectJ表達式可分爲四類:
方法切點函數:經過描述目標類方法信息定義鏈接點。
方法參數切點函數:經過描述目標類方法入參信息定義鏈接點。
目標類切點函數:經過描述目標類類型信息定義鏈接點。
代理類切點函數:經過描述代理類信息定義鏈接點。
常見的AspectJ表達式函數:
execution():知足匹配模式字符串的全部目標類方法的鏈接點
@annotation():任何標註了指定註解的目標方法連接點
args():目標類方法運行時參數的類型指定鏈接點
@args():目標類方法參數中是否有指定特定註解的鏈接點
within():匹配指定的包的全部鏈接點
target():匹配指定目標類的全部方法
@within():匹配目標對象擁有指定註解的類的全部方法
@target():匹配當前目標對象類型的執行方法,其中目標對象持有指定的註解
this():匹配當前AOP代理對象類型的全部執行方法
最經常使用的是:execution(<修飾符模式>?<返回類型模式><方法名模式>(<參數模式>)<異常模式>?)切點函數,能夠知足多數需求。
爲了展現各切點函數的功能如今新增一個類StrUtil,類以下:
package com.zhangguo.Spring052.aop03; import org.springframework.stereotype.Component; @Component("strUtil") public class StrUtil { public void show(){ System.out.println("Hello StrUtil!"); } }
測試代碼以下:
package com.zhangguo.Spring052.aop03; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("aop03.xml"); IMath math = ctx.getBean("math", Math.class); int n1 = 100, n2 = 5; math.add(n1, n2); math.sub(n1, n2); math.mut(n1, n2); math.div(n1, n2); StrUtil strUtil=ctx.getBean("strUtil",StrUtil.class); strUtil.show(); } }
3.一、切點函數execution,通知與切面的定義以下:
package com.zhangguo.Spring052.aop03; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; /** * 通知類,橫切邏輯 * */ @Component @Aspect public class Advices { @Before("execution(* com.zhangguo.Spring052.aop03.Math.*(..))") public void before(JoinPoint jp){ System.out.println("----------前置通知----------"); System.out.println(jp.getSignature().getName()); } //execution切點函數 //com.zhangguo.Spring052.aop03包下全部類的全部方法被切入 @After("execution(* com.zhangguo.Spring052.aop03.*.*(..))") public void after(JoinPoint jp){ System.out.println("----------最終通知----------"); } }
運行結果以下:
execution(<修飾符模式>?<返回類型模式><方法名模式>(<參數模式>)<異常模式>?)
3.二、切點函數within
//within切點函數 //com.zhangguo.Spring052.aop03包下全部類的全部方法被切入 @After("within(com.zhangguo.Spring052.aop03.*)") public void after(JoinPoint jp){ System.out.println("----------最終通知----------"); }
3.三、this切點函數
//this切點函數 //實現了IMath接口的代理對象的任意鏈接點 @After("this(com.zhangguo.Spring052.aop03.IMath)") public void after(JoinPoint jp){ System.out.println("----------最終通知----------"); }
3.四、args切點函數
//args切點函數 //要求方法有兩個int類型的參考纔會被織入橫切邏輯 @After("args(int,int)") public void after(JoinPoint jp){ System.out.println("----------最終通知----------"); }
若是參數類型不是基本數據類型則須要包名。
3.五、@annotation切點函數
先自定義一個能夠註解在方法上的註解
package com.zhangguo.Spring052.aop03; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyAnno { }
//@annotation切點函數 //要求方法必須被註解com.zhangguo.Spring052.aop03.MyAnno纔會被織入橫切邏輯 @After("@annotation(com.zhangguo.Spring052.aop03.MyAnno)") public void after(JoinPoint jp){ System.out.println("----------最終通知----------"); }
package com.zhangguo.Spring052.aop03; import org.springframework.stereotype.Component; @Component("strUtil") public class StrUtil { @MyAnno public void show(){ System.out.println("Hello StrUtil!"); } }
運行結果:
其它帶@的切點函數都是針對註解的
AspectJ通知註解共有6個,經常使用5個,引介少用一些。
先解決定義切點複用的問題,以下代碼所示,切點函數的內容徹底同樣:
package com.zhangguo.Spring052.aop04; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; /** * 通知類,橫切邏輯 * */ @Component @Aspect public class Advices { @Before("execution(* com.zhangguo.Spring052.aop04.Math.*(..))") public void before(JoinPoint jp){ System.out.println("----------前置通知----------"); System.out.println(jp.getSignature().getName()); } @After("execution(* com.zhangguo.Spring052.aop04.Math.*(..))") public void after(JoinPoint jp){ System.out.println("----------最終通知----------"); } }
能夠先定義一個切點而後複用,以下所示:
package com.zhangguo.Spring052.aop04; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; /** * 通知類,橫切邏輯 */ @Component @Aspect public class Advices { //切點 @Pointcut("execution(* com.zhangguo.Spring052.aop04.Math.*(..))") public void pointcut(){ } @Before("pointcut()") public void before(JoinPoint jp){ System.out.println("----------前置通知----------"); System.out.println(jp.getSignature().getName()); } @After("pointcut()") public void after(JoinPoint jp){ System.out.println("----------最終通知----------"); } }
修改Advices.java文件,增長各類通知類型以下:
package com.zhangguo.Spring052.aop04; 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.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; /** * 通知類,橫切邏輯 */ @Component @Aspect public class Advices { //切點 @Pointcut("execution(* com.zhangguo.Spring052.aop04.Math.a*(..))") public void pointcut(){ } //前置通知 @Before("pointcut()") public void before(JoinPoint jp){ System.out.println(jp.getSignature().getName()); System.out.println("----------前置通知----------"); } //最終通知 @After("pointcut()") public void after(JoinPoint jp){ System.out.println("----------最終通知----------"); } //環繞通知 @Around("execution(* com.zhangguo.Spring052.aop04.Math.s*(..))") public Object around(ProceedingJoinPoint pjp) throws Throwable{ System.out.println(pjp.getSignature().getName()); System.out.println("----------環繞前置----------"); Object result=pjp.proceed(); System.out.println("----------環繞後置----------"); return result; } //返回結果通知 @AfterReturning(pointcut="execution(* com.zhangguo.Spring052.aop04.Math.m*(..))",returning="result") public void afterReturning(JoinPoint jp,Object result){ System.out.println(jp.getSignature().getName()); System.out.println("結果是:"+result); System.out.println("----------返回結果----------"); } //異常後通知 @AfterThrowing(pointcut="execution(* com.zhangguo.Spring052.aop04.Math.d*(..))",throwing="exp") public void afterThrowing(JoinPoint jp,Exception exp){ System.out.println(jp.getSignature().getName()); System.out.println("異常消息:"+exp.getMessage()); System.out.println("----------異常通知----------"); } }
運行結果:
爲了實現零配置在原有示例的基礎上咱們新增一個類User,以下所示:
package com.zhangguo.Spring052.aop05; public class User { public void show(){ System.out.println("一個用戶對象"); } }
該類並未註解,容器不會自動管理。由於沒有xml配置文件,則使用一個做爲配置信息,ApplicationCfg.java文件以下:
package com.zhangguo.Spring052.aop05; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; @Configuration //用於表示當前類爲容器的配置類,相似<beans/> @ComponentScan(basePackages="com.zhangguo.Spring052.aop05") //掃描的範圍,至關於xml配置的結點<context:component-scan/> @EnableAspectJAutoProxy(proxyTargetClass=true) //自動代理,至關於<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy> public class ApplicationCfg { //在配置中聲明一個bean,至關於<bean id=getUser class="com.zhangguo.Spring052.aop05.User"/> @Bean public User getUser(){ return new User(); } }
該類的每一部份內容基本都與xml 配置有一對一的關係,請看註釋,這樣作要比寫xml方便,但不便發佈後修改。測試代碼以下:
package com.zhangguo.Spring052.aop05; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { // 經過類初始化容器 ApplicationContext ctx = new AnnotationConfigApplicationContext(ApplicationCfg.class); Math math = ctx.getBean("math", Math.class); int n1 = 100, n2 = 0; math.add(n1, n2); math.sub(n1, n2); math.mut(n1, n2); try { math.div(n1, n2); } catch (Exception e) { } User user=ctx.getBean("getUser",User.class); user.show(); } }
advices.java 同上,沒有任何變化,運行結果以下: