互联网科技程序员

关于Spring InitialzationBean遇到的坑及分

2018-07-03  本文已影响52人  Java小铺

背景

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

遇到的坑

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

importorg.springframework.beans.factory.InitializingBean;importorg.springframework.stereotype.Component;@ComponentpublicclassDoOnStartimplementsInitializingBean{@OverridepublicvoidafterPropertiesSet()throwsException{        System.out.println("xxxxxxxx");    }}

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

importorg.springframework.beans.BeansException;importorg.springframework.beans.factory.InitializingBean;importorg.springframework.context.ApplicationContext;importorg.springframework.context.ApplicationContextAware;importorg.springframework.stereotype.Component;@ComponentpublicclassDoOnStartimplementsInitializingBean,ApplicationContextAware{@OverridepublicvoidafterPropertiesSet()throwsException{        System.out.println("xxxxxxxx");    }@OverridepublicvoidsetApplicationContext(ApplicationContext applicationContext)throwsBeansException{        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 的配置文件分开呢?

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

@ServicepublicclassDoOnStartimplementsInitializingBean{@AutowiredprivateXXXController xxxController;@OverridepublicvoidafterPropertiesSet()throwsException{          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 讲解的非常清楚,源码如下:

protectedvoidinvokeInitMethods(String beanName,finalObject bean, RootBeanDefinition mbd)throwsThrowable{//判断该bean是否实现了实现了InitializingBean接口,如果实现了InitializingBean接口,则只掉调用bean的afterPropertiesSet方法  booleanisInitializingBean = (beaninstanceofInitializingBean);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(newPrivilegedExceptionAction() {publicObjectrun()throwsException{//直接调用afterPropertiesSet  ((InitializingBean) bean).afterPropertiesSet();returnnull;                    }                },getAccessControlContext());            }catch(PrivilegedActionException pae) {throwpae.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 的配置文件分开

如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java进阶群:617434785,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。

上一篇下一篇

猜你喜欢

热点阅读