首頁技術(shù)文章正文

Spring 核心 : IOC 處理器擴展

更新時間:2018-08-24 來源:黑馬程序員技術(shù)社區(qū) 瀏覽量:

Spring一直標注自己是一個非侵入式框架。非侵入式設(shè)計的概念并不新鮮,目標就是降低使用者和框架代碼的耦合,畢竟框架的開發(fā)者和使用者幾乎肯定不是同一個團隊。

Spring最早的非侵入式實現(xiàn)就是他的一系列XML配置,理想狀態(tài)下Spring框架的所有的功能都應(yīng)該是通過配置實現(xiàn)的。元編程在Java中的使用現(xiàn)給非侵入式的設(shè)計提供了更好的解決方案,在Java中通過注解(Annotation)即可標記某個類、方法、域的附加功能,而無需通過繼承的方式來擴展原始框架沒有的功能。下面通過3段代碼的例子來說明侵入式與非侵入式的區(qū)別。

文章中的代碼僅僅用于說明原理,已經(jīng)刪除了一些無關(guān)代碼,無法執(zhí)行。

可執(zhí)行代碼在:

https://github.com/chkui/spring-core-example

如有需要請自行clone,僅支持gradle依賴。

一個基本的容器

下面的代碼是大致模仿的IoC容器創(chuàng)建Bean的過程。

BeanFactory::createBeans方法傳入Bean的類型列表,而迭代器遍歷列表完成每一個類的實例創(chuàng)建:

/**框架代碼*/

package chkui.springcore.example.xml.beanpostprocessor.nopluging;

//創(chuàng)建Bean的工廠類,由框架開發(fā)者開發(fā)

class BeanFactory {

//創(chuàng)建一系列的Bean

public List<Object> createBeans(List<Class<?>> clslist){

return clslist.stream().map(cls->{

return createBean(cls);

}).collect(Collectors.toList());

}

//創(chuàng)建一個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來完成初始化:

/**使用端代碼*/

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。

源碼地址:

https://github.com/chkui/spring-core-example/
blob/master/src/main/java/chkui/springcore/example/xml/
beanpostprocessor/nopluging/IocExtensionSample.java
某個時刻,框架的使用者有個新需求是在要在每個Bean創(chuàng)建的前后進行一些處理。我們可以通過繼承的方式來實現(xiàn)功能。下面我們修改使用端代碼實現(xiàn)這個功能。

繼承實現(xiàn)功能擴展

通過繼承類BeanFactory,并修改createBean方法可以實現(xiàn)我們的需求:

package chkui.springcore.example.xml.beanpostprocessor.extend;

//執(zhí)行

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的實現(xiàn),添加使用者的處理邏輯

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。

源碼地址:

https://github.com/chkui/spring-core-example/
blob/master/src/main/java/chkui/springcore/example/xml/
beanpostprocessor/extend/IocExtensionSample.java
這里在使用者的代碼里新增了一個ModifyBeanFactory類,并重寫了createBean方法。

在重寫的方法中實現(xiàn)我們需要的功能邏輯。但是這樣開發(fā)會出現(xiàn)以下2點問題:

導(dǎo)致使用者的代碼與框架代碼產(chǎn)生了極強的耦合性。如果某天框架進行了調(diào)整,例如將方法名改為buildBean、或者增加了更多的代理模式會出現(xiàn)一些意想不到的問題。更麻煩的是可能會遇到一些到運行期才出現(xiàn)的問題。
我們需要先理解框架的源碼才能植入我們的功能,這和很多設(shè)計模式的原則是背道而馳的。也會大大影響我們的開發(fā)效率。
出現(xiàn)這些問題就叫做“侵入式”——框架代碼侵入到使用者的工程代碼,導(dǎo)致2者嚴重耦合,對未來的升級、擴展、二次開發(fā)都有深遠的影響。

通過注解(Annotation)擴展功能

實際上注解和在XML進行配置都是一樣的思路,只是注解講關(guān)系寫在了源碼上,而使用XML是將關(guān)系通過XML來描述。

這里實現(xiàn)的功能就類似于在 Bean的定義與控制 一文中介紹的Bean的生命周期方法。

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

無需和框架代碼耦合,更新升級框架風(fēng)險和成本都很小。
任何時候我們需要需要更換框架,只需修改配置或注解,而無需再去調(diào)整我們自己的功能代碼。
非侵入式也有一個問題,那就是接入的功能還是需要框架預(yù)設(shè),而不可能像繼承那樣隨心所欲。

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

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會對注解進行處理,確定添加容器前后的執(zhí)行方法。*/

Handle handle = processBeforeAndAfterHandle(ins);

handle.exeBefore();

wrapper = new BeanWrapper(ins);

handle.exeAfter();

return wrapper;

}

