PostConstruct注解标记类ApplicationContext未加载空指针

目录
  • 区别
  • 讲道理
    • 你这种写法是 可能出错的
  • 原因
  • 解决
  • BeanFactoryPostProcessor 为什么能解决这个问题?
    • 源码分析

今天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的资料请关注我们其它相关文章!

(0)

相关推荐

  • 基于Failed to load ApplicationContext异常的解决思路

    目录 Failed to load ApplicationContext异常 在使用spring连接数据库时出现了如下异常 Failed to load ApplicationContext之spring最常遇到的问题汇总 遇到几个极难解决的问题 Failed to load ApplicationContext异常 在使用spring连接数据库时出现了如下异常 java.lang.IllegalStateException: Failed to load ApplicationContext 

  • Spring中BeanFactory和ApplicationContext的作用和区别(推荐)

    作用: 1.BeanFactory负责读取bean配置文档,管理bean的加载,实例化,维护bean之间的依赖关系,负责bean的声明周期.2.ApplicationContext除了提供上述BeanFactory所能提供的功能之外,还提供了更完整的框架功能: a. 国际化支持 b. 资源访问:Resource rs = ctx. getResource("classpath:config.properties"), "file:c:/config.properties&qu

  • SpringBoot如何使用applicationContext.xml配置文件

    目录 使用applicationContext.xml配置文件 applicationContext 加载配置文件 案例 多文件的加载方法 使用applicationContext.xml配置文件 SpringBoot默认是通过Java代码进行依赖注入,但也为xml形式的依赖注入提供了入口,就是@ImportResource注解. 我们可以在SpringBoot的启动类上添加这个注解并在注解的locations属性中指定xml配置文件.(可以使用一个文件集合也可以只引入主配置文件然后在主配置文件

  • 一文学透ApplicationContext继承接口功能及与BeanFactory区别

    目录 BeanFactory与ApplicationContext关系的分析 ApplicationContext继承的接口与功能 BeanFactory应用代码示例 ApplicationContext各功能的应用代码示例 ResourceLoader 接口的示例 MessageSource 接口的示例 ApplicationEventPublisher 接口的示例 EnvironmentCapable 接口的示例 ListableBeanFactory接口示例 AutowireCapable

  • Spring ApplicationContext上下文核心容器深入探究

    目录 整体流程 prepareRefresh obtainFreshBeanFactory prepareBeanFactory postProcessBeanFactory 总结 Spring 容器核心可归纳为两个类: BeanFactory 和 ApplicationContext,ApplicationContext 继承自 BeanFactory ,其不仅包含 BeanFactory 所有功能,还扩展了容器功能.ApplicationContext 核心其实是 refresh 方法,容器

  • SpringBoot项目报错:"Error starting ApplicationContext...."解决办法

    目录 发现错误 一.编译出问题 二.请求接口重复 三.加@Mapper注解 四.加@SpringBootApplication注解,数据库问题 五.端口重复问题 六.包冲突 总结 发现错误 SpringBoot项目报错: Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled. 以下方案80%可以帮助您解决这些个‘可恶的’问题

  • ServletWebServerApplicationContext创建Web容器Tomcat示例

    目录 正文 创建Web服务 一.获取Web服务器工厂 1.1 选择导入Web工厂 二.getWebServer:获取Web服务 2.1 创建TomcatEmbeddedContext 2.2. 创建TomcatWebServer 2.2.1 启动Tomcat 初始化小结 startInternal:启动Internal NamingResources启动 Service启动 启动Engine 启动TomcatEmbeddedContext 启动Executor 启动MapperListener

  • Spring系列中的beanFactory与ApplicationContext

    目录 一.BeanFactory 二.ApplicationContext 三.二者区别 四.总结 一.BeanFactory BeanFactory 是 Spring 的“心脏”.它就是 Spring IoC 容器的真面目.Spring 使用 BeanFactory 来实例化.配置和管理 Bean. BeanFactory:是IOC容器的核心接口, 它定义了IOC的基本功能,我们看到它主要定义了getBean方法.getBean方法是IOC容器获取bean对象和引发依赖注入的起点.方法的功能是

  • Springboot使用@RefreshScope注解实现配置文件的动态加载

    目录 pom.xml properties 启动类 配置类 controller 打包 springcloud对应的springboot版本 参考: spring-boot-starter-actuator提供服务健康检查和暴露内置的url接口. spring-cloud-starter-config提供动态刷新的一些支持和注解. pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xml

  • php类的自动加载操作实例详解

    本文实例讲述了php类的自动加载操作.分享给大家供大家参考,具体如下: 类的自动加载 在外面的页面中,并不需要去引入类文件,但程序会在需要一个类的时候自动去"动态加载"该类. ① 创建一个对象的时候new ② 直接使用一个类名(操作静态属性与方法) 使用__autoload魔术函数 当出现两种情况时候,就会调用该函数,该函数需要我们预先定义,在其中写好加载类文件的通用语句 function __autoload($name){ require './lib/'.$name.'.clas

  • 第十二节 类的自动加载 [12]

    当你尝试使用一个未定义的类时,PHP会报告一个致命错误. 解决方法就是添加一个类,可以用include包含一个文件. 毕竟你知道要用到哪个类. 但是,PHP提供了类的自动加载功能, 这可以节省编程的时间. 当你尝试使用一个PHP没有组织到的类, 它会寻找一个__autoload的全局函数. 如果存在这个函数,PHP会用一个参数来调用它,参数即类的名称. 例子6.15说明了__autoload是如何使用的. 它假设当前目录下每个文件对应一个类. 当脚本尝试来产生一个类User的实例,PHP会执行_

  • 第十二节--类的自动加载

    /* +-------------------------------------------------------------------------------+ | = 本文为Haohappy读<<Core PHP Programming>>  | = 中Classes and Objects一章的笔记  | = 翻译为主+个人心得  | = 为避免可能发生的不必要的麻烦请勿转载,谢谢  | = 欢迎批评指正,希望和所有PHP爱好者共同进步!  +-------------

  • PHP类的自动加载机制实现方法分析

    本文实例讲述了PHP类的自动加载机制实现方法.分享给大家供大家参考,具体如下: Test1.class.php <?php class Test1 { public static function test() { echo "hello,world!\n"; } } Test2.class.php <?php class Test2 { public static function test() { echo "你好,世界!\n"; } } test.

  • PHP类的自动加载与命名空间用法实例分析

    本文实例讲述了PHP类的自动加载与命名空间用法.分享给大家供大家参考,具体如下: 作为一名合格的程序员,必定会有一个从面向过程编程到面向对象编程的转化过程,在这个过程中诸如命名空间,类,继承,接口,类自动加载等等都是需要我们去掌握的,之前对这些概念都不是很熟悉,只是能够基础地使用,在这里系统的记录一下关于命名空间与类的自动加载知识. 类的自动加载 什么是自动加载类? ? ? 从字面意思上就可以大概知道,当调用一个当前页面未定义的类的时候能够自动加载. 相信如果你之前了解过php的魔术方法的话,肯

  • 解决VUE mounted 钩子函数执行时 img 未加载导致页面布局的问题

    项目需求:图片加载时,当鼠标滚动至当前图片进行加载并加上上滑特效,实现这个效果需要对文档文档滚动位置和图片的当前位置进行比较.但是mounted 钩子函数执行时img图片并未加载出来也就是占位为空,导致图片位置计算出问题. 解决这个问题,目前想到几种种方法 一.对mounted 钩子函数 中init方法加上延时 mounted: function() { this.$nextTick(() => { // 加上延时避免 mounted 方法比页面加载早执行 或者 对img进行块级化设置宽高进行

  • js实现图片在未加载完成前显示加载中字样

    <html> <title>图片预加载</title> <body> <script> //判断浏览器 var Browser=new Object(); Browser.userAgent=window.navigator.userAgent.toLowerCase(); Browser.ie=/msie/.test(Browser.userAgent); Browser.Moz=/gecko/.test(Browser.userAgent);

  • jQuery Mobile页面跳转后未加载外部JS原因分析及解决

    在使用jQuery Mobile进行Web开发中,当页面跳转时(pageA => pageB),在pageB中引用的JS并未成功运行.因为,JQM并为将整个页面加载到当前的dom中,仅将data-role="page"元素加入当前的dom中. 因此,在<head>中引入的外部JS文件,以及<page>标签外的JS均不能正常运行,刷新页面后方可加载成功. 鉴于JQM这个特性不太可能主动更改,可以用两种方法来解决:一是在index页面中,注册所有需要使用到的外

  • 分享一个轻量级图片加载类 ImageLoader

    ImageLoader 这类的 图片加载网络上一大推,像比较出名的有nostra13 的-Image-Loader图片加载,xUtil的图片加载,还有 Facebook 的 Fresco .很多,但本着求学的态度,最近在做项目时有图片加载这个需求就自己写了个轻量级的 (本地)图片缓存加载 功能,分享给各位. 里面涉及了 LruCache ,ExecutorService,处理大图的 BitmapFactory 原理,view.setTag() . 好了,不多说,先一步一步来: 首先看一下我封装的

随机推荐