摘要:我們會寫切面來攔截對這些業(yè)務(wù)類和類的調(diào)用。切面定義何時攔截一個方法以及做什么和在一起成為切面連接點當(dāng)代碼開始執(zhí)行,并且切點的條件滿足時,通知被調(diào)用。
前言
這篇文章會幫助你使用Spring Boot Starter AOP實現(xiàn)AOP。我們會使用AspectJ實現(xiàn)四個不同的通知(advice),并且新建一個自定義的注解來追蹤方法的執(zhí)行時間。
你將會了解什么是交叉分割關(guān)注點(cross-cutting concern)?
在應(yīng)用中你如何實現(xiàn)交叉分割關(guān)注點?
- 如果你想要將對web應(yīng)用所有的訪問請求記入日志,你能想到什么方法? - 如果你想追蹤每個請求的性能,你能想到什么方法?
AOP中的切面(Aspects)和切點(Pointcut)是什么?
有哪些不同類型的AOP通知(advice)?
如何使用Spring Boot實現(xiàn)AOP?
如何使用Spring AOP和AspectJ實現(xiàn)切面?
有哪些AOP最佳實踐?
項目代碼結(jié)構(gòu)下圖是我們即將創(chuàng)建的項目結(jié)構(gòu)的截圖:
一些細節(jié):
SpringBootTutorialBasicsApplication.java: 由Spring Initializer初始化生成的Spring Boot應(yīng)用類。這個類是應(yīng)用啟動類。
pom.xml: 創(chuàng)建項目所需的全部依賴。我們將會使用Spring Boot Starter AOP依賴。
Business1.java, Business2.java, Dao1.java, Dao2.java: 業(yè)務(wù)類依賴于Dao類。我們會寫切面來攔截對這些業(yè)務(wù)類和DAO類的調(diào)用。
AfterAopAspect.java: 實現(xiàn)一些After通知。
UserAccessAspect.java: 實現(xiàn)一個Before通知,用來做訪問權(quán)限檢查
BusinessAopSpringBootTest.java:對業(yè)務(wù)方法進行單元測試
Maven3.0+:編譯工具
Eclipse: 開發(fā)工具
JDK1.8+
源碼Github地址介紹AOP
應(yīng)用通常劃分為多個層進行開發(fā),一個經(jīng)典的JAVA應(yīng)用有:
網(wǎng)絡(luò)層:用REST或是應(yīng)用的形式將服務(wù)暴露給外部使用
業(yè)務(wù)層:業(yè)務(wù)邏輯
數(shù)據(jù)層:數(shù)據(jù)持久化邏輯
雖然各個層的職責(zé)不同,但是每個層之間也有一些共通的地方
日志
安全
這些共通的切面成為交叉分割關(guān)注點(cross-cutting-concerns)
實現(xiàn)交叉分割關(guān)注點的一個方法是在每一個層分貝進行實現(xiàn)。但是,這樣會使得代碼難以維護。
面向切面編程為實現(xiàn)交叉分割關(guān)注點提供了一個解決方案:
將交叉分割切入點實現(xiàn)為一個切面
定義切點,說明這些切面在何時調(diào)用
這樣確保了交叉分割關(guān)注點定義在一個內(nèi)聚的代碼組件中,并且能夠在需要的時候使用。
初始化Spring Boot AOP項目使用Spring Initializer新建一個Spring AOP項目非常的方法。
Spring Initializer是創(chuàng)建Spring Boot項目的超級棒的工具。
備注:
啟動Spring Initializer并且選擇一下內(nèi)容
選擇com.in28minutes.springboot.tutorial.basics.example為Group
選擇 spring-boot-tutorial-basics為Artifact
選擇AOP依賴
點擊Generate Project
將項目導(dǎo)入Eclipse
Spring Boot AOP starterSpring Boot AOP Starter的關(guān)鍵依賴有:
Spring AOP提供的基本的AOP功能
AspectJ提供的完整的AOP框架
配置AOPorg.springframework spring-aop 5.0.1.RELEASE compile org.aspectj aspectjweaver 1.8.12 compile
讓我們添加一些業(yè)務(wù)邏輯類 - Business1和Business2。這些業(yè)務(wù)邏輯類依賴于一組數(shù)據(jù)類 - Data1和Data2。
@Service public class Business1 { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private Dao1 dao1; public String calculateSomething() { String value = dao1.retrieveSomething(); logger.info("In Business - {}", value); return value; } }
@Service public class Business2 { @Autowired private Dao2 dao2; public String calculateSomething() { //Business Logic return dao2.retrieveSomething(); } }
@Repository public class Dao1 { public String retrieveSomething() { return "Dao1"; } }
@Repository public class Dao2 { public String retrieveSomething() { return "Dao2"; } }
備注:
@Autowired private Dao1 dao1: DAO作為依賴注入業(yè)務(wù)類中
public String calculateSomething(): 每個業(yè)務(wù)類包含一個簡單的calculate方法
一個簡單的AOP單元測試讓我們寫一個簡單的單元測試來調(diào)用剛剛創(chuàng)建的業(yè)務(wù)類:
@RunWith(SpringRunner.class) @SpringBootTest public class BusinessAopSpringBootTest { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private Business1 business1; @Autowired private Business2 business2; @Test public void invokeAOPStuff() { logger.info(business1.calculateSomething()); logger.info(business2.calculateSomething()); } }
備注:
@RunWith(SpringRunner.class) @SpringBootTest public class BusinessAopSpringBootTest:: 我們將在單元測試用啟動一個完整的Spring Boot應(yīng)用
@Autowired private Business1 business1和@Autowiredprivate Business2 business2: 將業(yè)務(wù)類注入啟動測試的Spring上下文中
@Test public void invokeAOPStuff(){...}: 調(diào)用業(yè)務(wù)層的方法
這時,我們沒有實現(xiàn)任何的AOP邏輯,因此,測試的輸出應(yīng)該就是從DAO類和業(yè)務(wù)類中返回的簡單的信息:
c.i.s.t.b.e.a.BusinessAopSpringBootTest : In Business - Dao1 c.i.s.t.b.e.a.BusinessAopSpringBootTest : Dao1實現(xiàn)@Before通知
通常來講,當(dāng)我們使用AOP來實現(xiàn)安全時,我們會想要攔截對方法的調(diào)用并進行檢查。這可以直接通過@Before通知實現(xiàn)。
下面給出了一種實現(xiàn):
@Aspect @Configuration public class UserAccessAspect { private Logger logger = LoggerFactory.getLogger(this.getClass()); //What kind of method calls I would intercept //execution(* PACKAGE.*.*(..)) //Weaving & Weaver @Before("execution(* com.in28minutes.springboot.tutorial.basics.example.aop.data.*.*(..))") public void before(JoinPoint joinPoint) { //Advice logger.info(" Check for user access "); logger.info(" Allowed execution for {}", joinPoint); } }
備注:
@Aspect: 說明這是一個切面
@Configuration: 說明這是一個對切面的Spring Bean配置
@Before: 我們想要在方法執(zhí)行前執(zhí)行切面
("execution(* com.in28minutes.springboot.tutorial.basics.example.aop.data.*.*(..))": 定義了切點。我們想要攔截com.in28minutes.springboot.tutorial.basics.example.aop.data包中的所有方法。
當(dāng)我們運行單元測試時,你會看見,在執(zhí)行DAO方法之前,會執(zhí)行用戶權(quán)限檢查:
Check for user access Allowed execution for execution(String com.in28minutes.springboot.tutorial.basics.example.aop.data.Dao1.retrieveSomething()) c.i.s.t.b.e.a.BusinessAopSpringBootTest : In Business - Dao1 c.i.s.t.b.e.a.BusinessAopSpringBootTest : Dao1 Check for user access Allowed execution for execution(String com.in28minutes.springboot.tutorial.basics.example.aop.data.Dao2.retrieveSomething()) c.i.s.t.b.e.a.BusinessAopSpringBootTest : Dao2理解AOP術(shù)語: Pointcut, Advice, Aspect,Join Point
讓我們花點時間來了解一下AOP術(shù)語:
切點(Pointcut):該表達式用來定義何時方法應(yīng)當(dāng)被攔截。在上例中,切點為`execution(* com.in28minutes.springboot.tutorial.basics.
example.aop.data..(..))`。
通知(Advice):你想要做什么?一個通知是你在攔截方法時想要調(diào)用的邏輯。在上例中,通知為before(JoinPoint joinPoint)方法中的代碼。
切面(Aspect):定義何時攔截一個方法(Pointcut)以及做什么(Advice)和在一起成為切面
連接點(Join Point):當(dāng)代碼開始執(zhí)行,并且切點的條件滿足時,通知被調(diào)用。連接點是一個通知運行的特定實例。
織如(Weaver):實現(xiàn)AOP的框架 - AspectJ或Spring AOP
使用@After, @AfterReturning和@AfterThrowing通知讓我們現(xiàn)在來看看AOP提供的別的攔截選項:
@After: 在兩種場景下執(zhí)行 - 當(dāng)一個方法成功執(zhí)行或是拋出異常
@AfterReturning: 只有在方法成功執(zhí)行后運行
@AfterThrowing: 只有在方法拋出異常后運行
讓我們創(chuàng)建一個包含這些元素的簡單的切面:
@Aspect @Configuration public class AfterAopAspect { private Logger logger = LoggerFactory.getLogger(this.getClass()); @AfterReturning(value = "execution(* com.in28minutes.springboot.tutorial.basics.example.aop.business.*.*(..))", returning = "result") public void afterReturning(JoinPoint joinPoint, Object result) { logger.info("{} returned with value {}", joinPoint, result); } @After(value = "execution(* com.in28minutes.springboot.tutorial.basics.example.aop.business.*.*(..))") public void after(JoinPoint joinPoint) { logger.info("after execution of {}", joinPoint); } }
執(zhí)行后運行結(jié)果如下所示:
Check for user access Allowed execution for execution(String com.in28minutes.springboot.tutorial.basics.example.aop.data.Dao1.retrieveSomething()) In Business - Dao1 after execution of execution(String com.in28minutes.springboot.tutorial.basics.example.aop.business.Business1.calculateSomething()) execution(String com.in28minutes.springboot.tutorial.basics.example.aop.business.Business1.calculateSomething()) returned with value Dao1 c.i.s.t.b.e.a.BusinessAopSpringBootTest : Dao1 Check for user access Allowed execution for execution(String com.in28minutes.springboot.tutorial.basics.example.aop.data.Dao2.retrieveSomething()) after execution of execution(String com.in28minutes.springboot.tutorial.basics.example.aop.business.Business2.calculateSomething()) execution(String com.in28minutes.springboot.tutorial.basics.example.aop.business.Business2.calculateSomething()) returned with value Dao2 c.i.s.t.b.e.a.BusinessAopSpringBootTest : Dao2
可以看到,就在將值返回給調(diào)用的業(yè)務(wù)邏輯之前,after通知被執(zhí)行了。
其它AOP功能:@Around和注解能夠使用AOP實現(xiàn)的功能之一是通過自定義注釋來解析方法調(diào)用。
下面的例子展示了一個簡單的TrackTiem注釋:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface TrackTime {
我們可以添加一個切面來定義當(dāng)添加TrackTime注解以后執(zhí)行的邏輯。MethodExecutionCalculationAspect實現(xiàn)了一個簡單的時間追蹤功能。
@Aspect @Configuration public class MethodExecutionCalculationAspect { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Around("@annotation(com.in28minutes.springboot.tutorial.basics.example.aop.TrackTime)") public void around(ProceedingJoinPoint joinPoint) throws Throwable { long startTime = System.currentTimeMillis(); joinPoint.proceed(); long timeTaken = System.currentTimeMillis() - startTime; logger.info("Time Taken by {} is {}", joinPoint, timeTaken); } }
備注:
@Around: 是一個環(huán)繞型通知。它攔截方法調(diào)用后使用joinPoint.proceed()來執(zhí)行方法
@annotation(com.in28minutes.springboot.tutorial.basics.example.aop.TrackTime): 基于注解進行攔截的切點 - @annotation緊跟著完整的注解的名稱
定義了注解和通知之后,我們可以將注解運用到想要跟蹤的方法上,如下所示:
@Service public class Business1 { @TrackTime public String calculateSomething(){AOP最賤實踐
AOP最佳實踐之一是將所有的切點定義在一個類中。這樣有利于在一個地方維護所有的切點。
public class CommonJoinPointConfig { @Pointcut("execution(* com.in28minutes.spring.aop.springaop.data.*.*(..))") public void dataLayerExecution() {} @Pointcut("execution(* com.in28minutes.spring.aop.springaop.business.*.*(..))") public void businessLayerExecution() {} }
在定義其它切面的切入點時,可以這樣調(diào)用上面的定義:
@Around("com.in28minutes.spring.aop.springaop.aspect.CommonJoinPointConfig.businessLayerExecution()")
完整的代碼請前往GITHUB瀏覽
想要了解更多開發(fā)技術(shù),面試教程以及互聯(lián)網(wǎng)公司內(nèi)推,歡迎關(guān)注我的微信公眾號!將會不定期的發(fā)放福利哦~
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/76317.html
摘要:初次使用的人往往會困惑,不知道該使用哪種方法。目前來說,團隊推薦使用基于的方法來提供更高的靈活性。配置,從而在應(yīng)用啟動時執(zhí)行腳本來初始化數(shù)據(jù)庫。目前為止我們沒有任何消息需要配置,所以只在文件夾中創(chuàng)建一個空的文件。將配置為,它包含的上下文。 前言 spring是一個用于創(chuàng)建web和企業(yè)應(yīng)用的一個很流行的框架。和別的只關(guān)注于一點的框架不同,Spring框架通過投資并組合項目提供了大量的功能...
摘要:前言這周我準(zhǔn)備介紹一個有趣的但是很少使用的方法按照合約編程,又稱為合約編程,是一種軟件設(shè)計的方法。這些規(guī)則被稱為合約,可以比擬為商業(yè)合同中的條件和義務(wù)。通過將檢查和異常拋出指令包裝到方法中,人們可以很容易地實現(xiàn)合約式編程。 前言 這周我準(zhǔn)備介紹一個有趣的但是很少使用的方法 按照合約編程,又稱為合約編程,是一種軟件設(shè)計的方法。它規(guī)定了軟件設(shè)計師應(yīng)該為軟件組件定義正式,精確和可驗證的接口規(guī)...
摘要:本文簡介類概覽類構(gòu)造器總結(jié)類構(gòu)造方法類使用舉例類概覽是一個實現(xiàn)了接口,并且鍵為型的哈希表。中的條目不再被正常使用時,會被自動刪除。它的鍵值均支持。和絕大多數(shù)的集合類一樣,這個類不是同步的。 本文簡介 WeakHashMap類概覽 WeakHashMap類構(gòu)造器總結(jié) WeakHashMap類構(gòu)造方法 WeakHasjMap類使用舉例 1. WeakHashMap類概覽 Wea...
摘要:什么是為執(zhí)行字節(jié)碼提供一個運行環(huán)境。它的實現(xiàn)主要包含三個部分,描述實現(xiàn)規(guī)格的文檔,具體實現(xiàn)和滿足要求的計算機程序以及實例具體執(zhí)行字節(jié)碼。該類先被轉(zhuǎn)化為一組字節(jié)碼并放入文件中。字節(jié)碼校驗器通過字節(jié)碼校驗器檢查格式并找出非法代碼。 什么是Java Development Kit (JDK)? JDK通常用來開發(fā)Java應(yīng)用和插件?;旧峡梢哉J(rèn)為是一個軟件開發(fā)環(huán)境。JDK包含Java Run...
摘要:否則它就會用新的值替代當(dāng)前值。在這種情況下,鎖可能會優(yōu)于原子變量,但在實際的爭用級別中,原子變量的性能優(yōu)于鎖。在中引入了另外一個構(gòu)件。 題目要求 在我們深入了解CAS(Compare And Swap)策略以及它是如何在AtomicInteger這樣的原子構(gòu)造器中使用的,首先來看一下這段代碼: public class MyApp { private volatile int ...
閱讀 1668·2021-11-12 10:35
閱讀 1614·2021-08-03 14:02
閱讀 2681·2019-08-30 15:55
閱讀 2027·2019-08-30 15:54
閱讀 756·2019-08-30 14:01
閱讀 2427·2019-08-29 17:07
閱讀 2252·2019-08-26 18:37
閱讀 3031·2019-08-26 16:51