Spring 核心思想筆記

本文對Spring相關知識點作了概括整理,包括 Spring 優點、其框架結構、核心思想,並對IoC思想及AOP思想進行手動實現,加強對Spring 核心思想的理解。以後對Spring IoC、AOP 的實現方式和特性進行介紹,並對照源碼理解其實現思路。java

Spring 優點

  • 方便解耦,簡化開發web

    [注:IoC(下降組件耦合性)、DI(下降業務對象替換的複雜性)]spring

  • AOP編程的思想數據庫

    [注:通用任務集中管理,使得代碼可更好的複用]編程

  • 聲明式事務的支持bootstrap

    [注:@Transaction]緩存

  • 輕量級框架,代碼侵入性低,可自由選擇框架部分組件服務器

  • 方便集成各類優秀框架數據結構

Spring 核心結構

  • Spring核心容器(Core Container):容器是Spring框架最核心的部分,管理Bean的建立(IoC)、配置(DI)和管理(Context),全部的Spring模塊都構建於核心容器之上.
  • 面向切面編程(AOP):Spring應用系統中開發切面的基礎,代碼解耦,加強複用。
  • 數據訪問與集成(Data Access):Spring的JDBC和DAO模塊封裝大量樣板代碼,使得數據庫代碼變得簡潔,也可避免數據庫資源釋放失敗引起的問題。由JDBC、Transactions(AOP模塊支持)、ORM、OXM 和 JMS等模塊組成。
  • Web:提供了SpringMVC框架給Web應⽤,還提供了多種構建和其它應⽤交互的遠程調⽤⽅ 案。 SpringMVC框架在Web層提高了應⽤的鬆耦合⽔平。
  • Test 爲了使得開發者可以很⽅便的進⾏測試,Spring提供了測試模塊以至⼒於Spring應⽤的測試。 經過該模塊,Spring爲使⽤Servlet、JNDI等編寫單元測試提供了⼀系列的mock對象實現。

核心思想

IoC(Inversion of Control)控制反轉

IoC 是一個技術思想,而Spring對其進行了實現併發

控制反轉:對象建立(實例化、管理)的權利交給外部環境(Spring框架、IoC容器)

解決問題:解決對象間的耦合問題

IoC 和 DI 的區別

DI(Dependancy Injection)依賴注⼊

IoC和DI描述的是同一件事情(對象實例化及依賴關係維護),只不過角度不同罷了

IoC:站在對象的角度,對象實例化及其管理交給容器

DI:站在容器角度,容器會把對象依賴的其餘對象注入

AOP(Aspect oriented Programming)面向切面編程

OOP與AOP

OOP是一種垂直繼承體系(三大特徵:封裝、繼承和多態)

AOP爲解決縱向重複問題,抽取橫向邏輯代碼,分離業務邏輯和橫切邏輯,解耦合

AOP解決問題

解決縱向重複問題,抽取橫向邏輯代碼,分離業務邏輯和橫切邏輯,解耦合

IoC和AOP的手動實現

IoC與DI實現

  1. 定義beans.xml,其中定義:
    • 須要建立的對象的id和全限定類名 ⇒ 反射建立對象,id爲標識
    • 屬性名及依賴對象id ⇒ 經過對應類中的 Set方法 注入依賴屬性
  2. 定義BeanFactory,讀取解析xml,經過反射實例化對象,統一管理,並提供外部獲取對象的接口方法

AOP 事務管理

  1. 將 Connection 與當前線程綁定,手動控制 JDBC 的 Connection 事務(關閉自動提交事務)
  2. 建立代理工廠類,對代理類進行事務加強
  3. 使用代理類對象替代對象進行方法的調用

Spring IoC 應用

