1. 程式人生 > >精通Spring+4.x++企業開發與實踐之IOC容器

精通Spring+4.x++企業開發與實踐之IOC容器

#IOC容器

#類裝載器ClassLoader

尋找類的位元組碼檔案並構造出類再JVM內部標識物件的元件。再Java中,
類裝載器吧一個類裝入JVM,需要入如步驟:
(1)裝載:查詢和匯入Class檔案.
(2)連結:執行校驗,準備和解析步驟,其中解析步驟是可以選擇的。
    1.準備:給類的靜態遍歷分配儲存空間。
    2.校驗: 檢查載入Class檔案資料的正確性。
    3.解析:將符好引用轉換成直接引用。
(3)初始化:對類的靜態變數,靜態程式碼塊執行初始化工作.

#JVM裝載類時使用的機制

JVM裝載類時使用"全盤負責委託機制","全盤負責"是指當一個ClassLoader裝載一個類時
,除非顯示的使用另一個ClassLoader,該類所依賴的類也由這個ClassLoader載入;"委託機制"
是指原先委託父裝載器尋找目標類,只有找不到的情況下才從自己的類路徑中查詢並且裝載目標類
,這也是出於安全考慮。

#Java的反射機制

Class反射物件描述類雨衣結構,可以從class物件重獲取建構函式,成員變數,方法類等類元素的反射物件,並以程式設計的方式通過這些反射物件對目標類物件進行操作。這些反射物件類在java.reflect包中定義。
	- Constructor:類的建構函式反射類,通過Class#getConstructors()方法可以獲取類的所有建構函式反射物件陣列,在Java5.0中,還可以通過getConstructor(Class...parameterTypes)獲取特定入參的建構函式反射物件。Constructor的一個主要方法事newInstance(Object[] initargs),通過改方法可以建立一個物件的例項,相當於使用new關鍵字。在Java5.0中,改方法演化為更為靈活的形式:newInstance(Onject)
	- Method:類方法的反射類,通過Class#getDeclaredMethods()方法可以獲取類的所有方法反射類物件陣列Method[].在Java5.0中,可以通過getDeclaredMethod(String name,Class...parameterTypes)獲取特定簽名的方法
	name是方法名,Class...為方法入參型別列表。Method最主要的方法是invoke(Object obj,Object[] args),其中obj標識操作的目標物件;args為方法入參。Method的其他方法如下
	getTypeParameters
	getReturnType
	getGenericReturnType
	getParameterTypes
	getExceptionTypes

	- Field:類的成員變數的反射類,通過Class#getDeclaredFields()方法可以獲取類的程願變數反射物件陣列,通過Class#getDeclaredField(String name)則可以獲取某個特定名稱的成員變數反射物件。Field類最主要的方法是set(Object obj,Object value),其中object標識操作的目標物件,通過value為目標物件的成員變數設定值,如果為基礎型別可以使用Field類中提供的待型別名的值設定方法。

例項: github:https://github.com/chenanddom/SpringSection4/blob/master/src/main/java/com/flexible/ioc/reflect/ReflectDemo.java

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectDemo {
	public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
		//類裝載器獲取Car類物件
		ClassLoader loader = Thread.currentThread().getContextClassLoader();
		Class clazz = loader.loadClass("com.flexible.ioc.reflect.Person");
		//獲取類的預設構造器物件並且通過它例項化
//        Constructor constructor = clazz.getDeclaredConstructor((Class[]) null);
		Constructor constructor = clazz.getDeclaredConstructor((Class[]) null);
		Person person = (Person) constructor.newInstance();
		//通過反射方法設定屬性
		Method setUserName = clazz.getMethod("setUserName", String.class);
		setUserName.invoke(person, "zhangsan");
		Method setUserAge = clazz.getMethod("setUserAge", Integer.class);
		setUserAge.invoke(person, 26);
		System.out.println(person.toString());
//        System.out.println(setUserAge.getReturnType());//獲取返回值型別
//        System.out.println(setUserAge.getParameterTypes());//獲取引數型別
		System.out.println(setUserAge.getParameterAnnotations());
		System.out.println(loader);
		System.out.println(loader.getParent());
		//根記載器不是ClassLoader的子類,是C++寫的,無法獲取到
		System.out.println(loader.getParent().getParent());
	}
}

#資源訪問

