北京自助模板建站黑馬程序員培訓(xùn)機(jī)構(gòu)在哪
IOC AOP
一、 分層解耦
內(nèi)聚: 軟件中各個(gè)功能模塊內(nèi)部的功能聯(lián)系
耦合: 衡量軟件中各個(gè)層/模塊之間的依賴、關(guān)聯(lián)的程度
軟件設(shè)計(jì)原則:高內(nèi)聚、低耦合
? 控制反轉(zhuǎn):Inversion Of Control,簡(jiǎn)稱IOC。對(duì)象的創(chuàng)建控制權(quán)由程序自身轉(zhuǎn)移到外部(容器),這種思想成為控制反轉(zhuǎn)
? 依賴注入:Dependency Injection,簡(jiǎn)稱DI。容器為應(yīng)用程序提供運(yùn)行時(shí),所依賴的資源,稱為依賴注入。
? Bean對(duì)象: IOC容器中創(chuàng)建、管理的對(duì)象,稱為bean
1.1 IOC - 控制反轉(zhuǎn) 詳細(xì)
把某個(gè)對(duì)象交給IOC容器管理,需要添加如下注解之一:
聲名bean的時(shí)候,可以通過value屬性指定bean的名字,如果沒有指定,默認(rèn)是類名首字母小寫
使用以上四個(gè)注解都可以生命bean,但是在Springboot集成web開發(fā)中,聲名控制器bean只能用@Controller
bean的四大注解想要生效,需要被組件掃描注解@ComponentScan掃描
@ComponentScan注解雖然沒有顯示配置,但是實(shí)際上已經(jīng)包含在了啟動(dòng)類生命注解@SpringBootApplication中,默認(rèn)掃描的范圍是啟動(dòng)類所在包及其子包
如下包名是從“java”包后開始的,但是下面這種不推薦,我們希望的是按照Spring的規(guī)范,將包設(shè)置在啟動(dòng)類所在包及其子包
@ComponentScan({"dao","com.zhangjingqi"})
1.2 DI - 依賴注入 詳解
@Autowired 注解,默認(rèn)是按照類型進(jìn)行的,如果存在多個(gè)相同的bean,會(huì)報(bào)錯(cuò)。
EmpServiceA 實(shí)現(xiàn) EmpService類,EmpServiceB 實(shí)現(xiàn) EmpService類,我們?cè)谀硞€(gè)地方注入EmpService對(duì)象時(shí)便會(huì)出現(xiàn)注入錯(cuò)誤。
解決方案
@Primary 設(shè)置bean的優(yōu)先級(jí)
? 如果我們想要哪個(gè)bean填入容器,可以在類名之上添加@Primary
@Qualifier 指定bean的名字
@Qualifier("empServiceA")@Autowiredprivate EmpService empService;
@Resource 按照名稱注入
@Autowired 注解默認(rèn)按照類型注入,@Resource默認(rèn)按照類名進(jìn)行注入
@Resource(name = "empServiceB")private EmpService empService;
二、AOP
2.1 了解
Spring的第二大核心,第一大核心是IOC
AOP:面向切面編程、面向方面編程,其實(shí)就是面向特定方法編程
實(shí)現(xiàn):
**動(dòng)態(tài)代理是面向切面編程最主流的實(shí)現(xiàn)。**而SpringAOP是Spring框架的高級(jí)技術(shù),目的是在管理bean對(duì)象的過程中,主要通過底層的動(dòng)態(tài)代理機(jī)制,對(duì)特定的方法進(jìn)行編程
為什么要面向方法編程?
? 場(chǎng)景:案例部分功能運(yùn)行較慢,定位執(zhí)行耗時(shí)較長(zhǎng)的業(yè)務(wù)方法,此時(shí)需要統(tǒng)計(jì)每一個(gè)業(yè)務(wù)方法的執(zhí)行耗時(shí),找到耗時(shí)較長(zhǎng)的業(yè)務(wù)進(jìn)行優(yōu)化
? 按照之前的方式,就是在方法開始前和開時(shí)候分別獲取一個(gè)時(shí)間,兩個(gè)時(shí)間相減就是執(zhí)行耗時(shí),但是這種方式是非常繁瑣的
————————————————
版權(quán)聲明:本文為CSDN博主「我愛布朗熊」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_51351637/article/details/130779252二、AOP
2.1 了解
Spring的第二大核心,第一大核心是IOC
AOP:面向切面編程、面向方面編程,其實(shí)就是面向特定方法編程
實(shí)現(xiàn):
**動(dòng)態(tài)代理是面向切面編程最主流的實(shí)現(xiàn)。**而SpringAOP是Spring框架的高級(jí)技術(shù),目的是在管理bean對(duì)象的過程中,主要通過底層的動(dòng)態(tài)代理機(jī)制,對(duì)特定的方法進(jìn)行編程
為什么要面向方法編程?
? 場(chǎng)景:案例部分功能運(yùn)行較慢,定位執(zhí)行耗時(shí)較長(zhǎng)的業(yè)務(wù)方法,此時(shí)需要統(tǒng)計(jì)每一個(gè)業(yè)務(wù)方法的執(zhí)行耗時(shí),找到耗時(shí)較長(zhǎng)的業(yè)務(wù)進(jìn)行優(yōu)化
? 按照之前的方式,就是在方法開始前和開時(shí)候分別獲取一個(gè)時(shí)間,兩個(gè)時(shí)間相減就是執(zhí)行耗時(shí),但是這種方式是非常繁瑣的
如果我們基于AOP,面向方法編程,我們可以做到在不改動(dòng)原始方法的基礎(chǔ)上,來(lái)針對(duì)原始的方法進(jìn)行編程,可以是對(duì)原始方法功能的增強(qiáng),也可以改變?cè)挤椒ǖ墓δ?/p>
比如我們現(xiàn)在要統(tǒng)計(jì)方法的耗時(shí),我們只需要定義一個(gè)模板方法,將公共的代碼定義在模板方法中
原始業(yè)務(wù)方法在這里指的是需要統(tǒng)計(jì)執(zhí)行耗時(shí)的業(yè)務(wù)方法。而這樣面向一個(gè)或者多個(gè)方法進(jìn)行編程,就稱為面向切面編程
比如我們調(diào)用list()方法,此時(shí)并不會(huì)直接執(zhí)行原始的list方法,而是自動(dòng)的去執(zhí)行模板方法。
模板中所定義的代碼邏輯其實(shí)是創(chuàng)建出來(lái)的代理對(duì)象方法中的邏輯
2.2 快速入門 - AOP 開發(fā)步驟
需求:統(tǒng)計(jì)各個(gè)業(yè)務(wù)層方法執(zhí)行耗時(shí)
2.2.1 Maven依賴
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.2.2 代碼實(shí)現(xiàn)
針對(duì)于特定方法根據(jù)業(yè)務(wù)需要進(jìn)行編程
@Slf4j
@Component //交給容器IOC進(jìn)行管理
@Aspect //加上這個(gè)注解表示不是一個(gè)普通的類,而是一個(gè)AOP類,在此類中定義模板方法
public class TimeAspect {// 參數(shù)是一個(gè)表達(dá)式,表示針對(duì)哪些特定方法進(jìn)行編程
// com.zhangjingqi.service 包名
// 第一個(gè)*代表任意返回值 第二個(gè)*代表類名或者接口名 第三個(gè)*代表方法名@Around("execution(* com.zhangjingqi.service.*.*(..))") //切入點(diǎn)表達(dá)式public Object recordTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {long begin = System.currentTimeMillis();// result 原始方法執(zhí)行返回值Object result = proceedingJoinPoint.proceed();//調(diào)用原始方式運(yùn)行long end = System.currentTimeMillis();// proceedingJoinPoint.getSignature() 獲取方法的簽名,我們就知道是哪個(gè)方法了
// 如: List com.zhangjingqi.service.impl.DeptServiceImpl.list()執(zhí)行耗時(shí):239mslog.info(proceedingJoinPoint.getSignature() + "執(zhí)行耗時(shí):{}ms", end - begin);// 原始方法的返回值我們需要返回回去return result;}
}
2.2.3 AOP 應(yīng)用場(chǎng)景及優(yōu)勢(shì)
應(yīng)用場(chǎng)景
記錄操作日志
權(quán)限控制
事務(wù)管理
優(yōu)勢(shì)
代碼無(wú)侵入
減少重復(fù)代碼
提高開發(fā)效率
維護(hù)方便
2.3 核心概念
2.3.1 連接點(diǎn) - JoinPoint
連接點(diǎn):JoinPoint,可以被AOP控制的方法(暗含方法執(zhí)行時(shí)的相關(guān)信息)
通知:Advice,指那些重讀的邏輯,也就是共性功能(最終體現(xiàn)為一個(gè)方法)
切入點(diǎn):PointCut,匹配連接點(diǎn)的條件,通知僅會(huì)在切入點(diǎn)方法執(zhí)行時(shí)被應(yīng)用(就是實(shí)際被AOP控制的方法)
我們通常會(huì)使用下面的切入點(diǎn)表達(dá)式來(lái)描述切入點(diǎn)
@Around("execution(* com.zhangjingqi.service.*.*(..))")
切面:Aspect,描述通知與切入點(diǎn)的對(duì)應(yīng)關(guān)系(通知+切入點(diǎn)),被@Aspect注解修飾的類我們一般稱為切面類
目標(biāo)對(duì)象:Target,通知所應(yīng)用的對(duì)象。
2.3.2 AOP執(zhí)行流程
通知如何與目標(biāo)對(duì)象結(jié)合在一起對(duì)目標(biāo)對(duì)象中的方法進(jìn)行功能增強(qiáng)的?
? ①SpringAOP是基于動(dòng)態(tài)代理技術(shù)來(lái)實(shí)現(xiàn)的。程序運(yùn)行的時(shí)候會(huì)自動(dòng)的基于動(dòng)態(tài)代理技術(shù)為目標(biāo)對(duì)象生成一個(gè)對(duì)應(yīng)的代理對(duì)象。
? ② 在代理對(duì)象中就會(huì)對(duì)目標(biāo)對(duì)象中的原始方法進(jìn)行功能的增強(qiáng)。
? 如何來(lái)增強(qiáng)的?增強(qiáng)的邏輯是什么樣子的?
? 其實(shí)就是我們的通知
? ③最終在Spring容器中注入的是代理對(duì)象,調(diào)用的方法也是代理對(duì)象中的對(duì)應(yīng)方法
2.4 通知
2.4.1 通知類型
@Around:環(huán)繞通知,此注解標(biāo)注的通知方法在目標(biāo)方法前、后都被執(zhí)行,出現(xiàn)異常后后置代碼不會(huì)執(zhí)行。(因?yàn)樵挤椒ǔ霈F(xiàn)異常了)
@Before:前置通知,此注解標(biāo)注的通知方法在目標(biāo)方法前被執(zhí)行
@After:后置通知,此注解標(biāo)注的通知方法在目標(biāo)方法后被執(zhí)行,無(wú)論是否有異常都會(huì)執(zhí)行
@AfterReturning:返回后通知,此注解標(biāo)注的通知方法在目標(biāo)方法后被執(zhí)行,有異常不會(huì)執(zhí)行
@AfterThrowing:異常后通知,此注解標(biāo)注的通知方法在發(fā)生異常后執(zhí)行
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Slf4j
@Component
@Aspect
public class MyAspect1 {//前置通知@Before("execution(* com.zhangjingqi.service.*.*(..))")public void before(JoinPoint joinPoint) {log.info("before ...");}//環(huán)繞通知@Around("execution(* com.zhangjingqi.service.*.*(..))")public Object around(ProceedingJoinPoint proceedingJoinPoint)throws Throwable {log.info("around before ...");//調(diào)用目標(biāo)對(duì)象的原始方法執(zhí)行Object result = proceedingJoinPoint.proceed();//原始方法如果執(zhí)行時(shí)有異常,環(huán)繞通知中的后置代碼不會(huì)在執(zhí)行了log.info("around after ...");return result;}//后置通知@After("execution(* com.zhangjingqi.service.*.*(..))")public void after(JoinPoint joinPoint) {log.info("after ...");}//返回后通知(程序在正常執(zhí)行的情況下,會(huì)執(zhí)行的后置通知)@AfterReturning("execution(* com.zhangjingqi.service.*.*(..))")public void afterReturning(JoinPoint joinPoint) {log.info("afterReturning ...");}//異常通知(程序在出現(xiàn)異常的情況下,執(zhí)行的后置通知)@AfterThrowing("execution(* com.zhangjingqi.service.*.*(..))")public void afterThrowing(JoinPoint joinPoint) {log.info("afterThrowing ...");}
}
注意事項(xiàng)
@Around環(huán)繞通知需要自己調(diào)用ProceedingJoinPoint.proceed()來(lái)執(zhí)行原始方法,其他通知不需要考慮原始方法的執(zhí)行
@Around環(huán)繞通知的方法的返回值,必須指定為Object,來(lái)接收原始方法的返回值
? 如果不return,在調(diào)用這個(gè)方法的地方時(shí)拿不到返回值的
對(duì)切入點(diǎn)表達(dá)式進(jìn)行抽取
// 生命切入點(diǎn)表達(dá)式的注解,切點(diǎn)@Pointcut("execution(* com.zhangjingqi.service.*.*(..))")private void pt(){}//前置通知@Before("pt()")public void before(JoinPoint joinPoint) {log.info("before ...");}
其他類中也可以進(jìn)行抽取,只需要定位到切入點(diǎn)表達(dá)式的位置即可。
@Slf4j
@Component
@Aspect
public class MyAspect2 {
//引用MyAspect1切面類中的切入點(diǎn)表達(dá)式
@Before("com.zhangjingqi.aspect.MyAspect1.pt()")
public void before(){
log.info("MyAspect2 -> before ...");}
}
2.4.2 通知順序
? 當(dāng)有多個(gè)切面的切入點(diǎn)都匹配到了目標(biāo)方法,目標(biāo)方法運(yùn)行,多個(gè)通知方法都會(huì)被執(zhí)行。
? 下面研究多個(gè)切面類的通知順序。同個(gè)切面類的通知順序不再研究
不同切面類中,默認(rèn)按照切面類的類名字母排序
? 目標(biāo)方法前的通知方法:字母排名靠前的先執(zhí)行
? 目標(biāo)方法后的通知方法:字母排名靠前的后執(zhí)行
使用@Order(數(shù)字)加在切面類上來(lái)控制順序
2.5 切入點(diǎn)表達(dá)式
切入點(diǎn)表達(dá)式:描述切入點(diǎn)方法的一種表達(dá)式
作用:主要用來(lái)決定項(xiàng)目中哪些方法需要加入通知
常見形式
? execution(…):根據(jù)方法的簽名來(lái)匹配
? @annotation(…):根據(jù)注解匹配
2.5.1 execution
? 主要根據(jù)方法的返回值、包名、類名、方法名、方法參數(shù)等信息來(lái)匹配
? 下面來(lái)描述的時(shí)候,可以基于接口。也可以基于實(shí)現(xiàn)類
execution(訪問修飾符? 返回值 包名.類名.?方法名(方法參數(shù)) throws 異常?)
? 其中?表示可省略的部分
訪問修飾符:可省略,比如public、protected
包名.類名:可省略,但是不建議
throws 異常:可省略(注意是方法上聲明拋出的異常,不是實(shí)際拋出的異常)
2.5.1.1 execution通配符
*:單個(gè)獨(dú)立的任意符號(hào),可以匹配任意返回值、包名、類名、方法名、方法參數(shù)等信息來(lái)匹配
? 此案例表示返回值人任意,二級(jí)包任意,類或接口任意,方法參數(shù)任意但是有且只有一個(gè)
execution(* com.*.service.*.update(*)
匹配類名以Service結(jié)尾,方法以delete開頭的方法
execution(void
com.itheima.service.impl.*Service.delete*(java.lang.Integer)
)
…:多個(gè)連續(xù)的任意符號(hào),可以通配任意層級(jí)的包,或者任意類型、任意個(gè)數(shù)的參數(shù)
? 層級(jí)包任意,方法的參數(shù)任意
execution(* com.zhangjingqi..DeptService.*(..)
返回值任意,方法名任意,方法參數(shù)任意
execution(* *(..)
2.5.1.2 execution表達(dá)式案例
@Pointcut("execution(* com.zhangjingqi.service.*.*(..))")
省略異常
execution(public void
com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer)
)
省略方法訪問修飾符
參數(shù)是全類名
execution(void
com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer)
)
使用"…"省略包名
execution(public void com..DeptServiceImpl.delete(java.lang.Integer))
省略包名類名
? 指定方法名。
? 不建議將包名和方法名省略。一旦省略,將表達(dá)式的范圍擴(kuò)大,一是影響匹配的效率,而是可能匹配到其他不需要的方法
execution(public void delete(java.lang.Integer))
匹配所有的方法
? 此時(shí)表示匹配DeptServiceImpl類中的所有方法
execution(public void com..DeptServiceImpl.*(java.lang.Integer))
使用 且(&&)、或(||)、非(!) 來(lái)組合比較復(fù)雜的切入點(diǎn)表達(dá)式
execution(* com.zhangjingqi.service.DeptService.list(..)) ||
execution(* com.zhangjingqi.service.DeptService.delete(..))
2.5.1.3 切入點(diǎn)表達(dá)式建議
所有業(yè)務(wù)方法名在命名時(shí)盡量規(guī)范,方便切入點(diǎn)快速匹配。
? 如查詢方法find開頭,更新類方法update開頭
描述切入點(diǎn)方法通常基于接口描述,而不是直接描述實(shí)現(xiàn)類,增強(qiáng)拓展性
在滿足業(yè)務(wù)需要的前提下,盡量縮小切入點(diǎn)的匹配范圍
? 包名匹配進(jìn)行不使用“…”,使用“*”匹配單個(gè)包
2.5.2 @annotation
用于匹配標(biāo)識(shí)有特定注解的方法
@Before("@annotation(com.zhangjingqi.anno.MyLog)")
簡(jiǎn)化下列表達(dá)式
execution(* com.zhangjingqi.service.DeptService.list(..)) ||
execution(* com.zhangjingqi.service.DeptService.delete(..))
實(shí)現(xiàn)步驟
編寫自定義注解
在業(yè)務(wù)類要做為連接點(diǎn)的方法上添加自定義注解
創(chuàng)建自定義注解類
@Retention(RetentionPolicy.RUNTIME)//描述注解什么時(shí)候生效的:運(yùn)行時(shí)有效
@Target(ElementType.METHOD)//當(dāng)前注解可以作用在哪些地方
public @interface MyLog {
}
添加自定義注解
@Override
@MyLog //自定義注解(表示:當(dāng)前方法屬于目標(biāo)方法)
public List<Dept> list() { ... }@Override
@MyLog //自定義注解(表示:當(dāng)前方法屬于目標(biāo)方法)
public void delete(Integer id) { ... }
切面類
@Slf4j
@Component
@Aspect
public class MyAspect6 {
//針對(duì)list方法、delete方法進(jìn)行前置通知和后置通知
//前置通知
@Before("@annotation(com.zhangjingqi.anno.MyLog)")public void before(){log.info("MyAspect6 -> before ...");//后置通知
@After("@annotation(com.zhangjingqi.anno.MyLog)")public void after(){log.info("MyAspect6 -> after ...");}
}
2.5.3 切入點(diǎn)表達(dá)式總結(jié)
execution切入點(diǎn)表達(dá)式
? 根據(jù)我們所指定的方法的描述信息來(lái)匹配切入點(diǎn)方法,這種方式也是最為常用的一種方式
? 如果我們要匹配的切入點(diǎn)方法的方法名不規(guī)則,或者有一些比較特殊的需求,通過
? execution切入點(diǎn)表達(dá)式描述比較繁瑣
annotation 切入點(diǎn)表達(dá)式
? 基于注解的方式來(lái)匹配切入點(diǎn)方法。這種方式雖然多一步操作,我們需要自定義一個(gè)注解,但
? 是相對(duì)來(lái)比較靈活。我們需要匹配哪個(gè)方法,就在方法上加上對(duì)應(yīng)的注解就可以了
2.6 連接點(diǎn)
? 被AOP控制的方法,目標(biāo)對(duì)象中所有的方法都可以被AOP控制,在Spring AOP中又特制方法的執(zhí)行
在Spring中用JoinPoint抽象了連接點(diǎn),用它可以獲得方法執(zhí)行時(shí)的相關(guān)信息,如目標(biāo)類名、方法類名、方法參數(shù)等
? 對(duì)于@Around通知,獲取連接點(diǎn)信息只能用ProceedingJoinPoint
? 對(duì)于其他四種通知,獲取連接點(diǎn)信息只能使用JoinPoint,它是ProceedingJoinPoint父類型
對(duì)于@Around通知,為什么獲取連接點(diǎn)信息只能用ProceedingJoinPoint?
在Spring AOP中,@Around通知是最為強(qiáng)大和靈活的通知類型,它可以決定是否執(zhí)行連接點(diǎn),以及如何處理連接點(diǎn)返回的結(jié)果。因此,@Around通知需要通過ProceedingJoinPoint參數(shù)來(lái)獲取連接點(diǎn)信息。
? ProceedingJoinPoint是JoinPoint的子類,同時(shí)也是JoinPoint的擴(kuò)展版本。JoinPoint表示連接點(diǎn),也就是被Advice修飾的方法。而ProceedingJoinPoint除了表示連接點(diǎn)外,還具有一個(gè)proceed()方法,該方法是執(zhí)行目標(biāo)方法的關(guān)鍵。在@Before和@After通知中,JoinPoint足以滿足需要,因?yàn)樗鼈冎恍枰@取連接點(diǎn)信息即可,不需要執(zhí)行目標(biāo)方法。但在@Around中,除了獲取連接點(diǎn)信息,還需要控制目標(biāo)方法的執(zhí)行,因此需要用到ProceedingJoinPoint。
? 在@Around通知中,可以通過ProceedingJoinPoint的proceed()方法,手動(dòng)控制目標(biāo)方法的執(zhí)行。例如,可以在proceed()方法前后進(jìn)行一些預(yù)處理或后處理。同時(shí),ProceedingJoinPoint還提供了一些其他的工具方法,例如getArgs()獲取目標(biāo)方法參數(shù),getSignature()獲取目標(biāo)方法簽名等,這些方法在編寫@Around通知時(shí)也非常有用。