面向切面編程(即AOP):把項目中須要再多處使用的功能好比日誌、安全和事務等集中到一個類中處理,而不用在每一個須要用到該功能的地方顯式調用。java
橫切關注點:在軟件開發過程當中分散於應用中多處的功能正則表達式
切面:切點和通知的結合,通知和切點共同定義了切面的所有內容:是什麼以及在什麼時候和何處完成其功能express
通知:切面要完成的工做。除了描述切面要完成的工做,還解決了什麼時候完成什麼時候執行這個工做的問題。Spring的通知有5種類型:before、after、after-returnning、after-throwing和around這五種類型編程
鏈接點:鏈接點表示在何種操做發生時應用切面。好比方法調用時、修改字段時以及拋出異常時;安全
切點:通常使用明確的類和方法名稱或是利用正則表達式匹配的類和方法名稱來指定在何處應用切面,通常應用切面的點就被稱爲切點,通常使用切點來指定鏈接點。ide
引入:咱們能夠建立一個通知類建立新的屬性和方法,就能夠在不修改切點類的前提下讓他們具備新的行爲功能測試
織入:織入是指把切面應用到目標對象並建立新的代理對象的過程。切面在指定的鏈接點被織入到目標對象,在目標對象的生命週期裏有多個點能夠進行織入:this
編譯期:切面在目標類編譯時被織入(例如AspectJ的織入編譯器織入切面)spa
類加載期:切面在目標類加載到JVM時被織入(例如AspectJ5的加載時織入).net
運行期:切面在運行時的某個時刻被織入(Spring AOP以這種方式織入切面)
Spring提供了五種類型的AOP支持:
1)基於代理的經典Spring AOP;
2)純POJO切面;
3)@AspectJ註解驅動的切面;
4)注入式AspectJ切面
前三種方式均爲Spring AOP實現的變體,構建在動態代理的基礎上,所以Spring對AOP的支持侷限於方法攔截
Spring支持的AspectJ切點指示器
鏈接點=切點+執行時機
AspectJ指示器 | 描述 |
args() | 限制鏈接點匹配參數爲指定類型的執行方法 |
@args() | 限制鏈接點匹配參數由指定註解標註的執行方法 |
execution() | 用於匹配鏈接點的執行方法,一般用於編寫切點表達式的指示器 |
this() | 限制鏈接點匹配AOP代理的Bean引用爲指定類型的類 |
target | 限制鏈接點匹配目標對象爲指定類型的類 |
@target() | 限制鏈接點匹配特定的執行對象,這些對象對應的類要具備指定類型的註解 |
within() | 限制鏈接點匹配指定的類型 |
@within() | 限制鏈接點匹配指定註解標註的類型(當使用Spring AOP時,切點方法定義在由指定註解所標註的類裏) |
@annotation | 限定匹配帶有指定註解的鏈接點 |
切點表達式的組成詳解
execution(* concert.Performance.perform(..))
下面解析下表達式各個部分
* : 代表該切點表達式不關心方法返回值類型,任何返回值類型均匹配
concert.Performance : 這部分指定方法所屬類,用於限定切點匹配的類,可使用*模糊匹配
perform : 切點匹配的方法名
(..) : 兩個點號代表切點匹配任意perform的重載方法不管入參是什麼。
在切點表達式中限制選擇範圍
除了上述Spring支持的AspectJ切點指示器以外,Spring引入了新的bean()指示器,他支持基於Bean Id或Bean Name做爲參數限制切點只匹配特定Bean。咱們能夠在 xml或者註解配置中基於邏輯操做符and、or組合使用execution和其餘指示器來進一步限制匹配切點,例如:
execution(* concert.Performance.perform()) and bean('woodstock')
Spring提供的定義通知的五個註解
註解 | 通知 |
@After | 通知方法會在目標方法返回或拋出異常後調用 |
@AfterReturning | 通知方法會在目標方法返回後調用 |
@AfterThrowing | 通知方法會在目標方法拋出異常後調用 |
@Around | 通知方法會將目標方法封裝起來 |
@Before | 通知方法會在目標方法調用以前執行 |
目標類
@Component public class Performance { public void perform(){ System.out.println("method perform invoking"); } }
定義切面代碼
@Component @Aspect public class PerformanceAspect { @Pointcut("execution (* com.example.demo.service.Performance.perform(..))") public void performancePointCut(){ } @Before("performancePointCut()") public void beforePerform(){ System.out.println("before method invoke"); } @Around("performancePointCut()") public void handler(ProceedingJoinPoint point) { try { System.out.println("method perform invoke Around start"); point.proceed(); System.out.println("method perform invoke Around end"); }catch(Throwable ex){ System.out.println("method perform throw an exception "); } } }
測試代碼
@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = DemoApplication.class) public class PerformanceAspectTest { @Autowired private Performance performance; @Test public void testAspect(){ performance.perform(); } }
若是切面所通知的方法含有參數切點表達式怎麼將參數傳遞給通知方法,相關代碼以下:
目標類代碼
@Component public class Performance { public void perform(String userName){ System.out.println(userName+" is performing"); } }
切面類代碼
@Component @Aspect public class PerformanceAspect { @Pointcut("execution (* com.example.demo.service.Performance.perform(String))" +"&& args(userName)") public void performancePointCut(String userName){ } @Before("performancePointCut(userName)") public void beforePerform(String userName){ System.out.println(userName+" before perform"); } @Around("performancePointCut(userName)") public void handler(ProceedingJoinPoint point, String userName) { try { System.out.println(userName+" perform Around start"); point.proceed(); System.out.println(userName+" perform Around end"); }catch(Throwable ex){ System.out.println(userName+" perform throw an exception "); } } }
測試類代碼
@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = DemoApplication.class) public class PerformanceAspectTest { @Autowired private Performance performance; @Test public void testAspect(){ performance.perform("zhangsan"); } }
利用引入的AOP概念,切面能夠爲Spring Bean添加新的方法。其基本實現原理是基於自動動態代理機制,用戶經過實現一個新的接口並把該接口引入Bean中,這樣看起來Bean彷佛實現了該接口的方法但其實是Bean對應的代理類接口實現被拆分到了多個類中,每次Spring發現一個Bean實現了@Aspect註解時都會爲他建立一個代理,而後方法調用都會委託給被代理的bean或者被引入的接口實現,這取決於調用方法屬於被代理Bean仍是引入接口。如下是使用示例:
public interface PerformanceEncorable { void performanceEncore(); } @Component @Aspect public class PerformanceEncorableIntroducer { @DeclareParents(value="com.example.demo.service.Performance", defaultImpl = DefaultPerformanceEncorableImpl.class) @Autowired private PerformanceEncorable performanceEncorable; } @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = DemoApplication.class) public class PerformanceAspectTest { @Autowired private Performance performance; @Test public void testAspect(){ performance.perform("zhangsan"); PerformanceEncorable performanceEncorable = (PerformanceEncorable)performance; performanceEncorable.performanceEncore(); } }
Spring的AOP命名空間提供的聲明切面相關的元素
AOP配置元素 | 用途描述 |
<aop:advisor> | 定義aop通知器 |
<aop:after> | 定義aop後置通知 |
<aop:after-returning> | 定義aop返回通知 |
<aop:after-throwing> | 定義aop的異常通知 |
<aop:around> | 定義aop環繞通知 |
<aop:aspect> | 定義一個切面 |
<aop:asoectj-autoproxy> | 啓用@AspectJ註解驅動的切面 |
<aop:before> | 定義一個AOP前置通知 |
<aop:config> | 頂層的AOP配置元素 |
<aop:delare-parents> | 以透明的方式爲被通知的對象引入額外的接口 |
<aop:pointcut> | 定義一個切點 |
直接基於上述例子列舉XML方式的實現
1 - 在XML中聲明切面
<aop:config> <aop:aspect ref="performanceAspect"> <aop:pointcut id="performancePointCut" expression="execution(* com.example.demo.service.Performance.perform(..))" /> <aop:before pointcut-ref="performancePointCut" method="beforePerform" /> <aop:around pointcut-ref="performancePointCut" method="handler" /> </aop:aspect> </aop:config>
目標類和切面類
@Component public class Performance { public void perform(String userName){ System.out.println(userName+" is performing"); } } @Component public class PerformanceAspect { public void beforePerform(String userName){ System.out.println(userName+" before perform"); } public void handler(ProceedingJoinPoint point, String userName) { try { System.out.println(userName+" perform Around start"); point.proceed(); System.out.println(userName+" perform Around end"); }catch(Throwable ex){ System.out.println(userName+" perform throw an exception "); } } }
2 - 經過切面引入新的功能
引入接口於默認實現類
public interface PerformanceEncorable { void performanceEncore(); } public class DefaultPerformanceEncorableImpl implements PerformanceEncorable{ @Override public void performanceEncore() { System.out.println("method performanceEncore invoke"); } }
XML配置以下
<aop:config> <aop:aspect> <aop:declare-parents types-matching="com.example.demo.service.Performance+" implement-interface="com.example.demo.service.PerformanceEncorable" default-impl="com.example.demo.service.DefaultPerformanceEncorableImpl" /> </aop:aspect> </aop:config>
<aop:declare-parents>聲明瞭此切面所通知的bean要在他們的對象層次結構上有新的父類型,具體到本例子中即爲全部實現了Performance接口的Bean示例在他們的父類結構上會增長PerformanceEncorable接口,除了使用上述default-impl方式標識引入接口實現還可使用delegate-ref屬性來標識,以下所示:
<aop:config> <aop:aspect> <aop:declare-parents types-matching="com.example.demo.service.Performance+" implement-interface="com.example.demo.service.PerformanceEncorable" delegate-ref="defaultPerformanceEncorableImpl" /> </aop:aspect> </aop:config>
雖然Spring AOP可以知足許多應用的切面需求,可是與AspectJ相比,Spring AOP 是一個功能比較弱的AOP解決方案。AspectJ提供了Spring AOP所不能支持的許多類型的切點。例如,當咱們須要在建立對象時應用通知,構造器切點就很是方便。Spring基於代理的AOP沒法把通知應用於對象的建立過程。
1 - 定義切面
public aspect ConstructAspect { public ConstructAspect(){} /** * 定義切點 */ pointcut performance():execution(* com.example.demo.service.Performance.perform(..)); pointcut construct():execution(aop.kind4.CriticismEngineImpl.new()); before() : performance(){ System.out.println("before performance method invoke"); } after():construct(){ System.out.println("Performance construct end"); } before():construct(){ System.out.println("Performance construct begin"); } }
XML配置
<bean class="com.example.demo.service.ConstructAspect" factory-method="aspectOf"> </bean>