日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Spring底層核心原理解析

作者:彭先生吖 更新時間: 2022-07-26 編程語言

正文

先來看看入門使用Spring的代碼:

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); 
UserService userService = (UserService) context.getBean("userService"); 
userService.test(); 

這三行代碼底層都做了什么,比如:

  1. 第一行代碼,會構造一個ClassPathXmlApplicationContext對象,ClassPathXmlApplicationContext該如何理解,調用該構造方法除開會實例化得到一個對象,還會做哪些事情?
  2. 第二行代碼,會調用ClassPathXmlApplicationContext的getBean方法,會得到一個UserService對象,getBean()是如何實現的?返回的UserService對象和我們自 己直接new的UserService對象有區別嗎?
  3. 第三行代碼,就是簡單的調用UserService的test()方法,不難理解。
    但是用ClassPathXmlApplicationContext其實已經過時了,在新版的Spring MVC和Spring Boot的底層主要用的都是AnnotationConfigApplicationContext,比如:
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); 
UserService userService = (UserService) context.getBean("userService"); 
userService.test();

可以看到AnnotationConfigApplicationContext的用法和ClassPathXmlApplicationContext是非常類似的,只不過需要傳入的是一個class,而不是
一個xml文件。而AppConfig.class和spring.xml一樣,表示Spring的配置,比如可以指定掃描路徑,可以直接定義Bean,比如:spring.xml中的內容為:

<context:component-scan base-package="com.service"/> 
<bean id="userService" class="com.service.UserService"/> 

AppConfig中的內容為:

@ComponentScan("com.service") 
public class AppConfig { 
    @Bean 
    public UserService userService(){ 
        return new UserService(); 
    } 
}

所以spring.xml和AppConfig.class本質上是一樣的。
目前,我們基本很少直接使用上面這種方式來用Spring,而是使用Spring MVC,或者Spring Boot,但是它們都是基于上面這種方式的,都需要在內部去創建一個 ApplicationContext的,只不過:

  1. Spring MVC創建的是XmlWebApplicationContext 和 ClassPathXmlApplicationContext類似,都是基于XML配置的
  2. Spring Boot創建的是AnnotationConfigApplicationContext
  3. AnnotationConfigApplicationContext和ClassPathXmlApplicationContext大部分底層都是共同的。

Spring中是如何創建一個對象?

其實不管是AnnotationConfigApplicationContext還是ClassPathXmlApplicationContext,目前,我們都可以簡單的將它們理解為就是用來創建Java對象的,比如調用getBean()就會去創建對象(此處不嚴謹,getBean可能也不會去創建對象,后續課程詳解)。在Java語言中,肯定是根據某個類來創建一個對象的。我們在看一下實例代碼:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); 
UserService userService = (UserService) context.getBean("userService"); 
userService.test(); 

當我們調用context.getBean(“userService”)時,就會去創建一個對象,但是getBean方法內部怎么知道"userService"對應的是UserService類呢?所以,我們就可以分析出來,在調用AnnotationConfigApplicationContext的構造方法時,也就是第一行代碼,會去做一些事情:

  1. 解析AppConfig.class,得到掃描路徑
  2. 遍歷掃描路徑下的所有Java類,如果發現某個類上存在@Component、@Service等注解,那么Spring就把這個類記錄下來,存在一個Map中,比如Map<String, Class>。(實際上,Spring源碼中確實存在類似的這么一個Map,叫做BeanDefinitionMap)
  3. Spring會根據某個規則生成當前類對應的beanName,作為key存入Map,當前類作為value這樣,但調用context.getBean(“userService”)時,就可以根據"userService"找到UserService類,從而就可以去創建對象了。

Bean的創建過程

那么Spring到底是如何來創建一個Bean的呢,這個就是Bean創建的生命周期,大致過程如下:

  1. 利用該類的構造方法來實例化得到一個對象(但是如何一個類中有多個構造方法,Spring則會進行選擇,這個叫做推斷構造方法)
  2. 得到一個對象后,Spring會判斷該對象中是否存在被@Autowired注解了的屬性,把這些屬性找出來并由Spring進行賦值(依賴注入)
  3. 依賴注入后,Spring會判斷該對象是否實現了BeanNameAware接口、BeanClassLoaderAware接口、BeanFactoryAware接口,如果實現了,就表示當前對象必須實現該接口中所定義的setBeanName()、setBeanClassLoader()、setBeanFactory()方法,那Spring就會調用這些方法并傳入相應的參數(Aware回調)
  4. Aware回調后,Spring會判斷該對象中是否存在某個方法被@PostConstruct注解了,如果存在,Spring會調用當前對象的此方法(初始化前)
  5. 緊接著,Spring會判斷該對象是否實現了InitializingBean接口,如果實現了,就表示當前對象必須實現該接口中的afterPropertiesSet()方法,那Spring就會調用當前對象中的afterPropertiesSet()方法(初始化)
  6. 最后,Spring會判斷當前對象需不需要進行AOP,如果不需要那么Bean就創建完了,如果需要進行AOP,則會進行動態代理并生成一個代理對象做為Bean(初始化后)

