spring初始化方法的执行顺序及其原理分析

网友投稿 327 2022-08-28


spring初始化方法的执行顺序及其原理分析

目录Spring中初始化方法的执行顺序首先通过一个例子来看其顺序配置我们进入这个类看我们看到了annotation-config了我们重点看下这行代码我们直接看initializeBean这个方法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");

}

}

配置

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

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

http\://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());

}

}

我们看到了annotation-config了

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

public BeanDefinition parse(Element element, ParserContext parserContext) {

Object source = parserContext.extractSource(element);

// Obtain bean definitions for all relevant BeanPostProcessors.

Set processorDefinitions =

AnnotationConfigUtils.registerAnnotationConfigProcessors(parserContext.getRegistry(), source);

// Register component for the surrounding 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(processorDhttp://efinition));

}

// Finally register the composite component.

parserContext.popAndRegisterContainingComponent();

return null;

}

我们重点看下这行代码

Set processorDefinitions = AnnotationConfigUtils.registerAnnotationConfigProcessors(parserContext.getRegistry(), source);

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

public static Set 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(Commonhttp://AnnotationBeanPostProcessor.class);

def.setSource(source);

beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));

}

...

}

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

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

我们直接看initializeBean这个方法

protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {

if (System.getSecurityManager() != null) {

AccessController.doPrivileged(new PrivilegedAction() {

@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() {

@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的初始化方法调用顺序的解析就已经完了。

spring加载顺序典例

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

解决方案

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

需要执行日志插入操作(比如绑定到一个级别为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 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);

}


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:使用python删除一个文件&&删除一个文件夹&&删除一个文件中的某些内容&&删除一个文件夹os.rmdir和shutil.rmtree两个方法的区别(python如何删除文件)
下一篇:python第三方库openpyxl详解(openpyxl使用)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~