Spring核心——IOC处理器扩展

非侵入式框架

Spring一直标注自己是一个非侵入式框架。非侵入式设计的概念并不新鲜,目标就是降低使用者和框架代码的耦合,毕竟框架的开发者和使用者几乎肯定不是同一个团队。Spring最早的非侵入式实现就是他的一系列XML配置,理想状态下Spring框架的所有的功能都应该是通过配置实现的。元编程在Java中的使用现给非侵入式的设计提供了更好的解决方案,在Java中通过注解(Annotation)即可标记某个类、方法、域的附加功能,而无需通过继承的方式来扩展原始框架没有的功能。下面通过3段代码的例子来说明侵入式与非侵入式的区别。

文章中的代码仅仅用于说明原理,已经删除了一些无关代码,无法执行。可执行代码在:https://github.com/chkui/spring-core-example,如有需要请自行clone,仅支持gradle依赖。

一个基本的容器

下面的代码是大致模仿的IoC容器创建Bean的过程。BeanFactory::createBeans方法传入Bean的类型列表,而迭代器遍历列表完成每一个类的实例创建:

/**框架代码*/packagechkui.springcore.example.xml.beanpostprocessor.nopluging;//创建Bean的工厂类,由框架开发者开发classBeanFactory{//创建一系列的BeanpublicListcreateBeans(List<Class<?>> clslist){returnclslist.stream().map(cls->{returncreateBean(cls);}).collect(Collectors.toList());}//创建一个BeanObjectcreateBean(Class<?> cls){//添加到容器returnnewBeanWrapper(cls.newInstance());}}//包装代理classBeanWrapper{privateObject bean;publicBeanWrapper(Object bean){this.bean = bean;}@OverridepublicStringtoString(){return"Wrapper("+this.bean.toString() +")";}}

下面的代码是框架使用者的代码——将Bean1和Bean2交给BeanFactory来完成初始化:

/**使用端代码*/packagechkui.springcore.example.xml.beanpostprocessor.nopluging;//import ...publicclassIocExtensionSampleNoPluging{publicstaticvoidmain(String[] args){    List> classes = Arrays.asList(newClass[]{MyBean1.class, MyBean2.class});    List ins =newBeanFactory().createBeans(classes);    System.out.println("Result:"+ ins.toString());    }}//Bean1,由使用者编码classMyBean1{publicStringtoString(){return"MyBean1 Ins";}}//Bean2,使用者编码classMyBean2{publicStringtoString(){return"MyBean2 Ins";}}

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

某个时刻,框架的使用者有个新需求是在要在每个Bean创建的前后进行一些处理。我们可以通过继承的方式来实现功能。下面我们修改使用端代码实现这个功能。给你们推荐一个学习群659270626  里面有架构技术资料 以及免费的解答,不懂的问题都可以在本群提出来 之后还会有职业生涯规划以及面试指导 。

继承实现功能扩展

通过继承类BeanFactory,并修改createBean方法可以实现我们的需求:

packagechkui.springcore.example.xml.beanpostprocessor.extend;//执行publicclassIocExtensionSampleNoPluging{publicstaticvoidmain(String[] args){    List> classes = Arrays.asList(newClass[]{MyBean1.class, MyBean2.class});    List ins =newModifyBeanFactory().createBeans(classes);    System.out.println("Result:"+ ins.toString());    }}//新建一个BeanFactory的派生类,并修改createBean的实现,添加使用者的处理逻辑classModifyBeanFactoryextendsBeanFactory{ObjectcreateBean(Class<?> cls){Object ins = cls.newInstance();//添加容器之前的处理BeanWrapper wrapper =newBeanWrapper(ins);//添加容器之后的处理returnwrapper;}}

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

这里在使用者的代码里新增了一个ModifyBeanFactory类,并重写了createBean方法。在重写的方法中实现我们需要的功能逻辑。但是这样开发会出现以下2点问题:

导致使用者的代码与框架代码产生了极强的耦合性。如果某天框架进行了调整,例如将方法名改为buildBean、或者增加了更多的代理模式会出现一些意想不到的问题。更麻烦的是可能会遇到一些到运行期才出现的问题。