通過最后一步,我們可以發現,當Spring根據UserService類來創建一個Bean時:
1. 如果不用進行AOP,那么Bean就是UserService類的構造方法所得到的對象。
2. 如果需要進行AOP,那么Bean就是UserService的代理類所實例化得到的對象,而不是UserService本身所得到的對象。
Bean對象創建出來后:
3. 如果當前Bean是單例Bean,那么會把該Bean對象存入一個Map<String,Object>,Map的key為beanName,value為Bean對象。這樣下次getBean時就可以直接從Map中拿到對應的Bean對象了。(實際上,在Spring源碼中,這個Map就是單例池)
4. 如果當前Bean是原型Bean,那么后續沒有其他動作,不會存入一個Map,下次getBean時會再次執行上述創建過程,得到一個新的Bean對象。

推斷構造方法

Spring在基于某個類生成Bean的過程中,需要利用該類的構造方法來實例化得到一個對象,但是如果一個類存在多個構造方法,Spring會使用哪個呢? Spring的判斷邏輯如下:

  1. 如果一個類只存在一個構造方法,不管該構造方法是無參構造方法,還是有參構造方法,Spring都會用這個構造方法
  2. 如果一個類存在多個構造方法
    a. 這些構造方法中,存在一個無參的構造方法,那么Spring就會用這個無參的構造方法
    b. 這些構造方法中,不存在一個無參的構造方法,那么Spring就會報錯

Spring的設計思想是這樣的:

  1. 如果一個類只有一個構造方法,那么沒得選擇,只能用這個構造方法
  2. 如果一個類存在多個構造方法,Spring不知道如何選擇,就會看是否有無參的構造方法,因為無參構造方法本身表示了一種默認的意義3. 不過如果某個構造方法上加了@Autowired注解,那就表示程序員告訴Spring就用這個加了注解的方法,那Spring就會用這個加了@Autowired注解構造方法了需要重視的是,如果Spring選擇了一個有參的構造方法,Spring在調用這個有參構造方法時,需要傳入參數,那這個參數是怎么來的呢?Spring會根據入參的類型和入參的名字去Spring中找Bean對象(以單例Bean為例,Spring會從單例池那個Map中去找):
    1. 先根據入參類型找,如果只找到一個,那就直接用來作為入參
    2. 如果根據類型找到多個,則再根據入參名字來確定唯一一個
    3. 最終如果沒有找到,則會報錯,無法創建當前Bean對象確定用哪個構造方法,確定入參的Bean對象,這個過程就叫做推斷構造方法。

AOP大致流程

AOP就是進行動態代理,在創建一個Bean的過程中,Spring在最后一步會去判斷當前正在創建的這個Bean是不是需要進行AOP,如果需要則會進行動態代理。
如何判斷當前Bean對象需不需要進行AOP:

  1. 找出所有的切面Bean
  2. 遍歷切面中的每個方法,看是否寫了@Before、@After等注解
  3. 如果寫了,則判斷所對應的Pointcut是否和當前Bean對象的類是否匹配
  4. 如果匹配則表示當前Bean對象有匹配的的Pointcut,表示需要進行AOP利用cglib進行AOP的大致流程:
    1. 生成代理類UserServiceProxy,代理類繼承UserService
    2. 代理類中重寫了父類的方法,比如UserService中的test()方法
    3. 代理類中還會有一個target屬性,該屬性的值為被代理對象(也就是通過UserService類推斷構造方法實例化出來的對象,進行了依賴注入、初始化等步驟的對象)
    4. 代理類中的test()方法被執行時的邏輯如下:
      a. 執行切面邏輯(@Before)
      b. 調用target.test()
      當我們從Spring容器得到UserService的Bean對象時,拿到的就是UserServiceProxy所生成的對象,也就是代理對象。
      UserService代理對象.test()—>執行切面邏輯—>target.test(),注意target對象不是代理對象,而是被代理對象。

Spring事務

當我們在某個方法上加了@Transactional注解后,就表示該方法在調用時會開啟Spring事務,而這個方法所在的類所對應的Bean對象會是該類的代理對象。
Spring事務的代理對象執行某個方法時的步驟:

  1. 判斷當前執行的方法是否存在@Transactional注解
  2. 如果存在,則利用事務管理器(TransactionMananger)新建一個數據庫連接
  3. 修改數據庫連接的autocommit為false
  4. 執行target.test(),執行程序員所寫的業務邏輯代碼,也就是執行sql
  5. 執行完了之后如果沒有出現異常,則提交,否則回滾
    Spring事務是否會失效的判斷標準:某個加了@Transactional注解的方法被調用時,要判斷到底是不是直接被代理對象調用的,如果是則事務會生效,如果不是則失效。

原文鏈接:https://blog.csdn.net/yixuan_love/article/details/125919073

欄目分類
最近更新