絕對值得你收藏的 spring 筆記

spring概述

spring 優勢

1、低耦合高內聚

2、AOP編程支持

3、聲明式事物支持

4、支持測試

5、方便繼承其他優秀框架

核心結構

image-20200530103942091

spring的核心容器 就是最下面的core,context,Bean 。爲上層架構提供服務。

AOP 是利用動態代理實現的面向切面編程,用來抽取不同業務中相同的代碼,減少代碼重複和降低程序見的耦合度。

JDBC 封裝的對數據庫的操作,和可以輕易整合ORM框架,並且提供對數據庫事物的聲明試管理。

WEB 容器主要是 封裝Servlet,並且可以輕易的整合SpringMVC 框架,實現傳統的MVC 架構。開發web 應用項目。

TEST 是方便我們對各個模塊進行單元測試。

核心思想

IOC

IOC :inversion of control(控制翻轉)。也就是說我們找對象的時候,不在是原來需要new 一個對象出來,而是告訴IOC 控制器,我想要這個對象,那麼IOC 控制器就會給你分配這個對象,你直接是用就好了。就好比我們找媳婦,我們告訴婚介所我想要一個怎樣的對象,婚介所中有大量的資源,就會根據你的條件,找到你想要的對象。然後安排你們見面。你這用負責和他給你找到的對象談戀愛就完了,其他不用管,婚介所會爲你擦屁股料後事。

DI

DI:dependancy injection (依賴注入),依賴注入和IOC描述的是同一件事情,表示IOC 容器根據這個類需要的依賴對象,給這個類注入對應的對象。可以看到依賴注入是站在IOC 容器的角度。而IIOC 控制翻轉是站在類或者Bean 的角度,將控制創建對象的權利交給了IOC容器。

AOP

AOP Aspect oridented programming(面向切面編程)。就是爲了將不同業務間的相同業務帶來抽離出來做成一個切面。然後通過動態代理的的方式將這部分相同的業務代碼注入到原業務邏輯中,從而實現原業務邏輯的增強。降低了代碼見的重複和代碼間的耦合度。

手寫 IOC AOP

設計模式

工廠模式

工廠模式就是通過工廠來創建想要的對象,而不是自己去創建對象。這樣降低了代碼間的耦合度。比如一個服飾工廠可以生產衣服,褲子,鞋子,襪子,帽子等等。我們想要用衣服,不用自己來造了,而是和工廠說你想要什麼,那工廠就給你生產什麼。這就是工廠模式。主要是將創建對象的實例交給工廠類來完成,並進行管理。

工廠分爲簡單工廠模式、工廠方法模式和抽象工廠模式。

簡單工廠模式:是工廠模式最簡單的一種,實例有工廠直接創建。也就是上面的例子中,你想要衣服,那這個服飾工廠就給你做一件衣服給你。

工廠方法模式 :就是工廠本身不進行實體對象的創建,而是通過對應的下游工廠來創建然後在返回,有點總工廠子工廠的意思,總工廠管理着所有的子工廠,每個字工廠只生產指定的商品,當通知總部想要什麼東西時,總部就通知對應的子工廠生產,拿到產品後再返回客戶。

抽象工廠:就是一個抽象工廠類,裏面只是聲明可以實現哪些對象,但是具體的實現就交給具體的工廠完成。抽象工廠就好比包皮公司,它告訴你他可生產服飾,也就可以生產食品。那比如你想要衣服,它就給你一個服飾工廠的聯繫方式,你通過這個服飾工廠來獲取到衣服。想要辣條,那他就給你推一個食品公司的聯繫方式,讓這個食品公司給你做。

工廠方法模式和抽象工廠模式的區別:在於工廠方法模式主要是生產某一類商品。而抽象工廠,我不關你什麼實現,只要你說你能做這個商品,我就可以爲你代言。

單例模式

單例模式表示這個對象只會被創建一次。所以要下面特性:

1、私有的構造方法

2、靜態的成員變量

3、共有的get 方法,可以讓其他對象獲取到唯一的實例。

單例模式分餓漢懶漢雙重檢驗,線程安全的懶漢式,靜態內部流等。其實都是實現方式不同罷了,一樣的滿足上面特性。

餓漢式

peublic class Singleton{
	//靜態變量
	private static Singleton sing=new Singleton();
	//構造方法
	private Singleton(){
	}
	//外部調用方法
	public  static Singleton getInstance(){
		return sing;
	}
}

優點:這種方式在類加載時就完成初始化了,獲取對象但速度快。避免多線程但同步問題。
缺點:類加載較慢。沒有達到懶加載的效果,如果從始至終都未使用果這個實例,這會造成內存的浪費。

懶漢式

public class Singleton{
	private static Singleton sing;
	private Singleton(){
	} 
	private static Singleton getInstance(){
		if(sing==null){
			sing= new Singleton();
		}
		return sing;
	}
}

優點:相對於餓漢,節省資源。
缺點:第一次加載比較慢,且多線程會出現問題。

線程安全的懶漢式

public class Singleton{
	private Singleton(){
	}
	
	private Static Singleton sing;
	
	public static synchronized Singleton getInstance(){
		if(sing==null){
			sing=new Singleton();
		}
		return sing;
	}
	
}
優點:多線程能夠很好的使用
缺點:每次代用getInstance方法都會進行同步操作,造成不必要的同步開銷。畢竟大部分時候還是用不到同步的。

雙重校驗鎖

public class Singleton(){
	private Singleton(){}
	
	private volalite static Singleton sing;

	public Singleton getInstance(){
		if(sing==null){
			synchronized(Singleton.calss){
				if(sing==null){
					sing= new Singleton();
				}
			}
		}
		return sing;
	}

}

優點:線程安全,避免多餘的線程同步,節省資源
缺點:第一次實例比較慢,並且在高併發的情況下可能出現失效

靜態內部類

public calss Singleton(){
	private Singleton(){}
	private static class SingletonInner(){
		private static final Singleton sing=new Singleton();
	}
	
	public Singleton getInstance(){
		return SingletonInner.sing;
	}
}

第一次加載 Singleton 類時並不會初始化 sInstance。 只有在第一次調用 getInstance 方法時虛擬機纔會加載 SingletonHolder 並初始化 sInstance。

優點:資源利用率高,線程安全,避免多餘同步。

代理模式

代理模式就是我們想要執行的方法通過代理對象來完成,就好比我們要搶票回家,我們不想自己盯着搶,所以就找代理幫我們搶從而達到目的。代理模式分爲靜態代理和動態代理。

靜態代理:就是具體的代理類,在編譯階段就知道這個代理能做什麼事情。就好比搶票的代理一樣,它只能做搶票這個代理,而不能代你搶錢哈哈。

動態代理:動態代理就不同了,它在編譯階段也沒有具體的實現。而是在程序運行期間根據JVM的反射機制動態生成的。比較出名的就JDK 動態代理 和cglib 動態代理。

JDK 動態代理:主要實現InvocationHandle 接口並實現其 invoke 方法。

Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object result = null;

                        // 前置增強
                       ....
                        // 調用原有業務邏輯
                        result = method.invoke(obj,args);
                        //後置增強
                        ...

                        return result;
                    }
                })

cglib 動態代理:需要引入其依賴,使用方法和JDK動態代理差不多。實現MethodInterceptor 接口並實現 intercept 方法。

public Object getCglibProxy(Object obj) {
        return  Enhancer.create(obj.getClass(), new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                Object result = null;
                //前置增強
                ...
                result = method.invoke(obj,objects);
                //後置增強
                ...
                return result;
            }
        });
    }

