PostConstruct注解标记类ApplicationContext未加载空指针
作者:雨夜之寂
序
今天Code Review的时候 看到其他项目 static 方法需要使用 bean的实体方法,是从网上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); } }
虽然现在 代码运行没有毛病,但是 我们有公共类SpringUtils 实现了相同功能,其实不应该 重复在业务系统自己写。
但是这个时候 人家可能会问 我这么写和 用公共类 的效果不是一样么? 都一样
区别
- 一方面是 代码规范,公共功能都有现成的,不需要自己开发,节省错误的概率 和 提升效率
开发的时候 有人会说 我哪知道有哪些功能是现在有的,关于这个 我会提供一个搜索的网页,方便进行搜索,如果搜索不到就是没有,你感觉是公共功能,可以提交 让别人使用。
你既然给人家推荐用公共类,那你肯定要说清楚 公共类的好处,才能让人家信服。你不能说效果都一样,就是用我的吧。。。
讲道理
你这种写法是 可能出错的
定义一个 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服务启动过程中,spring会先去注册所有的bean,在注册过程中,如果发现该bean中包涵了被@PostConstruct注释的函数,那么就会先去执行这个函数,然后再继续注册其他未注册的bean。
但是在springUtils中,无论是继承ApplicationListener,还是继承自ApplicationContextAware,都只有在bean初始化完成后,才会执行注入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容器中获取相关降级的bean * * @param fallbackClass 降级的Class类对象 * @param paramValues 参数值 * @return 相关降级的bean */ public Object getBean(Class<?> fallbackClass, Object[] paramValues) { return getBeanFactory().getBean(fallbackClass, paramValues); } /** * 通过类型参考返回带泛型参数的Bean * * @param reference 类型参考,用于持有转换后的泛型类型 * @param <T> Bean类型 * @return 带泛型参数的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); } /** * 获取指定类型对应的所有Bean,包括子类 * * @param <T> Bean类型 * @param type 类、接口,null表示获取所有bean * @return 类型对应的bean,key是bean注册的name,value是Bean * @since 5.3.3 */ public <T> Map<String, T> getBeansOfType(Class<T> type) { return getBeanFactory().getBeansOfType(type); } /** * 获取指定类型对应的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); } /** * 获取应用程序名称 * * @return 应用程序名称 * @since 5.7.12 */ public String getApplicationName() { return getProperty("spring.application.name"); } /** * 获取当前的环境配置,无配置返回null * * @return 当前的环境配置 * @since 5.3.3 */ public static String[] getActiveProfiles() { if (null == applicationContext) { return null; } return applicationContext.getEnvironment().getActiveProfiles(); } /** * 获取当前的环境配置,当有多个环境配置时,只获取第一个 * * @return 当前的环境配置 * @since 5.3.3 */ public String getActiveProfile() { final String[] activeProfiles = getActiveProfiles(); return ArrayUtil.isNotEmpty(activeProfiles) ? activeProfiles[0] : null; } /** * 动态向Spring注册Bean * <p> * 由{@link org.springframework.beans.factory.BeanFactory} 实现,通过工具开放API * <p> * 更新: shadow 2021-07-29 17:20:44 增加自动注入,修复注册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注销,请谨慎使用 * * @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!"); } } /** * 发布事件 * * @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),可以调整Bean的属性
- 上下文可以自动检测BeanFactoryPostProcessor,并且在Bean实例化之前调用
源码分析
BeanFactoryPostProcessor是在Bean被实例化之前对Bean的定义信息进行修改,那么Spring是如何实现对自定义BeanFactoryPostProcessor的调用的,下面通过源码来看一下,首先还是从refresh()方法入手,在refresh()方法中会调用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 } //这里是优先级的处理,如果我们有多个自定义的BeanFactoryPostProcessor,可以通过优先级来定义执行顺序 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. //这里先处理实现了PriorityOrdered接口的BeanFactoryPostProcessor,也就是定义了优先级的先处理 sortPostProcessors(priorityOrderedPostProcessors, beanFactory); invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory); // Next, invoke the BeanFactoryPostProcessors that implement Ordered. //再处理实现了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,然后调用postProcessBeanFactory()方法,这个方法就是我们自定义BeanFactoryPostProcessor时需要去实现的方法,至此整个流程就已经很清晰了
以上就是PostConstruct注解标记类ApplicationContext未加载空指针的详细内容,更多关于PostConstruct ApplicationContext的资料请关注脚本之家其它相关文章!
您可能感兴趣的文章:
- Spring系列中的beanFactory与ApplicationContext
- Spring中BeanFactory和ApplicationContext的作用和区别(推荐)
- ServletWebServerApplicationContext创建Web容器Tomcat示例
- Spring ApplicationContext上下文核心容器深入探究
- SpringBoot项目报错:"Error starting ApplicationContext...."解决办法
- SpringBoot如何使用applicationContext.xml配置文件
- 基于Failed to load ApplicationContext异常的解决思路
- 一文学透ApplicationContext继承接口功能及与BeanFactory区别