博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
关于Spring InitialzationBean遇到的坑及分析思考
阅读量:6850 次
发布时间:2019-06-26

本文共 4513 字,大约阅读时间需要 15 分钟。

背景

在项目中,会遇到如下情况,即需要在 Tomcat 启动时去执行一些操作,首先我们想到的是继承 ServletContextListener,然后在 contextInitialized 加入需要执行的操作,这是一种方法;那么对于 Spring 项目来说,也可以继承 InitialzationBean 来实现,在初始化 bean 和销毁 bean 的时候执行某个方法,由于 ServletContextListener 需要在 web.xml 中进行配置,而且可能要注入其他 bean,所以笔者选择了继承 InitialzationBean 来实现。

遇到的坑

新建一个类,继承 InitialzationBean,代码如下:

import org.springframework.beans.factory.InitializingBean;import org.springframework.stereotype.Component;@Componentpublic class DoOnStart implements InitializingBean {    @Override    public void afterPropertiesSet() throws Exception {        System.out.println("xxxxxxxx");    }}复制代码

本以为这样就 OK 了,启动 Tomcat 后发现,afterPropertiesSet 方法被执行了两次,奇怪,难道 Spring 会初始化两次 Bean?带着这种猜测,又进行了如下验证:

import org.springframework.beans.BeansException;import org.springframework.beans.factory.InitializingBean;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.stereotype.Component;@Componentpublic class DoOnStart implements InitializingBean, ApplicationContextAware {    @Override    public void afterPropertiesSet() throws Exception {        System.out.println("xxxxxxxx");    }    @Override    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {        System.out.println("xxxxxxxx");    }}复制代码

通过 Debug 发现,setApplicationContext 方法确实执行了两次,也就是说,有两个容器被初始化了,通过查看 applicationContext 发现,第一次是 Root WebApplicationContext,第二次是 WebApplicationContext for namespace spring-servlet,看到这里,茅塞顿开:

第一次是 Spring 对 Bean 进行了初始化,第二次是 Spring MVC 又对 Bean 进行了初始化

那么如何解决加载两次对问题呢?那就是让 Spring MVC 只扫描 @Controller 注解,配置如下:

复制代码

为什么要将 Spring 的配置文件和 Spring MVC 的配置文件分开呢?

我们用以下代码进行测试:

@Service  public class DoOnStart implements InitializingBean {       @Autowired      private XXXController xxxController;        @Override      public void afterPropertiesSet() throws Exception {          System.out.println("xxxxxxxx");      }  }  复制代码

有如下情况:

  • Spring 加载全部 bean,MVC 加载 Controller
    • 可以
  • Spring 加载全部 bean,MVC 容器啥也不加载
    • 可以
  • Spring 加载所有除了 Controller 的 bean,MVC 只加载 Controller
    • 不可以,父容器不能访问子容器的 bean
  • Spring 不加载 bean,MVC 加载所有的 bean
    • 可以

原来 Spring 是父容器, Spring MVC 是子容器, 子容器可以访问父容器的 bean,父容器不能访问子容器的 bean

  • 单例的bean在父子容器中存在一个实例还是两个实例?

初始化两次,Spring 容器先初始化 bean,MVC 容器再初始化 bean,所以应该是两个 bean

  • 为啥不把所有 bean 都在子容器中扫描?

缺点是不利于扩展

源码分析

通过查看 Spring 的加载 bean 的源码类 AbstractAutowireCapableBeanFactory 可看出其中奥妙,AbstractAutowireCapableBeanFactory 类中的 invokeInitMethods 讲解的非常清楚,源码如下:

protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)        throws Throwable {    //判断该bean是否实现了实现了InitializingBean接口,如果实现了InitializingBean接口,则只掉调用bean的afterPropertiesSet方法    boolean isInitializingBean = (bean instanceof InitializingBean);    if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {        if (logger.isDebugEnabled()) {            logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");        }                if (System.getSecurityManager() != null) {            try {                AccessController.doPrivileged(new PrivilegedExceptionAction() {                    public Object run() throws Exception {                        //直接调用afterPropertiesSet                        ((InitializingBean) bean).afterPropertiesSet();                        return null;                    }                },getAccessControlContext());            } catch (PrivilegedActionException pae) {                throw pae.getException();            }        }                        else {            //直接调用afterPropertiesSet            ((InitializingBean) bean).afterPropertiesSet();        }    }    if (mbd != null) {        String initMethodName = mbd.getInitMethodName();        //判断是否指定了init-method方法,如果指定了init-method方法,则再调用制定的init-method        if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&                !mbd.isExternallyManagedInitMethod(initMethodName)) {                //进一步查看该方法的源码,可以发现init-method方法中指定的方法是通过反射实现            invokeCustomInitMethod(beanName, bean, mbd);        }    }  复制代码

总结

  • Spring 为 bean 提供了两种初始化 bean 的方式,实现 InitializingBean 接口,实现 afterPropertiesSet 方法,或者在配置文件中通过 init-method 指定,两种方式可以同时使用

  • 实现 InitializingBean 接口是直接调用 afterPropertiesSet 方法,比通过反射调用 init-method 指定的方法效率相对来说要高点。但是 init-method 方式消除了对 Spring 的依赖

  • 如果调用 afterPropertiesSet 方法时出错,则不调用 init-method 指定的方法

  • 要将 Spring 的配置文件和 Spring MVC 的配置文件分开

转载地址:http://cgrul.baihongyu.com/

你可能感兴趣的文章
解决Android中No resource found that matches android:TextAppearance.Material.Widget.Button.Inverse问题...
查看>>
【探索】在 JavaScript 中使用 C 程序
查看>>
CI框架 -- 核心文件 之 Exceptions.php
查看>>
poj 1018 Communication System
查看>>
如何通俗的理解spring的控制反转、依赖注入、面向切面编程等等
查看>>
【iOS知识学习】_iOS沙盒机制
查看>>
Java实现微信菜单json字符串拼接
查看>>
HTML设置超链接字体颜色和点击后的字体颜色
查看>>
Java后端WebSocket的Tomcat实现
查看>>
Chrome测试网站加载时间与流量消耗
查看>>
Linq-语句之存储过程
查看>>
Android开发之定义接口暴露数据
查看>>
Servlet监听器
查看>>
[LintCode] Intersection of Two Arrays II 两个数组相交之二
查看>>
【Objective-C】02-Objective-C学习及iOS开发的准备
查看>>
jTDS Java连接SQL Server 2000数据库
查看>>
转: java DES的算法片码
查看>>
Mysql 数据类型
查看>>
Android抽象布局——include、merge 、ViewStub
查看>>
EF架构~CodeFirst生产环境的Migrations
查看>>