Spring IOC 應用

BeanFactory 和ApplicationContext 的區別

beanFactory 是spring 的頂級接口,提供一些基礎的功能和規範,而ApplicationContext 是集成 beanFactory 的接口的接口,增加了一些功能,spring 中個更多的功能都是通過applicationContext 接口來實現的。

IOC的啓動方式

java 環境:

​ ClassPathXmlApplicationContext :從類的根路徑加載配置文件(相對路徑)。

​ FileSystemXmlApplicationContext: 從磁盤路徑加載配置文件(絕對路徑)。

​ AnnotationConfigApplicationContext:純註解模式下啓動spring 容器。

web 環境:

​ 配置web.xml 啓動。指定啓動需要加載的配置文件。

<context-param>
 	<param-name>contextConfigLocation</param-name>
 	<param-value>classpath:applicationContext.xml</param-value>
 </context-param>
 <!--使⽤監聽器啓動Spring的IOC容器-->
 <listener>
 	<listener-class>org.springframework.web.context.ContextLoaderListener
 	</listener-class>
 </listener>

​ 配置web.xml 啓動。指定啓動時需要加載的配置類。

<!--告訴ContextloaderListener知道我們使⽤註解的⽅式啓動ioc容器-->
 <context-param>
 	<param-name>contextClass</param-name>
 	<param-value>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>
 <listener>
 	<listener-class>org.springframework.web.context.ContextLoaderListener
 	</listener-class>
 </listener>
 

實例化 Bean 的三種方式

1、使用無參的構造方法。在默認情況下通過反射調用對象的無參構造函數來實例化。

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" factorymethod="getTransferService"></bean>

Bean 的作用範圍已經生命週期

Bean 在創建的時候可以通過scope 來選擇作用範圍,模式是單例的。可以選擇多例模式。

單例模式(singleton): 在創建容器時,也就是項目啓動初始化階段,對象就會被創建。只有當容器銷燬時,對象纔會被銷燬,也就是說和IOC容器的生命週期是一樣的。

多例模式(prototype): 在需要使用這個對象時,就會創建新的對象實例。ioc 容器只負責創建 對象,不負責銷燬。當對象一直被使用時,就會存活,當對象沒有使用時,就會等待JVM 垃圾回收銷燬。

依賴注入的三種方式

1、setter 注入

2、構造函數注入

3、註解注入:@Autowired

Autowired 和Resource 註解的區別

1、autowired 是spring 提供的註解,@Resource是javaee 提供的註解。

2、Autowired 採用的是按類型注入。當一個類有多個Bean的時候需要配合@Qualifier 來指定唯一的Bean。而@Resource 默認安裝byName 自動注入。

3、@Resource 可以執行 name 和type .可以通過type 注入,也可以通過name 來注入。

延遲加載

Bean 對象的創建的延時加載。對於singleton 的單例Bean,默認爲立即加載。

單個Bean:xml :配置lazy-init=true 爲開啓延遲加載,默認爲false.
註解:@lazy

整體Bean:在beans 標籤中增加: default-lazy-init 

延遲加載,在要使用這個Bean的時候纔開始創建。可以在applicationContext --beanFactory --singletonObjects 用來存放單例bean.使用的存儲結構爲currentHashMap.

FactoryBean 和BeanFactory

BeanFactory 接口是容器的頂級接口,定義容器的基礎行爲。負責創建和管理Bean的一個工廠。Beanfactory 有兩種,一種是普通的Bean,一種是工廠Bean(factoryBean)。

factoryBean 可以生成一個Bean 實例給我們 ,我們可以藉助factoryBean 來自定義Bean的創建過程。

後置處理器

spring 提供了兩種後置處理器的接口。BeanPostProcessor 和BeanFactoryPostProcessor.

在BeanFactory 初始化之後,可以實現BeanFactoryPostProcessor 進行後置處理做一些事情。

在Bean對象實例化但是沒有走完加載的整個流程,可以使用BeanPostProcessor 進行後置處理。

BeanPostProcessor 是針對Bean的,可以獲取到當前Bean,並做處理。

public interface BeanPostProcessor {
	@Nullable
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}
	@Nullable
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

}

該接⼝提供了兩個⽅法,分別在Bean的初始化⽅法前和初始化⽅法後執⾏,具體這個初始化⽅法指的是什麼⽅法,類似我們在定義bean時,定義了init-method所指定的⽅法定義⼀個類實現了BeanPostProcessor,默認是會對整個Spring容器中所有的bean進⾏處理。如果要對具體的某個bean處理,可以通過⽅法參數判斷,兩個類型參數分別爲Object和String,第⼀個參數是每個bean的實例,第⼆個參數是每個bean的name或者id屬性的值。所以我們可以通過第⼆個參數,來判斷我們將要處理的具體的bean。

BeanFactoryPostProcessor 是針對 beanFactory 的典型應⽤:PropertyPlaceholderConfifigurer。用來替換配置文件中外部文件的引用值替換,比如applicationContext.xml 引用jdbc.properties 獲取其實的數據庫配置信息。

@FunctionalInterface
public interface BeanFactoryPostProcessor {
	void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

}

//需要實現它的方法
@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
	System.out.println(beanFactory);
	BeanDefinition beanDefinition = beanFactory.getBeanDefinition("lagouBean");
		String beanClassName = beanDefinition.getBeanClassName();
	}

可以獲取到beanfactory ,從而通過beanFactory.getBeanDefinition()獲取到BeanDefinition對象,從而獲取到bean 對Bean屬性進行修改。

注意:調⽤ BeanFactoryPostProcessor ⽅法時,這時候bean還沒有實例化,此時 bean 剛被解析成BeanDefifinition對象

Spring IOC 源碼深度剖析

  • 好處:提⾼培養代碼架構思維、深⼊理解框架

  • 原則

    • 定焦原則:抓主線
    • 宏觀原則:站在上帝視⻆,關注源碼結構和業務流程(淡化具體某⾏代碼的編寫細節)
  • 讀源碼的⽅法和技巧

    • 斷點(觀察調⽤棧)

    • 反調(Find Usages)

    • 經驗(spring框架中doXXX,做具體處理的地⽅)

  • Spring源碼構建

    • 下載源碼(github)

    • 安裝gradle 5.6.3(類似於maven) Idea 2019.1 Jdk 11.0.5

    • 導⼊(耗費⼀定時間)

    • 編譯⼯程(順序:core-oxm-context-beans-aspects-aop)

      • ⼯程—>tasks—>compileTestJava

IOC 容器初始化 主體流程

  • prepareRefresh()刷新前預處理。主要是設置啓動時間,以及初始化配置文件中的佔位符,以及校驗配置信息是否正確。
  • 獲取Beanfactory。
  • Beanfactory 的準備工作,對BeanFactory 的一些屬性進行配置。比如context 的類加載器
  • BeanFactory 準備工作完成後,進行的後置處理工作。是一個鉤子函數。
  • 註冊BeanPostProcessor(Bean的後置處理器),在創建Bean的前後執行
  • 初始化MessageSource 組件,並將信息加入allpication.singletonObjects 中。
  • 初始化事件派發器,並加入到singletonObjects 中
  • onRefresh().子類重寫這個方法,在容器刷新時可以自定義邏輯。
  • 註冊應用監聽器。ApplicationListener 接口監聽器
  • 初始化所有沒有設置延時加載的Bean
  • 完成context 的刷新,發佈事件。

AbstractApplicationContext 中主要的refresh() 方法:

