您好,登錄后才能下訂單哦!
本篇內容介紹了“spring的含義以及作用是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
1、JDK 動態代理和 CGLIB 代理有什么區別?
2、FactoryBean、BeanFactory、ApplicationContext 有什么區別?
3、說一說Spring Bean 的生命周期?
4、依賴注入的實現方法,以及相關注解?
5、什么是 Spring IOC ?
6、Spring IOC 容器的構建流程(初始化過程)
7、依賴注入的過程(Bean 的加載流程)?
8、Bean 的作用范圍?
9、Spring事務傳播機制有哪些?
10、Spring 的事務隔離級別有哪些?
11、AOP 是什么?AOP有哪些應用場景?
12、AOP 的相關注解有哪些?
13、AOP 的相關術語有什么?
14、總結
JDK 動態代理主要是針對類實現了某個接口,AOP 則會使用 JDK 動態代理。他基于反射的機制實現,生成一個實現同樣接口的一個代理類,然后通過重寫方法的方式,實現對代碼的增強。
而如果某個類沒有實現接口,AOP 則會使用 CGLIB 代理。他的底層原理是基于 ASM 第三方框架,通過修改字節碼生成一個子類,然后重寫父類的方法,實現對代碼的增強。
詳細分析參考:【Java萌新】面試常問設計模式——代理模式
BeanFactory:是一個 Bean 工廠,使用簡單工廠模式,是 Spring IoC 容器頂級接口,是用于管理 Bean 的工廠,最核心的功能是通過 getBean()
方法加載 Bean 對象,通常我們不會直接使用該接口,而是使用其子接口 ApplicationContext。
FactoryBean:是一個工廠 Bean,使用了工廠方法模式,實現該接口的類可以自己定義要創建的 Bean 實例,只需要實現它的 getObject()
方法即可。
ApplicationConext:是 BeanFactory 的子接口,擴展了 BeanFactory 的功能(高級 IOC 容器)。
Spring Bean 生命周期簡單概括為 5 個階段:
Bean 的實例化階段:創建一個 Bean 對象。
Bean 實例的屬性填充階段:為 Bean 實例的屬性賦值。
Bean 實例的初始化階段:對 Bean 實例進行初始化。
Bean 實例的正常使用階段。
Bean 實例的銷毀階段:容器關閉后,將 Bean 實例銷毀。
構造方法注入、Setter 方法注入、接口注入 三種。
依賴注入的相關注解
@Autowired:自動按類型注入,如果有多個匹配則按照指定 Bean 的 id 查找,查找不到會報錯。
@Qualifier:在自動按照類型注入的基礎上再按照 Bean 的 id 注入,給變量注入時必須搭配@Autowired,給方法注入時可單獨使用。
@Resource :直接按照 Bean 的 id 注入,只能注入 Bean 類型。
@Value :用于注入基本數據類型和 String 類型。
IOC 即控制反轉,簡單來說就是把原來代碼里需要實現的對象創建、依賴反轉給容器來幫忙實現,Spring 中管理對象及其依賴關系都是通過 Spring 的 IOC 容器實現的。
IOC 的實現方式有依賴注入和依賴查找,由于依賴查找使用的很少,因此 IOC 也叫做依賴注入。
我們之前在創建一個對象的時候都是直接 new
一個對象實例,而有了 IOC ,對象實例的創建都交給容器去實現即可。
我們以 XML 方式的容器初始化為例:
通過 ClassPathXmlApplicationContext
,去創建 ApplicationContext
容器對象:
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
ClassPathXmlApplicationContext
創建容器對象時,構造方法做了如下兩件事:
① 調用父容器的構造方法為容器先設置好 Bean 資源加載器。
② 調用父類的 setConfigLocations() 方法設置 Bean 配置信息的定位路徑
③ 調用父類 AbstractApplicationContext 的 refresh() 方法啟動整個 IOC 容器對 Bean 的載入,在創建 IOC 容器前如果已有容器存在,需要把已有的容器銷毀,保證在 refresh() 方法后使用的是新創建的 IOC 容器。
容器創建完成后,通過 loadBeanDefinitions()
方法加載 Bean 配置資源,該方法在加載資源時,首先解析配置文件路徑,讀取配置文件的內容,然后通過 XML 解析器將 Bean 的配置信息轉換成文檔對象,之后按照 Spring Bean 的定義規則將文檔對象解析為 BeanDefinition 對象。
接下來,將解析得到的 BeanDefinition 對象存入本地緩存(一個 HashMap 集合,key 是字符串,值是 BeanDefinition)中。
最后,實例化所有的 Bean 實例(非懶加載):包括實例的創建,實例的屬性填充,實例的初始化。
源碼分析可以參考我的文章:Spring源碼分析——Bean的加載
先來看下面幾行代碼:
public class BeanFactoryTest { public static void main(String[] args) { // 加載與解析XML配置文件,獲得BeanFactory: BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring-bf.xml")); // 從BeanFactory中加載Bean對象 Object a = beanFactory.getBean("componentA"); Object b = beanFactory.getBean("componentB"); System.out.println(a);// com.myspring.test.xmltest.ComponentA@1c93084c System.out.println(b);// com.myspring.test.xmltest.ComponentB@6ef888f6 } }
首先通過 BeanFactory/ApplicationContext 調用getBean()
方法,來獲取 Bean 實例,該方法中,真正獲取 Bean 實例的是其內層方法 doGetBean()
方法(真正實現從 IOC 容器獲取 Bean ,也是觸發依賴注入的地方)。
在 doGetBean()
方法中,主要做了以下幾件事:
① beanName 的轉換方法 transformedBeanName(name),
該方法的作用是,根據傳入的 name 參數,獲取真正的 Bean 對應的 beanName。該方法的 name 參數,有可能是一個別名(alias 屬性設置的別名),也有可能是一個&開頭的 name (工廠 Bean 對象)。
② 嘗試從緩存中加載 Bean 的單實例,根據上面transformedBeanName
方法轉換 name 后得到的真實 beanName,getSingleton(beanName)
方法直接嘗試從緩存中獲取 Bean 的共享單實例,這時候獲取的是初始狀態,尚未實例化。(從緩存中加載的流程就是,根據 beanName 依次從一級緩存、二級緩存、三級緩存中嘗試獲取,通過三級緩存機制也可以有效避免循環依賴)
③ Bean 的實例化,getSingleton(beanName)
方法執行后,從緩存中得到了 Bean 的原始狀態,接下來需要對該 Bean 進行實例化。
④ Bean 的初始化:尋找依賴(循環依賴檢查、依賴注入),因為 Bean 的初始化過程中很可能會用到某些屬性,而某些屬性很可能是動態配置的,并且配置的成依賴于其他的 Bean,那么此時應該先加載依賴的 Bean。所以在流程中,Spring初始化一個 Bean,會先初始化其依賴的所有的其他 Bean。
⑤ 根據不同的 scope 作用域創建 Bean,調用doCreateBean()
方法創建 Bean。
⑥ 類型轉換,根據 scope 創建完 Bean 成功后,一般可以直接返回即可。但當傳入 doGetBean
方法中的 requireType
參數不為空時,意味著我們對最后返回的 Bean 有著類型上的要求。Spring 通過 類型轉換器 將第 ⑤ 步創建完成的 Bean 轉換為 requireType
指定的類型。
通過 scope 屬性指定 Bean 的作用范圍,包括:
① singleton
:單例模式,是默認作用域,不管收到多少 Bean 請求每個容器中只有一個唯一的 Bean 實例。
② prototype
:原型模式,和 singleton 相反,每次 Bean 請求都會創建一個新的實例。
③ request
:每次 HTTP 請求都會創建一個新的 Bean 并把它放到 request 域中,在請求完成后 Bean 會失效并被垃圾收集器回收。
④ session
:和 request 類似,確保每個 session 中有一個 Bean 實例,session 過期后 bean 會隨之失效。
⑤ global session
:當應用部署在 Portlet 容器時,如果想讓所有 Portlet 共用全局存儲變量,那么該變量需要存儲在 global session 中。
① REQUIRED:Spring 默認的事務傳播級別,如果上下文中已經存在事務,那么就加入到事務中執行,如果當前上下文中不存在事務,則新建事務執行。
② REQUIRES_NEW:每次都會新建一個事務,如果上下文中有事務,則將上下文的事務掛起,當新建事務執行完成以后,上下文事務再恢復執行。
③ SUPPORTS:如果上下文存在事務,則加入到事務執行,如果沒有事務,則使用非事務的方式執行。
④ MANDATORY:上下文中必須要存在事務,否則就會拋出異常。
⑤ NOT_SUPPORTED :如果上下文中存在事務,則掛起事務,執行當前邏輯,結束后恢復上下文的事務。
⑥ NEVER:上下文中不能存在事務,否則就會拋出異常。
⑦ ESTED:嵌套事務。如果上下文中存在事務,則嵌套事務執行,如果不存在事務,則新建事務。
Spring 的事務隔離級別底層其實是基于數據庫的,Spring 并沒有自己的一套隔離級別。
DEFAULT:使用數據庫的默認隔離級別。
READ_UNCOMMITTED:讀未提交,最低的隔離級別,會讀取到其他事務還未提交的內容,存在臟讀。
READ_COMMITTED:讀已提交,讀取到的內容都是已經提交的,可以解決臟讀,但是存在不可重復讀。
REPEATABLE_READ:可重復讀,在一個事務中多次讀取時看到相同的內容,可以解決不可重復讀,但是存在幻讀。
SERIALIZABLE:串行化,最高的隔離級別,對于同一行記錄,寫會加寫鎖,讀會加讀鎖。在這種情況下,只有讀讀能并發執行,其他并行的讀寫、寫讀、寫寫操作都是沖突的,需要串行執行。可以防止臟讀、不可重復度、幻讀,沒有并發事務問題。
AOP 概念: 即面向切面編程,使用動態代理技術,在不修改源碼的基礎上對目標方法進行增強。
Spring 中的 AOP 目前支持 JDK 動態代理和 Cglib 代理。如果被代理對象實現了接口,則使用 JDK 動態代理,否則使用 Cglib 代理。另外,也可以通過指定 proxyTargetClass=true
來實現強制走 Cglib 代理。
應用場景:
權限認證
日志打印
事務
…
@Aspect
:切面,聲明被注解標注的類是一個切面 Bean。
@Aspect @Component public class LogAspect { ... }
@Pointcut
:切入點,可以通過 @Pointcut("execution(* top.csp1999.service.impl.*.*(..))")
去指定要切入的目標對象,并對其符合表達式要求的方法進行增強。
@Pointcut("execution(* top.csp1999.service.impl.*.*(..))") public void operationLog(){}
@Before
:前置通知,指在某個連接點之前執行的通知。
@Before("operationLog()") public void doBeforeAdvice(JoinPoint joinPoint){ System.out.println("進入方法前執行....."); }
@After
:后置通知,指某個連接點退出時執行的通知(不論正常返回還是異常退出)。
@After("operationLog()") public void after(JoinPoint jp){ System.out.println("方法最后執行....."); }
@AfterReturning
:后置返回通知,指某連接點正常完成之后執行的通知,返回值可以在返回后通知方法里接收。
@AfterReturning(returning = "ret", pointcut = "operationLog()") public void doAfterReturning(Object ret) { System.out.println("方法的返回值 : " + ret); }
@AfterThrowing
:后置異常通知,指方法拋出異常導致退出時執行的通知,和@AfterReturning只會有一個執行,異常使用 throwing 屬性接收。
@AfterThrowing(throwing = "jp", pointcut = "operationLog()") public void throwss(JoinPoint jp){ System.out.println("方法異常時執行....."); }
@Around
:環繞通知,可以用來在調用一個具體方法前和調用后來完成一些具體的任務。
@Around("operationLog()") public Object run2(ProceedingJoinPoint joinPoint) throws Throwable { // 獲取方法參數值數組 Object[] args = joinPoint.getArgs(); // 得到其方法簽名 MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); // 獲取方法參數類型數組 Class[] paramTypeArray = methodSignature.getParameterTypes(); if (EntityManager.class.isAssignableFrom(paramTypeArray[paramTypeArray.length - 1])) { // 如果方法的參數列表最后一個參數是entityManager類型,則給其賦值 args[args.length - 1] = entityManager; } logger.info("請求參數為{}",args); // 動態修改其參數 // 注意,如果調用joinPoint.proceed()方法,則修改的參數值不會生效,必須調用joinPoint.proceed(Object[] args) Object result = joinPoint.proceed(args); logger.info("響應結果為{}",result); // 如果這里不返回result,則目標對象實際返回值會被置為null return result; }
Aspect
:切面,一個關注點的模塊化,這個關注點可能會橫切多個對象。
Joinpoint
:連接點,程序執行過程中的某一行為,即業務層中的所有方法。。
Advice
:通知,指切面對于某個連接點所產生的動作,包括前置通知、后置通知、返回后通知、異常通知和環繞通知。
Pointcut
:切入點,指被攔截的連接點,切入點一定是連接點,但連接點不一定是切入點。
Proxy
:代理,Spring AOP 中有 JDK 動態代理和 CGLib 代理,目標對象實現了接口時采用 JDK 動態代理,反之采用 CGLib 代理。
Target
:代理的目標對象,指一個或多個切面所通知的對象。
Weaving
:織入,指把增強應用到目標對象來創建代理對象的過程。
“spring的含義以及作用是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。