一、java代理模式 java代理模式是ioc的前置知識。代理模式非常簡單,看代碼就一目瞭然了。 public interface role { public void makeMoney(); } public class Boss implements role { @Override publ ...
一、java代理模式
java代理模式是ioc的前置知識。代理模式非常簡單,看代碼就一目瞭然了。
public interface role { public void makeMoney(); }role
public class Boss implements role { @Override public void makeMoney() { System.out.println("老闆正在談項目..."); } }boss
public class Secretary implements Role { private Role subject = new Boss(); @Override public void makeMoney() { before(); subject.makeMoney(); after(); } private void before(){ System.out.println("你可以先和秘書談一下合作意向..."); } private void after(){ System.out.println("秘書把老闆簽過的合同給你..."); } }secretary
上面是極簡的java靜態代理(也是介面代理模式),定義了介面、介面的實現類和代理類。或者稱實現類為目標類,代理類為封裝類。
一般我們先找老闆的秘書,說一下合作意向(Secretary的makeMoney),可以的話(before)調用老闆真正談項目(Boss的makeMoney),談妥了就可以簽合同了(after)。可以做一下測試:
public class Test { public static void main(String[] args){ // 測試靜態代理 /* 所謂靜態代理,是對介面實現類的某些方法進行了一次封裝 這個代理類需要實現介面的同樣方法,併在內部初始化一個實現類 在該方法中調用了實現類的方法 */ Secretary proxy = new Secretary(); proxy.makeMoney(); } }Test
可見,與其直接訪問一個目標類,更多的是訪問封裝類。這樣做的好處在於:
1.不操作目標類,不對目標類造成破壞;
2.自定義封裝類,具有充分的控制權;
3.可以增強目標類的特定方法,達到增強和擴展的目的;
但是,目標類和封裝類必須實現介面定義的同一個方法。
更多地,java使用動態代理來實現功能擴展。舉個例子:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class Test { public static void main(String[] args) { final Role role = new Boss(); Role secretary = (Role) Proxy.newProxyInstance( role.getClass().getClassLoader(), // 目標類的類載入器 role.getClass().getInterfaces(), // 目標類所實現的所有介面 new InvocationHandler() { // 內部匿名類 -- InvocationHandler介面類的實現類,必須重寫介面方法invoke @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // proxy: 代理對象 // method: 目標方法 // args: 目標方法的參數列表 before(); Object result = method.invoke(role, args); after(); return result; } private void before(){ System.out.println("先和秘書接洽約時間..."); } private void after(){ System.out.println("秘書送來合同書..."); } } ); secretary.makeMoney(); } }jdk動態代理
jdk動態代理同樣是介面代理模式,new InvocationHandler()實現類中的invoke方法被Proxy.newProxyInstance自動調用。secretary對象相當於上面的secretary對象,只不過不需要我們自己手動創建了。jdk動態代理幫助我們創建了一個代理對象,我們只需寫介面和實現類即可。
當然,上面的new InvocarionHandler()實現類可以單獨拿出來重寫:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class JdkProxy implements InvocationHandler { private Object impl; JdkProxy(Object impl){ super(); this.impl = impl; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); Object result = method.invoke(impl, args); after(); return result; } private void before(){ System.out.println("先和秘書接洽約時間..."); } private void after(){ System.out.println("秘書送來合同書..."); } }JdkProxy
做一下測試:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; public class JdkProxyTest { public static void main(String[] args) { Role role = new Boss(); InvocationHandler invocationHandler = new JdkProxy(role); Role boss = (Role) Proxy.newProxyInstance( role.getClass().getClassLoader(), role.getClass().getInterfaces(), invocationHandler ); boss.makeMoney(); } }test
除了jdk動態代理外,還有cglib動態代理。與jdk動態代理不同的是,cglib動態代理沒有介面類,它是通過子類繼承目標類,在子類同名方法內部調用目標類方法來實現的。這種實現方法的性能要優於jdk動態代理。
總結一下,jdk代理幫助我們在目標類的基礎上做了一次功能擴展,因此,對於日誌、事務等系統級業務,可以通過動態代理交給別的組件管理,自己則專註寫應用級業務,也就是上面的實現類(對應bean)和Test類(對應service)。這就是IOC控制反轉。
二、applicationContext.xml和ApplicationContext
“解耦(業務邏輯代碼和實體類分離)”、“功能擴展和托管(系統級業務交給框架處理,開發者只專註應用級業務處理)”是理解spring容器的兩個重要思想。
動態代理解決了“功能擴展和托管”這個問題,通過xml解析,spring容器(讀取applicationContext.xml生成的ApplicationContext實例)對目標類和封裝類、業務代碼兩者進行了拆解。
我們可以將目標類註入(DI)到applicationContetx.xml文件中,由spring容器(ApplicationContext實例)讀取applicationContext.xml來自動生成目標類對應的介面類和封裝類。在業務代碼中,只需獲取ApplicationContext實例,並從中獲取響應的封裝類即可。這個過程實現即是IOC控制反轉。
假設項目環境(IDEA)已經搭建完成(主要是JDK配置、maven配置、tomcat-->local伺服器配置、項目Open Modules Settings配置、以及maven下文件添加及屬性配置Mark Directory as)。
D:.
├─src
│ ├─main
│ │ ├─java // Sources Root
│ │ │ └─com
│ │ │ └─boke
│ │ ├─resources // Resources Root,可以手動添加並右鍵設置Mark Directory as --> Resources Root
│ │ │ ├─applicationContext.xml
│ │ │ └─log4j.properties
│ │ └─webapp // Sources Root,可以手動添加並右鍵設置Mark Directory as --> Sources Root
│ │ └─WEB-INF
│ └─test
│ └─java // Test Resources Root,可以手動添加並右鍵設置Mark Directory as --> Test Resources Root
│ └─com
│ └─node
│ ├─boke
└─target // Excluded,自動生成
備註:這些都可以在項目配置中設置。IDEA右上角"搜索"圖標左側第一個圖標,或者項目文件目錄上右鍵Open Module Settings設置。
maven項目文件配置
pom.xml需要引入的依賴:junit4.11、spring-core、spring-beans、spring-context、spring-aop、spring-expression、log4j1.2.17、spring-test、commons-logging1.1.1。springframework版本為4.2.1.RELEASE。
現在跑一遍applicationContext.xml-->ApplicationContext的整個流程。
定義介面類和實現類。
// MyInterface類 public interface MyInterface { public void doSome(); } // MyImplement類 public class MyImplement implements MyInterface { public MyImplement(){ super(); } public void doSome() { System.out.println("--------執行doSome()方法...----------"); } }介面類和實現類
將實現類註冊到applicationContext.xml文件中。通常,實現類是一個POJO(plain old java object),對應applicationCOntext.xml的某個bean標簽,因此也可稱為bean對象。
<?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"> <!-- 註冊MyImplement 它相當於:MyInterface implement = (MyInterface) new MyImplement(); --> <bean id="implement" class="com.boke.MyImplement" /> </beans>依賴註入
測試。
package com.node.boke; import com.boke.MyInterface; import com.boke.MyImplement; import org.junit.Test; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; import org.springframework.core.io.ClassPathResource; public class MyTest { /** * 示例一:傳統的開發模式:介面-->實現-->調用,都綁定在了一起,耦合度比較高 */ @Test public void test01(){ MyInterface implement = new MyImplement(); implement.doSome(); } /** * 示例二:spring容器解耦 */ @Test public void test02(){ /* ClassPathXmlApplicationContext:類路徑 把resources設置為source root,然後直接從其文件夾下讀取applicationContext.xml即可 解耦:看不到MyInterface實現類 */ ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); MyInterface implement = (MyInterface)ac.getBean("implement"); implement.doSome(); } /** * 示例三:文件路徑 */ @Test public void test03(){ /* FileSystemXmlApplicationContext:文件路徑,預設是項目根路徑下,也可以從其他路徑讀取applicationContext.xml文件 解耦:看不到ISomeimplementImpl實現類 */ ApplicationContext ac = new FileSystemXmlApplicationContext("applicationContext.xml"); MyInterface implement = (MyInterface)ac.getBean("implement"); implement.doSome(); } /* applicationContext與BeanFactory的區別: - applicationContext容器在進行初始化時,會將其中的所有Bean(對象)創建; 缺點:占用系統資源(記憶體,CPU等) 優點:響應速度快 - BeanFactory容器中的對象在容器初始化不會創建Bean對象,而是在真正獲取該對象時才創建; 缺點:響應速度慢 優點:占用系統資源相對較小 */ /** * 示例四:XmlBeanFactory介面類的實現 */ @SuppressWarnings("deprecation") @Test public void test04(){ BeanFactory ac = new XmlBeanFactory(new ClassPathResource("applicationContext.xml")); MyInterface implement = (MyInterface)ac.getBean("implement"); implement.doSome(); } }測試
總結:
Spring的主要作用就是為代碼"解耦",降低代碼間的耦合度。根據功能的不同,可以將一個系統中的代碼分為主業務邏輯和系統級業務邏輯兩類。主業務代碼邏輯聯繫緊密,有具體的專業業務應用場景,復用性相對較低;系統級業務邏輯相對功能獨立,沒有具體的專業業務應用場景,為主業務提供系統級服務,復用性強。
Spring根據代碼的功能特點,將降低耦合度的方式分為兩類:IOC和AOP。IOC使得主業務邏輯在相互調用的過程中不用自己創建對象和調用,而是由Spring容器統一管理,自動"註入"。AOP使得系統級服務得到了服用,由Spring容器統一完成"織入"。
Spring的特點:
- 1.非入侵:spring的api不會混入到應用業務開發中。
- 2.容器:Spring可以管理對象的生命周期以及對象間的依賴關係,並且可以通過配置文件來定義對象,以及設置與其它對象的依賴關係。
- 3.IOC: 控制反轉(inversion of control),即創建調用者的實例不是由調用者完成的,而是由Spring容器完成,並註入調用者。
即不是對象從容器中查找對象,這個事情是容器在對象初始化時不等對象請求就主動將依賴傳遞給它。
- 4.AOP:面向切麵編程(Aspect Orient Programming),是一種編程思想,是面向對象編程的OOP的補充。可以把日誌、安全、事務管理等服務理解成一個"切麵"。
三、ApplicationContext在bean註入時增擴了哪些功能--bean的生命始末
IOC(Inversion of Control)是一個概念,是一種思想,其實現方式多種多樣。當前比較流行的實現方式有兩種:依賴查找、依賴註入。依賴查找(Dependency Lookup DL):容器提供回調介面和上下文環境給組件,程式代碼則需要提供具體的查找方式。 依賴註入(Dependecy Injection DI):程式代碼不做定位查找,這些工作由容器自行完成。依賴註入是目前最優秀的解耦方式。依賴註入讓Spring的Bean之間以配置文件的方式組織在一起,而不是以硬編碼的方式耦合在一起。
通過以下代碼查看bean註入和銷毀的整個流程。
// 介面類 package com.boke; public interface MyInterface { public void doSome(); } // 實現類 package com.boke; import org.springframework.beans.BeansException; import org.springframework.beans.factory.*; import org.springframework.beans.factory.config.BeanPostProcessor; public class MyImplement implements MyInterface, BeanNameAware, BeanFactoryAware, InitializingBean, DisposableBean, BeanPostProcessor { private String aDao; private String bDao; public MyImplement(){ System.out.println("Step1.執行構造方法..."); } @Override public void doSome() { System.out.println("Step9:執行真正的業務處理邏輯doSome()方法..."); } public void setUp(){ System.out.println("Step7:bean生命開始..."); } public void tearDown(){ System.out.println("Step11:Bean銷毀之前..."); } public String getaDao() { return aDao; } public void setaDao(String aDao) { System.out.println("Step2:執行aDao的setter方法..."); this.aDao = aDao; } public String getbDao() { return bDao; } public void setbDao(String bDao) { System.out.println("Step2:執行bDao的setter方法..."); this.bDao = bDao; } @Override public void setBeanName(String name) { System.out.println("Step3:獲取bean的id = " + name); } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { System.out.println("Step4:獲取到BeanFactory容器..."); } @Override public void afterPropertiesSet() throws Exception { System.out.println("Step6:標志著bean已經初始化完畢..."); } @Override public void destroy() throws Exception { System.out.println("Step10:實現介面的銷毀之前..."); } @Override public Object postProcessBeforeInitialization(Object bean, String s) throws BeansException { System.out.println("Step5:初始化完畢之前,執行before方法..."); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String s) throws BeansException { System.out.println("Step8:初始化完畢之後,執行after方法..."); return bean; } }介面類和實現類
applicationContext.xml註入。
<?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--> <bean id="implement" class="com.boke.MyImplement" init-method="setUp" destroy-method="tearDown" > <!--初始化ISomeServiceImpl的成員變數--> <property name="aDao" value="aaa" /> <property name="bDao" value="bbb" /> </bean> </beans>applicationContext.xml
測試類。
package com.node.boke; import com.boke.MyInterface; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MyTest { @Test public void test01(){ ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); MyInterface service = (MyInterface)ac.getBean("implement"); service.doSome(); /* 要執行tearDown,需要兩個條件: - scope=singleton - 手動關閉ac */ ((ClassPathXmlApplicationContext)ac).close(); } }測試類
執行test,列印結果如下:
INFO [AbstractApplicationContext.java:prepareRefresh:573]- Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@ba4d54: startup date [Fri Dec 14 11:41:09 CST 2018]; root of context hierarchy
INFO [XmlBeanDefinitionReader.java:loadBeanDefinitions:317]- Loading XML bean definitions from class path resource [applicationContext.xml]
Step1.執行構造方法...
Step2:執行aDao的setter方法...
Step2:執行bDao的setter方法...
Step3:獲取bean的id = implement
Step4:獲取到BeanFactory容器...
Step5:初始化完畢之前,執行before方法...
Step6:標志著bean已經初始化完畢...
Step7:bean生命開始...
Step8:初始化完畢之後,執行after方法...
Step9:執行真正的業務處理邏輯doSome()方法...
INFO [AbstractApplicationContext.java:doClose:951]- Closing org.springframework.context.support.ClassPathXmlApplicationContext@ba4d54: startup date [Fri Dec 14 11:41:09 CST 2018]; root of context hierarchy
Step10:實現介面的銷毀之前...
Step11:Bean銷毀之前...
列印結果
分析:
在註入一個bean時,大致經過了11個步驟。這個流程對每一個註入的bean都適用。BeanNameAware, BeanFactoryAware, InitializingBean, DisposableBean, BeanPostProcessor是spring中自帶的bean見名知意,在bean初始化、調用和銷毀之際被spring自動調用。
我們可以通過實現(如上面的MyImplement)這些容器中自動調用的bean介面類中的抽象方法來實現功能的擴展,並交由spring自動處理。可見,spring在註入一個我們自寫的bean時,創建了一個動態代理對象,這個對象不僅僅封裝了我們自己的介面實現類,也封裝了spring預設的這些介面實現類,從而完成了一個bean註入的整個生命周期。
當然,我們可以把Implement實現的這些介面單獨拿出來寫一個實現類,註入到spring容器中。以BeanPostProcessor為例。
package com.boke; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String s) throws BeansException { System.out.println("Step5:初始化完畢之前,執行before方法..."); return bean; // 要返回這個bean,它表示當前正在初始化的bean對象 } @Override public Object postProcessAfterInitialization(Object bean, String s) throws BeansException { System.out.println("Step8:初始化完畢之後,執行after方法..."); return bean; // 要返回這個bean,它表示當前正在初始化的bean對象 } }MyBeanPostProcessor
將其註入到applicationContext.xml。
<?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--> <bean id="implement" class="com.boke.MyImplement" init-method="setUp" destroy-method="tearDown" > <!--初始化ISomeServiceImpl的成員變數--> <property name="aDao" value="aaa" /> <property name="bDao" value="bbb" /> </bean> <bean class="com.node.service.beanlife.MyBeanPostProcessor" /> </beans>applicationContext.xml
效果和直接繼承實現是一樣的。
在這些步驟中,我們可以定製一些針對所有bean都適用的功能來進行擴展。
BeanPostProcessor說明:
Bean後處理器是一種特殊的Bean,容器中所有的Bean在初始化時均會自動執行該類的兩個方法。由於該Bean是由其它Bean自動調用執行,不是程式員手工調用,故此Bean無須id屬性。Bean後處理器是org.springframework.beans.factory.config.BeanPostProcessor,它會被自動載入,並執行它的實現類的兩個方法:
public Object postProcessBeforeInitialization(Object bean, String beanId) throws BeanException,該方法會在目標Bean初始化完畢之前由容器自動調用。
public Object postProcessAfterInitialization(Object bean, String BeanId) throws BeansException,該方法會在目標Bean初始化完畢之後由容器自動調用。
它們的參數是:第一個參數是系統即將初始化的Bean實例,第二個參數是該Bean實例的id屬性值,若Bean沒有id就是name屬性值。實現類需要重寫這兩個方法,並註冊到bean中,spring容器會在每個bean調用之前(之後)自動執行者兩個方法。
四、基於XML的DI
applicationContext.xml中以bean標簽的方式註入屬於典型的xml方式的註入。上節梳理了bean註入的生命始末,本節記錄註入bean的一些方式。
1.無參構造註入
// Student類 package com.boke; public class Student { private String name; private int age; private School school; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public School getSchool() { return school; } public void setSchool(School school) { this.school = school; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + ", school=" + school + '}'; } } // School類 package com.boke; public class School { private String name; public void setName(String name) { this.name = name; } public String getName() { return name; } @Override public String toString() { return "School{" + "name='" + name + '\'' + '}'; } }實體類
<?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並註入成員變數--> <bean id="school" class="com.boke.School"> <property name="name" value="清華大學" /> </bean> <bean id="student" class="com.boke.Student"> <!--註入成員變數的值--> <property name="name" value="Alex" /> <property name="age" value="20" /> <!--對於一個對象,稱為域屬性,需要用ref來註入--> <property name="school" ref="school" /> </bean> </beans>applicationContext.xml
package com.node.boke; import com.boke.School; import com.boke.Student; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MyTest { @Test public void test01(){ ApplicationContext ac =