如何写好一个 Spring 组件
背景
Spring 框架提供了许多接口,可以使用这些接口来定制化 bean ,而非简单的 getter/setter 或者构造器注入。细翻 Spring Cloud Netflix、Spring Cloud Alibaba 等这些构建在 Spring Framework 的成熟框架源码,你会发现大量的扩展 bean 例如
- Eureka 健康检查
package org.springframework.cloud.netflix.eureka; public class EurekaHealthCheckHandler implements InitializingBean {}
- Seata Feign 配置
package com.alibaba.cloud.seata.feign; public class SeataContextBeanPostProcessor implements BeanPostProcessor {}
代码示例
- DemoBean
@Slf4j public class DemoBean implements InitializingBean { public DemoBean() { log.info("--> instantiate "); } @PostConstruct public void postConstruct() { log.info("--> @PostConstruct "); } @Override public void afterPropertiesSet() throws Exception { log.info("--> InitializingBean.afterPropertiesSet "); } public void initMethod() { log.info("--> custom initMehotd"); } }
- DemoBeanPostProcessor
@Configuration public class DemoBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if ("demoBean".equals(beanName)){ log.info("--> BeanPostProcessor.postProcessBeforeInitialization "); } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if ("demoBean".equals(beanName)){ log.info("--> BeanPostProcessor.postProcessAfterInitialization "); } return bean; } }
- DemoConfig
@Configuration public class DemoConfig { @Bean(initMethod = "initMethod") public DemoBean demoBean() { return new DemoBean(); } }
运行输出日志
- 整个 bean 的创建过程日志输出如下和文首图片横线以上 bean 创建周期一致
DemoBean : --> instantiate DemoBeanPostProcessor: --> BeanPostProcessor.postProcessBeforeInitialization DemoBean : --> @PostConstruct DemoBean : --> InitializingBean.afterPropertiesSet DemoBean : --> custom initMehotd DemoBeanPostProcessor: --> BeanPostProcessor.postProcessAfterInitialization
执行过程核心源码
- AbstractAutowireCapableBeanFactory.initializeBean
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) { // 执行BeanPostProcessor.postProcessBeforeInitialization Object wrappedBean = wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); ... // 执行用户自定义初始化and JSR 250 定义的方法 invokeInitMethods(beanName, wrappedBean, mbd); ... // 执行执行BeanPostProcessor.postProcessAfterInitialization wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); return wrappedBean; }
- 下文就详细说明一下每个 bean 的扩展周期的最佳使用场景
BeanPostProcessor
BeanPostProcessor 是一个可以自定义实现回调方法接口,来实现自己的实例化逻辑、依赖解决逻辑等,如果想要在 Spring 完成对象实例化、配置、初始化之后实现自己的业务逻辑,可以通过扩展实现一个或多个 BeanPostProcessor 处理。
- 多用于适配器模式,可以在实现同一接口 bean 创建前后进行包装转换
// seata 上下文转换,将其他类型 wrap 成 SeataFeignContext public class SeataContextBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName){ if (bean instanceof FeignContext && !(bean instanceof SeataFeignContext)) { return new SeataFeignContext(getSeataFeignObjectWrapper(), (FeignContext) bean); } return bean; } }
- 自定义 注解查找扩展
net.dreamlu.mica.redisson.stream.RStreamListenerDetector 查找自定义 @RStreamListener 实现 基于 Redisson 的 pub/sub public class RStreamListenerDetector implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { Class<?> userClass = ClassUtils.getUserClass(bean); ReflectionUtils.doWithMethods(userClass, method -> { RStreamListener listener = AnnotationUtils.findAnnotation(method, RStreamListener.class); .... do something }, ReflectionUtils.USER_DECLARED_METHODS); return bean; } }
PostConstruct
JavaEE5 引入了@PostConstruct 作用于 Servlet 生命周期的注解,实现 Bean 初始化之前的自定义操作。
- 只能有一个非静态方法使用此注解
- 被注解的方法不能有返回值和方法参数
- 被注解的方法不得抛出异常
这里需要注意的 这个注解不是 Spring 定义的,而是属于 JavaEE JSR-250 规范定义的注解,当你在使用 Java11 的时候要手动引入相关 jar(因为 Java11 移除了)
<dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> </dependency>
使用场景: 在之前的版本,我们可以在启动后通过 @PostConstruct 注解的方法执行初始化数据。但由于 Java 高版本已经移除相关 API ,我们不推荐使用此 注解,可以通过 Spring 相关 Event 回调事件处理
@PostConstruct 注解的方法在项目启动的时候执行这个方法,也可以理解为在 spring 容器启动的时候执行,可作为一些数据的常规化加载,比如数据字典之类的。
InitializingBean
InitializingBean 接口方法会在 容器初始化(getter/setter/构造器)完成 bean 的属性注入后执行。
应用场景: 动态修改容器注入的 Bean 参数
- 正常用户配置参数注入到 bean
security: oauth2: ignore-urls: - '/ws/**' @ConfigurationProperties(prefix = "security.oauth2") public class PermitAllUrlProperties { @Getter @Setter private List<String> ignoreUrls = new ArrayList<>(); }
- 我们发现此时用户配置并不完整,还有一些通用不需要用户维护,可通过实现 InitializingBean 接口回调扩展
@ConfigurationProperties(prefix = "security.oauth2.ignore") public class PermitAllUrlProperties implements InitializingBean { @Getter @Setter private List<String> urls = new ArrayList<>(); @Override public void afterPropertiesSet() { urls.add("/common/*"); } }
initMethod
上文 @PostConstruct 已经不推荐大家使用,可以使用 Bean(initMethod = 'initMehotd') 替代,相关的限制如上。
@Bean(initMethod = "initMethod") public DemoBean demoBean() { return new DemoBean(); } public void initMethod() { log.info("--> custom initMehotd"); }
总结
- 参考 https://docs.spring.io/spring/docs/5.2.6.RELEASE/spring-framework-reference/core.html#beans-factory-nature
- mica : https://github.com/lets-mica/mica
- pig: https://github.com/lltx/pig
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
利用Neo4j联邦实现LDBC社交网络分割
什么是Neo4j联邦以及水平分片 Neo4j 4.0发布了一个令人兴奋新功能:Neo4j联邦。Neo4j联邦的操作原理从本质上讲非常简单,它提供了一种方式来执行针对多个Neo4j数据库的Cypher查询。 可以通过多种方式来使用此功能,包括跨多个独立数据库的联合查询与分析,数据存储和处理可以水平扩展,也可以进行不同的混合部署。 接下来,我们将探讨如何使用联邦功能来实现著名且具有挑战性的LDBC社交网络基准图的水平缩放(即分片)。 对图数据进行分片是一个众所周知的难题。我们将展示如何使用Neo4j联邦实现分片,Neo4j联邦将分片存储为独立和不相交的图,这意味着关系不会跨越分片。而是使用我们称为代理节点并关联ID值的方法对此类关系进行建模。我们将依赖有关数据模型和将要运行的查询以及要针对哪些查询进行优化的知识,以针对特定用例创建分片数据模型。 最后,我们将通过比较分片版本和非分片版本之间的查询时间和吞吐量,展示联邦功能如何提高查询性能。 LDBC社交网络基准概述 LDBC社交网络基准提供了数据模型规范以及数据生成器,以及一组查询规范。需要注意的是,我们并没有使用基准所指定的基准工作负载,...
- 下一篇
聊聊依赖注入注解@Resource和@Autowired
1. 前言 @Resource和@Autowired注解都可以在Spring Framework应用中进行声明式的依赖注入。而且面试中经常涉及到这两个注解的知识点。今天我们来总结一下它们。 2. @Resource 全称javax.annotation.Resource,它属于JSR-250规范的一个注解,包含Jakarta EE(J2EE)中。Spring提供了对该注解的支持。我们来详细了解一下该注解的规则。 该注解使用在成员属性和setter方法上。默认情况下@Resource按照名称注入,如果没有显式声明名称则按照变量名称或者方法中对应的参数名称进行注入。 如果我们希望在目标Bean中体现多态我们可以这样写: /** * 多态的体现. * * @author felord.cn * @since 9 :26 */ @Component public class ResourceTest { @Resource private ApplicationRunner applicationRunner; @Resource private ApplicationRunner runne...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS7设置SWAP分区,小内存服务器的救世主
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS关闭SELinux安全模块