public void refresh() throws BeansException, IllegalStateException {
		//加對象鎖,避免refresh 和close 同時調用
		synchronized (this.startupShutdownMonitor) {
			/**
			 * 第⼀步:刷新前的預處理
			 * 刷新的預處理
			 * 設置系統啓動時間
			 * 驗證環境信息是否包含必要屬性
			 */
			prepareRefresh();

			/**
			 * 第⼆步:
			 *  獲取BeanFactory;默認實現是DefaultListableBeanFactory
			 *  加載BeanDefinition 並註冊到 BeanDefitionRegistry
			 */
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			/**
			 * 第三步:BeanFactory的預準備⼯作(BeanFactory進⾏⼀些設置,⽐如context的類加
			 * 載器等)
			 */
			prepareBeanFactory(beanFactory);

			try {
			
				/**
				 * 第四步:BeanFactory準備⼯作完成後進⾏的後置處理⼯作
				 */
				postProcessBeanFactory(beanFactory);

				/**
				 * 第五步:實例化並調⽤實現了 BeanFactoryPostProcessor 接⼝的Bean
				 */
				invokeBeanFactoryPostProcessors(beanFactory);

				/**
				 *  第六步:註冊BeanPostProcessor(Bean的後置處理器),在創建bean的前後等執
				 */
				registerBeanPostProcessors(beanFactory);

				/**
				 * 第七步:初始化MessageSource組件(做國際化功能;消息綁定,消息解析);
				 */
				initMessageSource();

				/**
				 *  第⼋步:初始化事件派發器
				 */
				initApplicationEventMulticaster();

				/**
				 * 第九步:⼦類重寫這個⽅法,在容器刷新的時候可以⾃定義邏輯
				 */
				onRefresh();

				/**
				 *  第⼗步:註冊應⽤的監聽器。就是註冊實現了ApplicationListener接⼝的監聽器
				 * bean
				 */
				registerListeners();

				/**
				 * 第⼗⼀步:
				 *  初始化所有剩下的⾮懶加載的單例bean
				 *  初始化創建⾮懶加載⽅式的單例Bean實例(未設置屬性)
				 *  填充屬性
				 *  初始化⽅法調⽤(⽐如調⽤afterPropertiesSet⽅法、init-method⽅法)
				 *  調⽤BeanPostProcessor(後置處理器)對實例bean進⾏後置處
				 */
				finishBeanFactoryInitialization(beanFactory);
				
				/**
				 * 第⼗⼆步:
				 *  完成context的刷新。主要是調⽤LifecycleProcessor的onRefresh()⽅法,並且發佈事
				 * 件 (ContextRefreshedEvent)
				 */
				finishRefresh();
			}
			catch (BeansException ex) {
				...
			}
			finally {
				resetCommonCaches();
			}
		}
	}

創建 BeanFactory 的流程

通過整體流程,在refresh() 方法中第二步,獲取beanFactory。獲取的子流程發生在obtainFreshBeanFactory() 方法中。

AbstractRefreshableApplicationContextprotected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
		//刷新Beanfactory,調用AbstractRefreshableApplicationContext
		refreshBeanFactory();
		//獲取Beanfactory
		return getBeanFactory();
	}
//AbstractRefreshableApplicationContext.java
protected final void refreshBeanFactory() throws BeansException {
		//判斷BeanFactory 中是否存在Bean。存在就銷燬Bean 並關閉beanfactory
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
			// 創建一個DefaultListableBeanFactory
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			//序列化beanFactory的id
			beanFactory.setSerializationId(getId());

			//自定義Bean工廠的一些屬性
			customizeBeanFactory(beanFactory);
			//加載應用的beanDefitions
			loadBeanDefinitions(beanFactory);
			synchronized (this.beanFactoryMonitor) {
				this.beanFactory = beanFactory;
			}
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}

image-20200601180122219

BeanDefifinition 加載解析註冊 流程

Resource定位:指對BeanDefifinition的資源定位過程。通俗講就是找到定義 Javabean 信息的XML⽂

件,並將其封裝成Resource對象。

BeanDefifinition載⼊ :把⽤戶定義好的Javabean表示爲IoC容器內部的數據結構,這個容器內部的數

據結構就是BeanDefifinition

過程

  • 判斷BeanFactory 中是否存在Bean。存在就銷燬Bean 並關閉beanfactory

  • 創建一個DefaultListableBeanFactory

  • 序列化beanFactory的id

  • 自定義beanFactory的一些屬性

  • 加載應用的beanDefinitions

    • 讀取XML 信息 ,將 XML 信息保存到Document對象中。

    • 解析document 對象,封裝BeanDefinition 對象並進行註冊

      • 獲取已有的BeanDefinition對象的個數

      • 註冊BeanDefinition

      • 返回新註冊的BeanDefinition的數量

所謂的註冊就是把封裝的 XML 中定義的 Bean信息封裝爲 BeanDefifinition 對象之後放⼊⼀個CurrentHashMap中,BeanFactory 是以 CurrentHashMap的結構組織這些 BeanDefifinition的。

image-20200602200851638

Bean創建流程

Bean的創建流程發生在AbstractApplicationContext.refresh()⽅法 finishBeanFactoryInitialization(beanFactory) 處。

做了如下操作:

*  初始化所有剩下的⾮懶加載的單例bean
*  初始化創建⾮懶加載⽅式的單例Bean實例(未設置屬性)
*  填充屬性
*  初始化⽅法調⽤(⽐如調⽤afterPropertiesSet⽅法、init-method⽅法)
*  調⽤BeanPostProcessor(後置處理器)對實例bean進⾏後置處

springBean 的生命週期

1、根據配置情況調用 Bean的構造方法或者工廠方法實例化Bean

2、利用依賴注入完成Bean的所以屬性值的配置注入

3、如果Bean 實現了BeanNameAware 接口,則spring 調用Bean的setBeanName() 傳入當前Bean的id

@Override
	public void setBeanName(String name) {
		System.out.println(name);
	}

4、如果Bean實現了BeanFactoryAware 接口,調用setBeanFactory() 方法傳入當前工廠實例的引用

@Override
	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		System.out.println(beanFactory);
	}

5、如果Bean 實現了ApplicationContextAware 接口,通過調用setApplicationContext 傳入當前applicationContext 實例的引用。

@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		System.out.println(applicationContext);
	}

6、如果BeanPostProcessor 和Bean 關聯,則Spring 將調用改接口的預初始化方法。postProcessBeforeInitialization() 是前置處理的方法,Spring的AOP就是利用它實現的。

7、如果Bean 實現了InitializingBean 接口,需要實現afterPropertiesSet 方法

@Override
	public void afterPropertiesSet() throws Exception {
		System.out.println("afterPropertiesSet");
	}

8、如果在配置文件中通過init-method 屬性指定了初始化方法。則的調用該方法。

9、如果BeanPostProcessor 和Bean 關聯,則Spring 將調用該接口的初始化方法postProcessAfterInitialization().此時Bean可以被應用系統使用。

10、如果在Bean標籤中指定了Bean的作用範圍的scope=“singleton” 則將改Bean 方法singletonObjects的緩存池中

11、如果Bean 實現了DisposableBean 接口,則spring會調用destory() 方法 將Spring中的Bean銷燬。

image-20200603092052274

延時加載

在初始化階段,會將所有的xml 中的Bean信息都解析到BeanDefinition 對象中保存到hashmap 中等待初始化。然後在refresh() 中的finishBeanFactoryInitialization。

image-20200603092653388

然後到preInstantiateSingletons() 方法。

image-20200603092735506