IoC 實現方式及其對應啓動方法

  1. 純 xml 實現(bean 信息定義所有配置在 xml 中)

    // JavaSE 應用啓動
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
    // 或者讀取絕對路徑下的配置文件(不推薦)
    ApplicationContext applicationContext = new FileSystemXmlApplicationContext("c:/beans.xml");
    
    // JavaWeb 應用
    /** 配置 ContextLoaderListener 類(web.xml) 監聽器機制加載 xml */
    複製代碼
    <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" >
    <web-app>
        <display-name>Archetype Created Web Application</display-name>
        <!--配置Spring ioc容器的配置⽂件-->
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </context-param>
        <!--使⽤監聽器啓動Spring的IOC容器-->
        <listener>
            <listenerclass>org.springframework.web.context.ContextLoaderListener</listenerclass>
        </listener>
    </web-app>
    複製代碼
  2. xml + 註解(部分 bean 使用 xml 定義,部分 bean 使用註解定義)

    和純 xml 實現一致

  3. 純註解 (全部 bean 都是用註解來定義)

    // JavaSE 應用啓動
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring.class);
    
    // JavaWeb 應用
    /** 配置 ContextLoaderListener 類(web.xml) 監聽器機制加載 註解配置類 */
    複製代碼
    <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" >
    <web-app>
        <display-name>Archetype Created Web Application</display-name>
        <!--告訴ContextloaderListener知道咱們使⽤註解的⽅式啓動ioc容器-->
        <context-param>
            <param-name>contextClass</param-name>
            <paramvalue>org.springframework.web.context.support.AnnotationConfigWebAppli cationContext</param-value>
        </context-param>
        <!--配置啓動類的全限定類名-->
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.lagou.edu.SpringConfig</param-value>
        </context-param>
        <!--使⽤監聽器啓動Spring的IOC容器-->
        <listener>
            <listenerclass>org.springframework.web.context.ContextLoaderListener</listenerclass>
        </listener>
    </web-app>
    複製代碼

BeanFactory 與 ApplicationContext 區別

BeanFactory是Spring框架中IoC容器的頂層接⼝,它只是⽤來定義⼀些基礎功能,定義⼀些基礎規範,⽽ApplicationContext是它的⼀個⼦接⼝,因此ApplicationContext是具有BeanFactory提供的所有功能的。

ApplicationContext是容器的⾼級接⼝,⽐ BeanFactory要擁有更多的功能,⽐如說國際化⽀持和資源訪問(xml,java配置類)等等

實例化Bean的三種方式

  1. 使用無參構造器

    <!--配置service對象-->
    <bean id="userService" class="com.lagou.service.impl.TransferServiceImpl">
    </bean>
    複製代碼
  2. 使用靜態方法建立

    <!--使⽤靜態⽅法建立對象的配置⽅式-->
    <bean id="userService" class="com.lagou.factory.BeanFactory" factory-method="getTransferService"></bean>
    複製代碼
  3. 使用實例化方法建立

    <!--使⽤實例⽅法建立對象的配置⽅式-->
    <bean id="beanFactory" class="com.lagou.factory.instancemethod.BeanFactory"></bean>
    <bean id="transferService" factory-bean="beanFactory" factory-method="getTransferService"></bean>
    複製代碼

Bean 的做用範圍及生命週期

  • singleton(單例模式)

    單例模式的 bean 對象生命週期與容器相同

  • prototype(多例模式)

    多例模式的 bean 對象,Spring 框架只負責建立,不負責銷燬

Xml 模式下 Bean 標籤的屬性

  • id:bean 的惟一標識
  • class:建立 Bean 對象的全限定類名
  • name:給 bean 提供一個或多個名稱
  • factory-bean:指定建立當前 bean 對象的工廠bean 的惟一標識(當指定此屬性,class 屬性失效)
  • factory-method:指定建立當前對象的工廠方法
    • 注:若配合 factory-bean 使用,class 屬性失效,若配合 class 屬性使用,方法必須是 static 修飾的
  • scope:bean 做用域,默認 singleton
  • init-method:指定 bean 對象初始化方法,在 bean 裝配後調用,必須是無參方法。
  • destory-method:指定 bean 對象銷燬方法,在 bean 銷燬前執行,只在 scope 爲 singleton 時起做用(由於多例模式下Spring框架只負責建立)

