溫故知新——Spring AOP

Spring AOP 面向切面編程,相信大家都不陌生,它和Spring IOC是Spring賴以成名的兩個(gè)最基礎(chǔ)的功能。在咱們平時(shí)的工作中,使用IOC的場景比較多,像咱們平時(shí)使用的@Controller、@Service、@Repository、@Component、@Autowired等,這些都和IOC相關(guān)。但是,使用AOP的場景卻非常少,也就是在事務(wù)控制這里使用到了AOP,隨著SpringBoot的流行,事務(wù)控制這塊也不用自己配置了,SpringBoot內(nèi)部已經(jīng)給咱們配置好了,我們只需要使用@Transactional這個(gè)注解就可以了。

Spring AOP作為Spring的基礎(chǔ)功能,大家在學(xué)習(xí)的時(shí)候肯定都學(xué)過,但是由于平時(shí)使用的比較少,漸漸的就遺忘了,今天我們就再來看看Spring AOP,全面的給大家講一下,我本人也忘記的差不多了,在面試的時(shí)候,人家問我Spring AOP怎么使用,我回答:呵呵,忘得差不多了。對方也是微微一笑,回了一個(gè)呵呵。好了,咱們具體看看Spring AOP吧。

Spring AOP解決的問題

面向切面編程,通俗的講就是將你執(zhí)行的方法橫切,在方法前、后或者拋出異常時(shí),執(zhí)行你額外的代碼。比如:你想要在執(zhí)行所有的方法前,要驗(yàn)證當(dāng)前的用戶有沒有權(quán)限執(zhí)行這個(gè)方法。如果沒有AOP,你的做法是寫個(gè)驗(yàn)證用戶權(quán)限的方法,然后在所有的方法中,都去調(diào)用這個(gè)公共方法,如果有權(quán)限再去執(zhí)行后面的方法。這樣做是可以的,但是顯得比較啰嗦,而且硬編碼比較多,如果哪個(gè)小朋友忘了這個(gè)權(quán)限驗(yàn)證,那就麻煩了。

現(xiàn)在我們有了AOP,只需要幾個(gè)簡單的配置,就可以在所有的方法之前,去執(zhí)行我們的驗(yàn)證權(quán)限的公共方法。

Sping AOP的核心概念

在AOP當(dāng)中,核心的術(shù)語非常多,有8個(gè),而且理解起來也是晦澀難懂,在這里給大家羅列一下,大家如果感興趣可以去查閱一下其他的資料。

AOP的8個(gè)術(shù)語:切面(Aspect)、連接點(diǎn)(Join point)、通知(Advice)、切點(diǎn)(Pointcut)、引入(Introduction)、目標(biāo)對象(Target object)、AOP代理(AOP proxy)、編織(Weaving)。

在這里,我個(gè)人覺得Spring AOP總結(jié)成以下幾個(gè)概念就可以了。

  • 切面(Aspect):在Spring AOP的實(shí)際使用中,只是標(biāo)識的這一段是AOP的配置,沒有其他的意義。
  • 切點(diǎn)(Pointcut):就是我們的方法中,哪些方法需要被代理,它需要一個(gè)表達(dá)式,凡是匹配成功的方法都會執(zhí)行你定義的通知。
  • 通知(Advice):就是你要另外執(zhí)行的方法,在前面的例子中,就是權(quán)限校驗(yàn)的方法。

好了,知道這幾個(gè)概念,我個(gè)人覺得在平時(shí)工作中已經(jīng)足夠了。

Sping AOP的具體配置——注解

我們的實(shí)例采用SpringBoot項(xiàng)目搭建,首先我們要把Spring AOP的依賴添加到項(xiàng)目中,具體的maven配置如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

具體的版本我們跟隨SpringBoot的版本即可。既然它是個(gè)starter,我比較好奇它在配置文件中有哪些配置,我們到application.properties中去看一下,

spring.aop.auto=true
spring.aop.proxy-target-class=true
  • spring.aop.auto:它的默認(rèn)值是true,它的含義是:Add @EnableAspectJAutoProxy,也就是說,我們在配置類上不再需要標(biāo)注@EnableAspectJAutoProxy了。這個(gè)算是Spring AOP中的一個(gè)知識點(diǎn)了,我們重點(diǎn)說一下。

我們在Spring中使用AOP,需要兩個(gè)條件,第一個(gè),要在我們的項(xiàng)目中引入aspectjweaver.jar,這個(gè),我們在引入spring-boot-starter-aop的時(shí)候已經(jīng)自動引入了。第二個(gè),就是我們我們的配置類中,標(biāo)注@EnableAspectJAutoProxy 這個(gè)注解,如果你使用的是xml的方式,需要你在xml文件中標(biāo)明<aop:aspectj-autoproxy/>。這樣才能在我們的項(xiàng)目使用Spring-AOP。

  • spring.aop.proxy-target-class:AOP代理的實(shí)現(xiàn),這個(gè)值默認(rèn)也是true。它的含義是是否使用CGLIB代理。這也是AOP中的一個(gè)知識點(diǎn)。