也就是說在Bean實例化階段只會實例化非抽象的,單例的,非延遲加載的Bean。並將Bean放到緩存池singletonObjects 中,然後在取用的時候直接從singletonObjects 中取用。

而設置了延時加載的Bean,在調用的時候。在getBean() 時纔會初始化,最終方法緩存中。

循環依賴

循環依賴,就是在A 對象實例化時依賴B 對象,在實例化B 對象時依賴A對象。那麼Spring是怎麼解決這個問題的,只有當通過setter 方式注入或者註解方式注入的時候,纔可以解決循環依賴的問題,而通過構造器方式注入,不能解決循環依賴。因爲通過構造器實例化一個對象,就直接實例化完成了,沒有辦法在實例化的過程中做到處理。

image-20200603094132061

spring 解決循環依賴的方法:

利用三級緩存:

一級:singletonObjects
二級:earlySingletonObjects
三級:singletonFactories

image-20200603145753189

流程:

1、先觸發所以非延時加載的Bean實例化。

image-20200603133419108

2、是 getBean 的時候會先從一級緩存池中檢查是否存在,並且判斷時候正在創建。這個時候 aBean 還沒有開始創建,所以是 false,所以直接返回 null.
在這裏插入圖片描述

3、返回的是 null 就說明沒有創建,所以就開始創建。調用 createBean() 來創建Bean

image-20200603134318256

4、createBean 實際調用 doCreateBean() 來創建 Bean

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-3h5Pp0EM-1591430719224)(http://zlf.quellanan.xyz/image-20200603135004874.png)]

5、在 doCreateBean() 方法中,會先通過構造方法創建 Bean 實例,但是這個時候Bean還沒有設置屬性。

image-20200603135307089

6、判斷是否需要提前暴露,需要的話就將 Bean 放入三級緩存中。

image-20200603135558110

image-20200603135825084

7、開始爲 Abean 對象添加屬性。調用 populateBean() 方法。

image-20200603140101600

8、在 populateBean() 方法中調用 applyPropertyValues() 處理屬性依賴。

image-20200603140515401

9、通過一系列的調用,在 resolveReference() 中要衝 beanfactory 中獲取 bBean 的實例爲 aBean 填充屬性。

image-20200603140911230

10、在獲取 bBean 的時候發現,發現 bBean,發現 bBean 沒有創建,就開始創建。和 aBean 創建的步驟相同。只到爲 bBean填充屬性的時候,發現依賴 aBean 。所有就從 beanFactory.getBean() 中獲取 aBean。

11、在獲取 aBean 的時候從緩存中查找,一級緩存沒有,但是發現 aBean正在創建,所以就從二級緩存中取,返現沒有,接着就到三級緩存中取,取到了,做些處理,然後將 aBean 加入到了二級緩存,並在三級緩存中刪除aBean。此時二級緩存 earlySingletonObjects 中只有 aBean。而三級緩存 singletonFactories 中只有 bBean.
在這裏插入圖片描述

12、獲取到 aBean 後,直接返回 Bean

image-20200603142928573

13、那麼就回到了爲 bBean 填充屬性 aBean 的時候,獲取 aBean 的地方。

image-20200603143218814

14、這樣 bBean 就完成了屬性注入,後面的就是其他操作了,之到 bBean 完成實例化了。

image-20200603143446221

15、bBean 在完成實例化之後,會加入到一級緩存池中。並充二級和三級緩存中刪除。

image-20200603144353082

16、這個時候就回到了 aBean 填充 bBean ,從 beanFactory.getBean() 中拿到了 bBean。所以接下來完成 aBean 的實例化流程。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-6rr43IkF-1591430719245)(http://zlf.quellanan.xyz/image-20200603144729814.png)]

17、aBean 和 bBean 一樣,將實例化完成的 aBean 加入到一級緩存,並從二級和三級緩存中刪除掉。
image-20200603145404637

到此循環依賴的問題就解決了。

類和方法的大致跳轉流程:

  • DefaultListableBeanFactory#preInstantiateSingletons//入口
    • AbstractBeanFactory#getBean()//創建BeanA
    • AbstractBeanFactory#doGetBean()//創建Bean A
      • DefaultSingletonBeanRegistry#getSingleton()//判斷是否創建,沒有繼續
      • DefaultSingletonBeanRegistry#getSingleton()調用createBean() 創建
        • AbstractAutowireCapableBeanFactory#createBean()
        • AbstractAutowireCapableBeanFactory#doCreateBean()
          • DefaultSingletonBeanRegistry#addSingletonFactory()//加入三級緩存
          • AbstractAutowireCapableBeanFactory#populateBean()//填充屬性
            • AbstractAutowireCapableBeanFactory#applyPropertyValues
              • BeanDefinitionValueResolver#resolveValueIfNecessary
              • BeanDefinitionValueResolver#resolveReference//發現BeanA依賴BeanB
                • AbstractBeanFactory#getBean()//獲取BeanB
                • AbstractBeanFactory#doGetBean()//創建Bean B
                • …重複BeanA 的操作
                • DefaultSingletonBeanRegistry#getSingleton()//從三級緩存中取到了BeanA,加入二級緩存
                • Bean B 完成了實例化。
                • DefaultSingletonBeanRegistry#addSingleton//添加到一級緩存中。
            • Bean A填充了 屬性
          • 完成實例化
          • 將Bean A 加入一級緩存,從二級和三級緩存中刪除。

時序圖:

循環依賴加載時序圖

Spring AOP 應用

AOP的概念

連接點:方法開始時,結束時,正常運行完畢時,方法異常時等這些特殊的時機點,我們稱之爲連接點,項目中每個方法都有連接點,連接點是候選的點。

切入點:指定AOP 思想想要影響的具體方法有哪些。

Advice增強

​ 1、指的是橫切邏輯

​ 2、方位點,在某一些連接點上加入橫切邏輯,那麼這些連接點就是方位點,描述具體的切入時機。

Aspect 切面 是切入點加增強

Aspect切⾯= 切⼊點+增強

​ = 切⼊點(鎖定⽅法) + ⽅位點(鎖定⽅法中的特殊時機)+ 橫切邏輯

這些定義主要是爲了鎖定在哪個地方插入橫切邏輯代碼

spring 實現AOP 思想主要是通過動態代理。

動態代理有JDK 動態代理和cglib 動態代理。

  • jdk 動態代理需要代理對象實現接口
  • cglib 動態代理不用實現代理接口

默認情況下,spring 會根據被代理的對象是否實現了接口來選擇jdk動態代理還是cglib 動態代理。也可以通過配置強制spring 是用哪種代理。

AOP代理方式

有三種

  • 純註解
  • XML + 註解
  • 純XML

XML

配置:

<beans xmlns="
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
">


<!--aspect   =    切入點(鎖定方法) + 方位點(鎖定方法中的特殊時機)+ 橫切邏輯 -->
    <aop:config>
        <aop:aspect id="logAspect" ref="logUtils">

            <!--切入點鎖定我們感興趣的方法,使用aspectj語法表達式-->
            <!--<aop:pointcut id="pt1" expression="execution(* *..*.*(..))"/>-->
            <aop:pointcut id="pt1" expression="execution(* com.lagou.edu.service.impl.TransferServiceImpl.*(..))"/>


            <!--方位信息,pointcut-ref關聯切入點-->
            <!--aop:before前置通知/增強-->
            <aop:before method="beforeMethod" pointcut-ref="pt1"/>
            <!--aop:after,最終通知,無論如何都執行-->
            <!--aop:after-returnning,正常執行通知-->
            <aop:around method="arroundMethod" pointcut-ref="pt1"/>

        </aop:aspect>
    </aop:config>

代碼:

public class LogUtils {

    /**
     * 業務邏輯開始之前執行
     */
    public void beforeMethod(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            Object arg = args[i];
            System.out.println(arg);
        }
        System.out.println("業務邏輯開始執行之前執行.......");
    }

    /**
     * 業務邏輯結束時執行(無論異常與否)
     */
    public void afterMethod() {
        System.out.println("業務邏輯結束時執行,無論異常與否都執行.......");
    }

    /**
     * 異常時時執行
     */
    public void exceptionMethod() {
        System.out.println("異常時執行.......");
    }

    /**
     * 業務邏輯正常時執行
     */
    public void successMethod(Object retVal) {
        System.out.println("業務邏輯正常時執行.......");
    }

    /**
     * 環繞通知
     *
     */
    public Object arroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("環繞通知中的beforemethod....");

        Object result = null;
        try{
            // 控制原有業務邏輯是否執行
            // result = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
        }catch(Exception e) {
            System.out.println("環繞通知中的exceptionmethod....");
        }finally {
            System.out.println("環繞通知中的after method....");
        }

        return result;
    }

}