DI 依賴注入的 xml 配置

  • 依賴注入分類

    • 按注入的方式分類

      1. 構造函數注入
        • 注:當沒有無參構造時,必須提供與構造參數個數及數據類型匹配的配置參數
        • 使用標籤 <constructor-arg> 爲構造函數賦值,其屬性以下:
          • name:對應參數名
          • index:對應構造函數中指定索引
          • value:指定基本類型或 String 類型數據
          • ref:指定其餘 Bean 類型的數據,值爲 bean 的 id
      2. set 方法注入
        • 使用標籤 <property> 對應 set 方法,標籤屬性以下:
          • name:注入時調用的 set 方法名去掉 set 後的詞綴
          • value:指定基本類型或 String 類型數據
          • ref:指定其餘 Bean 類型的數據,值爲 bean 的 id
    • 按注入的數據類型分類

      1. 基本類型、String

      2. 複雜類型 [Array,List,Set,Map,Properties]

        示例( 以set方法注入爲例,構造函數注入一樣 ):

        <!-- Array -->
        <property name="myArray">
        	<array>
                <value>array1</value>
                <value>array2</value>
            </array>
        </property>
        
        <!-- List -->
        <property name="myArray">
        	<list>
                <value>list1</value>
                <value>list2</value>
            </list>
        </property>
        
        <!-- Set -->
        <property name="mySet">
        	<set>
                <value>set1</value>
                <value>set2</value>
            </set>
        </property>
        
        <!-- Map -->
        <property name="myMap">
        	<map>
                <entry key="key1" value="value1"/>
                <entry key="key2" value="value2"/>
            </map>
        </property>
        
        <!-- Properties -->
        <property name="myProperties">
        	<map>
                <prop key="key1" value="value1"/>
                <prop key="key2" value="value2"/>
            </map>
        </property>
        複製代碼

        在List結構的集合數據注⼊時,array , list , set這三個標籤通⽤,另外注值的value標籤內部 能夠直接寫值,也可使⽤bean標籤配置⼀個對象,或者⽤ref標籤引⽤⼀個已經配合的bean 的惟⼀標識。 在Map結構的集合數據注⼊時,map標籤使⽤entry⼦標籤實現數據注⼊,entry標籤可使 ⽤key和value屬性指定存⼊map中的數據。使⽤value-ref屬性指定已經配置好的bean的引⽤。 同時entry標籤中也可使⽤ref標籤,可是不能使⽤bean標籤。⽽property標籤中不能使 ⽤ref或者bean標籤引⽤對象

      3. 其餘 Bean 類型

註解與 xml 標籤對應關係

xml 與註解相結合模式下,一般第三方jar 中的bean 定義在 xml 中,本身開發的 bean 定義使用註解

  1. 配置方面

    • @Configuration 註解,對應 beans.xml 文件
    • @ComponentScan 註解,替代 <context:component-scan>
    • @PropertySource,引⼊外部屬性配置⽂件
    • @Import 引⼊其餘配置類
    • @Value 對變量賦值,能夠直接賦值,也可使⽤ ${} 讀取資源配置⽂件中的信息
    • @Bean 將⽅法返回對象加⼊ SpringIoC 容器
  2. IoC

    xml形式 對應的註解形式
    <bean> 標籤 @Component("accountDao"),註解加在類上 bean的id屬性內容直接配置在註解後⾯若是不配置,默認定義個這個bean的id爲類 的類名⾸字⺟⼩寫;
    另外,針對分層代碼開發提供了@Componenet的三種別名@Controller、 @Service、@Repository分別⽤於控制層類、服務層類、dao層類的bean定義,這 四個註解的⽤法徹底⼀樣,只是爲了更清晰的區分⽽已
    scope屬性 @Scope("prototype"),默認單例,註解加在類上
    標籤的 init-method 屬性 @PostConstruct,註解加在⽅法上,該⽅法就是初始化後調⽤的⽅法
    destory-method 屬性 @PreDestory,註解加在⽅法上,該⽅法就是銷燬前調⽤的⽅法
  3. DI

    • @Autowired
      • 按類型注入,若是有多個 bean 值,需配合 @Qualifier(name="") 使用
    • @Resource
      • J2EE 提供,@Resource 在 Jdk 11中已經移除,使用需導入包 javax.annotation.Resource
      • 注入規則:
        • 若是同時指定了 name 和 type,則從Spring上下⽂中找到惟⼀匹配的bean進⾏裝配,找不到則拋出異常。
        • 若是指定了 name,則從上下⽂中查找名稱(id)匹配的bean進⾏裝配,找不到則拋出異常。
        • 若是指定了 type,則從上下⽂中找到相似匹配的惟⼀bean進⾏裝配,找不到或是找到多個, 都會拋出異常。
        • 若是既沒有指定name,⼜沒有指定type,則⾃動按照byName⽅式進⾏裝配;

Spring IoC 經常使用特性

lazy-Init 延遲加載

  • 關閉延遲加載(默認狀態) :ApplicationContext 容器的默認⾏爲是在啓動服務器時將全部 singleton bean 提早進⾏實例化
  • 開啓延遲加載:bean 將不會在 ApplicationContext 啓動時提早被實例化,⽽是第⼀次向容器經過 getBean 索取 bean 時實例化的。
    1. 在 <bean> 設置 lazy-init="true",控制單個 bean
    2. 在 <beans> 設置 default-lazy-init="true" ,控制該容器下全部 bean

做用:

  1. 開啓延遲加載⼀定程度提⾼容器啓動和運轉性能
  2. 對於不常使⽤的 Bean 設置延遲加載,這樣偶爾使⽤的時候再加載,沒必要要從⼀開始該 Bean 就佔⽤資源

FactoryBean 和 BeanFactory

  • BeanFactory接口:容器頂級接口,定義容器的一些基礎行爲

  • FactoryBean接口:藉助他自定義Bean,做用相似於 Bean 建立方式的靜態方法和實例化方法

    使用方法

    1. 建立 Bean 對應的 Factory 類,實現 FactoryBean 接口,重寫 getObject() 、getObjectType() 和 isSingleton()

    2. 在 xml 中配置該 Factory 類

    3. 容器初始化過程當中 會自動實例化 Factory 對象並調用其 getObject() 並管理

      注:若是須要 Factory 對象,可獲取容器中的 &${id} 對象

      <bean id="testBean" class="com.test.TestBeanFactory"></bean>
      
      <!-- getBean("testBean") 獲取的是 TestBean 對象 getBean("&testBean") 獲取的是 TestBeanFactory 對象 -->
      複製代碼

後置處理器

Spring 提供兩種後置處理 bean 的擴展接口

  • BeanPostProcessor:Bean 對象實例化且裝配後調用

    該接口提供兩個方法:

    1. postProcessBeforeInitialization:Bean 初始化方法前
    2. postProcessAfterInitialization:Bean 初始化方法後
  • BeanFactoryPostProcessor:BeanFactory 初始化後(bean 還未實例化,此時 bean 剛被解析成 BeanDefinition對象)調用

    此接口提供了⼀個⽅法,⽅法參數爲ConfigurableListableBeanFactory,該參數類型定義了⼀些⽅法其中有個⽅法名爲getBeanDefinition的⽅法,咱們能夠根據此⽅法,找到咱們定義bean 的 BeanDefinition對象。而後咱們能夠對定義的屬性進⾏修改。

    **BeanDefinition對象:**咱們在 XML 中定義的 bean標籤,Spring 解析 bean 標籤成爲⼀個 JavaBean, 這個JavaBean 就是 BeanDefinition

Spring IoC 源碼剖析

Spring IoC 容器繼承體系

![](img/Spring IoC容器繼承體系.jpg)

Bean 生命週期關鍵時機點

Bean對象建立的⼏個關鍵時機點代碼層級的調⽤都在 AbstractApplicationContext 類 的 refresh ⽅法中

關鍵點 觸發代碼
構造器 refresh#finishBeanFactoryInitialization(beanFactory)(beanFactory)
BeanFactoryPostProcessor 初始化 refresh#invokeBeanFactoryPostProcessors(beanFactory)
BeanFactoryPostProcessor ⽅法調⽤ refresh#invokeBeanFactoryPostProcessors(beanFactory)
BeanPostProcessor 初始化 registerBeanPostProcessors(beanFactory)
BeanPostProcessor ⽅法調⽤ refresh#finishBeanFactoryInitialization(beanFactory)

源碼以下:

public void refresh() throws BeansException, IllegalStateException {
	// 對象鎖加鎖
	synchronized (this.startupShutdownMonitor) {
		/* Prepare this context for refreshing. 刷新前的預處理 表示在真正作refresh操做以前須要準備作的事情: 設置Spring容器的啓動時間, 開啓活躍狀態,撤銷關閉狀態 驗證環境信息裏一些必須存在的屬性等 */
		prepareRefresh();

		/* Tell the subclass to refresh the internal bean factory. 獲取BeanFactory;默認實現是DefaultListableBeanFactory 加載BeanDefition 並註冊到 BeanDefitionRegistry */
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

		/* Prepare the bean factory for use in this context. BeanFactory的預準備工做(BeanFactory進行一些設置,好比context的類加載器等) */
		prepareBeanFactory(beanFactory);

		try {
            /* Allows post-processing of the bean factory in context subclasses. BeanFactory準備工做完成後進行的後置處理工做 */
            postProcessBeanFactory(beanFactory);

            /* Invoke factory processors registered as beans in the context. 實例化實現了BeanFactoryPostProcessor接口的Bean,並調用接口方法 */
            invokeBeanFactoryPostProcessors(beanFactory);

            /* Register bean processors that intercept bean creation. 註冊BeanPostProcessor(Bean的後置處理器),在建立bean的先後等執行 */
            registerBeanPostProcessors(beanFactory);
            
            /* Initialize message source for this context. 初始化MessageSource組件(作國際化功能;消息綁定,消息解析); */
            initMessageSource();

            /* Initialize event multicaster for this context. 初始化事件派發器 */
            initApplicationEventMulticaster();

            /* Initialize other special beans in specific context subclasses. 子類重寫這個方法,在容器刷新的時候能夠自定義邏輯;如建立Tomcat,Jetty等WEB服務器 */
            onRefresh();

            /* Check for listener beans and register them. 註冊應用的監聽器。就是註冊實現了ApplicationListener接口的監聽器bean */
            registerListeners();

            /* Instantiate all remaining (non-lazy-init) singletons. 初始化全部剩下的非懶加載的單例bean 初始化建立非懶加載方式的單例Bean實例(未設置屬性) 填充屬性 初始化方法調用(好比調用afterPropertiesSet方法、init-method方法) 調用BeanPostProcessor(後置處理器)對實例bean進行後置處理 */
            finishBeanFactoryInitialization(beanFactory);

            /* Last step: publish corresponding event. 完成context的刷新。主要是調用LifecycleProcessor的onRefresh()方法,而且發佈事件(ContextRefreshedEvent) */
            finishRefresh();
		}catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);}
            
				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
複製代碼

BeanFactory 建立流程

獲取 BeanFactory 子流程

BeanDefinition加載解析及註冊⼦流程

  1. Resource 定位:把 XML 文件封裝成 Resource 對象的過程
  2. BeanDefinition 載入:把⽤戶定義好的Javabean表示爲IoC容器內部的數據結構,這個容器內部的數 據結構就是BeanDefinition。
  3. 註冊BeanDefinition到 IoC 容器

Bean 建立流程

注:參考IoC 循環依賴時序圖

lazy-inti 延遲加載機制原理

public void preInstantiateSingletons() throws BeansException {
    if (logger.isTraceEnabled()) {
        logger.trace("Pre-instantiating singletons in " + this);
    }

	// Iterate over a copy to allow for init methods which in turn register new bean definitions.
    // While this may not be part of the regular factory bootstrap, it does otherwise work fine.
    // 全部bean的名字
    List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

    // Trigger initialization of all non-lazy singleton beans...
    // 觸發全部非延遲加載單例bean的初始化,主要步驟爲getBean
    for (String beanName : beanNames) {
        // 合併父BeanDefinition對象
        // map.get(beanName)
        RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
        
        if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
            if (isFactoryBean(beanName)) {
                Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
                // 若是是FactoryBean則加&
                if (bean instanceof FactoryBean) {
                    final FactoryBean<?> factory = (FactoryBean<?>) bean;
                    boolean isEagerInit;
                    if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                        isEagerInit = AccessController
                            .doPrivileged((PrivilegedAction<Boolean>)((SmartFactoryBean<?>) factory)::isEagerInit, getAccessControlContext());
                    }else {
                        isEagerInit = (factory instanceof SmartFactoryBean 
                                       && ((SmartFactoryBean<?>) factory).isEagerInit());
                    }
                    if (isEagerInit) {
                        getBean(beanName);
                    }
                }
            }else {
                // 實例化當前bean
                getBean(beanName);
            }
        }
    }

    // Trigger post-initialization callback for all applicable beans...
    for (String beanName : beanNames) {
        Object singletonInstance = getSingleton(beanName);
        if (singletonInstance instanceof SmartInitializingSingleton) {
            final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
            if (System.getSecurityManager() != null) {
                AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
                    smartSingleton.afterSingletonsInstantiated();
                    return null;
                }, getAccessControlContext());
            }else {
                smartSingleton.afterSingletonsInstantiated();
            }
        }
    }
}
複製代碼

如源碼:

  • 對於被修飾爲lazy-init的bean Spring 容器初始化階段不會進⾏ init 而且依賴注⼊,當第⼀次 進⾏getBean時候才進⾏初始化並依賴注⼊
  • 對於⾮懶加載的bean,getBean的時候會從緩存⾥頭獲取,由於容器初始化階段 Bean 已經 初始化完成並緩存了起來

Spring IoC 循環依賴問題

  • 單例 bean 構造器參數循環依賴(⽆法解決)

  • prototype 原型 bean循環依賴(⽆法解決)

    對於原型bean的初始化過程當中不管是經過構造器參數循環依賴仍是經過setXxx⽅法產⽣循環依 賴,Spring都 會直接報錯處理。

  • 單例bean經過setXxx或者@Autowired進⾏循環依賴

    Spring 的循環依賴的理論依據基於 Java 的引⽤傳遞,當得到對象的引⽤時,對象的屬性是能夠延後設置的,可是構造器必須是在獲取引⽤以前

Spring AOP 應用

相關概念

  • joinPoint(鏈接點):方法開始時、結束時、正常運行完畢時、方法異常時等這些特殊的時機點,鏈接點時一種候選點
  • pointCut(切入點):指定 AOP 影響的具體方法,描述感興趣的方法
  • Advice(通知/加強):切⾯類中⽤於提供加強功能的⽅法,也表示通知的類型,其分類有:前置通知、後置通知、異常通知、最終通知、環繞通知
    • 橫切邏輯
    • 方位點
  • Target(⽬標對象):被代理對象
  • Proxy(代理):代理對象
  • Weaving(織⼊):把加強應⽤到⽬標對象來建立新的代理對象的過程。spring採⽤動態代 理織⼊,⽽AspectJ採⽤編譯期織⼊和類裝載期織⼊。
  • Aspect(切⾯):加強的代碼所關注的方面。[例如:TransactionManage類]
    • Aspect切⾯:切⾯概念是對上述概念的⼀個綜合
    • Aspect切⾯= 切⼊點+加強 = 切⼊點(鎖定⽅法) + ⽅位點(鎖定⽅法中的特殊時機)+ 橫切邏輯

Spring中 AOP 的代理選擇

  • Spring 實現AOP 思想使用的是動態代理技術

  • 默認狀況下

    若是 被代理類 實現接口 ⇒ 選擇 JDK 動態代理

    不然 ⇒ 選擇 CGLIB 動態代理

  • 可經過配置方式設置強制使用 CGLIB 動態代理

Spring AOP 實現

1. XML 模式

  1. 引入依賴
  2. 把 Bean 交給 Spring 管理
  3. 配置 <aop:config>
    • 配置切面 <aop:aspect>
    • 在切面中 配置 通知加強,一是方位,二是切點

須要注意的點:

  1. 切入點表達式

    指的是遵循特定語法結構的字符串,其 做⽤是⽤於對符合語法格式的鏈接點進⾏加強。

// 示例
// 全限定⽅法名 訪問修飾符 返回值 包名.包名.包名.類名.⽅法名(參數列表)
// 全匹配⽅式:
public void com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account) // 訪問修飾符能夠省略
void com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account) 
// 返回值可使⽤*,表示任意返回值
* com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account)
// 包名可使⽤.表示任意包,可是有⼏級包,必須寫⼏個
* ....TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account)
// 包名可使⽤..表示當前包及其⼦包
* ..TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account)
// 類名和⽅法名,均可以使⽤.表示任意類,任意⽅法
* ...(com.lagou.pojo.Account)
// 參數列表,可使⽤具體類型 基本類型直接寫類型名稱 : int
// 引⽤類型必須寫全限定類名:java.lang.String
// 參數列表可使⽤*,表示任意參數類型,可是必須有參數
* *..*.*(*)
// 參數列表可使⽤..,表示有⽆參數都可。有參數能夠是任意類型
* *..*.*(..)
// 全通配⽅式:
* *..*.*(..)
複製代碼
  1. 改變代理方式的配置

    • 使用<aop:config> 標籤配置

      <aop:config proxy-target-class="true">
      複製代碼
    • 使⽤<aop:aspectj-autoproxy>標籤配置

      <!--此標籤是基於XML和註解組合配置AOP時的必備標籤,表示Spring開啓註解配置AOP 的⽀持-->
      <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectjautoproxy>
      複製代碼
  2. 五種通知類型

    1. 前置通知:切入點方法執行前執行

      <aop:before>

    2. 後置通知:切入點方法正常執行後執行

      <aop:after-returning>

    3. 異常通知:切⼊點⽅法執⾏產⽣異常以後執⾏

      <aop:after-throwing>

    4. 最終通知:切⼊點⽅法執⾏完成以後,切⼊點⽅法返回以前執⾏(不管是否異常都會執行,finally)

      <aop:after>

    5. 環繞通知:較特殊,藉助的ProceedingJoinPoint接⼝及其實現類, 實現⼿動觸發切⼊點⽅法的調⽤

2. XML + 註解模式

  1. XML 中開啓 Spring 對註解 AOP 的⽀持

    <!--開啓spring對註解aop的⽀持-->
    <aop:aspectj-autoproxy/>
    複製代碼
  2. 示例

    /** * 模擬記錄⽇志 */
    @Component
    @Aspect
    public class LogUtil {
        /** * 咱們在xml中已經使⽤了通⽤切⼊點表達式,供多個切⾯使⽤,那麼在註解中如何使⽤呢?* 第⼀步:編寫⼀個⽅法 * 第⼆步:在⽅法使⽤@Pointcut註解 * 第三步:給註解的value屬性提供切⼊點表達式 * 細節: * 1.在引⽤切⼊點表達式時,必須是⽅法名+(),例如"pointcut()"。 * 2.在當前切⾯中使⽤,能夠直接寫⽅法名。在其餘切⾯中使⽤必須是全限定⽅法名。 */
        @Pointcut("execution(* com.lagou.service.impl.*.*(..))")
        public void pointcut(){}
        
        @Before("pointcut()")
        public void beforePrintLog(JoinPoint jp){
            Object[] args = jp.getArgs();
            System.out.println("前置通知:beforePrintLog,參數是:"+Arrays.toString(args));
        }
        
        @AfterReturning(value = "pointcut()",returning = "rtValue")
        public void afterReturningPrintLog(Object rtValue){
            System.out.println("後置通知:afterReturningPrintLog,返回值是:"+rtValue);
        }
    
        @AfterThrowing(value = "pointcut()",throwing = "e")
        public void afterThrowingPrintLog(Throwable e){
            System.out.println("異常通知:afterThrowingPrintLog,異常是:"+e);
        }
    
        @After("pointcut()")
        public void afterPrintLog(){
            System.out.println("最終通知:afterPrintLog");
        }
    
        /** * 環繞通知 * @param pjp * @return */
        @Around("pointcut()")
        public Object aroundPrintLog(ProceedingJoinPoint pjp){
            //定義返回值
            Object rtValue = null;
            try{
                //前置通知
                System.out.println("前置通知");
                //1.獲取參數
                Object[] args = pjp.getArgs();
                //2.執⾏切⼊點⽅法
                rtValue = pjp.proceed(args);
                //後置通知
                System.out.println("後置通知");
            }catch (Throwable t){
                //異常通知
                System.out.println("異常通知");
                t.printStackTrace();
            }finally {
                //最終通知
                System.out.println("最終通知");
            }
            return rtValue;
        }
    }
    複製代碼

3. 註解模式

  1. 在配置類上加 @EnableAspectJAutoProxy 開啓spring對註解AOP的⽀持

Spring 聲明式事務的⽀持

**編程式事務:**在業務代碼中添加事務控制代碼,這樣的事務控制機制就叫作編程式事務

**聲明式事務:**經過xml或者註解配置的⽅式達到事務控制的⽬的,叫作聲明式事務

事務四大特性

  1. **原⼦性(Atomicity):**原⼦性是指事務是⼀個不可分割的⼯做單位,事務中的操做要麼都發⽣,要麼都不發⽣。

    是從操做的角度描述的

  2. ⼀致性(Consistency): 事務必須使數據庫從⼀個⼀致性狀態變換到另外⼀個⼀致性狀態。

    數據的角度描述

  3. **隔離性(Isolation):**事務的隔離性是多個⽤戶併發訪問數據庫時,數據庫爲每⼀個⽤戶開啓的事務, 每一個事務不能被其餘事務的操做數據所⼲擾,多個併發事務之間要相互隔離。

  4. **持久性(Durability):**持久性是指⼀個事務⼀旦被提交,它對數據庫中數據的改變就是永久性的,接下來即便數據庫發⽣故障 也不該該對其有任何影響。

事務隔離級別

問題:

  • 髒讀:⼀個線程中的事務讀到了另外⼀個線程中未提交的數據。
  • 不可重複讀:⼀個線程中的事務讀到了另外⼀個線程中已經提交update的數據(先後內容不⼀樣)
  • 虛讀(幻讀):⼀個線程中的事務讀到了另外⼀個線程中已經提交的insert或者delete的數據(先後條數不⼀樣)

數據庫四種隔離級別:

  • Serializable(串⾏化):可避免髒讀、不可重複讀、虛讀狀況的發⽣。(串⾏化) 最⾼

  • Repeatable read(可重複讀):可避免髒讀、不可重複讀狀況的發⽣。(幻讀有可能發⽣) 第⼆

    該機制下會對要update的⾏進⾏加鎖

  • Read committed(讀已提交):可避免髒讀狀況發⽣。不可重複讀和幻讀⼀定會發⽣。 第三

  • Read uncommitted(讀未提交):最低級別,以上狀況均⽆法保證。(讀未提交) 最低

注意:級別依次升⾼,效率依次下降

MySQL的默認隔離級別是:REPEATABLE READ

事務傳播行爲

經常使用前兩條(加粗)

PROPAGATION_REQUIRED 若是當前沒有事務,就新建⼀個事務,若是已經存在⼀個事務中, 加⼊到這個事務中。這是最常⻅的選擇。
PROPAGATION_SUPPORTS ⽀持當前事務,若是當前沒有事務,就以⾮事務⽅式執⾏。
PROPAGATION_MANDATORY 使⽤當前的事務,若是當前沒有事務,就拋出異常。
PROPAGATION_REQUIRES_NEW 新建事務,若是當前存在事務,把當前事務掛起。
PROPAGATION_NOT_SUPPORTED 以⾮事務⽅式執⾏操做,若是當前存在事務,就把當前事務掛起。
PROPAGATION_NEVER 以⾮事務⽅式執⾏,若是當前存在事務,則拋出異常。
PROPAGATION_NESTED 若是當前存在事務,則在嵌套事務內執⾏。若是當前沒有事務,則 執⾏與PROPAGATION_REQUIRED相似的操做。

Spring 聲明式事務配置

注:與AOP配置相似,配置<tx:advice>

開啓spring對註解事務的⽀持

<tx:annotation-driven transactionmanager="transactionManager"/>@EnableTransactionManagement

Spring AOP 源碼剖析

AOP源碼分析類⽅法調⽤關係

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean
調⽤
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization
調⽤
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization(後置處理器AbstractAutoProxyCreator完成bean代理對象建立)
調⽤
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#wrapIfNecessary
調⽤
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#createProxy(在這⼀步把委託對象的aop加強和通⽤攔截進⾏合併,最終給代理對象)
調⽤
org.springframework.aop.framework.DefaultAopProxyFactory#createAopProxy
調⽤
org.springframework.aop.framework.CglibAopProxy#getProxy(java.lang.ClassLoader )
複製代碼

Spring聲明式事務控制

@EnableTransactionManagement 註解
1)經過@import引⼊了TransactionManagementConfigurationSelector類
    它的selectImports⽅法導⼊了另外兩個類:AutoProxyRegistrar和ProxyTransactionManagementConfiguration 
2)AutoProxyRegistrar類分析
    ⽅法registerBeanDefinitions中,引⼊了其餘類,經過AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry)引⼊
InfrastructureAdvisorAutoProxyCreator,它繼承了AbstractAutoProxyCreator,是⼀個後置處理器類
3)ProxyTransactionManagementConfiguration 是⼀個添加了@Configuration註解的配置類 (註冊bean)
    註冊事務加強器(注⼊屬性解析器、事務攔截器)
    屬性解析器:AnnotationTransactionAttributeSource,內部持有了⼀個解析器集合Set<TransactionAnnotationParser> annotationParsers;具體使⽤的是SpringTransactionAnnotationParser解析器,⽤來解析@Transactional的事務屬性
    事務攔截器:TransactionInterceptor實現了MethodInterceptor接⼝,該通⽤攔截會在產⽣代理對象以前和aop加強合併,最終⼀起影響到代理對象
        TransactionInterceptor的invoke⽅法中invokeWithinTransaction會觸發原有業
務邏輯調⽤(加強事務)
複製代碼
相關文章
相關標籤/搜索