经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring » 查看文章
spring初始化方法的执行顺序及其原理分析
来源:jb51  时间:2022/2/14 11:35:35  对本文有异议

Spring中初始化方法的执行顺序

首先通过一个例子来看其顺序

  1. /**
  2. ?* 调用顺序 init2(PostConstruct注解) --> afterPropertiesSet(InitializingBean接口) --> init3(init-method配置)
  3. ?*/
  4. public class Test implements InitializingBean {
  5. ? ? public void init3(){
  6. ? ? ? ? System.out.println("init3");
  7. ? ? }
  8. ? ? @PostConstruct
  9. ? ? public void init2(){
  10. ? ? ? ? System.out.println("init2");
  11. ? ? }
  12. ? ? @Override
  13. ? ? public void afterPropertiesSet() throws Exception {
  14. ? ? ? ? System.out.println("afterPropertiesSet");
  15. ? ? }
  16. }

配置

  1. <context:annotation-config/>
  2. <bean class="com.cyy.spring.lifecycle.Test" id="test" init-method="init3"/>

通过运行,我们得出其执行顺序为init2(PostConstruct注解) --> afterPropertiesSet(InitializingBean接口) --> init3(init-method配置)。但是为什么是这个顺序呢?我们可以通过分析其源码得出结论。

首先在解析配置文件的时候,碰到context:annotation-config/自定义标签会调用其自定义解析器,这个自定义解析器在哪儿呢?在spring-context的spring.handlers中有配置

  1. http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler

我们进入这个类看

  1. public class ContextNamespaceHandler extends NamespaceHandlerSupport {
  2. ?? ?@Override
  3. ?? ?public void init() {
  4. ?? ??? ?registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
  5. ?? ??? ?registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
  6. ?? ??? ?registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
  7. ?? ??? ?registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
  8. ?? ??? ?registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
  9. ?? ??? ?registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
  10. ?? ??? ?registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
  11. ?? ??? ?registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
  12. ?? ?}
  13. }

我们看到了annotation-config了

我们只关心这个标签,那我们就进入AnnotationConfigBeanDefinitionParser类中,看它的parse方法

  1. public BeanDefinition parse(Element element, ParserContext parserContext) {
  2. ? ? Object source = parserContext.extractSource(element);
  3. ? ? // Obtain bean definitions for all relevant BeanPostProcessors.
  4. ? ? Set<BeanDefinitionHolder> processorDefinitions =
  5. ? ? ? ? ? ? AnnotationConfigUtils.registerAnnotationConfigProcessors(parserContext.getRegistry(), source);
  6. ? ? // Register component for the surrounding <context:annotation-config> element.
  7. ? ? CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
  8. ? ? parserContext.pushContainingComponent(compDefinition);
  9. ? ? // Nest the concrete beans in the surrounding component.
  10. ? ? for (BeanDefinitionHolder processorDefinition : processorDefinitions) {
  11. ? ? ? ? parserContext.registerComponent(new BeanComponentDefinition(processorDefinition));
  12. ? ? }
  13. ? ? // Finally register the composite component.
  14. ? ? parserContext.popAndRegisterContainingComponent();
  15. ? ? return null;
  16. }

我们重点看下这行代码

  1. Set<BeanDefinitionHolder> processorDefinitions = AnnotationConfigUtils.registerAnnotationConfigProcessors(parserContext.getRegistry(), source);

我们追踪进去(其中省略了一些我们不关心的代码)

  1. public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
  2. ? ? ? ? BeanDefinitionRegistry registry, Object source) {
  3. ? ? ...
  4. ? ? // Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
  5. ? ? if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
  6. ? ? ? ? RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
  7. ? ? ? ? def.setSource(source);
  8. ? ? ? ? beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
  9. ? ? }
  10. ? ? ...
  11. }

在这个方法其中注册了一个CommonAnnotationBeanPostProcessor类,这个类是我们@PostConstruct这个注解发挥作用的基础。

在bean实例化的过程中,会调用AbstractAutowireCapableBeanFactory类的doCreateBean方法,在这个方法中会有一个调用initializeBean方法的地方,

