超实用的Spring bean工具类 |
您所在的位置:网站首页 › 帕斯卡对绘画的指责 › 超实用的Spring bean工具类 |
1、背景
我们在项目开发过程中,可能会遇到下面的场景: (1)想在工具类中调用一个http接口请求数据,然后再调用spring容器中托管的service将请求结果保存到数据库 (2)想在工具类中使用spring容器中的环境变量(属性)信息 (3)在spring容器托管的service中只有一个方法需要调用另一个被spring容器托管的service 针对上面的场景,大伙一般会怎么处理呢?是不是也会像下面这样去处理? /** * 工具类 */ @Component public class AppUtil { @Autowired private OperateService operateService; // 注入spring容器中的属性信息 @Value("${url") private String url; //调用外部接口并将信息入库 public static boolean operate(){ //业务。。。。 URL url = new URL(url); HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection(); ..... .... //请求结果入库 operateService.save(...); } } 复制代码 @Service public class AppService{ @Autowired private OperateService operateService; // 注入spring容器中的recordService实例 @Autowired private RecordService recordService; //只有本方法会用到recordService public static boolean record(){ //业务。。。。 .... recordService.record(..); } ... 其他方法 ... } 复制代码上面的方法虽然能解决问题,但是还是有些弊端的。比如: (1)针对前两种场景,如果在工具类中使用spring容器中托管的信息(bean实例或属性配置信息),那么就要把工具类本身也要被spring容器托管,这样才能在工具类中注入bean实例或者一些属性配置信息。如果有一个工具类还好,那如果有多个呢?而且作为一个工具类,我们使用该工具类时都是直接调用工具类中的静态方法,虽然它(工具类)被托管在了spring容器中,但是并不会有别的bean实例来调用这个工具类对应的bean实例,因此这些工具类对应的bean实例们只会占用内存资源。 (2)针对第三种场景,AppService要依赖RecordService,将RecordService作为AppService类的成员变量而存在,会让人误以为这个变量的重要性很大,虽然它在AppService中只被调用了一次。当然这个也并不是重点。但是如果AppService和RecordService互相依赖,那么这就涉及到spring bean对象的循环依赖问题。考虑到springboot官方自spring boot 2.6.x起,就默认不支持循环依赖了,如果我们再通过这种方式互相注入依赖,也就并不合适了。 其实针对上面的问题,有更合适更优雅的处理办法~ 2、功能实现我们可以利用spring自身的特性,编写一个工具类,来帮我们处理以上的场景。实现代码如下: package com.xk.spring.util; import org.springframework.beans.BeansException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.EnvironmentAware; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; /** * spring应用工具类 * 可以获取spring容器中的bean实例以及环境变量信息 * @author xk * @since 2023.05.10 8:34 */ @Component public class SpringApplicationUtil implements ApplicationContextAware, EnvironmentAware { private static ApplicationContext applicationContext; private static Environment environment; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringApplicationUtil.applicationContext = applicationContext; } @Override public void setEnvironment(Environment environment) { SpringApplicationUtil.environment = environment; } /** * 根据bean的类型从spring容器中获取bean实例 * @param clazz bean的类型 * @param * @return 指定类型的bean实例 */ public static T getBean(Class clazz) { return applicationContext.getBean(clazz); } /** * 根据bean名称和bean的类型从spring容器中获取bean实例 * @param name bean的名称 * @param clazz bean的类型 * @param * @return 指定类型的bean实例 */ public static T getBean(String name, Class clazz) { return applicationContext.getBean(name, clazz); } /** * 根据bean的名称从spring容器中获取bean实例 * @param beanName bean的名称 * @return */ public static Object getBean(String beanName) { return applicationContext.getBean(beanName); } /** * 判断spring容器中是否有指定bean名称的bean实例 * @param name bean的名称 * @return */ public static boolean containsBean(String name) { return applicationContext.containsBean(name); } /** * 根据bean名称从spring容器中获取bean名称对应实例的类型 * @param name bean的名称 * @return * @throws NoSuchBeanDefinitionException */ public static Class getType(String name) throws NoSuchBeanDefinitionException { return applicationContext.getType(name); } /** * 根据配置项key的名称获取对应的配置项的值 * 比如application.properties配置文件中有 url=www.baidu.com的配置, * 则getProperty("url")的值就是www.baidu.com * @param key 配置项的名称,比如url * @return */ public static String getProperty(String key){ return environment.getProperty(key); } /** * 根据配置项key的名称获取配置项的值,并将值转换为指定的类型 * 比如application.properties配置文件中有age=18的配置项 * 因为age的值是个整数,所以我们可以直接通过本方法获取整数的age值 * getProperty("age",Integer.class)的结果就是整数18 * @param key 配置项的名称 * @param targetType 配置项对应的值的类型 * @param * @return 指定类型的配置项的值 */ public static T getProperty(String key,Class targetType) { return environment.getProperty(key,targetType); } } 复制代码如以上代码所述,我们用工具类实现了ApplicationContextAware,实现这个接口是方便我们操作spring容器中的bean实例。而实现EnvironmentAware接口,是为了方便获取spring容器中的环境变量信息。最后,我们在工具类上面还加了@Component注解,让我们工具类由spring容器托管。针对我们之前的描述场景,我们就可以这么使用: /** * 工具类 */ public class AppUtil { @Autowired private OperateService operateService; //调用外部接口并将信息入库 public static boolean operate(){ //业务。。。。 //改动的代码 String url = SpringApplicationUtil.getProperty("url"); URL url = new URL(url); HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection(); ..... .... //请求结果入库 operateService.save(...); } } 复制代码 @Service public class AppService{ @Autowired private OperateService operateService; //只有本方法会用到recordService public static boolean record(){ //业务。。。。 .... //改动的代码 RecordService recordService = SpringApplicationUtil.getBean(RecordService.class); recordService.record(..); } ... 其他方法 ... } 复制代码虽然我们将SpringApplicationUtil工具类托管到了spring容器中,但是我们只需要采用SpringApplicationUtil.getBean()的方式直接调用工具类的静态方法,来获取我们想要的数据。 3、实现原理为啥这么写SpringApplicationUtil工具类,就能满足我们以上场景的需要了呢?这就得结合spring bean的生命周期和spring BeanPostProcessor的特性来说了。 首先我们要理解,如果我们想编码获取spring容器中的bean对象和环境配置信息,就需要拿到application对象和environment对象。我在之前写的“浅谈spring bean的生命周期”的文章中的2.3部分,提到了“初始化bean”的这一操作。在这一步有如下的描述: (2)调用所有的BeanPostProcessor对象的postProcessBeforeInitialization方法 获取spring容器中所有的BeanPostProcessor对象,循环执行各个beanPostProcessor的postProcessBeforeInitialization方法,如果出现postProcessBeforeInitialization返回的对象为null,则终止循环,返回上一个postProcessBeforeInitialization方法返回的对象,然后进入第(3)步。 (3)调用bean的初始化方法 复制代码另外Spring容器中有一个很重要的BeanPostProcessor对象,它就是ApplicationContextAwareProcessor对象,它专门用来为那些实现了EnvironmentAware接口和ApplicationContextAware接口的bean服务,能往这些bean中注入application对象和environment对象。它的实现如下(以spring 5.3.20版本为例): class ApplicationContextAwareProcessor implements BeanPostProcessor { private final ConfigurableApplicationContext applicationContext; private final StringValueResolver embeddedValueResolver; /** * Create a new ApplicationContextAwareProcessor for the given context. */ public ApplicationContextAwareProcessor(ConfigurableApplicationContext applicationContext) { this.applicationContext = applicationContext; this.embeddedValueResolver = new EmbeddedValueResolver(applicationContext.getBeanFactory()); } @Override @Nullable public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (!(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware || bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware || bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware || bean instanceof ApplicationStartupAware)) { return bean; } AccessControlContext acc = null; if (System.getSecurityManager() != null) { acc = this.applicationContext.getBeanFactory().getAccessControlContext(); } if (acc != null) { AccessController.doPrivileged((PrivilegedAction) () -> { invokeAwareInterfaces(bean); return null; }, acc); } else { invokeAwareInterfaces(bean); } return bean; } private void invokeAwareInterfaces(Object bean) { if (bean instanceof EnvironmentAware) { ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment()); } if (bean instanceof EmbeddedValueResolverAware) { ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver); } if (bean instanceof ResourceLoaderAware) { ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext); } if (bean instanceof ApplicationEventPublisherAware) { ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext); } if (bean instanceof MessageSourceAware) { ((MessageSourceAware) bean).setMessageSource(this.applicationContext); } if (bean instanceof ApplicationStartupAware) { ((ApplicationStartupAware) bean).setApplicationStartup(this.applicationContext.getApplicationStartup()); } if (bean instanceof ApplicationContextAware) { ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext); } } } 复制代码可以看到,当初始化bean的时候,ApplicationContextAwareProcessor的postProcessBeforeInitialization方法就会被调用。 如果bean实现了EnvironmentAware接口,就会调用bean的setEnvironment方法,那么就会执行下面这行: ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment()); 复制代码bean对象中的Environment类型的成员变量就有值了。 而如果bean实现了ApplicationContextAware接口,就会调用bean的setApplicationContext方法,也就会执行下面这行, if (bean instanceof ApplicationContextAware) { ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext); } 复制代码此时bean对象中的ApplicationContext类型的成员变量就有值了。 当然,前提是我们的bean对象(所属的类)中定义了ApplicationContext类型的成员变量和Environment类型的成员变量,而且要分别在setApplicationContext和setEnvironment方法中对它们赋值才行。另外,最重要的一点是:我们讲的是spring bean的初始化,那我们的SpringApplicationUtil工具类就要被托管(注册)到spring容器中才行,这也就是为什么在SpringApplicationUtil类上面加上@Component注解的原因,就是为了将工具类标记为spring的一个组件(记得要确保该组件能被spring识别)。当然啦,我们在配置类中使用@Bean的方式将工具类托管(注册)到spring容器中也是可以的。 4、知识补充前面我们提到了ApplicationContextAwareProcessor这个后置处理器,spring容器初始化bean的时候,会从容器中拿到它来执行它内部的方法,那么它是什么时候被注入到spring容器中的呢? 我们再来看下spring容器启动过程中会调用到的AbstractApplicationContext.refresh方法,实现如下: @Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh"); // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process"); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); beanPostProcess.end(); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } //省略后面的代码 .... } } 复制代码里面会调用prepareBeanFactory方法,我们来看下它内部实现: protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) { // Tell the internal bean factory to use the context's class loader etc. beanFactory.setBeanClassLoader(getClassLoader()); if (!shouldIgnoreSpel) { beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader())); } beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment())); //就是这里,ApplicationContextAwareProcessor对象被加到了bean工厂,后面初始化bean时会使用 beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this)); beanFactory.ignoreDependencyInterface(EnvironmentAware.class); //省略后面的代码 。。。。。 } 复制代码另外,refresh方法内部还调用了一个registerBeanPostProcessors(beanFactory);方法,其实这个方法就是对容器中的各种BeanPostProcessor进行一个优先级排序。 结束语觉得有收获的朋友,麻烦点击下“关注”,或者进行分享或收藏,多谢支持~ |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |