目錄
Spring
Spring是什么?
Spring中重要的模塊
Spring中最重要的就是IOC(控制反轉)和AOP(面向切面編程)
什么是IOC
DI和IOC之間的區別
為什么要使用IOC呢?
IOC的實現機制
什么是AOP
Aop的核心概念
AOP的環繞方式
AOP發生的時期
AOP和OOP的關系
SpringIOC和AOP的核心設計模式?
Spring中的常用注解
Spring用了哪些設計模式?
Spring如何實現的單例模式?
?Spring中的單例bean是線程安全的嗎
如何解決單例bean線程不安全的問題呢
Spring容器和Web容器的區別
BeanFactory和ApplicationContext的區別
項目啟動時Spring的IOC會做什么
Spring的Bean實例化的方法
你是怎么理解bean的
Spring中Bean的配置方式
@Component和@Bean的區別
Bean的生命周期
Aware類型的接口作用
init-method和destroy-method的時機
@Autowired和@Resource?的區別
為什么不推薦使用@Autowired
什么是自動裝配
Bean的作用域
循環依賴
Spring能解決哪些情況的循環問題呢
如何解決呢?循環依賴問題
為什么三級,二級不可以嗎?
JDK動態代理和Cglib動態代理
選擇JDK動態代理和Cglib代理
Spring
Spring是什么?
Spring 是一個 Java 后端開發框架,其最核心的作用是幫我們管理 Java 對象。
Spring中重要的模塊
- Spring Core:?基礎,可以說 Spring 其他所有的功能都需要依賴于該類庫。主要提供 IoC 依賴注入功能。
- Spring Aspects?: 該模塊為與AspectJ的集成提供支持。
- Spring AOP?:提供了面向切面的編程實現。
- Spring JDBC?: Java數據庫連接。
- Spring JMS?:Java消息服務。
- Spring ORM?: 用于支持Hibernate等ORM工具。
- Spring Web?: 為創建Web應用程序提供支持。
- Spring Test?: 提供了對 JUnit 和 TestNG 測試的支持。
Spring中最重要的就是IOC(控制反轉)和AOP(面向切面編程)
什么是IOC
IoC 控制反轉是一種設計思想,它的主要作用是將對象的創建和對象之間的調用過程交給 Spring 容器來管理。
DI和IOC之間的區別
IoC 的思想是把對象創建和依賴關系的控制權由業務代碼轉移給 Spring 容器。這是一個比較抽象的概念,告訴我們應該怎么去設計系統架構。
而 DI,也就是依賴注入,它是實現 IoC 這種思想的具體技術手段。在 Spring 里,我們用?@Autowired
?注解就是在使用 DI 的字段注入方式。
為什么要使用IOC呢?
在日常開發中,我們可能需要多個對象來協助完成,這就要在自己new一個,A要使用B,A就對B產生了依賴,也就產生了一種耦合關系,有了Spring,那么創建工作就交給Spring,這種耦合度就降低了。
IOC的實現機制
第一步,掃描整個包,找到所有配置了@Component
、@Service
、@Repository
?這些注解的類,然后把這些類的元信息封裝成 BeanDefinition 對象。
第二步?Bean 工廠的準備。Spring 會創建一個 DefaultListableBeanFactory 作為 Bean 工廠來負責 Bean 的創建和管理。
第三步是 Bean 的實例化和初始化。這個過程比較復雜,Spring 會根據 BeanDefinition 來創建 Bean 實例。
對于單例 Bean,Spring 會先檢查緩存中是否已經存在,如果不存在就創建新實例。創建實例的時候會通過反射調用構造方法,然后進行屬性注入,最后執行初始化回調方法。
依賴注入的實現主要是通過反射來完成的。比如我們用?@Autowired
?標注了一個字段,Spring 在創建 Bean 的時候會掃描這個字段,然后從容器中找到對應類型的 Bean,通過反射的方式設置到這個字段上。
什么是AOP
AOP 面向切面編程,簡單點說就是把一些通用的功能從業務代碼里抽取出來,統一處理。
Aop的核心概念
- 切面(Aspect):類是對物體特征的抽象,切面就是對橫切關注點的抽象
- 連接點(Join Point):被攔截到的點,因為 Spring 只支持方法類型的連接點,所以在 Spring 中,連接點指的是被攔截到的方法,實際上連接點還可以是字段或者構造方法
- 切點(Pointcut):對連接點進行攔截的定位
- 通知(Advice):指攔截到連接點之后要執行的代碼,也可以稱作增強
- 目標對象?(Target):代理的目標對象
- 引介(introduction):一種特殊的增強,可以動態地為類添加一些屬性和方法
- 織入(Weabing):織入是將增強添加到目標類的具體連接點上的過程。
AOP的環繞方式
AOP 一般有?5 種環繞方式:
- 前置通知 (@Before)
- 返回通知 (@AfterReturning)
- 異常通知 (@AfterThrowing)
- 后置通知 (@After)
- 環繞通知 (@Around)
AOP發生的時期
運行時發生,這意味著 Spring AOP 是在運行時通過動態代理生成的,而不是在編譯時或類加載時生成的。
AOP和OOP的關系
AOP 和 OOP 是互補的編程思想:
- OOP 通過類和對象封裝數據和行為,專注于核心業務邏輯。
- AOP 提供了解決橫切關注點(如日志、權限、事務等)的機制,將這些邏輯集中管理。
SpringIOC和AOP的核心設計模式?
IOC是工廠模式
AOP是代理模式
Spring中的常用注解
首先是 Bean 管理相關的注解。@Component
?是最基礎的,用來標識一個類是 Spring 組件。像?@Service
、@Repository
、@Controller
?這些都是?@Component
?的特化版本,分別用在服務層、數據訪問層和控制器層。
依賴注入方面,@Autowired
?是用得最多的,可以標注在字段、setter 方法或者構造方法上。@Qualifier
?在有多個同類型 Bean 的時候用來指定具體注入哪一個。@Resource
?和?@Autowired
?功能差不多,不過它是按名稱注入的。
配置相關的注解也很常用。@Configuration
?標識配置類,@Bean
?用來定義 Bean,@Value
?用來注入配置文件中的屬性值。@PropertySource
?用來指定配置文件的位置。
@RestController
?相當于?@Controller
?加?@ResponseBody
,用來做 RESTful 接口。
@RequestMapping
?及其變體@GetMapping
、@PostMapping
、@PutMapping
、@DeleteMapping
?用來映射 HTTP 請求。@PathVariable
?獲取路徑參數,@RequestParam
?獲取請求參數,@RequestBody
?接收 JSON 數據。
AOP 相關的注解,@Aspect
?定義切面,@Pointcut
?定義切點,@Before
、@After
、@Around
?這些定義通知類型。
@Transactional
,保證事務。
生命周期相關的,@PostConstruct
?在 Bean 初始化后執行,@PreDestroy
?在 Bean 銷毀前執行。測試的時候?@SpringBootTest
?也經常用到。
@SpringBootApplication
?這個啟動類注解,@ConditionalOnProperty
?做條件裝配,@EnableAutoConfiguration
?開啟自動配置等等。
Spring用了哪些設計模式?
首先是工廠模式,這個在 Spring 里用得非常多。BeanFactory 就是一個典型的工廠,它負責創建和管理所有的 Bean 對象。我們平時用的 ApplicationContext 其實也是 BeanFactory 的一個實現。當我們通過?@Autowired
?獲取一個 Bean 的時候,底層就是通過工廠模式來創建和獲取對象的。?
?單例模式也是 Spring 的默認行為。默認情況下,Spring 容器中的 Bean 都是單例的
可以通過?@Scope
?注解來改變 Bean 的作用域,比如設置為 prototype 就是每次獲取都創建新實例。
代理模式在 AOP 中用得特別多。Spring AOP 的底層實現就是基于動態代理的,對于實現了接口的類用 JDK 動態代理,沒有實現接口的類用 CGLIB 代理。
模板方法模式在 Spring 里也很常見,比如 JdbcTemplate。它定義了數據庫操作的基本流程:獲取連接、執行 SQL、處理結果、關閉連接,但是具體的 SQL 語句和結果處理邏輯由我們來實現。
觀察者模式在 Spring 的事件機制中有所體現。我們可以通過 ApplicationEvent 和 ApplicationListener 來實現事件的發布和監聽。
Spring如何實現的單例模式?
傳統的單例模式是在類的內部控制只能創建一個實例,比如用 private 構造方法加?static getInstance()
?這種方式。但是 Spring 的單例是容器級別的,同一個 Bean 在整個 Spring 容器中只會有一個實例。
具體的實現機制是這樣的:Spring 在啟動的時候會把所有的 Bean 定義信息加載進來,然后在 DefaultSingletonBeanRegistry 這個類里面維護了一個叫 singletonObjects 的 ConcurrentHashMap,這個 Map 就是用來存儲單例 Bean 的。key 是 Bean 的名稱,value 就是 Bean 的實例對象。
當我們第一次獲取某個 Bean 的時候,Spring 會先檢查 singletonObjects 這個 Map 里面有沒有這個 Bean,如果沒有就會創建一個新的實例,然后放到 Map 里面。后面再獲取同一個 Bean 的時候,直接從 Map 里面取就行了,這樣就保證了單例。
?
還有一個細節就是 Spring 為了解決循環依賴的問題,還用了三級緩存。除了 singletonObjects 這個一級緩存,還有 earlySingletonObjects 二級緩存和 singletonFactories 三級緩存。這樣即使有循環依賴,Spring 也能正確處理。
?Spring中的單例bean是線程安全的嗎
分兩個層面
Spring管理bean是線程安全的。Spring 在容器啟動階段
使用 ConcurrentHashMap
(一級緩存 singletonObjects
)來存放并查找已經創建好的單例實例,確實保證了“多個線程同時去獲取同一個 Bean 時不會重復創建實例”。
單例 Bean 的業務代碼
容器把同一個實例注入到所有需要的地方后,如果該實例內部有可變的共享狀態(字段、緩存、集合等),Spring 不會幫你加任何鎖。并發讀寫這些字段時,線程安全問題照樣會出現。
如何解決單例bean線程不安全的問題呢
第一種,使用局部變量,也就是使用無狀態的單例 Bean
第二種,當確實需要維護線程相關的狀態時,可以使用ThreadLcoal?來保存狀態。
第三種,如果需要緩存數據或者計數,使用 JUC 包下的線程安全類,比如說?AtomicInteger、CCHashMap?等。
第四種,對于復雜的狀態操作,可以使用 synchronized 或 Lock
第五種,如果 Bean 確實需要維護狀態,可以考慮將其改為 prototype 作用域
Spring容器和Web容器的區別
Spring 容器是一個 IoC 容器,主要負責管理 Java 對象的生命周期和依賴關系。而 Web 容器,比如 Tomcat、Jetty 這些,是用來運行 Web 應用的容器,負責處理 HTTP 請求和響應,管理 Servlet 的生命周期。
從功能上看,Spring 容器專注于業務邏輯層面的對象管理,比如我們的 Service、Dao、Controller 這些 Bean 都是由 Spring 容器來創建和管理的。而 Web 容器主要處理網絡通信,比如接收 HTTP 請求、解析請求參數、調用相應的 Servlet,然后把響應返回給客戶端。
生命周期上:Web 容器的生命周期跟 Web 應用程序的部署和卸載相關,而 Spring 容器的生命周期是在 Web 應用啟動的時候初始化,應用關閉的時候銷毀。
BeanFactory和ApplicationContext的區別
BeanFactory 算是 Spring 的“心臟”,而 ApplicantContext 可以說是 Spring 的完整“身軀”。
BeanFactory 提供了最基本的 IoC 能力。它就像是一個 Bean 工廠,負責 Bean 的創建和管理。他采用的是懶加載的方式,也就是說只有當我們真正去獲取某個 Bean 的時候,它才會去創建這個 Bean。
它最主要的方法就是?getBean()
,負責從容器中返回特定名稱或者類型的 Bean 實例。
ApplicationContext 是 BeanFactory 的子接口,在 BeanFactory 的基礎上擴展了很多企業級的功能。它不僅包含了 BeanFactory 的所有功能,還提供了國際化支持、事件發布機制、AOP、JDBC、ORM 框架集成等等。
ApplicationContext 采用的是餓加載的方式,容器啟動的時候就會把所有的單例 Bean 都創建好,雖然這樣會導致啟動時間長一點,但運行時性能更好。
生命周期管理。ApplicationContext 會自動調用 Bean 的初始化和銷毀方法,而 BeanFactory 需要我們手動管理。
項目啟動時Spring的IOC會做什么
首先會進行掃描和注冊bean。Ioc會根據我們的配置以及注解,然后把這些帶注解的類的信息包裝成BeanDefinition對象,注冊到BeanDefinitionRegistry 中。但還么有創建。
第二件事是 Bean 的實例化和注入。這是最核心的過程,IoC 容器會按照依賴關系的順序開始創建 Bean 實例。對于單例 Bean,容器會通過反射調用構造方法創建實例,然后進行屬性注入,最后執行初始化回調方法。
在依賴注入時,容器會根據?@Autowired
、@Resource
?這些注解,把相應的依賴對象注入到目標 Bean 中。
Spring的Bean實例化的方法
第一種是通過構造方法實例化
第二種是通過靜態工廠方法實例化
第三種是通過實例工廠方法實例化。這種方式是先創建工廠對象,然后通過工廠對象的方法來創建Bean
第四種是通過 FactoryBean 接口實例化。
你是怎么理解bean的
Bean 本質上就是由 Spring 容器管理的 Java 對象
Spring中Bean的配置方式
第一種:根據XML配置(已經不怎么用了)
第二種:根據java配置類方式
第三種:基于注解的方式
@Component和@Bean的區別
@Component是標注在類上的,而?@Bean
?是標注在方法上的。@Component
?告訴 Spring 這個類是一個組件,請把它注冊為 Bean,而?@Bean
?則告訴 Spring 請將這個方法返回的對象注冊為 Bean。
Bean的生命周期
第一個階段是實例化。Spring 容器會根據 BeanDefinition,通過反射調用 Bean 的構造方法創建對象實例。
第二階段是屬性賦值。這個階段 Spring 會給 Bean 的屬性賦值
第三階段是初始化。
第四階段是使用 Bean。
最后是銷毀階段。當容器關閉或者 Bean 被移除的時候
Aware類型的接口作用
它們的作用是讓 Bean 能夠感知到 Spring 容器的一些內部組件。比如ApplicationContextAware,它可以讓 Bean 獲取到 ApplicationContext 容器本身。
init-method和destroy-method的時機
init-method 指定的初始化方法會在 Bean 的初始化階段被調用,具體的執行順序是:
- 先執行?
@PostConstruct
?標注的方法 - 然后執行 InitializingBean 接口的?
afterPropertiesSet()
?方法 - 最后再執行 init-method 指定的方法
destroy-method 會在 Bean 銷毀階段被調用。
@Autowired和@Resource
?的區別
@Autowired
?是 Spring 框架提供的注解,而?@Resource
?是 Java EE 標準提供的注解。換句話說,@Resource
?是 JDK 自帶的,而?@Autowired
?是 Spring 特有的。
@Autowired
?默認按照類型,也就是 byType 進行注入,而?@Resource
?默認按照名稱,也就是 byName 進行注入。
容器中存在多個相同類型的 Bean, 比如說有兩個 UserRepository 的實現類,直接用?@Autowired
?注入 UserRepository 時就會報錯,因為 Spring 容器不知道該注入哪個實現類。
為什么不推薦使用@Autowired
第一個是字段注入不利于單元測試。字段注入需要使用反射或 Spring 容器才能注入依賴,測試更復雜;而構造方法注入可以直接通過構造方法傳入 Mock 對象,測試起來更簡單。
第二個是字段注入會隱藏循環依賴問題,而構造方法注入會在項目啟動時就去檢查依賴關系,能更早發現問題。
第三個是構造方法注入可以使用 final 字段確保依賴在對象創建時就被初始化,避免了后續修改的風險。
什么是自動裝配
自動裝配的本質就是讓 Spring 容器自動幫我們完成 Bean 之間的依賴關系注入,而不需要我們手動去指定每個依賴。
自動裝配的工作原理簡單來說就是,Spring 容器在啟動時自動掃描?@ComponentScan
?指定包路徑下的所有類,然后根據類上的注解,比如?@Autowired
、@Resource
?等,來判斷哪些 Bean 需要被自動裝配。之后分析每個 Bean 的依賴關系,在創建 Bean 的時候,根據裝配規則自動找到合適的依賴 Bean,最后根據反射將這些依賴注入到目標 Bean 中。
Bean的作用域
singleton 是默認的作用域。整個 Spring 容器中只會有一個 Bean 實例。
prototype每次從容器中獲取 Bean 的時候都會創建一個新的實例。
如果作用于是 request,表示在 Web 應用中,每個 HTTP 請求都會創建一個新的 Bean 實例,請求結束后 Bean 就被銷毀。
如果作用于是 session,表示在 Web 應用中,每個 HTTP 會話都會創建一個新的 Bean 實例,會話結束后 Bean 被銷毀。?
循環依賴
AB 循環依賴,A 實例化的時候,發現依賴 B,創建 B 實例,創建 B 的時候發現需要 A,創建 A1 實例……無限套娃。。。。
Spring能解決哪些情況的循環問題呢
- AB 均采用構造器注入,不支持
- AB 均采用 setter 注入,支持
- AB 均采用屬性自動注入,支持
- A 中注入的 B 為 setter 注入,B 中注入的 A 為構造器注入,支持
- B 中注入的 A 為 setter 注入,A 中注入的 B 為構造器注入,不支持
第四種可以,第五種不可以的原因是 Spring 在創建 Bean 時默認會根據自然排序進行創建,所以 A 會先于 B 進行創建。
簡單總結下,當循環依賴的實例都采用 setter 方法注入時,Spring 支持,都采用構造器注入的時候,不支持;構造器注入和 setter 注入同時存在的時候,不確定。
如何解決呢?循環依賴問題
Spring 通過三級緩存機制來解決循環依賴:
- 一級緩存:存放完全初始化好的單例 Bean。
- 二級緩存:存放正在創建但未完全初始化的 Bean 實例。
- 三級緩存:存放 Bean 工廠對象,用于提前暴露 Bean。
為什么三級,二級不可以嗎?
不可以! 因為需要生成代理對象,如果沒有代理,二級也是可以的。
JDK動態代理和Cglib動態代理
①、JDK 動態代理是基于接口的代理,只能代理實現了接口的類。
使用 JDK 動態代理時,Spring AOP 會創建一個代理對象,該代理對象實現了目標對象所實現的接口,并在方法調用前后插入橫切邏輯。優點:只需依賴 JDK 自帶的?java.lang.reflect.Proxy
?類,不需要額外的庫;缺點:只能代理接口,不能代理類本身。
②、CGLIB 動態代理是基于繼承的代理,可以代理沒有實現接口的類。
使用 CGLIB 動態代理時,Spring AOP 會生成目標類的子類,并在方法調用前后插入橫切邏輯。
優點:可以代理沒有實現接口的類,靈活性更高;缺點:需要依賴 CGLIB 庫,創建代理對象的開銷相對較大。
選擇JDK動態代理和Cglib代理
- 如果目標對象沒有實現任何接口,則只能使用 CGLIB 代理。如果目標對象實現了接口,通常首選 JDK 動態代理。
- 雖然 CGLIB 在代理類的生成過程中可能消耗更多資源,但在運行時具有較高的性能。對于性能敏感且代理對象創建頻率不高的場景,可以考慮使用 CGLIB。
- JDK 動態代理是 Java 原生支持的,不需要額外引入庫。而 CGLIB 需要將 CGLIB 庫作為依賴加入項目中。