我们直接看initializeBean这个方法

  1. protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
  2. ? ? if (System.getSecurityManager() != null) {
  3. ? ? ? ? AccessController.doPrivileged(new PrivilegedAction<Object>() {
  4. ? ? ? ? ? ? @Override
  5. ? ? ? ? ? ? public Object run() {
  6. ? ? ? ? ? ? ? ? invokeAwareMethods(beanName, bean);
  7. ? ? ? ? ? ? ? ? return null;
  8. ? ? ? ? ? ? }
  9. ? ? ? ? }, getAccessControlContext());
  10. ? ? }
  11. ? ? else {
  12. ? ? ? ? invokeAwareMethods(beanName, bean);
  13. ? ? }
  14. ? ? Object wrappedBean = bean;
  15. ? ? if (mbd == null || !mbd.isSynthetic()) {
  16. ? ? ? ? // 调用@PostConstruct方法注解的地方
  17. ? ? ? ? wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);//①
  18. ? ? }
  19. ? ? try {
  20. ? ? ? ? // 调用afterPropertiesSet和init-method地方
  21. ? ? ? ? invokeInitMethods(beanName, wrappedBean, mbd);// ②
  22. ? ? }
  23. ? ? catch (Throwable ex) {
  24. ? ? ? ? throw new BeanCreationException(
  25. ? ? ? ? ? ? ? ? (mbd != null ? mbd.getResourceDescription() : null),
  26. ? ? ? ? ? ? ? ? beanName, "Invocation of init method failed", ex);
  27. ? ? }
  28. ? ? if (mbd == null || !mbd.isSynthetic()) {
  29. ? ? ? ? wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
  30. ? ? }
  31. ? ? return wrappedBean;
  32. }

先看①这行,进入applyBeanPostProcessorsBeforeInitialization方法

  1. public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
  2. ? ? ? ? throws BeansException {
  3. ? ? Object result = existingBean;
  4. ? ? for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
  5. ? ? ? ? result = beanProcessor.postProcessBeforeInitialization(result, beanName);
  6. ? ? ? ? if (result == null) {
  7. ? ? ? ? ? ? return result;
  8. ? ? ? ? }
  9. ? ? }
  10. ? ? return result;
  11. }

我们还记得前面注册的一个类CommonAnnotationBeanPostProcessor,其中这个类间接的实现了BeanPostProcessor接口,所以此处会调用CommonAnnotationBeanPostProcessor类的postProcessBeforeInitialization方法,它本身并没有实现这个方法,但他的父类InitDestroyAnnotationBeanPostProcessor实现了postProcessBeforeInitialization的方法,其中这个方法就实现调用目标类上有@PostConstruct注解的方法

  1. // 获取目标类上有@PostConstruct注解的方法并调用
  2. public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  3. ? ? LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
  4. ? ? try {
  5. ? ? ? ? metadata.invokeInitMethods(bean, beanName);
  6. ? ? }
  7. ? ? catch (InvocationTargetException ex) {
  8. ? ? ? ? throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException());
  9. ? ? }
  10. ? ? catch (Throwable ex) {
  11. ? ? ? ? throw new BeanCreationException(beanName, "Failed to invoke init method", ex);
  12. ? ? }
  13. ? ? return bean;
  14. }

然后接着看initializeBean方法中②这一行代码,首先判断目标类有没有实现InitializingBean,如果实现了就调用目标类的afterPropertiesSet方法,然后如果有配置init-method就调用其方法

  1. protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)
  2. ? ? ? ? throws Throwable {
  3. ? ? // 1、调用afterPropertiesSet方法
  4. ? ? boolean isInitializingBean = (bean instanceof InitializingBean);
  5. ? ? if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
  6. ? ? ? ? if (logger.isDebugEnabled()) {
  7. ? ? ? ? ? ? logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
  8. ? ? ? ? }
  9. ? ? ? ? if (System.getSecurityManager() != null) {
  10. ? ? ? ? ? ? try {
  11. ? ? ? ? ? ? ? ? AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
  12. ? ? ? ? ? ? ? ? ? ? @Override
  13. ? ? ? ? ? ? ? ? ? ? public Object run() throws Exception {
  14. ? ? ? ? ? ? ? ? ? ? ? ? ((InitializingBean) bean).afterPropertiesSet();
  15. ? ? ? ? ? ? ? ? ? ? ? ? return null;
  16. ? ? ? ? ? ? ? ? ? ? }
  17. ? ? ? ? ? ? ? ? }, getAccessControlContext());
  18. ? ? ? ? ? ? }
  19. ? ? ? ? ? ? catch (PrivilegedActionException pae) {
  20. ? ? ? ? ? ? ? ? throw pae.getException();
  21. ? ? ? ? ? ? }
  22. ? ? ? ? }
  23. ? ? ? ? else {
  24. ? ? ? ? ? ? ((InitializingBean) bean).afterPropertiesSet();
  25. ? ? ? ? }
  26. ? ? }
  27. ? ? // 2、调用init-method方法
  28. ? ? if (mbd != null) {
  29. ? ? ? ? String initMethodName = mbd.getInitMethodName();
  30. ? ? ? ? if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
  31. ? ? ? ? ? ? ? ? !mbd.isExternallyManagedInitMethod(initMethodName)) {
  32. ? ? ? ? ? ? invokeCustomInitMethod(beanName, bean, mbd);
  33. ? ? ? ? }
  34. ? ? }
  35. }