XML +註解

xml 中增加註解驅動

<!--開啓aop註解驅動
    proxy-target-class:true強制使用cglib
-->
<aop:aspectj-autoproxy/>

代碼:

@Component
@Aspect
public class LogUtils {


    @Pointcut("execution(* com.lagou.edu.service.impl.TransferServiceImpl.*(..))")
    public void pt1(){

    }


    /**
     * 業務邏輯開始之前執行
     */
    @Before("pt1()")
    public void beforeMethod(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < args.length; i++) {
            Object arg = args[i];
            System.out.println(arg);
        }
        System.out.println("業務邏輯開始執行之前執行.......");
    }


    /**
     * 業務邏輯結束時執行(無論異常與否)
     */
    @After("pt1()")
    public void afterMethod() {
        System.out.println("業務邏輯結束時執行,無論異常與否都執行.......");
    }


    /**
     * 異常時時執行
     */
    @AfterThrowing("pt1()")
    public void exceptionMethod() {
        System.out.println("異常時執行.......");
    }


    /**
     * 業務邏輯正常時執行
     */
    @AfterReturning(value = "pt1()",returning = "retVal")
    public void successMethod(Object retVal) {
        System.out.println("業務邏輯正常時執行.......");
    }

    /**
     * 環繞通知
     *
     */
    @Around("pt1()")
    public Object arroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("環繞通知中的beforemethod....");

        Object result = null;
        try{
            // 控制原有業務邏輯是否執行
            // result = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
        }catch(Exception e) {
            System.out.println("環繞通知中的exceptionmethod....");
        }finally {
            System.out.println("環繞通知中的after method....");
        }

        return result;
    }
}

純註解

替換配置文件中開啓的功能的配置。

所以在config 中增加配置:

@EnableAspectJAutoProxy 

image-20200603194135236

web.xml


<web-app>
  <display-name>Archetype Created Web Application</display-name>

  <!--配置Spring ioc容器的配置類-->
  <context-param>
    <param-name>contextClass</param-name>
    <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
  </context-param>

  <!--配置Spring ioc容器的配置文件-->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <!--<param-value>classpath:applicationContext.xml</param-value>-->
    <param-value>com.lagou.edu.config.SpringConfig</param-value>
  </context-param>
  <!--使用監聽器啓動Spring的IOC容器-->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
</web-app>

Spring 聲明式事物支持

事務

事務一般是指數據庫事務,表示一組邏輯處理,這一組處理中所有邏輯單元要麼都全部成功,要麼都全部失敗。確保數據的一致性和安全性。

事務的特性

事物的四大特性:ACID ,一致性,原子性,隔離性,持續性

  • 原⼦性(Atomicity):表示事務的操作是原子的,不可分割的最小單元。要麼全部執行,要麼全部不執行。
  • ⼀致性(Consistency):表示事務發生錢前後,數據庫的狀態是一致的,比如a 和賬戶減100,b,賬戶加100,確保數據的和是不變的。
  • 隔離性(Isolation):表示事務的執行是相互隔離的,一個事務的執行不能影響另一個事務。
  • 持久性(Durability):事務操作改變數據庫的數據是永久性的,不可以回退的,也就是說事務一旦提交,那數據庫的數據就會修改。

事務的隔離級別

事務有四大隔離級別,讀未提交,讀已提交,可重複讀,串行化

  • 讀未提交:一個事務可以讀取到另個一事務沒有提交操作的數據。會產生髒讀。隔離級別最低
  • 讀已提交:一個事務只能讀取到其他事務提交後的數據。會存在不可重複讀問題,隔離級別第二。不可重複讀,主要是一個事務在前後兩次查詢期間,另一個事務提交了,導致前後讀取的兩次結果不一致。
  • 可重複讀:一個事務在執行過程中,讀取到的數據都是一樣的。解決上面不可重複的讀的問題。隔離級別第三。但是會產生幻讀,幻讀是前後兩次讀取獲取到的數據條數不一致。在這期間,其他事務執行了刪除獲取增加數據的操作。
  • 串行化:隔離級別最高,線程安全,一個事務在讀取的時候,會對錶加鎖,其他事務處於等待操作。

mysql 默認的隔離級別爲可重複度,但是mysql 解決的幻讀問題,主要也是利用鎖機制,不過不是鎖表,而是鎖定行。

事務的傳播行爲

事務往往在service層進⾏控制,如果出現service層⽅法A調⽤了另外⼀個service層⽅法B,A和B⽅法本

身都已經被添加了事務控制,那麼A調⽤B的時候,就需要進⾏事務的⼀些協商,這就叫做事務的傳播⾏

爲。

A調⽤B,我們站在B的⻆度來觀察來定義事務的傳播⾏爲

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

代碼

聲明式事務,增加註解就可以了

@EnableTransactionManagement 
@Transactional

Spring AOP 源碼深度剖析

手寫AOP和IOC 代碼講解

基於XML 模式

