溫故知新——Spring AOP
- 2020 年 8 月 27 日
- 筆記
Spring AOP 面向切面編程,相信大家都不陌生,它和Spring IOC是Spring賴以成名的兩個最基礎的功能。在咱們平時的工作中,使用IOC的場景比較多,像咱們平時使用的@Controller、@Service、@Repository、@Component、@Autowired等,這些都和IOC相關。但是,使用AOP的場景卻非常少,也就是在事務控制這裡使用到了AOP,隨著SpringBoot的流行,事務控制這塊也不用自己配置了,SpringBoot內部已經給咱們配置好了,我們只需要使用@Transactional這個註解就可以了。
Spring AOP作為Spring的基礎功能,大家在學習的時候肯定都學過,但是由於平時使用的比較少,漸漸的就遺忘了,今天我們就再來看看Spring AOP,全面的給大家講一下,我本人也忘記的差不多了,在面試的時候,人家問我Spring AOP怎麼使用,我回答:呵呵,忘得差不多了。對方也是微微一笑,回了一個呵呵。好了,咱們具體看看Spring AOP吧。
Spring AOP解決的問題
面向切面編程,通俗的講就是將你執行的方法橫切,在方法前、後或者拋出異常時,執行你額外的程式碼。比如:你想要在執行所有的方法前,要驗證當前的用戶有沒有許可權執行這個方法。如果沒有AOP,你的做法是寫個驗證用戶許可權的方法,然後在所有的方法中,都去調用這個公共方法,如果有許可權再去執行後面的方法。這樣做是可以的,但是顯得比較啰嗦,而且硬編碼比較多,如果哪個小朋友忘了這個許可權驗證,那就麻煩了。
現在我們有了AOP,只需要幾個簡單的配置,就可以在所有的方法之前,去執行我們的驗證許可權的公共方法。
Sping AOP的核心概念
在AOP當中,核心的術語非常多,有8個,而且理解起來也是晦澀難懂,在這裡給大家羅列一下,大家如果感興趣可以去查閱一下其他的資料。
AOP的8個術語:切面(Aspect)、連接點(Join point)、通知(Advice)、切點(Pointcut)、引入(Introduction)、目標對象(Target object)、AOP代理(AOP proxy)、編織(Weaving)。
在這裡,我個人覺得Spring AOP總結成以下幾個概念就可以了。
- 切面(Aspect):在Spring AOP的實際使用中,只是標識的這一段是AOP的配置,沒有其他的意義。
- 切點(Pointcut):就是我們的方法中,哪些方法需要被代理,它需要一個表達式,凡是匹配成功的方法都會執行你定義的通知。
- 通知(Advice):就是你要另外執行的方法,在前面的例子中,就是許可權校驗的方法。
好了,知道這幾個概念,我個人覺得在平時工作中已經足夠了。
Sping AOP的具體配置——註解
我們的實例採用SpringBoot項目搭建,首先我們要把Spring AOP的依賴添加到項目中,具體的maven配置如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
具體的版本我們跟隨SpringBoot的版本即可。既然它是個starter,我比較好奇它在配置文件中有哪些配置,我們到application.properties中去看一下,
spring.aop.auto=true
spring.aop.proxy-target-class=true
- spring.aop.auto:它的默認值是true,它的含義是:Add @EnableAspectJAutoProxy,也就是說,我們在配置類上不再需要標註@EnableAspectJAutoProxy了。這個算是Spring AOP中的一個知識點了,我們重點說一下。
我們在Spring中使用AOP,需要兩個條件,第一個,要在我們的項目中引入aspectjweaver.jar
,這個,我們在引入spring-boot-starter-aop
的時候已經自動引入了。第二個,就是我們我們的配置類中,標註@EnableAspectJAutoProxy 這個註解,如果你使用的是xml的方式,需要你在xml文件中標明<aop:aspectj-autoproxy/>
。這樣才能在我們的項目使用Spring-AOP。
- spring.aop.proxy-target-class:AOP代理的實現,這個值默認也是true。它的含義是是否使用CGLIB代理。這也是AOP中的一個知識點。
Spring AOP的代理有兩種,一種是標準的JDK動態代理,它只能代理介面,也即是我們在使用的時候,必須寫一個介面和實現類,在實現類中寫自己的業務邏輯,然後通過介面實現AOP。另外一種是使用CGLIB 代理,它可以實現對類的代理,這樣我們就不用去寫介面了。
Spring AOP默認使用的是JDK動態代理,只能代理介面,而我們在開發的時候為了方便,希望可以直接代理類,這就需要引入CGLIB ,spring.aop.proxy-target-class默認是true,使用CGLIB,我們可以放心大膽的直接代理類了。
通過前面的步驟,我們已經可以在項目中使用Spring AOP代理了,下面我們先創建個Service,再寫個方法,如下:
@Service
public class MyService {
public void gotorun() {
System.out.println("我要去跑步!");
}
}
我們寫了個「我要去跑步」的方法,然後再通過AOP,在方法執行之前,列印出「我穿上了跑鞋」。下面重點看一下這個AOP代理怎麼寫,
@Component
@Aspect
public class MyAspect {
@Pointcut("execution(public * com.example.springaopdemo.service.*.*(..))")
private void shoes() {}
@Before("com.example.springaopdemo.aspect.MyAspect.shoes()")
public void putonshoes() {
System.out.println("我穿上跑步鞋。");
}
}
首先,要創建一個切面,我們在類上使用@Aspect註解,標識著這個類是一個切面,而@Component註解是將這個類實例化,這樣這個切面才會起作用。如果只有@Aspect而沒有被Spring實例是不起作用的。當然Spring實例化的方法有很多,不一定就非要使用@Component。
再來看看切點的聲明,切點的作用是匹配哪些方法要使用這個代理,我們使用@Pointcut註解。@Pointcut註解中有個表達式就是匹配我們的方法用到,它的種類也有很多,這裡我們給大家列出幾個比較常用的execution表達式吧,
execution(public * *(..)) //匹配所有的public方法
execution(* set*(..)) //匹配所有以set開頭的方法
execution(* com.xyz.service.AccountService.*(..)) //匹配所有AccountService中的方法
execution(* com.xyz.service.*.*(..)) //匹配所有service包中的方法
execution(* com.xyz.service..*.*(..)) //匹配所有service包及其子包中的方法
示例中,我們匹配的是service包中的所有方法。
有沒有同學比較好奇@Pointcut下面的那個方法?這個方法到底有沒有用?方法中如果有其他的操作會不會執行?答案是:方法里的內容不會被執行。那麼它有什麼用呢?它僅僅是給@Pointcut一個落腳的地方,僅此而已。但是,Spring對一個方法也是有要求的,這個方法的返回值類型必須是void。原文是這麼寫的:**
the method serving as the pointcut signature must have a
void
return type
最後我們再來看看通知,通知的種類有5種,分別為:
- @Before:前置通知,在方法之前執行;
- @AfterReturning:返回通知,方法正常返回以後,執行通知方法;
- @AfterThrowing:異常通知,方法拋出異常後,執行通知方法;
- @After:也是返回通知,不管方法是否正常結束,都會執行這個方法,類似於finally;
- @Around:環繞通知,在方法執行前後,都會執行通知方法;
在示例中,使用的是@Before前置通知,我們最關心的是@Before里的內容:
@Before("com.example.springaopdemo.aspect.MyAspect.shoes()")
public void putonshoes() {
System.out.println("我穿上跑步鞋。");
}
@Before里的內容是切點的方法,也就是我們定義的shoes()方法。那麼所有匹配了shoes()切點的方法,都會執行@Before這個註解的方法,也就是putonshoes()。
在@Before里除了寫切點的方法,還可以直接寫切點表達式,例如:
@Before("execution(public * com.example.springaopdemo.service.*.*(..))")
public void putonshoes() {
System.out.println("我穿上跑步鞋。");
}
如果我們使用這種表達式的寫法,就可以省去前面的@Pointcut了,這種方法還是比較推薦的。我們再寫個測試類運行一下,看看效果吧,
@SpringBootTest
class SpringAopDemoApplicationTests {
@Autowired
private MyService myService;
@Test
public void testAdvice() {
myService.gotorun();
}
}
運行結果如下:
我穿上跑步鞋。
我要去跑步!
沒有問題,在執行「我要去跑步」之前,成功的執行了「我穿上跑步鞋」的方法。
好了,今天先到這裡,下一篇我們看看如何使用xml的方式配置Spring AOP。