JDK缺少從類路徑或者Web容器上下文中獲取資源的操作類。Spring針對此限制設計了一個Resource介面,它為應用提供了更強的底層資源訪問能力。該介面用於對應的不同的資源型別的實現類。Resource介面的主要方法如下:
	//資源是否存在
	boolean exists();
	//資源是否可讀
	boolean isReadable();
	//資源是否被開啟
	boolean isOpen();
	//如果底層資源可以標識成URL,則該方法返回對應的URL物件
	URL getURL() throws IOException;
	//獲取圖片的uri
	URI getURI() throws IOException;
	//底層資源對應一個檔案,則該方法返回對應的File物件
	File getFile() throws IOException;
	//
	long contentLength() throws IOException;

	long lastModified() throws IOException;

	Resource createRelative(String var1) throws IOException;

	String getFilename();

	String getDescription();
	//因為集成了InputStreamSource,所以也有獲取流的方法
	InputStream getInputStream() throws IOException;

Resource在Spring起到了不可獲取的作用,Spring框架使用Resource裝載各種資源,包括配置檔案資源,國際化屬性檔案資源等。它的具體實現類圖如下:

- WritableResource:可寫資源介面,Spring3.1新家的介面,實現累如圖所示,FileSystemResource和PathResource(Spring4.0加的)
- ByteArrayResource->AbstractResource:繼承了AbstractResource,標識二進位制陣列的資源,二進位制陣列的資源可以在記憶體中通過程式構造。
- ClassPathResource:類路徑下的資源,資源以相對路徑的方式標識,如果程式碼所示:

public class FileSourceDemo {

public static void main(String[] args) {

    String filePath = "E:\\BaiduNetdiskDownload\\wangpan\\SpringSection4\\src\\main\\resources\\conf\\file1.txt";
    try {
    //使用系統檔案路徑方式載入檔案
    WritableResource res1 = new PathResource(filePath);
    //使用類路徑方式載入檔案
    Resource res2 = new ClassPathResource("conf/file1.txt");
    //使用WritableResource介面寫資原始檔
        OutputStream stream1 = res1.getOutputStream();
        stream1.write("這是使用spring Resource介面的例子".getBytes());
        stream1.close();
     //使用Resource介面讀資原始檔
        InputStream ins1 = res1.getInputStream();
        InputStream ins2 = res2.getInputStream();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int i;
        while ((i=ins1.read())!=-1){
            baos.write(i);
        }
        System.out.println(baos.toString());
        System.out.println("res1:"+res1.getFilename());
        System.out.println("res2:"+res2.getFilename());
    } catch (IOException e) {
        e.printStackTrace();
    }
}
}

-FileSystemResource:檔案系統資源,資源以檔案系統路徑的方式所示,如D:/conf/bean.xml.
- InpustreamResource:以輸入流返回標識的資源。
- ServletContextResource:為訪問Web容器上下文中的資源而設定的類,負責以相對於web應用根目錄的路徑載入資源,它支援以流和URL的方式訪問,在WAR解包的情況下,也可以通過File方式訪問。該類還可以直接從jar包中訪問資源。
- UrlResource:URL封裝了java.net.URL,它使使用者能夠訪問任何可以通URL標識的資源,如檔案系統的資源,HTTP資源,FTP資源等。
-PathResource:Spring4.0提供的讀取資原始檔的新類,Path封裝了java.net.URL,java.nio.file.Path(Java7提供),檔案系統資源,它使使用者呢剛剛訪問任何可以通過URL,Path,系統檔案路徑標識的資源,檔案系統,HTTP,FTP資源。

##資源載入 為了訪問不型別的資源,需要使用相應的Resource實現類,這是比較麻煩的。但是Spring提供了一套強大的載入資源的的機制,不但可以通過"classpath:","file:"等資源地址字首識別不同的資源型別,還支援Ant風格帶萬用字元的資源地址。

資源表示式如下圖所示:

##資源載入器

ResourceLoader介面僅有一個getResource(String location)方阿飛,可以根據一個資源地址載入檔案資源。不過,資源地址僅僅支援帶自u按型別字首得表示式,不支援Ant風格得資源路徑表示式。ResourcePatternResolver拓展ResourceLoader介面,定義了一個新得介面方法getResources(String locationPattern),該方法支援帶資源型別字首及Ant風格得資源路徑表示式。PathMathchingResourcePatternResolver是Spring提供得標準實現類。

程式碼實現:

package com.flexible.resources;

import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;

import static junit.framework.TestCase.assertNotNull;

public class PatternResolverDemo {
	public static void main(String[] args) throws IOException {
		ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
		//載入所有類包com.flexible.resources下的,以.xml為字尾的資原始檔
		Resource[] resources = resourcePatternResolver.getResources("classpath:com/flexible/resources/**/*.xml");
		assertNotNull(resources);
		List<Resource> resourceList = Arrays.asList(resources);
		resourceList.forEach(e->{
			System.out.println(e.getFilename());
		});
	}
}

