0%

【踩坑】抽象类setApplicationContext被执行多次

setApplicationContext 是 Spring 类加载实例化前的后处理器应用,用于在实体 bean 实例化前 (调用 doCreate 方法) 将 ApplicationContext 注入该 bean。

问题分析

抽象类文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Slf4j
@Service
public abstract class AbstractReloadMethod implements ApplicationContextAware {

private ApplicationContext applicationContext;

private static Set<AbstractMethodAfterReload> reloadMethods = Sets.newConcurrentHashSet();

/**
* Spring加载时初始化reload方法
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
Map<String, AbstractMethodAfterReload> map = applicationContext.getBeansOfType(AbstractMethodAfterReload.class);
reloadMethods.addAll(map.values());
log.info("回调新增服务:{}", map.keySet());
}
}

这是一个抽象类,有两个 Service 拓展了该抽象类。在 Spring 容器加载时,“回调新增服务” 这行日志被打印了两次,就意味这本来应该单例(仅仅是刚开始我错误以为的单例),被创建了两次,这对于容器来说是不可能的。

源码分析

于是选择通过新建一个Throwable来打印出这里调用的调用栈,两次调用都是同样的调用栈。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
java.lang.Throwable: null
at com.travelco.rdf.reload.AbstractReloadMethod.setApplicationContext(AbstractReloadMethod.java:83) ~[travelco-rdf-FLIGHT-105319-SNAPSHOT.jar:na]
at org.springframework.context.support.ApplicationContextAwareProcessor.invokeAwareInterfaces(ApplicationContextAwareProcessor.java:119) [spring-context-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.context.support.ApplicationContextAwareProcessor.postProcessBeforeInitialization(ApplicationContextAwareProcessor.java:94) [spring-context-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:408) [spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1570) [spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:545) [spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482) [spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) [spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) [spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) [spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) [spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansOfType(DefaultListableBeanFactory.java:534) [spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansOfType(DefaultListableBeanFactory.java:523) [spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.getBeansOfType(AbstractApplicationContext.java:1159) [spring-context-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at com.travelco.rdf.reload.ReloadMessageHandler.setApplicationContext(ReloadMessageHandler.java:56) [travelco-rdf-FLIGHT-105319-SNAPSHOT.jar:na]
at org.springframework.context.support.ApplicationContextAwareProcessor.invokeAwareInterfaces(ApplicationContextAwareProcessor.java:119) [spring-context-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.context.support.ApplicationContextAwareProcessor.postProcessBeforeInitialization(ApplicationContextAwareProcessor.java:94) [spring-context-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:408) [spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1570) [spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:545) [spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482) [spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) [spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) [spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) [spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) [spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:772) [spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:838) [spring-context-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:537) [spring-context-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:446) [spring-web-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:328) [spring-web-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:107) [spring-web-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4939) [catalina.jar:7.0.47]
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5434) [catalina.jar:7.0.47]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) [catalina.jar:7.0.47]
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:901) [catalina.jar:7.0.47]
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:877) [catalina.jar:7.0.47]
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:633) [catalina.jar:7.0.47]
at org.apache.catalina.startup.HostConfig.deployDirectory(HostConfig.java:1120) [catalina.jar:7.0.47]
at org.apache.catalina.startup.HostConfig$DeployDirectory.run(HostConfig.java:1678) [catalina.jar:7.0.47]
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_60]
at java.util.concurrent.FutureTask.run(FutureTask.java:266) [na:1.8.0_60]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_60]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_60]
at java.lang.Thread.run(Thread.java:745) [na:1.8.0_60]

这里涉及到bean的加载的一个完整的流程,由于篇幅有限不详述,向上看到 preInstantiateSingletons() 这个方法把 BeanDefinition 注册表中的所有单例、非懒加载的bean全部实例化,详细代码解释如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public void preInstantiateSingletons() throws BeansException {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Pre-instantiating singletons in " + this);
}

// 得到所有beanName
List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);

// 遍历所有beanName
for (String beanName : beanNames) {

// 根据BeanName得到BeanDefinition
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);

// 如果不是抽象的,不是懒加载,是单例
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {

// 如果是FactoryBean
if (isFactoryBean(beanName)) {
final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
@Override
public Boolean run() {
return ((SmartFactoryBean<?>) factory).isEagerInit();
}
}, getAccessControlContext());
} else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
getBean(beanName);
}
}

// 如果不是FactoryBean
else {
getBean(beanName);
}
}
}

// 触发所有Bean的后期初始化(post-initialization)回调方法
for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton) {
final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
smartSingleton.afterSingletonsInstantiated();
return null;
}
}, getAccessControlContext());
} else {
smartSingleton.afterSingletonsInstantiated();
}
}
}
}

结论

在这里选择打了个断点,查看这里的 beanNames,并没有 AbstractReloadMethod 这个类,而是只有两个它的拓展类的名字。所以说抽象类并没有被加载(即使我加上了注解)。这两个拓展类共享抽象类的 public 和 protected 方法和变量。可以在 setApplicationContext 加个 AtomicBoolean 控制只初始化一次。

抽象类的使用

  • 抽象类不能被实例化。因为抽象类中方法未具体化,这是一种不完整的类,所以直接实例化也就没有意义了。
  • 抽象类的使用必须有子类,使用extends继承,一个子类只能继承一个抽象类。
  • 子类(如果不是抽象类)则必须覆写抽象类之中的全部抽象方法(如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。)。
  • 抽象类可以不包含抽象方法,但如果类中包含抽象方法,就必须将该类声明为抽象类。