// 通過反射來確定Bean被添加到容器前后的執(zhí)行方法。

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處理器和對應(yīng)的注解的代碼:

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的對應(yīng)的方法上:

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());

}

}

//預(yù)設(shè)的Bean1

class MyBean1 {

public String toString() {

return "MyBean1 Ins";

}

@before

public void init() {

System.out.println("Before Init:" + this.toString());

}

}

//預(yù)設(shè)的Bean2

class MyBean2 {

public String toString() {

return "MyBean2 Ins";

}

@after

public void post() {

System.out.println("After Init:" + this.toString());

}

}

我們?yōu)镸yBean1和MyBean2分別添加了init、post方法和對應(yīng)的@before、@after注解。執(zhí)行之后輸出一下內(nèi)容:

Before Init:MyBean1 Ins

After Init:MyBean2 Ins

Result:[Wrapper(MyBean1 Ins), Wrapper(MyBean2 Ins)]

classpath:chkui.springcore.example.xml.beanpostprocessor.annotation.IocExtensionSample。

源碼地址:

https://github.com/chkui/spring-core-example/
blob/master/src/main/java/chkui/springcore/example/xml/
beanpostprocessor/annotation/IocExtensionSample.java
注解對應(yīng)的方法都順利執(zhí)行。

通過注解,我們實現(xiàn)了擴展功能,任何時候只需要通過添加或修改注解即可向容器擴展功能。在Spring核心功能里,Bean的生命周期管理都是通過這種思路實現(xiàn)的,除了注解之外還有XML支持。

在使用spring的過程中,我想各位碼友多多少少都通過繼承Spring某些類來實現(xiàn)了一些需要擴展的功能。而且我發(fā)現(xiàn)網(wǎng)上很多使用spring某些功能的例子也是通過繼承實現(xiàn)的。建議盡量不要去采用這種加深耦合的方式實現(xiàn)擴展,Spring提供了多種多樣的容器擴展機制,后面的文章會一一介紹。

后置處理器

后置處理器——BeanPostProcessor是Spring核心框架容器擴展功能之一,作用和Bean的生命周期方法類似,也是在Bean完成初始化前后被調(diào)用。

但是和生命周期方法不同的是,他無需在每一個Bean上去實現(xiàn)代碼,而是通過一個獨立的Bean來處理全局的初始化過程。

BeanPostProcessor與Bean生命周期方法體現(xiàn)出的差異是:我們無論任何時候都可以加入處理器來實現(xiàn)擴展功能,這樣做的好處是無需調(diào)整之前的Bean的任何代碼也可以植入功能。

這種實現(xiàn)方式與切面(AOP)有一些相似的地方,但是實現(xiàn)的方式是完全不一樣的,而且處理器會對所有Bean進行處理。

BeanPostProcessor的實現(xiàn)非常簡單,只添加一個Bean實現(xiàn)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

一個服務(wù)接口和服務(wù)類: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

作者:黑馬程序員技術(shù)社區(qū)
首發(fā):http://python.itheima.com/
分享到:
在線咨詢 我要報名
和我們在線交談!