提示: 使用Resource操作檔案時,如果資源配置檔案在專案釋出時會被達到JAR中,,那麼不能使用Resource#getFile()方法,否則會被丟擲FileNotFoundException.但是可以使用Resource#getInputStream方法讀取。 錯誤的方式: (new DefaultResourceLoader()).getResource("classpath:conf/sys.properties").getFile() 正確的讀取方式: (new DefaultResourceLoader()).getResource("classpath:conf/sys.properties").getInputStream() 在Jar包中getFile()無法讀取,應該儘量使用流來處理。

#BeanFactory和ApplicationContext 在Spring中是通過配置檔案描述Bean和Bean之間的依賴關係,利用Java語言的反射功能例項化Bean並簡歷 Bean之間的依賴關係。除此之外還提供了Bean的例項快取,生命週期管理,Bean例項代理,事件釋出,資源裝載等高階服務。 一般稱BeanFactory為IoC容器,而稱ApplicationContext為應用上下文,但是為了方便也將ApplicationContext成為Spring容器。BeanFactory是面向Spring本身的,ApplicationContext是面向開發者的。

###BeanFactory 可以被建立和管理的Java物件常被成為Bean,但是這個JavaBean是要滿足一定的規範的,例如需要提供ige預設的不帶引數的建構函式,不依賴於某特定的容器等,但是Spring的Bean比JavaBean更加的廣泛一些,所以可以被Spring容器例項化並甘麗的Java類可以成為Bean。

  • BeanFactory介面位於類結構樹的頂端,它最主要的方法就是getBean(String beanName),該方法從容器中返回特定名稱的Bean.BeanFactory的功能通過其他介面得到不斷的拓展。

    • ListableBeanFactory:該介面定義了訪問容器中Bean基本資訊的若干方法,例如檢視Bean的個數,獲取某一型別的Bean配置名,檢視容器是否包括某一個Bean等。 containsBeanDefinition getBeanDefinitionCount getBeanDefinitionNames getBeanNamesForType getBeanNamesForType getBeanNamesForType getBeansOfType getBeansOfType getBeanNamesForAnnotation getBeansWithAnnotation findAnnotationOnBean
    • HierachicalBeanFactory:父子級聯IoC容器的介面,子容器可以通過介面方法訪問父容器的介面。

      getParentBeanFactory
      containsLocalBean
      
    • CofigurableBeanFactory:這是一個重要的介面增強了IoC容器的可定製性。它定義了設定類裝載器的,屬性編輯器,容器初始化後置處理器等方法。 setParentBeanFactory setBeanClassLoader getBeanClassLoader setTempClassLoader getTempClassLoader setCacheBeanMetadata isCacheBeanMetadata setBeanExpressionResolver getBeanExpressionResolver setConversionService getConversionService addPropertyEditorRegistrar registerCustomEditor copyRegisteredEditorsTo setTypeConverter getTypeConverter addEmbeddedValueResolver hasEmbeddedValueResolver resolveEmbeddedValue addBeanPostProcessor getBeanPostProcessorCount registerScope getRegisteredScopeNames getRegisteredScope getAccessControlContext copyConfigurationFrom registerAlias resolveAliases getMergedBeanDefinition isFactoryBean setCurrentlyInCreation isCurrentlyInCreation registerDependentBean getDependentBeans getDependenciesForBean destroyBean destroyScopedBean destroySingletons
    • AutowireCapableBeanFactory:定義了將容器中的Bean按照某種規則(名字,型別匹配等)進行自動裝配的方法。

      createBean
      autowireBean
      configureBean
      createBean
      autowire
      autowireBeanProperties
      applyBeanPropertyValues
      initializeBean
      applyBeanPostProcessorsBeforeInitialization
      applyBeanPostProcessorsAfterInitialization
      destroyBean
      resolveNamedBean
      resolveDependency
      resolveDependency
      
    • SingletonBanRegistry:定義允許在執行期間向容器註冊單例項Bean的方法,

      registerSingleton
      getSingleton
      containsSingleton
      getSingletonNames
      getSingletonCount
      getSingletonMutex
      
    • BeanDefinitionRegistry:Spring配置檔案中每一個<bean></bean>節點元素在Spring容器裡都通過一個BeanDefinition物件標識,它描述了Bean的配置資訊,而BeanDefinitionRegistry介面提了向容器手工註冊BeanDefinition物件的方法。 registerBeanDefinition removeBeanDefinition getBeanDefinition containsBeanDefinition getBeanDefinitionNames getBeanDefinitionCount isBeanNameInUse

