Spring一直標註本身是一個非侵入式框架。非侵入式設計的概念並不新鮮,目標就是下降使用者和框架代碼的耦合,畢竟框架的開發者和使用者幾乎確定不是同一個團隊。Spring最先的非侵入式實現就是他的一系列XML配置,理想狀態下Spring框架的全部的功能都應該是經過配置實現的。元編程在Java中的使用現給非侵入式的設計提供了更好的解決方案,在Java中經過註解(Annotation)便可標記某個類、方法、域的附加功能,而無需經過繼承的方式來擴展原始框架沒有的功能。下面經過3段代碼的例子來講明侵入式與非侵入式的區別。java
文章中的代碼僅僅用於說明原理,已經刪除了一些無關代碼,沒法執行。可執行代碼在:https://github.com/chkui/spring-core-example,若有須要請自行clone,僅支持gradle依賴。git
下面的代碼是大體模仿的IoC容器建立Bean的過程。BeanFactory::createBeans方法傳入Bean的類型列表,而迭代器遍歷列表完成每個類的實例建立:github
/**框架代碼*/ package chkui.springcore.example.xml.beanpostprocessor.nopluging; //建立Bean的工廠類,由框架開發者開發 class BeanFactory { //建立一系列的Bean public List<Object> createBeans(List<Class<?>> clslist){ return clslist.stream().map(cls->{ return createBean(cls); }).collect(Collectors.toList()); } //建立一個Bean Object createBean(Class<?> cls){ //添加到容器 return new BeanWrapper(cls.newInstance()); } } //包裝代理 class BeanWrapper { private Object bean; public BeanWrapper(Object bean) { this.bean = bean; } @Override public String toString() { return "Wrapper(" + this.bean.toString() + ")"; } }
下面的代碼是框架使用者的代碼——將Bean1和Bean2交給BeanFactory來完成初始化:spring
/**使用端代碼*/ package chkui.springcore.example.xml.beanpostprocessor.nopluging; //import ... public class IocExtensionSampleNoPluging { public static void main(String[] args) { List<Class<?>> classes = Arrays.asList(new Class<?>[]{MyBean1.class, MyBean2.class}); List<Object> ins = new BeanFactory().createBeans(classes); System.out.println("Result:" + ins.toString()); } } //Bean1,由使用者編碼 class MyBean1 { public String toString() { return "MyBean1 Ins"; } } //Bean2,使用者編碼 class MyBean2 { public String toString() { return "MyBean2 Ins"; } }
classpath:chkui.springcore.example.xml.beanpostprocessor.nopluging.IocExtensionSample。源碼地址。編程
某個時刻,框架的使用者有個新需求是在要在每一個Bean建立的先後進行一些處理。咱們能夠經過繼承的方式來實現功能。下面咱們修改使用端代碼實現這個功能。設計模式
經過繼承類BeanFactory,並修改createBean方法能夠實現咱們的需求:app
package chkui.springcore.example.xml.beanpostprocessor.extend; //執行 public class IocExtensionSampleNoPluging { public static void main(String[] args) { List<Class<?>> classes = Arrays.asList(new Class<?>[]{MyBean1.class, MyBean2.class}); List<Object> ins = new ModifyBeanFactory().createBeans(classes); System.out.println("Result:" + ins.toString()); } } //新建一個BeanFactory的派生類,並修改createBean的實現,添加使用者的處理邏輯 class ModifyBeanFactory extends BeanFactory { Object createBean(Class<?> cls){ Object ins = cls.newInstance(); //添加容器以前的處理 BeanWrapper wrapper = new BeanWrapper(ins); //添加容器以後的處理 return wrapper; } }
classpath:chkui.springcore.example.xml.beanpostprocessor.extend.IocExtensionSample。源碼地址。框架
這裏在使用者的代碼裏新增了一個ModifyBeanFactory類,並重寫了createBean方法。在重寫的方法中實現咱們須要的功能邏輯。可是這樣開發會出現如下2點問題:ide
出現這些問題就叫作「侵入式」——框架代碼侵入到使用者的工程代碼,致使2者嚴重耦合,對將來的升級、擴展、二次開發都有深遠的影響。post
實際上註解和在XML進行配置都是同樣的思路,只是註解講關係寫在了源碼上,而使用XML是將關係經過XML來描述。這裏實現的功能就相似於在 Bean的定義與控制 一文中介紹的Bean的生命週期方法。
使用註解最大的價值就是非侵入式。非侵入式的好處顯而易見:
非侵入式也有一個問題,那就是接入的功能仍是須要框架預設,而不可能像繼承那樣爲所欲爲。
咱們將前面的代碼進行一些修改,支持經過註解來指定擴展的功能:
package chkui.springcore.example.xml.beanpostprocessor.annotation; class BeanFactory { public List<Object> createBeans(List<Class<?>> clslist){ //同前文... } Object createBean(Class<?> cls){ BeanWrapper wrapper = null; Object ins = cls.newInstance(); /**這裏增長了一個Handle對象。 Handle會對註解進行處理,肯定添加容器先後的執行方法。*/ Handle handle = processBeforeAndAfterHandle(ins); handle.exeBefore(); wrapper = new BeanWrapper(ins); handle.exeAfter(); return wrapper; } // 經過反射來肯定Bean被添加到容器先後的執行方法。 private Handle processBeforeAndAfterHandle(Object obj) { Method[] methods = obj.getClass().getDeclaredMethods(); Handle handle = new Handle(obj); for(Method method : methods) { Annotation bef = method.getAnnotation(before.class); Annotation aft = method.getAnnotation(after.class); if(null != bef) handle.setBefore(method); if(null != aft) handle.setBefore(method); } return handle; } }
下面是Handle處理器和對應的註解的代碼:
class Handle{ Object instance; Method before; Method after; Handle(Object ins){ this.instance = ins; } void setBefore(Method method) { this.before = method; } void setAfter(Method method) { this.after = method; } void exeBefore(){ if(null != this.before) { this.before.invoke(this.instance, null); } } void exeAfter(){ if(null != this.after) { this.after.invoke(this.instance, null); } } } //註解---------------------------------------- @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @interface before {} @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @interface after{}
使用者的代碼,咱們將註解添加到Bean的對應的方法上:
public class IocExtensionSampleNoPluging { public static void main(String[] args) { List<Class<?>> classes = Arrays.asList(new Class<?>[]{MyBean1.class, MyBean2.class}); List<Object> ins = new BeanFactory().createBeans(classes); System.out.println("Result:" + ins.toString()); } } //預設的Bean1 class MyBean1 { public String toString() { return "MyBean1 Ins"; } @before public void init() { System.out.println("Before Init:" + this.toString()); } } //預設的Bean2 class MyBean2 { public String toString() { return "MyBean2 Ins"; } @after public void post() { System.out.println("After Init:" + this.toString()); } }
咱們爲MyBean1和MyBean2分別添加了init、post方法和對應的@before、@after註解。執行以後輸出一下內容:
Before Init:MyBean1 Ins After Init:MyBean2 Ins Result:[Wrapper(MyBean1 Ins), Wrapper(MyBean2 Ins)]
classpath:chkui.springcore.example.xml.beanpostprocessor.annotation.IocExtensionSample。源碼地址。
註解對應的方法都順利執行。
經過註解,咱們實現了擴展功能,任什麼時候候只須要經過添加或修改註解便可向容器擴展功能。在Spring核心功能裏,Bean的生命週期管理都是經過這種思路實現的,除了註解以外還有XML支持。
在使用spring的過程當中,我想各位碼友多多少少都經過繼承Spring某些類來實現了一些須要擴展的功能。並且我發現網上不少使用spring某些功能的例子也是經過繼承實現的。建議儘可能不要去採用這種加深耦合的方式實現擴展,Spring提供了多種多樣的容器擴展機制,後面的文章會一一介紹。
後置處理器——BeanPostProcessor是Spring核心框架容器擴展功能之一,做用和Bean的生命週期方法相似,也是在Bean完成初始化先後被調用。可是和生命週期方法不一樣的是,他無需在每個Bean上去實現代碼,而是經過一個獨立的Bean來處理全局的初始化過程。
BeanPostProcessor與Bean生命週期方法體現出的差別是:咱們不管任什麼時候候均可以加入處理器來實現擴展功能,這樣作的好處是無需調整以前的Bean的任何代碼也能夠植入功能。
這種實現方式與切面(AOP)有一些類似的地方,可是實現的方式是徹底不同的,並且處理器會對全部Bean進行處理。
BeanPostProcessor的實現很是簡單,只添加一個Bean實現BeanPostProcessor接口便可:
package chkui.springcore.example.xml.beanpostprocessor; import org.springframework.beans.factory.config.BeanPostProcessor; public class Processor implements BeanPostProcessor { //初始化以前 public Object postProcessBeforeInitialization(Object bean, String beanName) { return bean; } //初始化以後 public Object postProcessAfterInitialization(Object bean, String beanName) { System.out.println("Bean '" + beanName + "' created : " + bean.toString()); return bean; } }
BeanPostProcessor的使用案例請查看實例代碼中 chkui.springcore.example.xml.beanpostprocessor 包中的代碼,包含:
一個實體類:chkui.springcore.example.xml.entity.User
一個服務接口和服務類:chkui.springcore.example.xml.service.UserService
處理器:chkui.springcore.example.xml.beanpostprocessor.Processor
Main入口:chkui.springcore.example.xml.beanpostprocessor.BeanPostProcessor
配置文件:/src/main/resources/xml/config.xml
見:https://www.chkui.com/article/spring/spring_core_post_processor_of_official