環境搭建

  • 創建一個maven 項目,將webapp 目錄導入到src/main 目錄下。

  • 數據庫創建一張bank表以及初始化數據

    DROP TABLE IF EXISTS `account`;
    CREATE TABLE `account` (
      `cardNo` varchar(20) NOT NULL,
      `money` int DEFAULT NULL,
      `name` varchar(20) DEFAULT NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    -- ----------------------------
    -- Records of account
    -- ----------------------------
    INSERT INTO `account` VALUES ('6029621011000', '1000', '李大雷');
    INSERT INTO `account` VALUES ('6029621011001', '1000', '韓梅梅');
    

代碼編寫

pojo 實體類

public class Account {

    private String cardNo;
    private String name;
    private int money;
    //getter/setter 方法
}

public class Result {

    private String status;
    private String message;
    //getter/setter 方法
}

DAO層

接口:

public interface AccountDao {

    Account queryAccountByCardNo(String cardNo) throws Exception;

    int updateAccountByCardNo(Account account) throws Exception;
}

實現類:就是一個轉賬一個查詢 的操作。這裏的connectionUtils是直接new 的後續通過set或者註解注入。

public class AccountDaoImpl implements AccountDao {

    private ConnectionUtils connectionUtils=new ConnectionUtils() ;

    @Override
    public Account queryAccountByCardNo(String cardNo) throws Exception {
        Connection con = connectionUtils.getCurrentThreadConn();
        String sql = "select * from account where cardNo=?";
        PreparedStatement preparedStatement = con.prepareStatement(sql);
        preparedStatement.setString(1,cardNo);
        ResultSet resultSet = preparedStatement.executeQuery();

        Account account = new Account();
        while(resultSet.next()) {
            account.setCardNo(resultSet.getString("cardNo"));
            account.setName(resultSet.getString("name"));
            account.setMoney(resultSet.getInt("money"));
        }

        resultSet.close();
        preparedStatement.close();
        return account;
    }

    @Override
    public int updateAccountByCardNo(Account account) throws Exception {

        // 從連接池獲取連接
        // 改造爲:從當前線程當中獲取綁定的connection連接
        Connection con = connectionUtils.getCurrentThreadConn();
        String sql = "update account set money=? where cardNo=?";
        PreparedStatement preparedStatement = con.prepareStatement(sql);
        preparedStatement.setInt(1,account.getMoney());
        preparedStatement.setString(2,account.getCardNo());
        int i = preparedStatement.executeUpdate();

        preparedStatement.close();
        return i;
    }
}

service 層

這裏一樣的調DAO 直接new 的。

public interface TransferService {

    void transfer(String fromCardNo, String toCardNo, int money) throws Exception;
}



public class TransferServiceImpl implements TransferService{



    private AccountDao accountDao=new AccountDaoImpl();

    @Override
    public void transfer(String fromCardNo, String toCardNo, int money) throws Exception {

            Account from = accountDao.queryAccountByCardNo(fromCardNo);
            Account to = accountDao.queryAccountByCardNo(toCardNo);

            from.setMoney(from.getMoney()-money);
            to.setMoney(to.getMoney()+money);

            accountDao.updateAccountByCardNo(to);
            int c = 1/0;
            accountDao.updateAccountByCardNo(from);
    }
}

servlet 層

這裏沒有寫controller ,應爲我們要手寫ioc 和Aop 就沒喲引入spring的任何東西,spring的controller 實際上就是一個經過封裝的servlet 。所以我們這裏直接用一個servlet 來代替。代碼都很簡單就是在 dopost 或者doget 方法中寫我們想要執行的代碼。


@WebServlet(name="transferServlet",urlPatterns = "/transferServlet")
public class TransferServlet extends HttpServlet {


    private TransferService transferService;


    @Override
    public void init() throws ServletException {

        transferService = new TransferServiceImpl();
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        // 設置請求體的字符編碼
        req.setCharacterEncoding("UTF-8");

        String fromCardNo = req.getParameter("fromCardNo");
        String toCardNo = req.getParameter("toCardNo");
        String moneyStr = req.getParameter("money");
        int money = Integer.parseInt(moneyStr);

        Result result = new Result();

        try {

            // 2. 調用service層方法
            transferService.transfer(fromCardNo,toCardNo,money);
            result.setStatus("200");
        } catch (Exception e) {
            e.printStackTrace();
            result.setStatus("201");
            result.setMessage(e.toString());
        }

        // 響應
        resp.setContentType("application/json;charset=utf-8");
        resp.getWriter().print(JsonUtils.object2Json(result));
    }
}

還又一個數據庫連接的類

public class ConnectionUtils {

    private ThreadLocal<Connection> threadLocal = new ThreadLocal<>(); // 存儲當前線程的連接

    /**
     * 從當前線程獲取連接
     */
    public Connection getCurrentThreadConn() throws SQLException {
        //判斷當前線程中是否已經綁定連接,如果沒有綁定,需要從連接池獲取一個連接綁定到當前線程
        Connection connection = threadLocal.get();
        if(connection == null) {
            // 從連接池拿連接並綁定到線程
            connection = DruidUtils.getInstance().getConnection();
            // 綁定到當前線程
            threadLocal.set(connection);
        }
        return connection;

    }
}

xml 編寫和代碼調整

上面是基礎代碼,也可以說是業務代碼。這裏我們就不多說,我們主要來分析,我們主要考慮下面幾個問題:

1、DAO 層調 ConnectionUtils 連接工具使用的是new .怎麼實現IOC的功能實現依賴注入呢?

2、service 層一樣的調用DAO 一樣的實現IOC

3、轉賬一個用戶扣錢一個用戶加錢,怎確保事物一致性?

基於上面這三個問題,我們先用基於XML 的方式來處理。

創建一個beans.xml

內容如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!--跟標籤beans,裏面配置一個又一個的bean子標籤,每一個bean子標籤都代表一個類的配置-->
<beans>
    <!--id標識對象,class是類的全限定類名-->
    <bean id="accountDao" class="cn.quellanan.dao.impl.AccountDaoImpl">
        <property name="ConnectionUtils" ref="connectionUtils"/>
    </bean>
    <bean id="transferService" class="cn.quellanan.service.impl.TransferServiceImpl">
        <!--set+ name 之後鎖定到傳值的set方法了,通過反射技術可以調用該方法傳入對應的值-->
        <property name="AccountDao" ref="accountDao"></property>
    </bean>


    <!--配置新增的三個Bean-->
    <bean id="connectionUtils" class="cn.quellanan.utils.ConnectionUtils"></bean>

    <!--事務管理器-->
    <bean id="transactionManager" class="cn.quellanan.utils.TransactionManager">
        <property name="ConnectionUtils" ref="connectionUtils"/>
    </bean>

    <!--代理對象工廠-->
    <bean id="proxyFactory" class="cn.quellanan.factory.ProxyFactory">
        <property name="TransactionManager" ref="transactionManager"/>
    </bean>
</beans>

可以看到和spring中配置的有點類似。如果要注入一個對象,那就給這個類添加一個Bean標籤,這個對象需要注入的屬性是用property 標籤。

BeanFactory

我們有了這個配置文件,那怎麼初始化他們呢?

所以我們來創建一個BeanFactory 來解析初始化他們


public class BeanFactory {

/**
     * 任務一:讀取解析xml,通過反射技術實例化對象並且存儲待用(map集合)
     * 任務二:對外提供獲取實例對象的接口(根據id獲取)*/



    private static Map<String, Object> map = new HashMap<>();  // 存儲對象


    static {
        // 加載xml
        InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
        // 解析xml
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();
            List<Element> beanList = rootElement.selectNodes("//bean");
            for (int i = 0; i < beanList.size(); i++) {
                Element element =  beanList.get(i);
                // 處理每個bean元素,獲取到該元素的id 和 class 屬性
                String id = element.attributeValue("id");        // accountDao
                String clazz = element.attributeValue("class");  // com.lagou.edu.dao.impl.JdbcAccountDaoImpl
                // 通過反射技術實例化對象
                Class<?> aClass = Class.forName(clazz);
                Object o = aClass.newInstance();  // 實例化之後的對象
                // 存儲到map中待用
                map.put(id,o);

            }

            // 實例化完成之後維護對象的依賴關係,檢查哪些對象需要傳值進入,根據它的配置,我們傳入相應的值
            // 有property子元素的bean就有傳值需求
            List<Element> propertyList = rootElement.selectNodes("//property");
            // 解析property,獲取父元素
            for (int i = 0; i < propertyList.size(); i++) {
                Element element =  propertyList.get(i);   //<property name="AccountDao" ref="accountDao"></property>
                String name = element.attributeValue("name");
                String ref = element.attributeValue("ref");

                // 找到當前需要被處理依賴關係的bean
                Element parent = element.getParent();

                // 調用父元素對象的反射功能
                String parentId = parent.attributeValue("id");
                Object parentObject = map.get(parentId);
                // 遍歷父對象中的所有方法,找到"set" + name
                Method[] methods = parentObject.getClass().getMethods();
                for (int j = 0; j < methods.length; j++) {
                    Method method = methods[j];
                    if(method.getName().equalsIgnoreCase("set" + name)) {  // 該方法就是 setAccountDao(AccountDao accountDao)
                        method.invoke(parentObject,map.get(ref));
                    }
                }

                // 把處理之後的parentObject重新放到map中
                map.put(parentId,parentObject);

            }


        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }

    // 任務二:對外提供獲取實例對象的接口(根據id獲取)
    public static Object getBean(String id) {
        return map.get(id);
    }

}

然後將通過new 創建的,去掉,增加一個屬性的set 方法。如這種格式:

private ConnectionUtils connectionUtils;

public void setConnectionUtils(ConnectionUtils connectionUtils) {
	this.connectionUtils = connectionUtils;
}

事物一致性

創建一個代理對象,通過動態代理將事物邏輯封裝起來,然後切入到業務邏輯中,也就是我們spring 的AOP實現。

我們先也一個事物控制的類。

public class TransactionManager {

    @Autowired
    private ConnectionUtils connectionUtils;

    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }

    // 開啓手動事務控制
    public void beginTransaction() throws SQLException {
        connectionUtils.getCurrentThreadConn().setAutoCommit(false);
        System.out.println("開啓事務.....");
    }


    // 提交事務
    public void commit() throws SQLException {
        connectionUtils.getCurrentThreadConn().commit();
        System.out.println("提交事務.....");
    }

    // 回滾事務
    public void rollback() throws SQLException {
        connectionUtils.getCurrentThreadConn().rollback();
        System.out.println("回滾事務.....");
    }
}