#WebApplicationContext類體結構 WebApplicationContext是專門為Web準備的,它允許從相對於web根目錄的路徑裝載配置檔案完成初始化工作。從WebApplicationContext中可以獲得ServletContext的引用,整個Web應用上下文物件將作為屬性放置到ServletContext中,以便Web應用環境可以訪問Spring應用上下文。Spring專門為此提供了已個工具類WebApplicationContextUtils,通過該類的getWebApplicationContext(ServletContext sc)方法,可以從ServletContext中獲取WebApplicationContext例項。WebApplicationContext下的Bean除了singleton和prototype兩種作用域之外還有request,session和global session

由於Web應用比一般的應用有更多的特性,因此WebApplicationContext拓展了ApplicationContext.WebApplicationContext定義了一個常量ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,在上下文起到的時候,WebApplicationContext例項即以此為鍵放置在ServletContext的屬性列表中,可以通過以下語句從Web容器中獲取WebApplicationContext:

WebApplication wac = (WebApplicationContext)servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);

這個就是WebApplicationContextUtils工具類getWebApplicationContext(ServletContext sc)方法的內部實現方式,這樣Spring的Web應用上下文就和Web容器上下文應用就可以實現互訪。

ConfigurableWebApplicationContext拓展了WebApplication,它允許通過配置的方式例項化WebApplicationContext,同時定義了兩個重要的方法

  • void setServletContext(ServletContext servletContext);為spring設定web應用上下文,以便二者結合

  • void setConfigLocations(String... configLocations);允許載入帶字首的配置檔案

###父子容器

通過HierachicalBeanFactory介面,Spring的IoC容器可以簡歷父子層級關聯的容器體系,子容器可以訪問父容器的Bean,但是父容器無法訪問自容器的內容,SpringMvc中,展現層Bean位於一個子容器中,而業務層和持久層的Bean位於父容器中,這樣展現層就可以引用業務層和持久層。而業務和持久層看不到展現層的Bean。

#Bean的生命週期

###BeanFactory中Bean的生命週期 具體的過程如下:

1.當呼叫者通過getBean(beanName)向容器請求某一個Bean時,如果容器註冊了org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor介面,則在例項化Bean之前,將呼叫postProcessBeforeInstantiation()方法
2.根據配置檔案的情況呼叫Bean建構函式或者工廠方法例項化Bean。
3.如果容器註冊了org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor,那麼例項化Bean之後呼叫介面postProcessAfterInstantiation()方法,可以裝載這裡對已經例項化的物件進行一些"裝飾"
4.如果Bean配置了屬性資訊,那麼容器在這一步著手將配置檔案中的屬性值設定到對應的屬性,設定每個屬性之前先呼叫org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor介面的postProcessPropertyValue()方法。
5.呼叫Bean的屬性設定方法設定屬性
6.如果Bean實現了org.springframework.beans.factory.BeanNameAware介面,那麼簡化呼叫setBeanName()介面方法,將配置檔案中搞Bean對應的名稱設定到Bean中。
7.如果Bean實現了org.springframework.beans.factory.BeanFactoryAware介面,則將呼叫setBeanFactory()介面方法,將BeanFactory容器例項設定給Bean。
8.如果BeanFactory裝配了org.springframework.beans.factory.config.BeanPostProcessor後處理器,則將呼叫
postProcessBeforeInitialization(Object bean, String beanName)介面方法對Bean進行加工操作,bean就是當前需要加工操作的Bean,而BeanName就是當前Bean的配置名。返回的對現貨加工處理後的Bean。這個是實現AOP的重要基石。
9.如果Bean實現了org.springframework.beans.factory.InitializingBean介面則呼叫	void afterPropertiesSet() 方法
10.如果<bean>中榮光init-method屬性定義了初始化方法,則將執行這個方法。
11.BeanPostProcessor後處理器定義了兩個方法,一.Object postProcessBeforeInitialization(Object bean, String beanName)步驟8呼叫。二.Object postProcessAfterInitialization(Object bean, String beanName) ,此時呼叫它可以獲得對Bean的再次加工。
12.作用域是prototype的將返回生成的bean給呼叫者,呼叫者負責該bean的生命週期,如果是singleton,那麼則將Bean放入SpringIoC容器的緩衝池中,並且將Bean引用返回給呼叫者,Spring繼續對這些Bean進行後續的生命週期的維護。
13.作用域是singleton的Bean,當容器關閉,如果實現DisposableBean介面,則將呼叫介面的destroy()方法,可以編寫釋放資源,記錄日誌等操作。
14.對於singleton的Bean,如果配置了destroy-method屬性指定了Bean的銷燬方法,那麼會執行該方法釋放資源。

程式碼例項連結: https://github.com/chenanddom/SpringSection4/tree/master/src/main/java/com/flexible/beanfactory/beanfactorylifecyle

#ApplicationContext中Bean的生命週期