前面已經講解了
bean
的裝配技術,接着學習Spring
中另一個核心概念:切面
。java
切面可以幫助模塊化
橫切關注點
,橫切關注點
能夠被描述爲影響應用的功能,如爲業務添加安全和事務管理等。git
Spring
切面能夠應用5
種類型的通知。
JVM
時被織入,須要特殊類加載器。AOP
容器會爲目標對象動態建立代理對象,這也是Spring AOP
的織入方式。
Spring
對AOP
的支持在不少方面借鑑了AspectJ
項目,提供以下四種支持。github
Spring AOP
。POJO
切面。@AspectJ
註解的切面。AspectJ
切面(適用於Spring
各版本)。Spring AOP
構建在動態代理基礎上,只能侷限於對方法攔截;Spring
在運行時通知對象(經過在代理類中包裹切面,在運行期把切面織入到Spring
管理的bean
中,代理類封裝了目標類,並攔截被通知方法的調用,執行切面邏輯,再把調用轉發給真正的目標bean
);Spring
只支持方法級別的鏈接點(基於動態代理決定)。spring
首先先定義一個方法express
package ch4 public interface Performance { void perform(); }
而後使用切點表達式設置當
perform
方法執行時觸發通知的調用execution(* ch4.Performance.perform(..)),*表示並不關心返回值,而後指定具體的方法名,方法中的..
表示切點要選擇任意的perform
方法。還可以使用&&、and、||、or
對切點進行限定。編程
切點表達式中可以使用
bean
的ID
來標識bean
,以下切點表達式execution(* ch4.Performance.perform(..)) && bean(musicPerformance),表示限定beanID
爲musicPerformance
時調用通知,其中musicPerformance
是Performance
的一個子類實現。安全
定義一個切面以下。app
package ch4; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Aspect @Component public class Audience { @Before("execution(* ch4.Performance.perform(..))") public void silenceCellPhones() { System.out.println("Silencing cell phones"); } @Before("execution(* ch4.Performance.perform(..))") public void takeSeats() { System.out.println("Taking seats"); } @AfterReturning("execution(* ch4.Performance.perform(..))") public void applause() { System.out.println("CLAP CLAP CLAP CLAP"); } @AfterThrowing("execution(* ch4.Performance.perform(..))") public void demandRefund() { System.out.println("Demanding a refund"); } }
能夠看到配合註解和切點表達式可使得在執行
perform
方法以前、以後完成指定動做,固然,對於每一個方法都使用了execution
切點表達式,能夠進一步進行精簡。模塊化
package ch4; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Aspect @Component public class Audience { @Pointcut("execution(* ch4.Performance.perform(..))") public void performance() { } @Before("performance()") public void silenceCellPhones() { System.out.println("Silencing cell phones"); } @Before("performance()") public void takeSeats() { System.out.println("Taking seats"); } @AfterReturning("performance()") public void applause() { System.out.println("CLAP CLAP CLAP CLAP"); } @AfterThrowing("performance()") public void demandRefund() { System.out.println("Demanding a refund"); } }
能夠看到使用
@Pointcut
定義切點,而後在其餘方法中直接使用註解和切點方法便可,不須要再繁瑣的使用execution
切點表達式。單元測試
在定義了註解後,須要啓動,不然沒法識別,啓動方法分爲在
JavaConfig
中顯式配置和XML
註解。
package ch4; 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 @EnableAspectJAutoProxy @ComponentScan public class ConcertConfig { @Bean public Audience audience() { return new Audience(); } }
<?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-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd"> <aop:aspectj-autoproxy/> <context:component-scan base-package="ch4"/> </beans>
將被通知的目標方法徹底包裝起來,就像在一個通知方法中同時編寫前置通知和後置通知。
package ch4; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Aspect @Component public class Audience { @Pointcut("execution(* ch4.Performance.perform(..)) && ! bean(musicPerformance)") public void performance() { } @Around("performance()") public void watchPerformance(ProceedingJoinPoint jp) { try { System.out.println("Silencing cell phones"); System.out.println("Taking seats"); jp.proceed(); System.out.println("CLAP CLAP CLAP CLAP"); } catch (Throwable e) { System.out.println("Demanding a refund"); } } }
使用
Around
註解表示環繞通知,注意須要調用proceed()
方法來調用實際的通知方法。
在
perform
方法中添加int number
參數表示有多少觀衆,使用以下切點表達式execution(\* ch4.Performance.perform(int)) && args(number)
,表示須要匹配perform(int)
型方法而且通知方法的參數名爲number
。
MusicPerformance
以下package ch4; import org.springframework.stereotype.Service; @Service public class MusicPerformance implements Performance { public void perform(int number) { System.out.println("perform music, and the audience number is " + number); } }
Audience
以下package ch4; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Aspect @Component public class Audience { @Pointcut("execution(* ch4.Performance.perform(int)) && args(number)") public void performance(int number) { } @Before("performance(int)") public void silenceCellPhones() { System.out.println("Silencing cell phones"); } @Before("performance(int)") public void takeSeats() { System.out.println("Taking seats"); } @AfterReturning("performance(int)") public void applause() { System.out.println("CLAP CLAP CLAP CLAP"); } @AfterThrowing("performance(int)") public void demandRefund() { System.out.println("Demanding a refund"); } @Around("performance(int)") public void watchPerformance(ProceedingJoinPoint jp) { try { System.out.println("Silencing cell phones"); System.out.println("Taking seats"); jp.proceed(); System.out.println("CLAP CLAP CLAP CLAP"); } catch (Throwable e) { System.out.println("Demanding a refund"); } } }
AOPTest
以下package ch4; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import static org.junit.Assert.assertNotNull; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath*:spring-learning.xml") public class AOPTest { @Autowired private Performance performance; @Test public void notNull() { assertNotNull(performance); performance.perform(100); System.out.println("++++++++++++++++++"); performance.perform(999); System.out.println("++++++++++++++++++"); } }
運行結果:
Silencing cell phones
Taking seats
Taking seats
Silencing cell phones
perform music, and the audience number is 100
CLAP CLAP CLAP CLAP
CLAP CLAP CLAP CLAP
++++++++++++++++++
Silencing cell phones
Taking seats
Taking seats
Silencing cell phones
perform music, and the audience number is 999
CLAP CLAP CLAP CLAP
CLAP CLAP CLAP CLAP
++++++++++++++++++
除了使用註解方式聲明切面外,還可經過
XML
方式聲明切面。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" 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-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd"> <aop:aspectj-autoproxy/> <context:component-scan base-package="ch4"/> <aop:config> <aop:aspect ref="audience"> <aop:before pointcut="execution(* ch4.Performance.perform(..))" method="silenceCellPhones" /> <aop:before pointcut="execution(* ch4.Performance.perform(..))" method="takeSeats" /> <aop:after-returning pointcut="execution(* ch4.Performance.perform(..))" method="applause" /> <aop:after-throwing pointcut="execution(* ch4.Performance.perform(..))" method="demandRefund" /> </aop:aspect> </aop:config> </beans>
將
Audience
註解刪除後運行單元測試可得出正確結果;固然上述XML
也有點複雜,可進一步簡化。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" 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-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd"> <aop:aspectj-autoproxy/> <context:component-scan base-package="ch4"/> <aop:config> <aop:aspect ref="audience"> <aop:pointcut id="performance" expression="execution(* ch4.Performance.perform(..))" /> <aop:before pointcut-ref="performance" method="silenceCellPhones" /> <aop:before pointcut-ref="performance" method="takeSeats" /> <aop:after-returning pointcut-ref="performance" method="applause" /> <aop:after-throwing pointcut-ref="performance" method="demandRefund" /> </aop:aspect> </aop:config> </beans>
XML以下
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" 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-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd"> <aop:aspectj-autoproxy/> <context:component-scan base-package="ch4"/> <aop:config> <aop:aspect ref="audience"> <aop:pointcut id="performance" expression="execution(* ch4.Performance.perform(..))"/> <aop:around pointcut-ref="performance" method="watchPerformance" /> </aop:aspect> </aop:config> </beans>
運行單元測試,可得正確結果。
XML文件以下
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" 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-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd"> <aop:aspectj-autoproxy/> <context:component-scan base-package="ch4"/> <aop:config> <aop:aspect ref="audience"> <aop:pointcut id="performance" expression="execution(* ch4.Performance.perform(int)) and args(int)" /> <aop:before pointcut-ref="performance" method="silenceCellPhones" /> <aop:before pointcut-ref="performance" method="takeSeats" /> <aop:after-returning pointcut-ref="performance" method="applause" /> <aop:after-throwing pointcut-ref="performance" method="demandRefund" /> </aop:aspect> </aop:config> </beans>
運行單元測試,可得正確結果。
AOP
是Spring
的核心概念,經過AOP
,咱們能夠把切面插入到方法執行的周圍,經過本篇博文能夠大體瞭解AOP
的使用方法。源碼已經上傳至github,歡迎fork and star
。