主要就三個方法,開啓事物,成功提交事物,失敗回滾事物。

然後我們創建一個代理類,用來處理代理的對象,這裏我們需要對TransferServiceImpl 這個類進行代理。

ProxyFactory

public class ProxyFactory {


    @Autowired
    private TransactionManager transactionManager;


    /**
     * Jdk動態代理
     * @param obj  委託對象
     * @return   代理對象
     */
    public Object getJdkProxy(Object obj) {

        // 獲取代理對象
        return  Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object result = null;

                        try{
                            // 開啓事務(關閉事務的自動提交)
                            transactionManager.beginTransaction();

                            result = method.invoke(obj,args);
                            //提交
                            transactionManager.commit();

                        }catch (Exception e) {
                            e.printStackTrace();
                            // 回滾事務
                            transactionManager.rollback();

                            // 拋出異常便於上層servlet捕獲
                            throw e;

                        }

                        return result;
                    }
                });

    }


    /**
     * 使用cglib動態代理生成代理對象
     * @param obj 委託對象
     * @return
     */
    public Object getCglibProxy(Object obj) {
        return  Enhancer.create(obj.getClass(), new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                Object result = null;
                try{
                    // 開啓事務(關閉事務的自動提交)
                    transactionManager.beginTransaction();

                    result = method.invoke(obj,objects);

                    // 提交事務

                    transactionManager.commit();
                }catch (Exception e) {
                    e.printStackTrace();
                    // 回滾事務
                    transactionManager.rollback();

                    // 拋出異常便於上層servlet捕獲
                    throw e;

                }
                return result;
            }
        });
    }
}

有兩種實現方式,jdk 動態代理和cglib 動態代理。邏輯都是一樣的。到此爲止整個就改糙完成了。只要在servlet 中獲取到map 中的TransferServiceImpl對象就可以實現ioc 和AOP 的功能。

基於註解的方式

我們不能是用xml 配置了,而是使用註解,所以需要我們實現自定義註解,這裏我們仿照spring 來實現幾個自定義的註解。

自定義註解

創建一個annotation 包,創建一個自定義註解。

Autowired

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    boolean required() default true;
}

Component

可以設置value值,默認爲“”;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Component {
    String value() default "";
}

Service

Component註解一樣,主要是不同標識

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
    String value() default "";
}

Transactional

事務註解,這裏設置一個枚舉類型的type 可以指定使用哪種動態代理。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

    ProxyType type() default ProxyType.DEFAULT;

}

public enum ProxyType {

    //jdk動態代理
    JDK,

    //cglib 動態代理
    CGLIB,

    //默認
    DEFAULT
}

代碼修改

創建好我們自定義的註解後,我們需要將這些使用xml 注入的地方改下。

AccountDaoImpl

@Service(value = "accountDao")
public class AccountDaoImpl implements AccountDao {

    @Autowired
    private ConnectionUtils connectionUtils;
    ......
}

image-20200605135346431

可以看到增加了一個自定的Autowired 註解和Service 註解,Service 註解可以設置value 值。

TransferServiceImpl

一樣添加註解@Service、@Transactional、@Autowired

@Service
@Transactional
public class TransferServiceImpl implements TransferService{


    @Autowired
    private AccountDao accountDao;
    ......
    
}

這裏注意一點,我們在service層調用的是 AccountDao接口,而實例化應該實例化AccountDaoImpl 。那這個怎麼控制呢?就可以通過上面的在AccountDaoImpl 類上的@Service 的value 值來指定。

核心代理

現在有一個問題,我們在這些類上添加了這些註解,但是我們還沒有爲這些註解賦予能力。所以他們現在還沒有什麼作用,那我們現在就來給他們賦予至高無上的權利。

核心思想:掃描項目指定包下的所有類,發現類上包含註解我們就對這個類進行對應的處理,然後將處理好呢的對象保存在map 中也就是對應spring 中的singletonObjects一級緩存池。

所以我們創建一個BeanFactoryByAnno來做這件事。

流程:

  • 提供的自定路徑獲取到包路徑名
  • 將這些包路徑名保存到set 結合中
  • 遍歷set 集合,是否包含@Service,@Component 註解,包含的話,創建Bean 保存到map 集合中
  • 上一步完成後,再次遍歷集合,判斷是否包含Autowired註解,爲屬性賦值
  • 上一步完成後,再次遍歷集合,判斷是否包含Transactional註解,包含的話說明需要創建代理對象來實現。
public static void searchClass(String basePack) throws Exception {
        Set<String> classPaths = new HashSet<>();
        //先把包名轉換爲路徑,首先得到項目的classpath
        String classpath = BeanFactoryByAnno.class.getResource("/").getPath();
        //然後把我們的包名basPach轉換爲路徑名
        basePack = basePack.replace(".", File.separator);
        //然後把classpath和basePack合併
        String searchPath = classpath + basePack;
        doPath(new File(searchPath),classPaths);
        //這個時候我們已經得到了指定包下所有的類的絕對路徑了。我們現在利用這些絕對路徑和java的反射機制得到他們的類對象
        for (String s : classPaths) {
            //把絕對路徑轉換爲全類名
            s = s.replace(classpath.replace("/","\\").replaceFirst("\\\\",""),"").replace("\\",".").replace(".class","");
            //處理@Service 註解,實例化對象
            createBeans(s);
        }

        //同理處理@Autowired 註解,爲這個屬性注入實例化的Bean。
        for (String s : classPaths) {
            //把絕對路徑轉換爲全類名
            s = s.replace(classpath.replace("/","\\").replaceFirst("\\\\",""),"").replace("\\",".").replace(".class","");
            //實例化對象
            populateBean(s);
        }

        //如果包含@Transactional註解,說明需要對這個類進行代理
        for (String s : classPaths) {
            //把絕對路徑轉換爲全類名
            s = s.replace(classpath.replace("/","\\").replaceFirst("\\\\",""),"").replace("\\",".").replace(".class","");
            //代理對象
            ProxyBean(s);
        }

    }

