您好,登錄后才能下訂單哦!
這篇文章主要講解了“spring初始化方法的執行順序及其原理是什么”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“spring初始化方法的執行順序及其原理是什么”吧!
/** * 調用順序 init2(PostConstruct注解) --> afterPropertiesSet(InitializingBean接口) --> init3(init-method配置) */ public class Test implements InitializingBean { public void init3(){ System.out.println("init3"); } @PostConstruct public void init2(){ System.out.println("init2"); } @Override public void afterPropertiesSet() throws Exception { System.out.println("afterPropertiesSet"); } }
<context:annotation-config/> <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中有配置
http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
public class ContextNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser()); registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser()); registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser()); registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser()); registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser()); registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser()); registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser()); registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser()); } }
我們只關心這個標簽,那我們就進入AnnotationConfigBeanDefinitionParser類中,看它的parse方法
public BeanDefinition parse(Element element, ParserContext parserContext) { Object source = parserContext.extractSource(element); // Obtain bean definitions for all relevant BeanPostProcessors. Set<BeanDefinitionHolder> processorDefinitions = AnnotationConfigUtils.registerAnnotationConfigProcessors(parserContext.getRegistry(), source); // Register component for the surrounding <context:annotation-config> element. CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source); parserContext.pushContainingComponent(compDefinition); // Nest the concrete beans in the surrounding component. for (BeanDefinitionHolder processorDefinition : processorDefinitions) { parserContext.registerComponent(new BeanComponentDefinition(processorDefinition)); } // Finally register the composite component. parserContext.popAndRegisterContainingComponent(); return null; }
Set<BeanDefinitionHolder> processorDefinitions = AnnotationConfigUtils.registerAnnotationConfigProcessors(parserContext.getRegistry(), source);
我們追蹤進去(其中省略了一些我們不關心的代碼)
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors( BeanDefinitionRegistry registry, Object source) { ... // Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor. if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); } ... }
在這個方法其中注冊了一個CommonAnnotationBeanPostProcessor類,這個類是我們@PostConstruct這個注解發揮作用的基礎。
在bean實例化的過程中,會調用AbstractAutowireCapableBeanFactory類的doCreateBean方法,在這個方法中會有一個調用initializeBean方法的地方,
protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) { if (System.getSecurityManager() != null) { AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { invokeAwareMethods(beanName, bean); return null; } }, getAccessControlContext()); } else { invokeAwareMethods(beanName, bean); } Object wrappedBean = bean; if (mbd == null || !mbd.isSynthetic()) { // 調用@PostConstruct方法注解的地方 wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);//① } try { // 調用afterPropertiesSet和init-method地方 invokeInitMethods(beanName, wrappedBean, mbd);// ② } catch (Throwable ex) { throw new BeanCreationException( (mbd != null ? mbd.getResourceDescription() : null), beanName, "Invocation of init method failed", ex); } if (mbd == null || !mbd.isSynthetic()) { wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); } return wrappedBean; }
先看①這行,進入applyBeanPostProcessorsBeforeInitialization方法
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) { result = beanProcessor.postProcessBeforeInitialization(result, beanName); if (result == null) { return result; } } return result; }
我們還記得前面注冊的一個類CommonAnnotationBeanPostProcessor,其中這個類間接的實現了BeanPostProcessor接口,所以此處會調用CommonAnnotationBeanPostProcessor類的postProcessBeforeInitialization方法,它本身并沒有實現這個方法,但他的父類InitDestroyAnnotationBeanPostProcessor實現了postProcessBeforeInitialization的方法,其中這個方法就實現調用目標類上有@PostConstruct注解的方法
// 獲取目標類上有@PostConstruct注解的方法并調用 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass()); try { metadata.invokeInitMethods(bean, beanName); } catch (InvocationTargetException ex) { throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException()); } catch (Throwable ex) { throw new BeanCreationException(beanName, "Failed to invoke init method", ex); } return bean; }
然后接著看initializeBean方法中②這一行代碼,首先判斷目標類有沒有實現InitializingBean,如果實現了就調用目標類的afterPropertiesSet方法,然后如果有配置init-method就調用其方法
protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd) throws Throwable { // 1、調用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<Object>() { @Override public Object run() throws Exception { ((InitializingBean) bean).afterPropertiesSet(); return null; } }, getAccessControlContext()); } catch (PrivilegedActionException pae) { throw pae.getException(); } } else { ((InitializingBean) bean).afterPropertiesSet(); } } // 2、調用init-method方法 if (mbd != null) { String initMethodName = mbd.getInitMethodName(); if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) && !mbd.isExternallyManagedInitMethod(initMethodName)) { invokeCustomInitMethod(beanName, bean, mbd); } } }
至此Spring的初始化方法調用順序的解析就已經完了。
借用log4j2,向數據庫中新增一條記錄,對于特殊的字段需要借助線程的環境變量。其中某個字段需要在數據庫中查詢到具體信息后插入,在借助Spring MVC的Dao層時遇到了加載順序問題。
log4j2插入數據庫的方案參考文章:
<Column name="user_info" pattern="%X{user_info}" isUnicode="false" />
需要執行日志插入操作(比如綁定到一個級別為insert、logger.insert())的線程中有環境變量user_info。
解決環境變量的方法:
攔截器:
@Component public class LogInterceptor implements HandlerInterceptor { /** * 需要記錄在log中的參數 */ public static final String USER_INFO= "user_info"; @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object arg) throws Exception { String userName = LoginContext.getCurrentUsername(); ThreadContext.put(USER_INFO, getUserInfo()); } @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object arg, Exception exception) throws Exception { ThreadContext.remove(USER_INFO); }
需要攔截的URL配置:
@Configuration public class LogConfigurer implements WebMvcConfigurer { String[] logUrl = new String[] { "/**", }; String[] excludeUrl = new String[] { "/**/*.js", "/**/*.css", "/**/*.jpg", "/**/*.png", "/**/*.svg", "/**/*.woff", "/**/*.eot", "/**/*.ttf", "/**/*.less", "/favicon.ico", "/license/lackofresource", "/error" }; /** * 注冊一個攔截器 * * @return HpcLogInterceptor */ @Bean public LogInterceptor setLogBean() { return new LogInterceptor(); } @Override public void addInterceptors(InterceptorRegistry reg) { // 攔截的對象會進入這個類中進行判斷 InterceptorRegistration registration = reg.addInterceptor(setLogBean()); // 添加要攔截的路徑與不用攔截的路徑 registration.addPathPatterns(logUrl).excludePathPatterns(excludeUrl); } }
如下待優化:
問題就出在如何獲取信息這個步驟,原本的方案是:
通過Dao userDao從數據庫查詢信息,然后填充進去。
出現的問題是:userDao無法通過@Autowired方式注入。
原因:
調用處SpringBoot未完成初始化,導致dao層在調用時每次都是null。
因此最后采用的方式如下:
@Component public class LogInterceptor implements HandlerInterceptor { /** * 需要記錄在log中的參數 */ public static final String USER_INFO= "user_info"; @Resource(name = "jdbcTemplate") private JdbcTemplate jdbcTemplate; @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object arg) throws Exception { String userName = LoginContext.getCurrentUsername(); ThreadContext.put(USER_INFO, getUserInfo()); } @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object arg, Exception exception) throws Exception { ThreadContext.remove(USER_INFO); } public String getUserInfo(String userName) { String sqlTemplate = "select user_info from Test.test_user where user_name = ?"; List<String> userInfo= new ArrayList<>(); userInfo= jdbcTemplate.query(sqlTemplate, preparedStatement -> { preparedStatement.setString(1, userName); }, new SecurityRoleDtoMapper()); if (userInfo.size() == 0) { return Constants.HPC_NORMAL_USER; } return userInfo.get(0); }
感謝各位的閱讀,以上就是“spring初始化方法的執行順序及其原理是什么”的內容了,經過本文的學習后,相信大家對spring初始化方法的執行順序及其原理是什么這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。