Spring核心——IOC處理器擴展

非侵入式框架

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

  1. 致使使用者的代碼與框架代碼產生了極強的耦合性。若是某天框架進行了調整,例如將方法名改成buildBean、或者增長了更多的代理模式會出現一些意想不到的問題。更麻煩的是可能會遇到一些到運行期纔出現的問題。
  2. 咱們須要先理解框架的源碼才能植入咱們的功能,這和不少設計模式的原則是背道而馳的。也會大大影響咱們的開發效率。

出現這些問題就叫作「侵入式」——框架代碼侵入到使用者的工程代碼,致使2者嚴重耦合,對將來的升級、擴展、二次開發都有深遠的影響。post

經過註解(Annotation)擴展功能

實際上註解和在XML進行配置都是同樣的思路,只是註解講關係寫在了源碼上,而使用XML是將關係經過XML來描述。這裏實現的功能就相似於在 Bean的定義與控制 一文中介紹的Bean的生命週期方法。

使用註解最大的價值就是非侵入式。非侵入式的好處顯而易見:

  1. 無需和框架代碼耦合,更新升級框架風險和成本都很小。
  2. 任什麼時候候咱們須要須要更換框架,只需修改配置或註解,而無需再去調整咱們本身的功能代碼。

非侵入式也有一個問題,那就是接入的功能仍是須要框架預設,而不可能像繼承那樣爲所欲爲。

咱們將前面的代碼進行一些修改,支持經過註解來指定擴展的功能:

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

相關文章
相關標籤/搜索