該方法會得到所有的類,將類的絕對路徑寫入到classPaths中

/**
     * 該方法會得到所有的類,將類的絕對路徑寫入到classPaths中
     * @param file
     */
    private static void doPath(File file,Set<String> classPaths) {
        if (file.isDirectory()) {//文件夾
            //文件夾我們就遞歸
            File[] files = file.listFiles();
            for (File f1 : files) {
                doPath(f1,classPaths);
            }
        } else {//標準文件
            //標準文件我們就判斷是否是class文件
            if (file.getName().endsWith(".class")) {
                //如果是class文件我們就放入我們的集合中。
                classPaths.add(file.getPath());
            }
        }
    }

創建Bean

private static void createBeans(String classPath) throws Exception{
        Class<?> aClass = Class.forName(classPath);
        if (aClass.isAnnotationPresent(Service.class)||aClass.isAnnotationPresent(Component.class) ) {
            // 實例化之後的對象
            Object o = aClass.newInstance();
            //獲取key
            String key = getMapKey(aClass);
            // 存儲到map中待用
            map.put(key,o);
        }
    }

上面就是爲包含有Service或者Component的註解,創建一個實例並添加到map 集合中。這裏key 我單獨封裝成一個方法。因爲要考慮到這兩個註解是可以設置value 的值的。如果設置了值那麼就用設置的值做key ,如果沒有設置值那就用當前類的類名來做key.爲了最大化兼容,將key 全部轉化成大寫。

private static String getMapKey(Class<?> aClass){
        String key = aClass.getSimpleName();

        //獲取註解的屬性值
        String value="";
        if (aClass.isAnnotationPresent(Service.class)) {
            value = aClass.getAnnotation(Service.class).value();
        }else if(aClass.isAnnotationPresent(Component.class)){
            value=aClass.getAnnotation(Component.class).value();
        }
        if(!value.isEmpty()){
            key=value;
        }
        key=key.toUpperCase();
        return key;
    }

創建完所有的含註解的Bean 後,需要爲這些Bean中包含Autowired 註解填充上屬性。也就是將Bean和Bean之間建立聯繫。

private static void populateBean(String classPath) throws  Exception{
        Class<?> aClass = Class.forName(classPath);
        //獲取類的屬性
        Field[] declaredFields = aClass.getDeclaredFields();
        for (Field field : declaredFields) {
            //如果屬性上有註解,則爲這個屬性填充Bean
            if(field.isAnnotationPresent(Autowired.class)){
                //獲取到需要填充屬性的類
                Object obj = map.get(getMapKey(aClass));
                //設置屬性可以賦值
                field.setAccessible(true);
                //獲取到填充的屬性
                String name = field.getType().getSimpleName().toUpperCase();
                //通過key 從map 中獲取到對象值
                Object value = getMapValue(name);
                //爲屬性賦值。
                if(value!=null && obj!=null){
                    field.set(obj,value);
                }
            }
        }

    }

遍歷類,並獲取類的屬性,如果屬性上包含Autowired 註解,我們就進行處理。我們先通過類從map中獲取當前類的實例。然後通過屬性的類型獲取需要注入的類。最後通過field.set()爲當前對象的屬性賦值。

這裏通過key 從map 中取值,我也做了封裝處理。主要是如果Autowired 註解獲取的是接口,具體通過子類實現的,我這裏按照慣性添加了一個IMPL 後綴獲取值。

private static Object getMapValue(String key){
        key=key.toUpperCase();
        Object value = map.get(key);

        //通過子類子類實現
        if (value == null) {
            value=map.get(key+"IMPL");
        }
        return value;
    }

然後我們接下再次遍歷,處理包含Transactional註解的,有這個註解,說明需要對這個類是是用代理對象。

private static void ProxyBean(String classPath)throws Exception{
        Class<?> aClass = Class.forName(classPath);
        //如果包含事務註解
        if (aClass.isAnnotationPresent(Transactional.class)) {

            //獲取實際對象的Bean
            String mapKey = getMapKey(aClass);
            Object mapValue = getMapValue(mapKey);
            //獲取代理對象
            ProxyFactory proxyFactory = (ProxyFactory) getMapValue("ProxyFactory");


            //獲取 Transactional 註解指定的類型
            ProxyType type = aClass.getAnnotation(Transactional.class).type();
            //判斷是否指定了某種代理
            switch (type){
                case JDK:useJdkProxy(proxyFactory,mapKey,mapValue);
                    break;
                case CGLIB:usecGLIBProxy(proxyFactory,mapKey,mapValue);
                    break;
                case DEFAULT:useDefaultProxy(aClass,proxyFactory,mapKey,mapValue);
                default:break;
            }
        }

    }

先通過類名獲取到當前類的key 和實例vaule 。然後從map 中獲取代理對象,判斷註解指定的類型。

如果是JDK就是用JDK動態代理,如果是CGLIB 就是用cglib 動態代理。如果是默認的,那就再判斷。我這裏都抽離出了單獨方法。

如果是默認,就判斷當前類是否實現接口,這裏排除實現業務接口。如果沒有就是用cglib 動態代理,如果使用了就用jdk 動態代理。

private static void useDefaultProxy(Class<?> aClass,ProxyFactory proxyFactory,String mapKey,Object mapValue){
        //判讀是否包含接口
        Class<?>[] interfaces = aClass.getInterfaces();
        //包含除了自身實現接口外的接口接口
        if(interfaces.length>1){
            useJdkProxy(proxyFactory,mapKey,mapValue);
        }else {
            usecGLIBProxy(proxyFactory, mapKey, mapValue);
       
       }
    }

JDK 動態代理,通過當前對象獲取到代理對象,代替原來的對象保存在map 中。cglib 一樣。

private static void useJdkProxy(ProxyFactory proxyFactory,String mapKey,Object mapValue){
        Object jdkProxy = proxyFactory.getJdkProxy(mapValue);
        map.put(mapKey,jdkProxy);
    }
    
private static void usecGLIBProxy(ProxyFactory proxyFactory,String mapKey,Object mapValue){
        Object cglibProxy = proxyFactory.getCglibProxy(mapValue);
        map.put(mapKey,cglibProxy);
    }
    

最後拋出一個通過key 來獲取實例的方法接口

public static Object getBean(String key) {
        return getMapValue(key);
    }

增加監聽

我們添加一個監聽,在項目啓動的時候,就初始化核心代碼。

創建一個監聽類實現ServletContextListener


public class ContextListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        String packageName = "cn.quellanan";
        try {
            BeanFactoryByAnno.searchClass(packageName);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("初始化異常。。。。");
        }

    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {

    }
}

最後在servlet 總增加修改.,主要是爲了獲取transferService 實例。

private TransferService transferService;

    @Override
    public void init() throws ServletException {
        transferService = (TransferService) BeanFactoryByAnno.getBean("transferService");
    }

測試

初始數據庫

image-20200605144516320

轉賬100

image-20200605144535673

界面報錯

image-20200605144629695

控制檯log

image-20200605144654679

image-20200605144711976

再次查看數據庫,沒有變化

image-20200605144745080

去掉異常。重啓動項目

image-20200605144832833

轉賬100,顯示成功
在這裏插入圖片描述

看控制檯日誌:

image-20200605145020798

看數據庫:

image-20200605145035343

說明整個流程都可以了,實現了轉賬成功事物提交和轉賬失敗事物回滾的操作。

番外

覺得有用的兄弟們記得收藏啊。

厚顏無恥的求波點贊!!!

厚顏無恥的求波點贊!!!

厚顏無恥的求波點贊!!!

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章