Spring一直標(biāo)注自己是一個(gè)非侵入式框架。非侵入式設(shè)計(jì)的概念并不新鮮,目標(biāo)就是降低使用者和框架代碼的耦合,畢竟框架的開(kāi)發(fā)者和使用者幾乎肯定不是同一個(gè)團(tuán)隊(duì)。
Spring最早的非侵入式實(shí)現(xiàn)就是他的一系列XML配置,理想狀態(tài)下Spring框架的所有的功能都應(yīng)該是通過(guò)配置實(shí)現(xiàn)的。元編程在Java中的使用現(xiàn)給非侵入式的設(shè)計(jì)提供了更好的解決方案,在Java中通過(guò)注解(Annotation)即可標(biāo)記某個(gè)類(lèi)、方法、域的附加功能,而無(wú)需通過(guò)繼承的方式來(lái)擴(kuò)展原始框架沒(méi)有的功能。下面通過(guò)3段代碼的例子來(lái)說(shuō)明侵入式與非侵入式的區(qū)別。
文章中的代碼僅僅用于說(shuō)明原理,已經(jīng)刪除了一些無(wú)關(guān)代碼,無(wú)法執(zhí)行。
可執(zhí)行代碼在:
https://github.com/chkui/spring-core-example
如有需要請(qǐng)自行clone,僅支持gradle依賴(lài)。
一個(gè)基本的容器
下面的代碼是大致模仿的IoC容器創(chuàng)建Bean的過(guò)程。
BeanFactory::createBeans方法傳入Bean的類(lèi)型列表,而迭代器遍歷列表完成每一個(gè)類(lèi)的實(shí)例創(chuàng)建:
/**框架代碼*/
package chkui.springcore.example.xml.beanpostprocessor.nopluging;
//創(chuàng)建Bean的工廠(chǎng)類(lèi),由框架開(kāi)發(fā)者開(kāi)發(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)建一個(gè)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來(lái)完成初始化:
/**使用端代碼*/
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
某個(gè)時(shí)刻,框架的使用者有個(gè)新需求是在要在每個(gè)Bean創(chuàng)建的前后進(jìn)行一些處理。我們可以通過(guò)繼承的方式來(lái)實(shí)現(xiàn)功能。下面我們修改使用端代碼實(shí)現(xiàn)這個(gè)功能。
繼承實(shí)現(xiàn)功能擴(kuò)展
通過(guò)繼承類(lèi)BeanFactory,并修改createBean方法可以實(shí)現(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());
}
}
//新建一個(gè)BeanFactory的派生類(lèi),并修改createBean的實(shí)現(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
這里在使用者的代碼里新增了一個(gè)ModifyBeanFactory類(lèi),并重寫(xiě)了createBean方法。
在重寫(xiě)的方法中實(shí)現(xiàn)我們需要的功能邏輯。但是這樣開(kāi)發(fā)會(huì)出現(xiàn)以下2點(diǎn)問(wèn)題:
導(dǎo)致使用者的代碼與框架代碼產(chǎn)生了極強(qiáng)的耦合性。如果某天框架進(jìn)行了調(diào)整,例如將方法名改為buildBean、或者增加了更多的代理模式會(huì)出現(xiàn)一些意想不到的問(wèn)題。更麻煩的是可能會(huì)遇到一些到運(yùn)行期才出現(xiàn)的問(wèn)題。
我們需要先理解框架的源碼才能植入我們的功能,這和很多設(shè)計(jì)模式的原則是背道而馳的。也會(huì)大大影響我們的開(kāi)發(fā)效率。
出現(xiàn)這些問(wèn)題就叫做“侵入式”——框架代碼侵入到使用者的工程代碼,導(dǎo)致2者嚴(yán)重耦合,對(duì)未來(lái)的升級(jí)、擴(kuò)展、二次開(kāi)發(fā)都有深遠(yuǎn)的影響。
通過(guò)注解(Annotation)擴(kuò)展功能
實(shí)際上注解和在XML進(jìn)行配置都是一樣的思路,只是注解講關(guān)系寫(xiě)在了源碼上,而使用XML是將關(guān)系通過(guò)XML來(lái)描述。
這里實(shí)現(xiàn)的功能就類(lèi)似于在 Bean的定義與控制 一文中介紹的Bean的生命周期方法。
使用注解最大的價(jià)值就是非侵入式。非侵入式的好處顯而易見(jiàn):
無(wú)需和框架代碼耦合,更新升級(jí)框架風(fēng)險(xiǎn)和成本都很小。
任何時(shí)候我們需要需要更換框架,只需修改配置或注解,而無(wú)需再去調(diào)整我們自己的功能代碼。
非侵入式也有一個(gè)問(wèn)題,那就是接入的功能還是需要框架預(yù)設(shè),而不可能像繼承那樣隨心所欲。
我們將前面的代碼進(jìn)行一些修改,支持通過(guò)注解來(lái)指定擴(kuò)展的功能:
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();
/**這里增加了一個(gè)Handle對(duì)象。
Handle會(huì)對(duì)注解進(jìn)行處理,確定添加容器前后的執(zhí)行方法。*/
Handle handle = processBeforeAndAfterHandle(ins);
handle.exeBefore();
wrapper = new BeanWrapper(ins);
handle.exeAfter();
return wrapper;
}
// 通過(guò)反射來(lái)確定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處理器和對(duì)應(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的對(duì)應(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方法和對(duì)應(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
注解對(duì)應(yīng)的方法都順利執(zhí)行。
通過(guò)注解,我們實(shí)現(xiàn)了擴(kuò)展功能,任何時(shí)候只需要通過(guò)添加或修改注解即可向容器擴(kuò)展功能。在Spring核心功能里,Bean的生命周期管理都是通過(guò)這種思路實(shí)現(xiàn)的,除了注解之外還有XML支持。
在使用spring的過(guò)程中,我想各位碼友多多少少都通過(guò)繼承Spring某些類(lèi)來(lái)實(shí)現(xiàn)了一些需要擴(kuò)展的功能。而且我發(fā)現(xiàn)網(wǎng)上很多使用spring某些功能的例子也是通過(guò)繼承實(shí)現(xiàn)的。建議盡量不要去采用這種加深耦合的方式實(shí)現(xiàn)擴(kuò)展,Spring提供了多種多樣的容器擴(kuò)展機(jī)制,后面的文章會(huì)一一介紹。
后置處理器
后置處理器——BeanPostProcessor是Spring核心框架容器擴(kuò)展功能之一,作用和Bean的生命周期方法類(lèi)似,也是在Bean完成初始化前后被調(diào)用。
但是和生命周期方法不同的是,他無(wú)需在每一個(gè)Bean上去實(shí)現(xiàn)代碼,而是通過(guò)一個(gè)獨(dú)立的Bean來(lái)處理全局的初始化過(guò)程。
BeanPostProcessor與Bean生命周期方法體現(xiàn)出的差異是:我們無(wú)論任何時(shí)候都可以加入處理器來(lái)實(shí)現(xiàn)擴(kuò)展功能,這樣做的好處是無(wú)需調(diào)整之前的Bean的任何代碼也可以植入功能。
這種實(shí)現(xiàn)方式與切面(AOP)有一些相似的地方,但是實(shí)現(xiàn)的方式是完全不一樣的,而且處理器會(huì)對(duì)所有Bean進(jìn)行處理。
BeanPostProcessor的實(shí)現(xiàn)非常簡(jiǎn)單,只添加一個(gè)Bean實(shí)現(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的使用案例請(qǐng)查看實(shí)例代碼中 chkui.springcore.example.xml.beanpostprocessor 包中的代碼,
包含一個(gè)實(shí)體類(lèi):
chkui.springcore.example.xml.entity.User
一個(gè)服務(wù)接口和服務(wù)類(lèi):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/