Spring 核心 —— IOC 处理器扩展

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> Spring 核心 —— IOC 处理器扩展

来源:随风溜达的向日葵@ ,
www.chkui.com/article/spring/spring_core_bean_post_processors

www.chkui.com/article/spring/spring_core_bean_post_processors

 

非侵入式框架

 

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

 

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

 

一个基本的容器

 

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

 

/*框架代码/
package chkui.springcore.example.xml.beanpostprocessor.nopluging;
 
//创建Bean的工厂类,由框架开发者开发
class BeanFactory {
//创建一系列的Bean
public ListObject createBeans(ListClass? 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() + “)”;
}
}

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

class BeanFactory {

public ListObject createBeans(ListClass? clslist){

return createBean(cls);

}

Object createBean(Class? cls){

return new BeanWrapper(cls.newInstance());

}

class BeanWrapper {

public BeanWrapper(Object bean) {

}

public String toString() {

}

 

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

 

/*使用端代码/
package chkui.springcore.example.xml.beanpostprocessor.nopluging;
 
//import …
 
public class IocExtensionSampleNoPluging {
    public static void main(String[] args) {
    ListClass? classes = Arrays.asList(new Class?[]{MyBean1.class, MyBean2.class});
    ListObject 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”;
}
}

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

public class IocExtensionSampleNoPluging {

    ListClass? classes = Arrays.asList(new Class?[]{MyBean1.class, MyBean2.class});

    System.out.println(“Result:” + ins.toString());

}

class MyBean1 {

return “MyBean1 Ins”;

}

class MyBean2 {

return “MyBean2 Ins”;

}

 

classpath:chkui.springcore.example.xml.beanpostprocessor.nopluging.IocExtensionSample。源码地址。

 

某个时刻,框架的使用者有个新需求是在要在每个Bean创建的前后进行一些处理。我们可以通过继承的方式来实现功能。下面我们修改使用端代码实现这个功能。

 

继承实现功能扩展

 

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

 

package chkui.springcore.example.xml.beanpostprocessor.extend;
 
//执行
public class IocExtensionSampleNoPluging {
    public static void main(String[] args) {
    ListClass? classes = Arrays.asList(new Class?[]{MyBean1.class, MyBean2.class});
    ListObject 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;
}
}

//执行

    public static void main(String[] args) {

    ListObject ins = new ModifyBeanFactory().createBeans(classes);

    }

//新建一个BeanFactory的派生类,并修改createBean的实现,添加使用者的处理逻辑

Object createBean(Class? cls){

//添加容器之前的处理

//添加容器之后的处理

}

 

classpath:chkui.springcore.example.xml.beanpostprocessor.extend.IocExtensionSample。源码地址。

 

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

 

  • 导致使用者的代码与框架代码产生了极强的耦合性。如果某天框架进行了调整,例如将方法名改为buildBean、或者增加了更多的代理模式会出现一些意想不到的问题。更麻烦的是可能会遇到一些到运行期才出现的问题。
  • 我们需要先理解框架的源码才能植入我们的功能,这和很多设计模式的原则是背道而驰的。也会大大影响我们的开发效率。
  • 我们需要先理解框架的源码才能植入我们的功能,这和很多设计模式的原则是背道而驰的。也会大大影响我们的开发效率。

     

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

     

    通过注解(Annotation)扩展功能

     

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

     

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

     

  • 无需和框架代码耦合,更新升级框架风险和成本都很小。
  • 任何时候我们需要需要更换框架,只需修改配置或注解,而无需再去调整我们自己的功能代码。
  • 任何时候我们需要需要更换框架,只需修改配置或注解,而无需再去调整我们自己的功能代码。

     

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

     

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

     

    package chkui.springcore.example.xml.beanpostprocessor.annotation;
     
    class BeanFactory {
    public ListObject createBeans(ListClass? 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; } }

    class BeanFactory {

    //同前文…

    Object createBean(Class? cls){

    Object ins = cls.newInstance();

               Handle会对注解进行处理,确定添加容器前后的执行方法。*/

    handle.exeBefore();

    handle.exeAfter();

    }

        // 通过反射来确定Bean被添加到容器前后的执行方法。

    Method[] methods = obj.getClass().getDeclaredMethods();

    for(Method method : methods) {

    Annotation aft = method.getAnnotation(after.class);

    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{}

    Object instance;

    Method after;

    this.instance = ins;

    void setBefore(Method method) {

    }

    this.after = method;

    void exeBefore(){

    this.before.invoke(this.instance, null);

    }

    if(null != this.after) {

    }

    }

    @Target({ElementType.METHOD})

    @interface before {}

    @Retention(RetentionPolicy.RUNTIME)

     

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

     

    public class IocExtensionSampleNoPluging {
        public static void main(String[] args) {
        ListClass? classes = Arrays.asList(new Class?[]{MyBean1.class, MyBean2.class});
        ListObject 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()); } }

        public static void main(String[] args) {

        ListObject ins = new BeanFactory().createBeans(classes);

        }

    //预设的Bean1

    public String toString() {

    }

    @before

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

    }

    class MyBean2 {

    return “MyBean2 Ins”;

    public void post() {

    }

     

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

     

    Before Init:MyBean1 Ins
    After Init:MyBean2 Ins
    Result:[Wrapper(MyBean1 Ins), Wrapper(MyBean2 Ins)]

    After Init: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;
        }
    }

    import org.springframework.beans.factory.config.BeanPostProcessor;

        //初始化之前

            return bean;

    //初始化之后

            System.out.println(“Bean ‘“ + beanName + “‘ created : “ + bean.toString());

        }

     

    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
  • 一个服务接口和服务类:chkui.springcore.example.xml.service.UserService

    Main入口:chkui.springcore.example.xml.beanpostprocessor.BeanPostProcessor

     

    更多的后置处理器说明见:

     

  • https://www.chkui.com/article/spring/spring_core_post_processor_of_official
  • 本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

    本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

    原文链接:blog.ouyangsihai.cn >> Spring 核心 —— IOC 处理器扩展


     上一篇
    下一篇 
    Spring MVC+Spring+Mybatis实现支付宝支付功能(图文详解) Spring MVC+Spring+Mybatis实现支付宝支付功能(图文详解)
    前言本教程详细介绍了如何使用ssm框架实现支付宝支付功能。本文章分为两大部分,分别是「支付宝测试环境代码测试」和「将支付宝支付整合到ssm框架」,详细的代码和图文解释,自己实践的时候一定仔细阅读相关文档,话不多说我们开始。 本教程源代码:
    2021-04-05