?IOC
參考:
Spring基礎 - Spring核心之控制反轉(IOC) | Java 全棧知識體系 (pdai.tech)
概述:
- Ioc 即 Inverse of Control (控制反轉),是一種設計思想,就是將原本在程序中手動創建對象的控制權,交由Spring框架來管理。
- 控制反轉是目的,依賴注入是手段,Spring通過DI(依賴注入)實現IOC(控制反轉)。
- 控制反轉,是一種理論、概念、思想。把代碼的創建、賦值、管理工作都交給代碼之外的容器實現。
- 控制:創建對象、屬性賦值、對象之間的關系管理。
- 反轉:把原先開發人員管理、創建對象的權限交給容器去做。
- 正傳:由開發人員在代碼中使用new構造方主動創建、管理對象。
參與者有哪些?
1)對象
2)IOC/DI容器
3)某個對象的外部資源
依賴,誰依賴誰?為什么需要依賴?
依賴嘛,很好理解的,對象依賴于IOC/DI容器,至于為什么要依賴呢?對象需要IOC/DI 容器來提供對象需要的外部資源。
注入,誰注入誰?又注入了什么呢?
顯而易見是IOC/DI容器注入對象,注入了what呢?肯定注入的是某個需要的東西那就 是注入對象所需要的資源,肯定不會注入無關緊要的內容,你說呢?
類A需要類B的時候,直接在A中注入類B。
誰控制誰?
Ioc容器管理控制對象,對象的創建不再是由使用者創建,而是由第三方統一 創建和 管理。
為何要反轉?
正轉就是正常創建對象,一個個為屬性賦值。
哪些方面被反轉了?
創建對象的權力被反轉了,以前創建對象的權利是資源的使用者,現在由第三方去托管。 實現了解耦。
敘述:
1996年,Michael Mattson在一篇有關探討面向對象框架的文章中,首先提出了IOC 這個概念。對于面向對象設計及編程的基本思想,前面我們已經講了很多了,不再贅述,簡單來說就是把復雜系統分解成相互合作的對象,這些對象類通過封裝以后,內部實現對外部是透明的,從而降低了解決問題的復雜度,而且可以靈活地被重用和擴展。
IOC理論提出的觀點大體是這樣的:借助于“第三方”實現具有依賴關系的對象之間的解耦。如下圖:
大家看到了吧,由于引進了中間位置的“第三方”,也就是IOC容器,使得A、B、C、D這4個對象沒有了耦合關系,齒輪之間的傳動全部依靠“第三方”了,全部對象的控制權全部上繳給“第三方”IOC容器,所以,IOC容器成了整個系統的關鍵核心,它起到了一種類似“粘合劑”的作用,把系統中的所有對象粘合在一起發揮作用,如果沒有這個“粘合劑”,對象與對象之間會彼此失去聯系,這就是有人把IOC容器比喻成“粘合劑”的由來。
我們再來做個試驗:把上圖中間的IOC容器拿掉,然后再來看看這套系統:
我們現在看到的畫面,就是我們要實現整個系統所需要完成的全部內容。這時候,A、B、C、D這4個對象之間已經沒有了耦合關系,彼此毫無聯系,這樣的話,當你在實現A的時候,根本無須再去考慮B、C和D了,對象之間的依賴關系已經降低到了最低程度。所以,如果真能實現IOC容器,對于系統開發而言,這將是一件多么美好的事情,參與開發的每一成員只要實現自己的類就可以了,跟別人沒有任何關系!
我們再來看看,控制反轉(IOC)到底為什么要起這么個名字?我們來對比一下:
軟件系統在沒有引入IOC容器之前,如圖1所示,對象A依賴于對象B,那么對象A在初始化或者運行到某一點的時候,自己必須主動去創建對象B或者使用已經創建的對象B。無論是創建還是使用對象B,控制權都在自己手上。
軟件系統在引入IOC容器之后,這種情形就完全改變了,如圖3所示,由于IOC容器的加入,對象A與對象B之間失去了直接聯系,所以,當對象A運行到需要對象B的時候,IOC容器會主動創建一個對象B注入到對象A需要的地方。
通過前后的對比,我們不難看出來:對象A獲得依賴對象B的過程,由主動行為變為了被動行為,控制權顛倒過來了,這就是“控制反轉”這個名稱的由來。
作用:
管理對象的創建和依賴關系的維護。
對象的創建并不是一件簡單的事,在對象關系比較復雜時,如果依賴關系需要程序猿來維護的話,那是相當頭疼的解耦,由容器去維護具體的對象托管了類的產生過程,比如我們需要在類的產生過程中做一些處理,最直接的例子就是代理,如果有容器程序可以把這部分處理交給容器,應用程序則無需去關心類是如何完成代理的。
實現原理:
工廠模式?+ 反射機制
優缺點:
優點:
- 降低了應用的額代碼量。
- 實現組件之間的解耦,提高程序的靈活性和可維護性。
- IOC容器支持加載服務時的餓漢式初始化和懶加載。
缺點:
- 創建對象的步驟變復雜了,不直觀,當然這是對不習慣這種方式的人來說的。
- 因為使用反射來創建對象,所以在效率上會有些損耗。但相對于程序的靈活性 和可維護性來說,這點損耗是微不足道的。
- 缺少IDE重構的支持,如果修改了類名,還需到XML文件中手動修改,這似乎 是所有XML方式的缺憾所在。
什么是耦合?
如在web開發中,表現層需要持有業務層的對象,業務層中需要持有持久層的對象,這種(強的)依賴關系叫耦合。
高耦合的缺點:
連鎖反應,修改一個模塊,多個模塊都需要進行修改
當AccountDao的標簽名稱被修改為時,業務層所有持有AccountDao的實例對象的類 都需要進行修改。如AccountDao被修改為AccountDao1那么所有的業務層的private accountDao = new AccountDao()都要修改成為private accountDao = new AccountDao1()。當業務層的此種類過多時,修改需要大量的時間。
由于模塊之間的相依性,模塊的組合會需要更多的精力及時間。
解耦思路?
? 第一步:需要一個配置文件來配置我們的service和dao(配置文件可以選用 xml或者properties)
? 第二部:讀取配置文件,通過反射創建對象
? 第三部:不需要主動new對象,直接從工廠通過反射獲取對象,當需要修改一個對 象名的時候,只需要將配置文件中類的全限定名修改即可。不需要大范圍修改業務 層中的代碼,這樣代碼維護起來更加省時。
相關問題:
常用的依賴注入方式有哪幾種?
1、構造方法注入
public class Chinese implements Person
{private final A a;// 構造方法注入A對象public Chinese (A a){this.a= a;}
}
2、setter注入
在一個類中通過setter注入需要的對象。
public class Chinese implements Person
{private A a;// 設值注入所需的setter方法public void setA(A a){this.a= a;}
}
3、基于注解的注入
直接使用注解:Autowired、Resource、Qualifier、Service、Controller、Repository、Component。
什么樣的類需要放入spring的IOC容器中?
Dao類、service類、controller類、工具類
什么樣的類不需要放入spring的IOC容器中?
實體類對象,servlet等。
AOP
參考:
Spring基礎 - Spring核心之面向切面編程(AOP) | Java 全棧知識體系 (pdai.tech)
概述:
(Aspect Oriented Programming)面向切面編程
傳統的面向對象編程OOP解決不了為分散對象引入公共行為的問題,而AOP則可以,AOP可以打開封裝對象的內部,將那些影響了多個類的公共行為封裝到一個可重用模塊,稱為方面。
?AOP的核心思想就是“將應用程序中的商業邏輯同對其提供支持的通用服務進行分離。”
詳述:
OOP(Object-Oriented Programing,面向對象編程),AOP可以說是OOP的補充和完善,?OOP引入封裝、繼承和多態性等概念來建立一種對象層次結構,用以模擬公共行為的一個集合。當我們需要為分散的對象引入公共行為的時候,OOP則顯得無能為力。也就是說,OOP允許你定義從上到下的關系,但并不適合定義從左到右的關系。例如日志功能。日志代碼往往水平地散布在所有對象層次中,而與它所散布到的對象的核心功能毫無關系。對于其他類型的代碼,如安全性、異常處理和透明的持續性也是如此。這種散布在各處的無關的代碼被稱為橫切(cross-cutting)代碼,在OOP設計中,它導致了大量代碼的重復,而不利于各個模塊的重用。
而AOP技術則恰恰相反,它利用一種稱為“橫切”的技術,剖解開封裝的對象內部,并將那些影響了多個類的公共行為封裝到一個可重用模塊,并將其名為“Aspect”,即方面。所謂“方面”,簡單地說,就是將那些與業務無關,卻為業務模塊所共同調用的邏輯或責任封裝起來,便于減少系統的重復代碼,降低模塊間的耦合度,并有利于未來的可操作性和可維護性。AOP代表的是一個橫向的關系,如果說“對象”是一個空心的圓柱體,其中封裝的是對象的屬性和行為;那么面向方面編程的方法,就仿佛一把利刃,將這些空心圓柱體剖開,以獲得其內部的消息。而剖開的切面,也就是所謂的“方面”了。然后它又以巧奪天功的妙手將這些剖開的切面復原,不留痕跡。
使用“橫切”技術,AOP把軟件系統分為兩個部分:核心關注點和橫切關注點。業務處理的主要流程是核心關注點,與之關系不大的部分是橫切關注點。橫切關注點的一個特點是,他們經常發生在核心關注點的多處,而各處都基本相似。比如權限認證、日志、事務處理。Aop 的作用在于分離系統中的各種關注點,將核心關注點和橫切關注點分離開來。正如Avanade公司的高級方案構架師Adam Magee所說,AOP的核心思想就是“將應用程序中的商業邏輯同對其提供支持的通用服務進行分離。”
相關術語:
- ①、JoinPoint(連接點):指那些被攔截到的點。(在Spring中這些點指的是方法,因為Spring只支持方法類型的連接點)。
- ②、PointCut(切入點):指的是要對哪些JoinPoint進行攔截的定義。
- ③、Advice(通知/增強):指攔截到JoinPoint之后所要做的事情。
- ④、Target(目標對象):代理的目標對象。
- ⑤、Weaving(織入):指把增強應用到目標對象來創建新的代理對象的過程。spring采用動態代理織入;Aspect采用編譯器織入和類裝載期織入。
- ⑥、Aspect(切面):是切入點和通知的結合。
通知:
前置通知、后置通知、異常通知、最終通知、環繞通知。
前置通知:在切入點方法執行之前執行。
后置通知:在切入點方法執行之后執行。它和異常通知永遠只能執行一個。
異常通知:在切入點方法執行產生異常之后執行。它和后置通知永遠只能執行一個。
最終通知:無論切入點方法是否正常執行,它都會在其后面執行。
?
使用分類:
環繞通知:
@Around("execution(* com..*controller..*(..))")
環繞通知=前置+目標方法執行+后置通知,proceed方法就是用于啟動目標方法執行的。(也就是接口的業務處理)
執行目標方法之前會進入前置@Before執行,然后執行目標方法,在執行后置,最后返回到環繞,return ewsult。有攔截器的話,最后執行攔截器的afterCompletion。
@Around("execution(* com..*controller..*(..))")
public Object doControllerAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {if (StringUtil.isBlank(MDC.get(TRACE_ID))) {MDC.put(TRACE_ID, StringUtil.uuid());}Object result = proceedingJoinPoint.proceed();MDC.clear();return result;
}
前置通知:
@Before(value = "execution(* com..*controller..*(..))")
后置通知:
@After(value = "execution(* com..*controller..*(..))")
@AfterReturning(pointcut = "deleteScheduleJobPointcut()", returning = "rvt")
@AfterThrowing(value = "execution(* com..*controller..*(..))", returning = "retVal")
@AfterReturning(pointcut = "deleteScheduleJobPointcut()", returning = "rvt")public void afterDeleteScheduleJob(JoinPoint joinPoint, int rvt) {if (rvt == 1) {//獲取修飾符+ 包名+組件名(類名) +方法名System.out.println(joinPoint.getSignature());//獲取方法名System.out.println(joinPoint.getSignature().getName());//獲取包名+組件名(類名)System.out.println(joinPoint.getSignature().getDeclaringType());// 獲取連接點的參數TbScheduleJob job = (TbScheduleJob) joinPoint.getArgs()[0];String json = jacksonUtil.object2Json(job);log.info("刪除任務,通知其他節點,數據:" + json);Message<String> message = MessageBuilder.withPayload(json).setHeader("KEYS", EVENT_MQ_KEY_DELETE).build();SendResult sendResult = rocketMQTemplate.syncSend(BaseListener.MCMS_TOPIC+ EVENT_TAG_SCHEDULE_JOB, message);log.info("發送到mq,結果[{}]", sendResult.getSendStatus());}}
執行順序:
所有controller環繞切點:
環繞通知=前置+目標方法執行+后置通知,proceed方法就是用于啟動目標方法執行的。
攔截器preHandle執行之后進入環繞通知,環繞通知proceedingJoinPoint.proceed()作用是執行目標方法,
執行目標方法之前會進入前置@Before執行,然后執行目標方法,在執行后置,最后返回到環繞,return ewsult,最后執行攔截器的afterCompletion。
package com.eastcom.aop;import com.eastcom.base.MDCConstants;
import com.eastcom.util.DateUtil;
import com.eastcom.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Date;
import java.util.Objects;@Slf4j
@Aspect
@Component
public class ControllerAspect {@Before(value = "execution(* com..*controller..*(..))")public void before(JoinPoint joinPoint) {log.info("[start]:------------------------------->");HttpServletRequest request = ((ServletRequestAttributes)Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();StringBuffer stringBuffer = new StringBuffer();stringBuffer.append("REQ_TIME[" + DateUtil.formatDate(new Date()) + "],");stringBuffer.append("URL[" + request.getRequestURL().toString() + "],");stringBuffer.append("HTTP_METHOD[" + request.getMethod() + "],");stringBuffer.append("IP[" + request.getRemoteAddr() + "],");stringBuffer.append("CLASS_METHOD[" + joinPoint.getSignature().getDeclaringTypeName()+ "." + joinPoint.getSignature().getName() + "],");if (joinPoint.getArgs() != null) {stringBuffer.append("ARGS[" + Arrays.toString(joinPoint.getArgs()) + "],");}log.info(stringBuffer.toString());}@AfterReturning(value = "execution(* com..*controller..*(..))", returning = "retVal")public void afterreturning(JoinPoint joinPoint, Object retVal) {HttpServletRequest request = ((ServletRequestAttributes)Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();StringBuffer stringBuffer = new StringBuffer();stringBuffer.append("RES_TIME[" + DateUtil.formatDate(new Date()) + "],");stringBuffer.append("RETURN[" + (retVal != null ? retVal.toString() : null) + "],");log.info(stringBuffer.toString());log.info("[ end ]:------------------------------->");}/*** 所有controller環繞切點:環繞通知=前置+目標方法執行+后置通知,proceed方法就是用于啟動目標方法執行的.* 攔截器preHandle執行之后進入環繞通知,環繞通知proceedingJoinPoint.proceed()作用是執行目標方法,* 執行目標方法之前會進入前置@Before執行,然后執行目標方法,在執行后置,最后返回到環繞,return ewsult* 最后執行攔截器的afterCompletion** @return Object* @throws Throwable 異常*/@Around("execution(* com..*controller..*(..))")public Object doControllerAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {if (StringUtil.isBlank(MDC.get(MDCConstants.TRACE_ID))) {MDC.put(MDCConstants.TRACE_ID, StringUtil.uuid());}Object result = proceedingJoinPoint.proceed();MDC.clear();return result;}}
@Pointcut切入點表達式介紹:
1 表達式類型
標準的Aspectj?Aop的pointcut的表達式類型是很豐富的,但是Spring Aop只支持其中的9種,外加Spring Aop自己擴充的一種一共是10種類型的表達式,分別如下。
- execution:一般用于指定方法的執行,用的最多。
- within:指定某些類型的全部方法執行,也可用來指定一個包。
- this:Spring Aop是基于代理的,生成的bean也是一個代理對象,this就是這個代理對象,當這個對象可以轉換為指定的類型時,對應的切入點就是它了,Spring Aop將生效。
- target:當被代理的對象可以轉換為指定的類型時,對應的切入點就是它了,Spring Aop將生效。
- args:當執行的方法的參數是指定類型時生效。
- @target:當代理的目標對象上擁有指定的注解時生效。
- @args:當執行的方法參數類型上擁有指定的注解時生效。
- @within:與@target類似,看官方文檔和網上的說法都是@within只需要目標對象的類或者父類上有指定的注解,則@within會生效,而@target則是必須是目標對象的類上有指定的注解。而根據筆者的測試這兩者都是只要目標類或父類上有指定的注解即可。
- @annotation:當執行的方法上擁有指定的注解時生效。
- bean:當調用的方法是指定的bean的方法時生效。
2?使用示例
execution是使用的最多的一種Pointcut表達式,表示某個方法的執行,其標準語法如下。
-
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?
-
name-pattern(param-pattern) throws-pattern?)
@Pointcut(value = "execution(* com.xxx.service.impl.XXXServiceImpl.mothod01(..))")public void xxxPointcut() {}
modifiers-pattern表示方法的訪問類型,public等;ret-type-pattern表示方法的返回值類型,如String表示返回類型是String,“*”表示所有的返回類型;declaring-type-pattern表示方法的聲明類,如“com.elim..*”表示com.elim包及其子包下面的所有類型;name-pattern表示方法的名稱,如“add*”表示所有以add開頭的方法名;param-pattern表示方法參數的類型,name-pattern(param-pattern)其實是一起的表示的方法集對應的參數類型,如“add()”表示不帶參數的add方法,“add(*)”表示帶一個任意類型的參數的add方法,“add(*,String)”則表示帶兩個參數,且第二個參數是String類型的add方法;throws-pattern表示異常類型;其中以問號結束的部分都是可以省略的。
- 1、“execution(* add())”匹配所有的不帶參數的add()方法。
- 2、“execution(public * com.elim..*.add*(..))”匹配所有com.elim包及其子包下所有類的以add開頭的所有public方法。
- 3、“execution(* *(..) throws Exception)”匹配所有拋出Exception的方法。
within是用來指定類型的,指定類型中的所有方法將被攔截。
- 1、“within(com.elim.spring.aop.service.UserServiceImpl)”匹配UserServiceImpl類對應對象的所有方法外部調用,而且這個對象只能是UserServiceImpl類型,不能是其子類型。
- 2、“within(com.elim..*)”匹配com.elim包及其子包下面所有的類的所有方法的外部調用。
Spring Aop是基于代理的,this就表示代理對象。this類型的Pointcut表達式的語法是this(type),當生成的代理對象可以轉換為type指定的類型時則表示匹配。基于JDK接口的代理和基于CGLIB的代理生成的代理對象是不一樣的。
- 1、“this(com.elim.spring.aop.service.IUserService)”匹配生成的代理對象是IUserService類型的所有方法的外部調用。
Spring Aop是基于代理的,target則表示被代理的目標對象。當被代理的目標對象可以被轉換為指定的類型時則表示匹配。
- 1、“target(com.elim.spring.aop.service.IUserService)”則匹配所有被代理的目標對象能夠轉換為IUserService類型的所有方法的外部調用。
args用來匹配方法參數的。
- 1、“args()”匹配任何不帶參數的方法。
- 2、“args(java.lang.String)”匹配任何只帶一個參數,而且這個參數的類型是String的方法。
- 3、“args(..)”帶任意參數的方法。
- 4、“args(java.lang.String,..)”匹配帶任意個參數,但是第一個參數的類型是String的方法。
- 5、“args(..,java.lang.String)”匹配帶任意個參數,但是最后一個參數的類型是String的方法。
@target匹配當被代理的目標對象對應的類型及其父類型上擁有指定的注解時。
- 1、“@target(com.elim.spring.support.MyAnnotation)”匹配被代理的目標對象對應的類型上擁有MyAnnotation注解時。
@args匹配被調用的方法上含有參數,且對應的參數類型上擁有指定的注解的情況。
- 1、“@args(com.elim.spring.support.MyAnnotation)”匹配方法參數類型上擁有MyAnnotation注解的方法調用。如我們有一個方法add(MyParam param)接收一個MyParam類型的參數,而MyParam這個類是擁有注解MyAnnotation的,則它可以被Pointcut表達式“@args(com.elim.spring.support.MyAnnotation)”匹配上。
@within用于匹配被代理的目標對象對應的類型或其父類型擁有指定的注解的情況,但只有在調用擁有指定注解的類上的方法時才匹配。
- 1、“@within(com.elim.spring.support.MyAnnotation)”匹配被調用的方法聲明的類上擁有MyAnnotation注解的情況。比如有一個ClassA上使用了注解MyAnnotation標注,并且定義了一個方法a(),那么在調用ClassA.a()方法時將匹配該Pointcut;如果有一個ClassB上沒有MyAnnotation注解,但是它繼承自ClassA,同時它上面定義了一個方法b(),那么在調用ClassB().b()方法時不會匹配該Pointcut,但是在調用ClassB().a()時將匹配該方法調用,因為a()是定義在父類型ClassA上的,且ClassA上使用了MyAnnotation注解。但是如果子類ClassB覆寫了父類ClassA的a()方法,則調用ClassB.a()方法時也不匹配該Pointcut。
@annotation用于匹配方法上擁有指定注解的情況。
- 1、“@annotation(com.elim.spring.support.MyAnnotation)”匹配所有的方法上擁有MyAnnotation注解的方法外部調用。
bean用于匹配當調用的是指定的Spring的某個bean的方法時。
- 1、“bean(abc)”匹配Spring Bean容器中id或name為abc的bean的方法調用。
- 2、“bean(user*)”匹配所有id或name為以user開頭的bean的方法調用。
3 表達式組合
表達式的組合其實就是對應的表達式的邏輯運算,與、或、非。可以通過它們把多個表達式組合在一起。
- 1、“bean(userService) && args()”匹配id或name為userService的bean的所有無參方法。
- 2、“bean(userService) || @annotation(MyAnnotation)”匹配id或name為userService的bean的方法調用,或者是方法上使用了MyAnnotation注解的方法調用。
- 3、“bean(userService) && !args()”匹配id或name為userService的bean的所有有參方法調用。
4 基于Aspectj注解的Pointcut表達式應用
在使用基于Aspectj注解的Spring Aop時,我們可以把通過@Pointcut注解定義Pointcut,指定其表達式,然后在需要使用Pointcut表達式的時候直接指定Pointcut。
@Component
@Aspect
public class MyAspect {@Pointcut("execution(* add(..))")private void beforeAdd() {}@Before("beforeAdd()")public void before() {System.out.println("-----------before-----------");}}
上面的代碼中我們就是在@Before()中直接指定使用當前類定義的beforeAdd()方法對應的Pointcut的表達式,如果我們需要指定的Pointcut定義不是在當前類中的,我們需要加上類名稱,如下面這個示例中引用的就是定義在MyService中的add()方法上的Pointcut的表達式。
@Before("com.elim.spring.aop.service.MyService.add()")
public void before2() {System.out.println("-----------before2-----------");
}
當然了,除了通過引用Pointcut定義間接的引用其對應的Pointcut表達式外,我們也可以直接使用Pointcut表達式的,如下面這個示例就直接在@Before中使用了Pointcut表達式。
/*** 所有的add方法的外部執行時*/
@Before("execution(* add())")
public void beforeExecution() {System.out.println("-------------before execution---------------");
}
@Pointcut切入點表達式例子:
// 任意公共方法的執行:
execution(public * *(..))// 任何一個名字以“set”開始的方法的執行:
execution(* set*(..))// AccountService接口定義的任意方法的執行:
execution(* com.xyz.service.AccountService.*(..))// 在service包中定義的任意方法的執行:
execution(* com.xyz.service.*.*(..))// 在service包或其子包中定義的任意方法的執行:
execution(* com.xyz.service..*.*(..))// 在service包中的任意連接點(在Spring AOP中只是方法執行):
within(com.xyz.service.*)// 在service包或其子包中的任意連接點(在Spring AOP中只是方法執行):
within(com.xyz.service..*)// 實現了AccountService接口的代理對象的任意連接點 (在Spring AOP中只是方法執行):
this(com.xyz.service.AccountService)// 'this'在綁定表單中更加常用// 實現AccountService接口的目標對象的任意連接點 (在Spring AOP中只是方法執行):
target(com.xyz.service.AccountService) // 'target'在綁定表單中更加常用// 任何一個只接受一個參數,并且運行時所傳入的參數是Serializable 接口的連接點(在Spring AOP中只是方法執行)
args(java.io.Serializable) // 'args'在綁定表單中更加常用; 請注意在例子中給出的切入點不同于 execution(* *(java.io.Serializable)): args版本只有在動態運行時候傳入參數是Serializable時才匹配,而execution版本在方法簽名中聲明只有一個 Serializable類型的參數時候匹配。// 目標對象中有一個 @Transactional 注解的任意連接點 (在Spring AOP中只是方法執行)
@target(org.springframework.transaction.annotation.Transactional)// '@target'在綁定表單中更加常用// 任何一個目標對象聲明的類型有一個 @Transactional 注解的連接點 (在Spring AOP中只是方法執行):
@within(org.springframework.transaction.annotation.Transactional) // '@within'在綁定表單中更加常用// 任何一個執行的方法有一個 @Transactional 注解的連接點 (在Spring AOP中只是方法執行)
@annotation(org.springframework.transaction.annotation.Transactional) // '@annotation'在綁定表單中更加常用// 任何一個只接受一個參數,并且運行時所傳入的參數類型具有@Classified 注解的連接點(在Spring AOP中只是方法執行)
@args(com.xyz.security.Classified) // '@args'在綁定表單中更加常用// 任何一個在名為'tradeService'的Spring bean之上的連接點 (在Spring AOP中只是方法執行)
bean(tradeService)// 任何一個在名字匹配通配符表達式'*Service'的Spring bean之上的連接點 (在Spring AOP中只是方法執行)
bean(*Service)
此外Spring 支持如下三個邏輯運算符來組合切入點表達式:
&&:要求連接點同時匹配兩個切入點表達式
||:要求連接點匹配任意個切入點表達式
!::要求連接點不匹配指定的切入點表達式
相關注解
相關問題:
Spring AOP 和 AspectJ 之間的關鍵區別?
總結來說就是?Spring AOP更易用,AspectJ更強大。
IOC/AOP模塊歸屬
在 Spring 框架中,IoC(控制反轉)和AOP(面向切面編程)?分屬不同的核心模塊,具體如下:
1.?IoC(控制反轉)
-
所屬模塊:
IoC 的核心實現位于?Spring Core Container?模塊,具體由以下子模塊支持:-
spring-core
:提供 IoC 的基礎設施(如?BeanFactory
)。 -
spring-beans
:實現 Bean 的定義、創建和管理。 -
spring-context
(上下文模塊):擴展?BeanFactory
,提供更高級的 IoC 容器(如?ApplicationContext
),支持國際化、事件傳播等。
-
-
關鍵功能:
-
通過?
BeanFactory
?或?ApplicationContext
?管理對象的生命周期和依賴注入。 -
依賴注入(DI)是 IoC 的核心實現方式,通過?
@Autowired
、@Resource
?等注解實現。
-
Maven 依賴示例:
xml
復制
<dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>5.3.22</version> </dependency> <dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId><version>5.3.22</version> </dependency> <dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.22</version> </dependency>
運行 HTML
2.?AOP(面向切面編程)
-
所屬模塊:
AOP 的核心實現位于?spring-aop
?模塊,同時依賴?spring-aspects
?模塊(集成 AspectJ)。 -
關鍵功能:
-
通過動態代理(JDK Proxy 或 CGLIB)實現方法攔截。
-
支持切面(Aspect)、通知(Advice)、切點(Pointcut)等核心概念。
-
與?
@AspectJ
?注解風格結合,簡化切面編程。
-
Maven 依賴示例:
xml
復制
<dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>5.3.22</version> </dependency> <!-- 集成 AspectJ --> <dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.3.22</version> </dependency>
運行 HTML
3.?模塊關系總結
功能 | 核心模塊 | 說明 |
---|---|---|
IoC | spring-core | 基礎容器與工具類 |
spring-beans | Bean 的定義與管理 | |
spring-context | 高級容器與應用上下文 | |
AOP | spring-aop | 動態代理與 AOP 核心實現 |
spring-aspects | 集成 AspectJ 注解式切面 |
4.?實際應用
-
IoC 示例:通過?
@Component
?定義 Bean,由?ApplicationContext
?管理依賴注入:java
復制
@Service public class UserService {@Autowiredprivate UserRepository userRepository; }
-
AOP 示例:通過?
@Aspect
?實現日志切面:java
復制
@Aspect @Component public class LogAspect {@Before("execution(* com.example.service.*.*(..))")public void logMethodCall(JoinPoint joinPoint) {System.out.println("方法調用:" + joinPoint.getSignature());} }
5.?常見問題
-
AOP 不生效:
-
確保啟用?
@EnableAspectJAutoProxy
(Spring Boot 中默認啟用)。 -
檢查切面類是否被 Spring 容器掃描到(如添加?
@Component
)。
-
-
依賴沖突:
-
若使用 CGLIB 代理,需確保項目中包含?
cglib
?依賴。
-
總結
-
IoC?的核心實現在?
spring-core
、spring-beans
?和?spring-context
。 -
AOP?的核心實現在?
spring-aop
,結合?spring-aspects
?實現高級切面功能。 -
二者共同構成 Spring 的基石,支撐聲明式事務、安全攔截等企業級功能。