PostConstruct注解標(biāo)記類ApplicationContext未加載空指針
序
今天Code Review的時候 看到其他項目 static 方法需要使用 bean的實體方法,是從網(wǎng)上copy的 大概是
public class SpringUtils implements ApplicationListener<ApplicationEvent> {
private static ApplicationContext applicationContext;
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ContextRefreshedEvent) {
ContextRefreshedEvent e = (ContextRefreshedEvent)event;
if (e.getApplicationContext().getParent() == null) {
applicationContext = e.getApplicationContext();
}
}
}
public static <T> T getBean(Class<T> clazz){
return getApplicationContext().getBean(clazz);
}
}
雖然現(xiàn)在 代碼運行沒有毛病,但是 我們有公共類SpringUtils 實現(xiàn)了相同功能,其實不應(yīng)該 重復(fù)在業(yè)務(wù)系統(tǒng)自己寫。
但是這個時候 人家可能會問 我這么寫和 用公共類 的效果不是一樣么? 都一樣
區(qū)別
- 一方面是 代碼規(guī)范,公共功能都有現(xiàn)成的,不需要自己開發(fā),節(jié)省錯誤的概率 和 提升效率
開發(fā)的時候 有人會說 我哪知道有哪些功能是現(xiàn)在有的,關(guān)于這個 我會提供一個搜索的網(wǎng)頁,方便進行搜索,如果搜索不到就是沒有,你感覺是公共功能,可以提交 讓別人使用。
你既然給人家推薦用公共類,那你肯定要說清楚 公共類的好處,才能讓人家信服。你不能說效果都一樣,就是用我的吧。。。
講道理
你這種寫法是 可能出錯的
定義一個 Service
@Service
public class TestService{
}
定義 一個初始化方法
@Component
public class TestInit{
@PostConstruct
public void init(){
SpringUtils.getBean(TestService.class);
}
}
報錯信息
Caused by: java.lang.NullPointerException: null
at com.example.demo.utils.SpringUtils.getBean(SpringUtils.java:25) ~[classes/:na]
at com.example.demo.service.TestInit.init(TestInit.java:12) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_322]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_322]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_322]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_322]
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:363) ~[spring-beans-5.1.5.RELEASE.jar:5.1.5.RELEASE]
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:307) ~[spring-beans-5.1.5.RELEASE.jar:5.1.5.RELEASE]
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:136) ~[spring-beans-5.1.5.RELEASE.jar:5.1.5.RELEASE]
18 common frames omitted
原因
在spring服務(wù)啟動過程中,spring會先去注冊所有的bean,在注冊過程中,如果發(fā)現(xiàn)該bean中包涵了被@PostConstruct注釋的函數(shù),那么就會先去執(zhí)行這個函數(shù),然后再繼續(xù)注冊其他未注冊的bean。
但是在springUtils中,無論是繼承ApplicationListener,還是繼承自ApplicationContextAware,都只有在bean初始化完成后,才會執(zhí)行注入applicationContext。
解決
可以直接拿著用
@Component
public class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware {
private static ConfigurableListableBeanFactory beanFactory;
private static ApplicationContext applicationContext;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
SpringUtils.beanFactory = beanFactory;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
SpringUtils.applicationContext = applicationContext;
}
/**
* 獲取{@link ApplicationContext}
*
* @return {@link ApplicationContext}
*/
public ApplicationContext getApplicationContext() {
return applicationContext;
}
public ListableBeanFactory getBeanFactory() {
return null == beanFactory ? applicationContext : beanFactory;
}
public ConfigurableListableBeanFactory getConfigurableBeanFactory() throws UtilException {
final ConfigurableListableBeanFactory factory;
if (null != beanFactory) {
factory = beanFactory;
} else if (applicationContext instanceof ConfigurableApplicationContext) {
factory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
} else {
throw new UtilException("No ConfigurableListableBeanFactory from context!");
}
return factory;
}
@SuppressWarnings("unchecked")
public <T> T getBean(String name) {
return (T) getBeanFactory().getBean(name);
}
/**
* 通過class獲取Bean
*
* @param <T> Bean類型
* @param clazz Bean類
* @return Bean對象
*/
public <T> T getBean(Class<T> clazz) {
return getBeanFactory().getBean(clazz);
}
/**
* 通過name,以及Clazz返回指定的Bean
*
* @param <T> bean類型
* @param name Bean名稱
* @param clazz bean類型
* @return Bean對象
*/
public <T> T getBean(String name, Class<T> clazz) {
return getBeanFactory().getBean(name, clazz);
}
/**
* 從spring容器中獲取相關(guān)降級的bean
*
* @param fallbackClass 降級的Class類對象
* @param paramValues 參數(shù)值
* @return 相關(guān)降級的bean
*/
public Object getBean(Class<?> fallbackClass, Object[] paramValues) {
return getBeanFactory().getBean(fallbackClass, paramValues);
}
/**
* 通過類型參考返回帶泛型參數(shù)的Bean
*
* @param reference 類型參考,用于持有轉(zhuǎn)換后的泛型類型
* @param <T> Bean類型
* @return 帶泛型參數(shù)的Bean
* @since 5.4.0
*/
@SuppressWarnings("unchecked")
public <T> T getBean(TypeReference<T> reference) {
final ParameterizedType parameterizedType = (ParameterizedType) reference.getType();
final Class<T> rawType = (Class<T>) parameterizedType.getRawType();
final Class<?>[] genericTypes = Arrays.stream(parameterizedType.getActualTypeArguments()).map(type -> (Class<?>) type).toArray(Class[]::new);
final String[] beanNames = getBeanFactory().getBeanNamesForType(ResolvableType.forClassWithGenerics(rawType, genericTypes));
return getBean(beanNames[0], rawType);
}
/**
* 獲取指定類型對應(yīng)的所有Bean,包括子類
*
* @param <T> Bean類型
* @param type 類、接口,null表示獲取所有bean
* @return 類型對應(yīng)的bean,key是bean注冊的name,value是Bean
* @since 5.3.3
*/
public <T> Map<String, T> getBeansOfType(Class<T> type) {
return getBeanFactory().getBeansOfType(type);
}
/**
* 獲取指定類型對應(yīng)的Bean名稱,包括子類
*
* @param type 類、接口,null表示獲取所有bean名稱
* @return bean名稱
* @since 5.3.3
*/
public String[] getBeanNamesForType(Class<?> type) {
return getBeanFactory().getBeanNamesForType(type);
}
/**
* 獲取配置文件配置項的值
*
* @param key 配置項key
* @return 屬性值
* @since 5.3.3
*/
public String getProperty(String key) {
if (null == applicationContext) {
return null;
}
return applicationContext.getEnvironment().getProperty(key);
}
/**
* 獲取應(yīng)用程序名稱
*
* @return 應(yīng)用程序名稱
* @since 5.7.12
*/
public String getApplicationName() {
return getProperty("spring.application.name");
}
/**
* 獲取當(dāng)前的環(huán)境配置,無配置返回null
*
* @return 當(dāng)前的環(huán)境配置
* @since 5.3.3
*/
public static String[] getActiveProfiles() {
if (null == applicationContext) {
return null;
}
return applicationContext.getEnvironment().getActiveProfiles();
}
/**
* 獲取當(dāng)前的環(huán)境配置,當(dāng)有多個環(huán)境配置時,只獲取第一個
*
* @return 當(dāng)前的環(huán)境配置
* @since 5.3.3
*/
public String getActiveProfile() {
final String[] activeProfiles = getActiveProfiles();
return ArrayUtil.isNotEmpty(activeProfiles) ? activeProfiles[0] : null;
}
/**
* 動態(tài)向Spring注冊Bean
* <p>
* 由{@link org.springframework.beans.factory.BeanFactory} 實現(xiàn),通過工具開放API
* <p>
* 更新: shadow 2021-07-29 17:20:44 增加自動注入,修復(fù)注冊bean無法反向注入的問題
*
* @param <T> Bean類型
* @param beanName 名稱
* @param bean bean
* @author shadow
* @since 5.4.2
*/
public <T> void registerBean(String beanName, T bean) {
final ConfigurableListableBeanFactory factory = getConfigurableBeanFactory();
factory.autowireBean(bean);
factory.registerSingleton(beanName, bean);
}
/**
* 注銷bean
* <p>
* 將Spring中的bean注銷,請謹(jǐn)慎使用
*
* @param beanName bean名稱
* @author shadow
* @since 5.7.7
*/
public void unregisterBean(String beanName) {
final ConfigurableListableBeanFactory factory = getConfigurableBeanFactory();
if (factory instanceof DefaultSingletonBeanRegistry) {
DefaultSingletonBeanRegistry registry = (DefaultSingletonBeanRegistry) factory;
registry.destroySingleton(beanName);
} else {
throw new UtilException("Can not unregister bean, the factory is not a DefaultSingletonBeanRegistry!");
}
}
/**
* 發(fā)布事件
*
* @param event the event to publish
* @since 5.7.12
*/
public void publishEvent(ApplicationEvent event) {
if (null != applicationContext) {
applicationContext.publishEvent(event);
}
}
}
BeanFactoryPostProcessor 為什么能解決這個問題?
@FunctionalInterface
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory var1) throws BeansException;
}
從注釋可以看出來:
- BeanFactoryPostProcessor接口允許修改上下文中Bean的定義(definitions),可以調(diào)整Bean的屬性
- 上下文可以自動檢測BeanFactoryPostProcessor,并且在Bean實例化之前調(diào)用
源碼分析
BeanFactoryPostProcessor是在Bean被實例化之前對Bean的定義信息進行修改,那么Spring是如何實現(xiàn)對自定義BeanFactoryPostProcessor的調(diào)用的,下面通過源碼來看一下,首先還是從refresh()方法入手,在refresh()方法中會調(diào)用invokeBeanFactoryPostProcessors(beanFactory);
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
//主要是這一行
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
// Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
// (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}
}
public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
/**因代碼太長,省略了***/
//這里從beanFacoty中通過BeanFactoryPostProcessor類型來獲取Bean名稱,就可以拿到我們自定義的BeanFactoryPostProcessor
String[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);
List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
List<String> orderedPostProcessorNames = new ArrayList<>();
List<String> nonOrderedPostProcessorNames = new ArrayList<>();
for (String ppName : postProcessorNames) {
if (processedBeans.contains(ppName)) {
// skip - already processed in first phase above
}
//這里是優(yōu)先級的處理,如果我們有多個自定義的BeanFactoryPostProcessor,可以通過優(yōu)先級來定義執(zhí)行順序
else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
}
else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
orderedPostProcessorNames.add(ppName);
}
else {
nonOrderedPostProcessorNames.add(ppName);
}
}
// First, invoke the BeanFactoryPostProcessors that implement PriorityOrdered.
//這里先處理實現(xiàn)了PriorityOrdered接口的BeanFactoryPostProcessor,也就是定義了優(yōu)先級的先處理
sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);
// Next, invoke the BeanFactoryPostProcessors that implement Ordered.
//再處理實現(xiàn)了Ordered接口的BeanFactoryPostProcessor
List<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<>();
for (String postProcessorName : orderedPostProcessorNames) {
orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
}
sortPostProcessors(orderedPostProcessors, beanFactory);
invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);
// Finally, invoke all other BeanFactoryPostProcessors.
List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<>();
for (String postProcessorName : nonOrderedPostProcessorNames) {
nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
}
//這里才到了處理普通的自定義BeanFactoryPostProcessors
invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);
// Clear cached merged bean definitions since the post-processors might have
// modified the original metadata, e.g. replacing placeholders in values...
beanFactory.clearMetadataCache();
}
private static void invokeBeanFactoryPostProcessors(
Collection<? extends BeanFactoryPostProcessor> postProcessors, ConfigurableListableBeanFactory beanFactory) {
for (BeanFactoryPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessBeanFactory(beanFactory);
}
}
invokeBeanFactoryPostProcessors()方法的邏輯很簡單,就是去遍歷容器中的BeanFactoryPostProcessor,然后調(diào)用postProcessBeanFactory()方法,這個方法就是我們自定義BeanFactoryPostProcessor時需要去實現(xiàn)的方法,至此整個流程就已經(jīng)很清晰了
以上就是PostConstruct注解標(biāo)記類ApplicationContext未加載空指針的詳細(xì)內(nèi)容,更多關(guān)于PostConstruct ApplicationContext的資料請關(guān)注腳本之家其它相關(guān)文章!
- Spring系列中的beanFactory與ApplicationContext
- Spring中BeanFactory和ApplicationContext的作用和區(qū)別(推薦)
- ServletWebServerApplicationContext創(chuàng)建Web容器Tomcat示例
- Spring ApplicationContext上下文核心容器深入探究
- SpringBoot項目報錯:"Error?starting?ApplicationContext...."解決辦法
- SpringBoot如何使用applicationContext.xml配置文件
- 基于Failed?to?load?ApplicationContext異常的解決思路
- 一文學(xué)透ApplicationContext繼承接口功能及與BeanFactory區(qū)別
相關(guān)文章
Knife4j的請求示例當(dāng)中有很多空白行的問題解決辦法
這篇文章主要介紹了Knife4j的請求示例當(dāng)中有很多空白行的問題解決辦法,按正常來說不應(yīng)該有上方的空白,當(dāng)然如果只是查看我也不至于非要解決他,主要是假如接口是json傳參,調(diào)試界面都沒辦法修改參數(shù),遇到同樣問題的同學(xué)可以參考閱讀本文2024-09-09
Spring MVC學(xué)習(xí)之DispatcherServlet請求處理詳析
這篇文章主要給大家介紹了關(guān)于Spring MVC學(xué)習(xí)教程之DispatcherServlet請求處理的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11

