很長一段時間關注在Java Web開發的方向上,說起到Jave Web開發就繞不開Spring全家桶系列,使用面向百度,谷歌的編程方法可以完成大部分的工做。可是這種不繫統的瞭解總以爲本身的知識有所欠缺。因此有了系統瞭解Spring的想法,瞭解了Spring,纔可以更好的學習Spring全家桶系列,Spring的書籍也是琳琅滿目,固然也能夠閱讀Spring官方的reference,相信那個纔是最好的材料,可是鑑於英語的閱讀速度有限。因此就挑選了這本《精通Spring 4.X企業應用開發實戰》。這裏記錄一下讀書筆記,主要是方便之後快速的查閱~html
以前零碎的信息瞭解到Spring的兩大基礎其實就是 IoC和AOP了,最近花了好久的時間閱讀了《精通Spring 4.X企業應用開發實戰》關於 DI 的這部份內容。仍是瞭解了很多內容,固然也產生了很多的疑問。java
IoC容器web
IoC(Inversion of Control,控制反轉),另一個詞DI(Dependency Injection,依賴注入),在Spring中能夠把這兩個詞等價起來,糾結這些概念我以爲沒有必要,個人理解,它就是一種成熟的軟件設計模式,可以實現軟件開發的高內聚,低耦合。系統在改動,擴展起來可以輕鬆應對。網絡上面不少關於這個概念的解釋,他們也都舉例說明了它的好處,可是都是很小的實例,不是一個複雜的系統對於減輕應對改動、擴展的功效印象並不深入。因此仍是應該潛心碼代碼,當本身碼的代碼到達必定的規模,天然而然應該就會了解到IoC的概念,設計模式們的優雅之處了。 面試
用書中的一句話來描述,某一接口具體實現類的選擇控制權從調用類中移除,轉交給第三方決定(一頭霧水)。簡單點說由Spring容器集中管理實現類,須要該實現類的時候由Spring容器根據名稱或類型動態的注入該實現類。spring
從注入方法上區分,IoC主要包含構造函數注入、屬性注入和接口注入;Spring支持構造函數注入和屬性注入。Spring容器經過xml配置文件、註解描述、JavaConfig、Groovy DSL四種方式對實現類的信息及其它們之間的依賴關係進行描述,目前比較經常使用的應該是基於註解,而後配合JavaConfig的方式(Spring boot在我看來是一個anti-xml的產品,最近它比較流行,xml配置文件的方式在spring boot中有點顯得格格不入),不過《精通Spring 4.X企業應用開發實戰》這本書不太好的地方就在於,書中仍是保留了大量的基於xml配置文件的介紹,不過也是瞭解一下歷史吧(Spring由於一直向下保持兼容,致使即便Spring boot如今流行起來,網上仍是大量的關於Spring xml配置的介紹,在推廣註解、Java Config的方向上仍是阻力重重。這也是以前看到相對於Guice這種後起的IoC框架,Spring的不足之處)。編程
說到這裏其實我以前面試的時候被問到一個問題:Spring有什麼優缺點,咱們看Spring的書,滿滿的都是Spring的優勢,輕量級的框架,當初就是由於EJB太笨重纔有的Spring;依賴注入幫助咱們設計出低耦合的程序;AOP特性使得咱們寫出異常簡潔的代碼,java web程序幾乎離不開這個框架的身影,如今Spring已然是全家桶的解決方案,原有的SSH框架到如今的SSM輕量級框架,其中Spring,SpringMVC都屬於Spring社區的,Spring的優勢太多了,我也只是依照我我的理解簡述了一下。缺點呢?前段時間看到一篇對比Spring和Guice的,角度挺好的,Spring雖然隨着發展,擁抱了註解、java配置的形式對bean進行描述,可是依然保留了xml形式的bean定義,致使如今網上搜索資料不少都是xml形式的解答,這樣使得社區變化的比較慢。可是依然推薦使用Spring,由於相對於Guice,Spring有一個更加優秀的社區,你遇到問題可以搜索到不少的Spring解答,社區也活躍。若是你使用Guice,遇到了問題社區相對於Spring活躍度就大打折扣了。設計模式
Java反射(這個知識自己很基礎,聽說效率不高,可是幫助你理解Spring框架,xml的bean聲明都是反射生成的吧?)數組
Java中有一個特殊的類,Class,每一個Class內部都有一個Class類,是否是很拗口。可是確實是這樣,這是一個特殊的類,它包含類的全部信息;全部的構造函數,全部的Field,全部的Method...能夠動態的建立對象;用代碼展現比較合適(很容易理解,就是異常聲明的有點多)。tomcat
package com.test.spider; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class JavaRefection { public static class MockClass { private String firstName; private String secondName; public String getFirstName() { return this.firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } @Override public String toString() { return "MockClass [firstName=" + firstName + ", secondName=" + secondName + "]"; } } public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException { Class c1 = MockClass.class; Class c2 = Class.forName("com.test.spider.JavaRefection$MockClass"); Class c3 = new MockClass().getClass(); //check that 3 methods obtain same class instance System.out.println("Whether T.class is same with Class.forName():" + String.valueOf(c1 == c2) + "\n" + "Whether Class.forName is same with object.getClass():" + String.valueOf(c2 == c3)); //using nullary constructor MockClass mockClass1 = (MockClass)c1.newInstance(); //another method, first get Constructor Constructor<MockClass> constructor = c1.getConstructor(new Class[]{}); MockClass mockClass2 = (MockClass)constructor.newInstance(new Object[]{}); //get Method Method[] methods = c1.getDeclaredMethods(); for(Method method : methods) { System.out.println(method.getName()); } //get Field Field[] fields = c1.getDeclaredFields(); for(Field field : fields) { System.out.println(field.getName()); } fields[0].setAccessible(true); fields[0].set(mockClass2, "Hello"); fields[1].setAccessible(true); fields[1].set(mockClass2, "World!"); System.out.println(mockClass2.toString()); //invoke method System.out.println(methods[1].invoke(mockClass2, new Object[]{})); System.out.println(methods[1].invoke(mockClass1, new Object[]{})); } }
用書中正式的話描述:每一個類在JVM中都擁有一個對應的java.lang.Class對象,提供類結構信息的描述。數組、枚舉、註解及基本的Java類型(int,double等),甚至void都擁有對應的Class對象。Class沒有public的構造方法。Class對象是在裝載類時由JVM經過調用類裝載器中的defineClass()方法自動構造的。網絡
//for classLoader //全盤負責委託機制(委託機制防止自定義的ClassLoader惡意加載基礎類) ClassLoader loader = c1.getClassLoader(); System.out.println("currentLoad:"+ loader); System.out.println("parent:" + loader.getParent()); System.out.println("grandParent:" + loader.getParent().getParent()); System.out.println(loader.getResource("java/net/URL.class")); System.out.println(loader.getResource("java/lang/String.class"));
輸出:
currentLoad:sun.misc.Launcher$AppClassLoader@73d16e93 parent:sun.misc.Launcher$ExtClassLoader@6d06d69c grandParent:null jar:file:/G:/Develop/JDK1.8/jre/lib/rt.jar!/java/net/URL.class jar:file:/G:/Develop/JDK1.8/jre/lib/rt.jar!/java/lang/String.class jar:file:/G:/Develop/JDK1.8/jre/lib/rt.jar!/java/net/URL.class
ClassLoader,(1)裝載:查找和導入class文件(2)連接:執行校驗(檢查class文件正確性)、準備(靜態變量分配存儲空間)和解析步驟(符號引用轉換成直接飲用),其中解析步驟是可選的;(3)初始化:對類的靜態變量,靜態代碼塊執行初始化工做;
幾個ClassLoader的關係,以及Class、Object、ClassLoader的關係:
介紹反射的概念仍是由於Spring中很多地方應該都用到了反射的知識,我我的理解,xml中聲明bean的時候須要指明class屬性,其中須要使用全類名,此時我相信Spring建立這個bean對象的時候使用的就是反射的技術。首先使用Class.forName("")獲取到其類型,而後newInstance()或者獲取到構造函數,使用有參數的構造函數;還有一個能想到的使用反射的地方。目前注入bean的時候能夠在類的private屬性上面添加@Autowired註釋注入bean,即便沒有相關的set方法,這裏必定使用了反射技術,否則private類型是沒法被外部的對象訪問的,反射能夠動態調整屬性的訪問類別,而後直接設置屬性;應該還有不少地方。
IoC容器
話題繼續切回IoC容器,Spring中的IoC容器,BeanFactory、ApplicationContext,ApplicationContext相比BeanFactory有更強大的功能,通常直接使用ApplicationContext;
BeanFactory
最初接觸Spring的時候,不太能理解Bean是什麼概念,有時候又看到JavaBean,最初JavaBean只是一個可複用的Java類,有必定的條件,好比無參構造函數、setter,getter獲取屬性以及可序列化,而後發展爲後來的EJB(企業級JavaBean),爲了簡化企業級開發,就有了Spring框架,Spring裏面的Bean就比較寬泛了,能被Spring容器實例化的Java類均可以成爲Bean,因此幾乎全部的類均可以是Bean。固然也能夠不糾結這些概念。
XmlBeanDefinitionReader和DefaultListableBeanFactory,繼承圖如上圖。(Idea真的挺好用的,Ctrl+N能夠搜索到想要的類,而後生成類圖,固然也能夠看代碼),經過Idea查看接口、類擁有什麼方法最方便了,反正能夠看出從BeanFactory,ListableBeanFactory,HierarchicalBeanFactory,ConfigurableBeanFactory(類裝載器,屬性編輯器,容器初始化後置處理器等),AutowireCapableBeanFactory功能逐漸增長的;
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="car" class="com.smart.beanfactory.Car"> <property name="brand" value="redCA72"></property> <property name="color" value="black"></property> <property name="maxSpeed" value="200"></property> </bean> </beans>
這裏不得不說Idea的xml代碼提示也挺好用的。
public class BeanFactoryTest { @Test public void getBean() throws Exception { ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource resource = resolver.getResource("classpath:beans.xml"); System.out.println(resource.getURL()); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(factory); xmlBeanDefinitionReader.loadBeanDefinitions(resource); Car car = (Car)factory.getBean("car"); System.out.println(car.toString()); } }
ApplicationContext
以上是最基本的ApplicationContext接口的類圖
Lifecycle這個接口好像tomcat的設計中也有相似的設計,tomcat的Lifecyle負責容器的生命週期?
主要使用的4個類
ClassPathXmlApplicationContext,構造參數中的路徑classpath:... FileSystemXmlApplicationContext,構造參數中的路徑file:... AnnotationConfigApplicationContext,基於@Configuration註解bean以及@Component方式聲明Bean支持 GenericGroovyApplicationContext,基於Groovy聲明Bean的方式
第三種目前使用的最多,SpringBoot中推崇的方式,Xml的方式遺留系統中使用較多;
WebApplicationContext
WebApplicationContext的初始化方式不一樣於BeanFactory、ApplicationContext,由於其須要ServletContext實例。web.xml中配置自啓動的Servlet或自定義Web容器監聽器(ServletContextListener),藉助而這種任何一個,完成啓動Spring Web應用上下文的工做。
ContextLoaderServlet和ContextLoaderListener分別是Spring提供的用於啓動WebApplicationContext的Servlet和Web容器的監聽器。
二者的內部都實現了啓動WebApplicationContext實例的邏輯,根據Web容器具體狀況進行選擇,web.xml中進行配置便可。
經過Web容器監聽器引導
... <!-- 指定配置文件 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/smart-dao.xml, /WEB-INF/smart-service.xml </param-value> </context-param> <!-- 聲明web容器監聽器 --> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> ...
不支持監聽器的低版本web容器,採用自啓動的Servlet;
... <!-- 指定配置文件 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/smart-dao.xml, /WEB-INF/smart-service.xml </param-value> </context-param> <!-- 聲明自啓動的servlet --> <servlet> <servlet-name>springContextLoaderServlet</servlet-name> <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> ...
Bean的生命週期
Bean的完整生命週期從Spring容器着手實例化Bean開始,知道最終銷燬Bean,其中通過了許多關鍵點,每一個關鍵點都設計特定的方法調用,方法主要分爲4個種類:
1. Bean自身的方法:如調用Bean構造函數實例化Bean、調用Setter設置Bean的屬性以及經過Bean的init-method和destroy-method所指定的方法
2.Bean級生命週期接口方法:如BeanAware、BeanFactoryAware、InitializingBean和DisposableBean,這些接口方法由Bean類實現
3.容器級別生命週期接口方法:InstantiationAwareBeanPostProcessor和BeanPostProcessor這兩個接口實現,通常稱它們的實現類爲「後處理器」。後處理器接口通常不禁Bean自己實現,它們獨立於Bean,實現類以容器附加裝置的形式註冊到Spring容器中,並經過接口反射未Spring容器掃描識別。當Spring容器建立任何Bean的時候,這些後處理器就會發生做用,因此這些後處理器的影響是全局性的。
4.工廠後處理器接口方法:包括AspectJWeavingEnabler、CustomAutowireConfigurer、ConfigurationClassPostProcessor等方法。工廠後處理器也是容器級的,在應用上下文裝配配置文件後當即調用。
Bean級生命週期和容器級生命週期接口是個性和共性的結合,Bean級別解決了個性化處理問題,容器級解決了某些Bean共性化處理的問題,Spring容器中能夠註冊多個後處理器,他們同時實現org.springframework.core.Ordered接口便可。
BeanFactory
public class BeanFactoryTest { @Test public void getBean() throws Exception { ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource resource = resolver.getResource("classpath:beans.xml"); System.out.println(resource.getURL()); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(factory); xmlBeanDefinitionReader.loadBeanDefinitions(resource); factory.addBeanPostProcessor(new BeanPostProcessor1()); factory.addBeanPostProcessor(new BeanPostProcessor2()); factory.addBeanPostProcessor(new MyInstantiationAwareBeanPostProcessor1()); factory.addBeanPostProcessor(new MyInstantiationAwareBeanPostProcessor2()); Car car = (Car)factory.getBean("car"); System.out.println(car.toString());
factory.destorySingletons(); } } public class Car implements BeanFactoryAware, BeanNameAware, InitializingBean, DisposableBean{ private String brand; private String color; private String maxSpeed; public String getBrand() { return brand; } public String getColor() { return color; } public String getMaxSpeed() { return maxSpeed; } public void setBrand(String brand) { this.brand = brand; } public void setColor(String color) { this.color = color; } public void setMaxSpeed(String maxSpeed) { this.maxSpeed = maxSpeed; } @Override public String toString() { return "Car{" + "brand='" + brand + '\'' + ", color='" + color + '\'' + ", maxSpeed='" + maxSpeed + '\'' + '}'; } private BeanFactory beanFactory; private String beanName; public void selfInit() { System.out.println("init-method指定的方法的調用。"); } public void selfDestory() { System.out.println("destory-method指定的方法的調用。"); } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { System.out.println("調用BeanFactoryAware接口的setBeanFactory方法。"); this.beanFactory = beanFactory; } @Override public void setBeanName(String s) { System.out.println("調用BeanNameAware接口的setBeanName方法。參數:" + s); this.beanName = s; } @Override public void destroy() throws Exception { System.out.println("調用DisposableBean接口的destory方法。"); } @Override public void afterPropertiesSet() throws Exception { System.out.println("調用InitializingBean接口的afterPropertiesSet方法。"); } } public class BeanPostProcessor1 implements BeanPostProcessor, Ordered { @Override public Object postProcessBeforeInitialization(Object o, String s) throws BeansException { System.out.println("調用BeanPostProcessor0接口的postProcessBeforeInitialization方法:" + s); return o; } @Override public Object postProcessAfterInitialization(Object o, String s) throws BeansException { System.out.println("調用BeanPostProcessor0接口的postProcessAfterInitialization方法:" + s); return o; } @Override public int getOrder() { return 0; } } public class BeanPostProcessor2 implements BeanPostProcessor, Ordered { @Override public Object postProcessBeforeInitialization(Object o, String s) throws BeansException { System.out.println("調用BeanPostProcessor1接口的postProcessBeforeInitialization方法:" + s); return o; } @Override public Object postProcessAfterInitialization(Object o, String s) throws BeansException { System.out.println("調用BeanPostProcessor1接口的postProcessAfterInitialization方法:" + s); return o; } @Override public int getOrder() { return 1; } } public class MyInstantiationAwareBeanPostProcessor1 extends InstantiationAwareBeanPostProcessorAdapter implements Ordered { @Override public int getOrder() { return 0; } public Object postProcessBeforeInstantiation(Class<?> var1, String var2) throws BeansException { System.out.println("調用InstantiationAwareBeanPostProcessor0接口的postProcessBeforeInstantiation方法:" + var2); return null; } public boolean postProcessAfterInstantiation(Object var1, String var2) throws BeansException { System.out.println("調用InstantiationAwareBeanPostProcessor0接口的postProcessAfterInstantiation方法:" + var2); return true; } public PropertyValues postProcessPropertyValues(PropertyValues var1, PropertyDescriptor[] var2, Object var3, String var4) throws BeansException { System.out.println("調用InstantiationAwareBeanPostProcessor0接口的postProcessPropertyValues方法:" + var4); return var1; } } public class MyInstantiationAwareBeanPostProcessor2 extends InstantiationAwareBeanPostProcessorAdapter implements Ordered { @Override public int getOrder() { return 1; } public Object postProcessBeforeInstantiation(Class<?> var1, String var2) throws BeansException { System.out.println("調用InstantiationAwareBeanPostProcessor1接口的postProcessBeforeInstantiation方法:" + var2); return null; } public boolean postProcessAfterInstantiation(Object var1, String var2) throws BeansException { System.out.println("調用InstantiationAwareBeanPostProcessor1接口的postProcessAfterInstantiation方法:" + var2); return true; } public PropertyValues postProcessPropertyValues(PropertyValues var1, PropertyDescriptor[] var2, Object var3, String var4) throws BeansException { System.out.println("調用InstantiationAwareBeanPostProcessor1接口的postProcessPropertyValues方法:" + var4); return var1; } }
Spring提倡的是非侵入式編程,以上其實框架代碼和業務代碼融合了;因此在應用編程的時候,儘可能少用4個Bean生命週期接口類,使用init-method,destory-method,或者支持@PostConstruct,@PreDestory的InitDestoryAnnotationBeanPostProcessor(ApplicationContext默認裝載)這些不入侵代碼的編程方式。
ApplicationContext
其與BeanFactory中的bean的生命週期一點不一樣在於生命週期中多了一些接口調用;
另一點最大的不一樣時ApplicationContext利用Java反射機制自動識別出配置文件中定義的BeanPostProcessor、InstantiationAwareBeanPostProcessor和BeanFactoryPostProcessor,而且自動將他們註冊到ApplicationContext中;BeanFactory的代碼中展現須要手動調用addBeanPostProcessor()等方法註冊;
因此應用中使用ApplicationContext更加方便;