这篇文章主要讲解了“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初始化方法的执行顺序及其原理是什么这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。