至此Spring的初始化方法调用顺序的解析就已经完了。 

spring加载顺序典例

借用log4j2,向数据库中新增一条记录,对于特殊的字段需要借助线程的环境变量。其中某个字段需要在数据库中查询到具体信息后插入,在借助Spring MVC的Dao层时遇到了加载顺序问题。

解决方案

log4j2插入数据库的方案参考文章:

  1. <Column name="user_info" pattern="%X{user_info}" isUnicode="false" />

需要执行日志插入操作(比如绑定到一个级别为insert、logger.insert())的线程中有环境变量user_info。

解决环境变量的方法:

拦截器: 

  1. @Component
  2. public class LogInterceptor implements HandlerInterceptor {
  3. /**
  4. * 需要记录在log中的参数
  5. */
  6. public static final String USER_INFO= "user_info";
  7. @Override
  8. public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object arg)
  9. throws Exception {
  10. String userName = LoginContext.getCurrentUsername();
  11. ThreadContext.put(USER_INFO, getUserInfo());
  12. }
  13. @Override
  14. public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
  15. Object arg, Exception exception) throws Exception {
  16. ThreadContext.remove(USER_INFO);
  17. }

需要拦截的URL配置:

  1. @Configuration
  2. public class LogConfigurer implements WebMvcConfigurer {
  3. String[] logUrl = new String[] {
  4. "/**",
  5. };
  6. String[] excludeUrl = new String[] {
  7. "/**/*.js", "/**/*.css", "/**/*.jpg", "/**/*.png", "/**/*.svg", "/**/*.woff", "/**/*.eot", "/**/*.ttf",
  8. "/**/*.less", "/favicon.ico", "/license/lackofresource", "/error"
  9. };
  10. /**
  11. * 注册一个拦截器
  12. *
  13. * @return HpcLogInterceptor
  14. */
  15. @Bean
  16. public LogInterceptor setLogBean() {
  17. return new LogInterceptor();
  18. }
  19. @Override
  20. public void addInterceptors(InterceptorRegistry reg) {
  21. // 拦截的对象会进入这个类中进行判断
  22. InterceptorRegistration registration = reg.addInterceptor(setLogBean());
  23. // 添加要拦截的路径与不用拦截的路径
  24. registration.addPathPatterns(logUrl).excludePathPatterns(excludeUrl);
  25. }
  26. }

如下待优化:

问题就出在如何获取信息这个步骤,原本的方案是:

通过Dao userDao从数据库查询信息,然后填充进去。

出现的问题是:userDao无法通过@Autowired方式注入。

原因:

调用处SpringBoot未完成初始化,导致dao层在调用时每次都是null。

因此最后采用的方式如下: 

  1. @Component
  2. public class LogInterceptor implements HandlerInterceptor {
  3. /**
  4. * 需要记录在log中的参数
  5. */
  6. public static final String USER_INFO= "user_info";
  7. @Resource(name = "jdbcTemplate")
  8. private JdbcTemplate jdbcTemplate;
  9. @Override
  10. public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object arg)
  11. throws Exception {
  12. String userName = LoginContext.getCurrentUsername();
  13. ThreadContext.put(USER_INFO, getUserInfo());
  14. }
  15. @Override
  16. public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
  17. Object arg, Exception exception) throws Exception {
  18. ThreadContext.remove(USER_INFO);
  19. }
  20. public String getUserInfo(String userName) {
  21. String sqlTemplate = "select user_info from Test.test_user where user_name = ?";
  22. List<String> userInfo= new ArrayList<>();
  23. userInfo= jdbcTemplate.query(sqlTemplate, preparedStatement -> {
  24. preparedStatement.setString(1, userName);
  25. }, new SecurityRoleDtoMapper());
  26. if (userInfo.size() == 0) {
  27. return Constants.HPC_NORMAL_USER;
  28. }
  29. return userInfo.get(0);
  30. }

以上为个人经验,希望能给大家一个参考,也希望大家多多支持w3xue。 

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号