Spring AOP的代理有兩種,一種是標(biāo)準(zhǔn)的JDK動態(tài)代理,它只能代理接口,也即是我們在使用的時(shí)候,必須寫一個(gè)接口和實(shí)現(xiàn)類,在實(shí)現(xiàn)類中寫自己的業(yè)務(wù)邏輯,然后通過接口實(shí)現(xiàn)AOP。另外一種是使用CGLIB 代理,它可以實(shí)現(xiàn)對類的代理,這樣我們就不用去寫接口了。

Spring AOP默認(rèn)使用的是JDK動態(tài)代理,只能代理接口,而我們在開發(fā)的時(shí)候?yàn)榱朔奖?,希望可以直接代理類,這就需要引入CGLIB ,spring.aop.proxy-target-class默認(rèn)是true,使用CGLIB,我們可以放心大膽的直接代理類了。

通過前面的步驟,我們已經(jīng)可以在項(xiàng)目中使用Spring AOP代理了,下面我們先創(chuàng)建個(gè)Service,再寫個(gè)方法,如下:

@Service
public class MyService {

    public void gotorun() {
        System.out.println("我要去跑步!");
    }

}

我們寫了個(gè)“我要去跑步”的方法,然后再通過AOP,在方法執(zhí)行之前,打印出“我穿上了跑鞋”。下面重點(diǎn)看一下這個(gè)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("我穿上跑步鞋。");
    }
}

首先,要創(chuàng)建一個(gè)切面,我們在類上使用@Aspect注解,標(biāo)識著這個(gè)類是一個(gè)切面,而@Component注解是將這個(gè)類實(shí)例化,這樣這個(gè)切面才會起作用。如果只有@Aspect而沒有被Spring實(shí)例是不起作用的。當(dāng)然Spring實(shí)例化的方法有很多,不一定就非要使用@Component。

再來看看切點(diǎn)的聲明,切點(diǎn)的作用是匹配哪些方法要使用這個(gè)代理,我們使用@Pointcut注解。@Pointcut注解中有個(gè)表達(dá)式就是匹配我們的方法用到,它的種類也有很多,這里我們給大家列出幾個(gè)比較常用的execution表達(dá)式吧,

execution(public * *(..))      //匹配所有的public方法
execution(* set*(..))          //匹配所有以set開頭的方法
execution(* com.xyz.service.AccountService.*(..))     //匹配所有AccountService中的方法
execution(* com.xyz.service.*.*(..))    //匹配所有service包中的方法
execution(* com.xyz.service..*.*(..))   //匹配所有service包及其子包中的方法

示例中,我們匹配的是service包中的所有方法。

有沒有同學(xué)比較好奇@Pointcut下面的那個(gè)方法?這個(gè)方法到底有沒有用?方法中如果有其他的操作會不會執(zhí)行?答案是:方法里的內(nèi)容不會被執(zhí)行。那么它有什么用呢?它僅僅是給@Pointcut一個(gè)落腳的地方,僅此而已。但是,Spring對一個(gè)方法也是有要求的,這個(gè)方法的返回值類型必須是void。原文是這么寫的:**

the method serving as the pointcut signature must have a void return type

最后我們再來看看通知,通知的種類有5種,分別為:

  • @Before:前置通知,在方法之前執(zhí)行;
  • @AfterReturning:返回通知,方法正常返回以后,執(zhí)行通知方法;
  • @AfterThrowing:異常通知,方法拋出異常后,執(zhí)行通知方法;
  • @After:也是返回通知,不管方法是否正常結(jié)束,都會執(zhí)行這個(gè)方法,類似于finally;
  • @Around:環(huán)繞通知,在方法執(zhí)行前后,都會執(zhí)行通知方法;

在示例中,使用的是@Before前置通知,我們最關(guān)心的是@Before里的內(nèi)容:

@Before("com.example.springaopdemo.aspect.MyAspect.shoes()")
public void putonshoes() {
    System.out.println("我穿上跑步鞋。");
}

@Before里的內(nèi)容是切點(diǎn)的方法,也就是我們定義的shoes()方法。那么所有匹配了shoes()切點(diǎn)的方法,都會執(zhí)行@Before這個(gè)注解的方法,也就是putonshoes()。

在@Before里除了寫切點(diǎn)的方法,還可以直接寫切點(diǎn)表達(dá)式,例如:

@Before("execution(public * com.example.springaopdemo.service.*.*(..))")
public void putonshoes() {
    System.out.println("我穿上跑步鞋。");
}

如果我們使用這種表達(dá)式的寫法,就可以省去前面的@Pointcut了,這種方法還是比較推薦的。我們再寫個(gè)測試類運(yùn)行一下,看看效果吧,

@SpringBootTest
class SpringAopDemoApplicationTests {
    @Autowired
    private MyService myService;

    @Test
    public void testAdvice() {
        myService.gotorun();
    }
}

運(yùn)行結(jié)果如下:

我穿上跑步鞋。
我要去跑步!

沒有問題,在執(zhí)行“我要去跑步”之前,成功的執(zhí)行了“我穿上跑步鞋”的方法。

好了,今天先到這里,下一篇我們看看如何使用xml的方式配置Spring AOP。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容