我们需要先理解框架的源码才能植入我们的功能,这和很多设计模式的原则是背道而驰的。也会大大影响我们的开发效率。

出现这些问题就叫做“侵入式”——框架代码侵入到使用者的工程代码,导致2者严重耦合,对未来的升级、扩展、二次开发都有深远的影响。

通过注解(Annotation)扩展功能

实际上注解和在XML进行配置都是一样的思路,只是注解讲关系写在了源码上,而使用XML是将关系通过XML来描述。这里实现的功能就类似于在 Bean的定义与控制一文中介绍的Bean的生命周期方法。

使用注解最大的价值就是非侵入式。非侵入式的好处显而易见:

无需和框架代码耦合,更新升级框架风险和成本都很小。

任何时候我们需要需要更换框架,只需修改配置或注解,而无需再去调整我们自己的功能代码。

非侵入式也有一个问题,那就是接入的功能还是需要框架预设,而不可能像继承那样随心所欲。

我们将前面的代码进行一些修改,支持通过注解来指定扩展的功能:

packagechkui.springcore.example.xml.beanpostprocessor.annotation;classBeanFactory{publicListcreateBeans(List<Class<?>> clslist){//同前文...}ObjectcreateBean(Class<?> cls){BeanWrapper wrapper =null;Object ins = cls.newInstance();/**这里增加了一个Handle对象。

          Handle会对注解进行处理,确定添加容器前后的执行方法。*/Handle handle = processBeforeAndAfterHandle(ins);handle.exeBefore();wrapper =newBeanWrapper(ins);handle.exeAfter();returnwrapper;}// 通过反射来确定Bean被添加到容器前后的执行方法。privateHandleprocessBeforeAndAfterHandle(Object obj){Method[] methods = obj.getClass().getDeclaredMethods();Handle handle =newHandle(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);}returnhandle;}}

下面是Handle处理器和对应的注解的代码:

classHandle{Object instance;Method before;Method after;Handle(Object ins){this.instance = ins;}voidsetBefore(Method method){this.before = method;}voidsetAfter(Method method){this.after = method;}voidexeBefore(){if(null!=this.before) {this.before.invoke(this.instance,null);}}voidexeAfter(){if(null!=this.after) {this.after.invoke(this.instance,null);}}}//注解----------------------------------------@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@interfacebefore {}@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@interfaceafter{}

使用者的代码,我们将注解添加到Bean的对应的方法上:

publicclassIocExtensionSampleNoPluging{publicstaticvoidmain(String[] args){    List> classes = Arrays.asList(newClass[]{MyBean1.class, MyBean2.class});    List ins =newBeanFactory().createBeans(classes);    System.out.println("Result:"+ ins.toString());    }}//预设的Bean1classMyBean1{publicStringtoString(){return"MyBean1 Ins";}@beforepublicvoidinit(){    System.out.println("Before Init:"+this.toString());}}//预设的Bean2classMyBean2{publicStringtoString(){return"MyBean2 Ins";}@afterpublicvoidpost(){    System.out.println("After Init:"+this.toString());}}

我们为MyBean1和MyBean2分别添加了init、post方法和对应的@before、@after注解。执行之后输出一下内容:

BeforeInit:MyBean1 InsAfterInit:MyBean2 InsResult:[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接口即可:

packagechkui.springcore.example.xml.beanpostprocessor;importorg.springframework.beans.factory.config.BeanPostProcessor;publicclassProcessorimplementsBeanPostProcessor{//初始化之前publicObjectpostProcessBeforeInitialization(Object bean, String beanName){returnbean;    }//初始化之后publicObjectpostProcessAfterInitialization(Object bean, String beanName){        System.out.println("Bean '"+ beanName +"' created : "+ bean.toString());returnbean;    }}

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,820评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,648评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,324评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,714评论 1 297
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,724评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,328评论 1 310
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,897评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,804评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,345评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,431评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,561评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,238评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,928评论 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,417评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,528评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,983评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,573评论 2 359

推荐阅读更多精彩内容