Spring底層原理由淺入深探究
Spring簡介
ClassPathXmlApplicationContext context = new classPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.test();
上面一段代碼是我們開始學習spring時看到的,光看這三行代碼,其實并不能體現出來Spring的強大之處。
但其實ClassPathXmlApplicationContext早已經過時,新版的Spring MVC和SpringBoot的底層中主要用的都是AnnotationConfigApplication,例如:
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
//ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.test();
兩者寫法基本類似,區(qū)別在于前者需要傳入的是一個xml文件,后者傳入的是一個class。都可以指定掃描路徑,也可以定義Bean。例如:
spring.xml文件樣例:
<context:component-scan base-package="com.zhouyu"/> <bean id="userService" class="com.zhouyu.service.UserService"/>
AppConfig.java文件樣例:
@ComponentScan("com.zhouyu")
public class AppConfig {
@Bean
public UserService userService(){
return new UserService();
}
}
不過我們很少這樣使用Spring,而是使用Spring MVC 或者 SpringBoot,但是它們都是基于上面這種方式的,都需要在內部去創(chuàng)建一個ApplicationContext的,只不過:
- Spring MVC創(chuàng)建的是XmlWebApplicationContext,和ClassPathXmlApplicationContext類似,都是基于XML配置的
- Spring Boot創(chuàng)建的是AnnotationConfigApplicationContext
Spring中是如何創(chuàng)建一個對象
先看下面代碼
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) context.getBean("userService");
userService.test();
當我們調用context.getBean(“userService”)時,就會去創(chuàng)建一個對象,但是getBean方法內部怎么知道"userService"對應的是UserService類呢?
所以,我們就可以分析出來,在調用AnnotationConfigApplicationContext的構造方法時,也就是第一行代碼,會去做一些事情:
- 解析APPConfig.class,得到包掃描路徑
- 遍歷掃描路徑下的所有Java類,如果如果發(fā)現帶有@Component、@Service等注解時,Spring會把這個類記錄下來,存在一個Map中,比如Map<String,Class>。(實際上,Srping源碼中確實存在這么類似的Map,叫BeanDefinitionMap)
- Spring會根據某個規(guī)則生成當前類對應的beanName,作為key存入Map,并把當前作為Value存入
這樣,但調用context.getBean(“userService”)時,就可以根據"userService"找到UserService類,從而就可以去創(chuàng)建對象了。
Bean的創(chuàng)建過程
- 利用該類的構造方法來實例化得到一個對象
- 得到一個對象后,Spring 會判斷該對象中是否存在被@Autowire注解了的屬性,吧這些屬性找出來并由Spring進行賦值(依賴注入)。
- 依賴注入后,Spring會判斷該對象是否實現了BeanNameAware接口、BeanClassLoaderAware接口、BeanFactoryAware接口,如果實現了,就表示當前對象必須實現該接口中所定義的setBeanName()、setBeanClassLoader()、setBeanFactory()方法,那Spring就會調用這些方法并傳入相應的參數(Aware回調)
- Aware回調后,Spring會判斷該對象中是否存在某個方法被@PostConstruct注解了,如果存在,Spring會調用當前對象的此方法(初始化前)。
- 緊接著,Spring會判斷該對象是否實現了InitializingBean接口,如果實現了,就表示當前對象必須實現afterPropertiesSet()方法,那Spring就會調用當前對象中的afterPropertiesSet()方法(初始化)
- 最后Spring會判斷當前對象需不需進行AOP,如果不需要,那么Bean就創(chuàng)建完成了,如果需要進行AOP,則會進行動態(tài)代理并生成一個代理對象作為Bean(初始化后)。
通過最后一步,我們發(fā)現,當Spring根據UserService類來創(chuàng)建一個Bean時:
- 如果不用進行AOP,那么Bean就是UserService累的構造方法所得到的對象。
- 如果進行AOP,那么Bean就是UserService的代理類所實例化得到的對象,而不是UserService本身所得到的對象。
Bean創(chuàng)建出來后:
- 如果當前Bean是單例Bean,那么會把該Bean存入一個Map<String,Object>,Map的key為beanName,value為Bean對象。這樣下次getBean時就可以直接從Map中拿到Bean對象了。(實際上,在Spring源碼中,這個Map就是單例池)
- 如果當前Bean是原型Bean,那么后續(xù)沒有其他動作,不會存入一個Map,下次getBean時會再次執(zhí)行上述創(chuàng)建過程,得到一個新的Bean對象。
UserService.class —> 無參構造方法 —>普通對象—>依賴注入(屬性賦值、BeanNameAware接口、BeanClassLoaderAware接口、BeanFactoryAware接口)—>初始化前(postconstruct)—>初始化(initializingBean)—>初始化后(aop)—>代理對象—>Bean
推斷構造方法
Spring在基于某個類生成Bean的過程中,需要利用該類的構造方法來實例化一個對象,但是如果一個類存在多個構造方法,Spring會使用哪個呢?
Spring的判斷邏輯如下:
如果一個類只存在一個構造方法,不管該構造方法是無參構造方法還是有參構造方法,Spring都會使用這個構造方法。
如果一個類存在多個構造方法
- 這些構造方法中,存在一個無參的構造方法,那么Spring就會用這個無參的構造方法
- 這些構造方法中,如果不存在無參的構造方法,那么Spring就會報錯
Spring的設計思想是這樣的:
- 如果一個類只有一個構造方法,那么沒得選擇,只能用這個構造方法
- 如果一個類存在多個構造方法,Spring不知道如何選擇,就會看是否有無參的構造方法,因為無參構造方法本身表示了一種默認的意義
- 不過如果某個構造方法上加了@Autowired注解,那就表示程序員告訴Spring就用這個加了注解的方法,那Spring就會用這個加了@Autowired注解構造方法了
如果Spring選擇了一個有參的構造方法,Spring在調用這個有參構造方法時,需要傳入參數,那這個參數是怎么來的呢?
- 現根據入參類型找,如果只找到一個,那么久直接用來作為入參
- 如果根據類型找到多個,則再根據入參名字來確定唯一一個
- 最終如果沒有找到,則會報錯,無法創(chuàng)建Bean對象
確定用哪個構造方法,確定入參的Bean對象,這個過程就叫做推斷構造方法。
AOP大致流程
AOP就是動態(tài)代理,在創(chuàng)建一個Bean的過程中,Spring在最后一步會去判斷這個Bean是不是需要進行AOP,如果需要則會進行動態(tài)代理。
如何判斷當前Bean對象是否需要進行AOP:
- 找出所有的切面Bean
- 遍歷切面中的每個方法,看看是否寫了@before、@After等注解
- 如果寫了,則判斷所對應的pointcut是否和當前Bean對象的類是否匹配。
- 如果匹配則表示當前Bean對象有匹配的pointcut,表示需要進行AOP
利用cglib進行AOP的大致流程:
生成代理類UserServiceProxy,代理類繼承UserService
代理類中重寫了父類的方法,比如UserService的Test()方法
代理類中還會有一個target屬性,該屬性的值為被代理的對象
代理類中的test()方法被執(zhí)行時邏輯如下:
- 執(zhí)行切面邏輯(@Before)
- 調用target.test()
當我們從Spring容器得到UserService的Bean對象時,拿到的就是UserServiceProxy所生成的對象,也就是代理對象。
UserService代理對象.test()—>執(zhí)行切面邏輯—>target.test(),注意target對象不是代理對象,而是被代理對象。
Spring事務
當我們在某個方法上加了@Transactional注解后
Spring事務的代理對象執(zhí)行某個方法時的步驟:
- 判斷當前執(zhí)行的方法是否存在@Transactional注解
- 如果存在,則利用事務管理器(transactionManager)新建一個數據庫連接
- 修改數據庫連接的autocommit為false
- 執(zhí)行target.test(),執(zhí)行業(yè)務邏輯代碼,執(zhí)行SQL
- 執(zhí)行完成如果無異常,則提交,否則回滾。
Spring事務是否會失效的判斷標準:某個加了@Transaction注解的方法被調用時,要判斷到底是不是直接被代理的對象調用的,如果是則事務會生效,如果不是則失敗。
到此這篇關于Spring底層原理由淺入深探究的文章就介紹到這了,更多相關Spring底層原理內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
jdk8使用stream實現兩個list集合合并成一個(對象屬性的合并)
本文主要介紹了jdk8使用stream實現兩個list集合合并成一個(對象屬性的合并),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-01-01
Java使用JDBC向MySQL數據庫批次插入10W條數據(測試效率)
使用JDBC連接MySQL數據庫進行數據插入的時候,特別是大批量數據連續(xù)插入(100000),如何提高效率呢?今天小編通過本教程給大家介紹下2016-12-12
SpringBoot在自定義類中調用service層mapper層方式
這篇文章主要介紹了SpringBoot在自定義類中調用service層mapper層方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-03-03

