Spring 相关基础组件扩展与抽象
示例工程 spring-project-infras-examples
0.前言
Spring 框架作为 Java 生态中重要的基础设施,我想每一个 Jvr 都对其非常熟悉甚至 精通。
本文也着重讨论在实际开发工作中常用到的一些基础抽象,大致分如下几个模块:
starterStarter模块2.x版本系3.x版本系
Beans- 一些组件抽象
Core- 一些基础设施抽象
WebWeb模块抽象
Common- 公共模块抽象
使用
Spring Boot 2.x
xml
<!-- ${spring-project-infras.version} == ${latest.version} -->
<!-- 版本号查看 -->
<!-- 根据项目实际情况引入不同的包即可 -->
<!-- https://central.sonatype.com/artifact/io.github.photowey/spring-project-infras/versions -->
<dependency>
<groupId>io.github.photowey</groupId>
<artifactId>spring-infras-spring-boot-starter</artifactId>
<version>${spring-project-infras.version}</version>
</dependency>
Spring Boot 3.x
xml
<dependency>
<!-- ... -->
<artifactId>spring-infras-spring-boot3-starter</artifactId>
<!-- ... -->
</dependency>
1.模块介绍
1.1.Common
公共模块
1.1.1.Lambda
在 Java 的 Lambda 函数式编程中,提供了比较多内置函数 :
FunctionFunctionBiFunction
ConsumerConsumerBiConsumer
SupplierSupplier
- ...
在实际的开发场景中,可能由于不同的需求,在定义回调函数时,会有多参数的情况。因此, Common 模块就扩展了一些常用函数接口,比如:
DoubleConsumer TripleConsumer QuadraConsumer PentaConsumer
DoubleFunction TripleFunction QuadraFunction PentaFunction
1.1.2.JSON
在实际项目开发过程中 JSON 在很多场景下使用, 我相信 Fastjson 大家一定不陌生, API 使用起来很方便,但是由于其他原因,项目中可能需要取舍而放弃它,进而转 Jackson 等其他框架替代。
本身就介绍一下对 JSON 的扩展 JSON 枚举,变体如下:
Jackson- 支持
- 模仿
Fastjson的API风格,设计API名称 - 支持: 自定义注入
ObjectMapper - 说明:
- 由于
ObjectMapper对列表对象的反序列化支持和Fastjson设计不一样,所以还得多传一个参数TypeReference
- 由于
Fastjson- 不支持
Gson- 不支持
1.1.3.调度线程池
SafeScheduledThreadPoolExecutor 继承 ScheduledThreadPoolExecutor
着重解决了 当 ScheduledThreadPoolExecutor 发起 定周期 或 定频率 任务调度,但是在子线程内又没有很好的处理异常时会阻塞调度的情况。
JDK 源码见: java.util.concurrent.FutureTask#runAndReset
SafeScheduledThreadPoolExecutor 做的扩展就是会默认捕捉异常.同样通过回调函数也可开发者自行处理.这样就能保证任务的正常调度。
1.1.4.其他
Common 模块还做了其他的扩展或抽象,见 spring-project-infras-common-example
1.2.Core
基础设施模块
1.2.1.Getter
JDK 支持接口设计 default 函数或方法之后,为 API 接口的设计提供了新思路。
一些公共的方法可以直接在 API 接口的 default 方法中实现。但是会遇到一些小问题,如下:
- 如果需要用到
IOC容器中的Bean怎么办?- 细想我们可以通过哪些途径拿到
BeanBeanFactoryApplicationContext
- 细想我们可以通过哪些途径拿到
- 如果当前 对象 本身没有被
IOC关系,怎么获取IOC容器?
针对上面的问题,我们扩展了 Getter 设计模式,同时为了支持非 IOC 管理模块能获取到 IOC 容器,就设计了 ApplicationContextHolder
如果,引入的不是 Starter 模块,则需要开发者自行将 ApplicationContextInjector 交由 IOC 管理即可。
Getter 模式内置了如下实现:
ApplicationContextGetterBeanFactoryGetterEnvironmentGetterObjectMapperGetter
有了 Getter 模式的支持, API 接口设计将变得非常灵活。
java
public interface BeanFactoryGetter {
/**
* GET {@link BeanFactory}
*
* @return {@link BeanFactory}
*/
BeanFactory beanFactory();
/**
* GET {@link ListableBeanFactory}
*
* @return {@link ListableBeanFactory}
*/
default ListableBeanFactory listableBeanFactory() {
return (ListableBeanFactory) this.beanFactory();
}
/**
* GET {@link ConfigurableBeanFactory}
*
* @return {@link ConfigurableBeanFactory}
*/
default ConfigurableBeanFactory configurableBeanFactory() {
return (ConfigurableBeanFactory) this.beanFactory();
}
/**
* GET {@link ConfigurableListableBeanFactory}
*
* @return {@link ConfigurableListableBeanFactory}
*/
default ConfigurableListableBeanFactory configurableListableBeanFactory() {
return (ConfigurableListableBeanFactory) this.beanFactory();
}
}
java
public interface StrategySupporter {
/**
* Supports ot not.
*
* @param strategy the strategy.
* @return {@code boolean}
*/
boolean supports(String strategy);
}
示例:
java
// BeanFactoryStrategySupporter 就具有了能够获取到 BeanFactory 实例的方法 `beanFactory()` 以及其他扩展方法。
public interface BeanFactoryStrategySupporter extends StrategySupporter, BeanFactoryAware, BeanFactoryGetter {}
1.2.2.策略管道
相当大家对如下代码并不陌生
java
// 在一个循环体里面采用 if-supports 实现不同策略
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
我们模仿上面的设计来实现自己的 策略管道 抽象
- 策略
- 管道
策略好理解,采用 if-supports 方式就能够实现,那怎么实现管道? 想一想 Netty 怎么实现的管道?
既然是 Spring 基础设施扩展和抽象,能否采用 Spring 的方式实现呢,答案当然是可以的。借助于 Ordered 实现。
扩展如下:
java
public interface StrategySupporter {
/**
* Supports or not.
*
* @param strategy the strategy.
* @return {@code boolean}
*/
boolean supports(String strategy);
}
基于 BeanFactoryGetter
java
public interface BeanFactoryStrategySupporter extends StrategySupporter, BeanFactoryAware, BeanFactoryGetter {}
java
public interface OrderedBeanFactoryStrategySupporter extends BeanFactoryStrategySupporter, Ordered {
@Override
default int getOrder() {
return 0;
}
}
基于 ApplicationContextGetter
kava
public interface ApplicationContextStrategySupporter extends StrategySupporter, ApplicationContextGetter {}
java
public interface OrderedApplicationContextStrategySupporter extends ApplicationContextStrategySupporter, Ordered {
@Override
default int getOrder() {
return 0;
}
}
设计的 API 有了,那咱们怎么使用呢?示例如下:
具体示例工程: spring-project-infras-core-example
java
@Override
public Employee load(Long employeeId) {
// this.listableBeanFactory() 通过 BeanFactoryGetter 保证能够获取到 BeanFactory 示例
Map<String, EmployeeLoader> beans = this.listableBeanFactory().getBeansOfType(EmployeeLoader.class);
ArrayList<EmployeeLoader> employeeLoaders = new ArrayList<>(beans.values());
// 通过 Ordered 接口的 getOrder() 实现 管道
AnnotationAwareOrderComparator.sort(employeeLoaders);
for (EmployeeLoader employeeLoader : employeeLoaders) {
// 通过 if-supports 实现策略
if (employeeLoader.supports(this.props.getCache().getLoader())) {
return employeeLoader.load(employeeId);
}
}
throw new UnsupportedOperationException("Unreachable here.");
}
通过 策略管道 的设计模式我们可以看出,该方法非常的灵活。
- 不同场景通过
if-supports路由到不同的handler进行处理- 场景1
- 微信支付
- 支付宝支付
- 划分区域的系统
- 区域1
- 区域2
- 场景1
- 在管道之间可以通过 Ordered 实现不同的排列组合
- 我们可以通过定义不同的模块,使用不同的
Order取键范围来组合不同的实现- 模块间可以自由组合
- 模块内也可以自由组合
- 这样方便管理
- 不然随便定义
Order到最后会非常难管理, 真实不知道到底是一个怎样的执行顺序
- 我们可以通过定义不同的模块,使用不同的
为什么灵活?
- 即使业务需求变更了
- 我们可以通过增加不同的
hanadler来实现业务路由或切换,主程不需要做任何的修改和切换既能够实现需求
- 我们可以通过增加不同的
- 执行顺序变化
- 我们可以在不同模块的
Order变动,甚至跨模块变动,修改的地方仅仅是Order值。- 比如:
Pre10xxHandle20xxPost30xx- ...
- 比如:
- 我们可以在不同模块的
1.2.3Profile
我们在实际的开发过程中可能会遇到一些情况,一个 API 接口的不同实现,需要在不同的场景或环境生效,我们应该怎么实现?
熟悉 Spring 的小伙伴,我想一定会异口同声的说 @Profile 注解。在 Spring Boot 框架下是 @ConditionalOnProperty。
如果我们的变量不能通过 Profile 来区分或者变量有多个值的时候,该怎么处理呢?
@ConditionalOnProfile 注解应运而生。
示例如下:
java
@ConditionalOnProfile("${io.github.photowey.github.accessor.mock.profiles}")
public class MockGithubAccessor implements GithubAccessor {}
java
// 支持通过 ! 来取反
@ConditionalOnProfile("!${io.github.photowey.github.accessor.mock.profiles}")
public class NonMockGithubAccessor implements GithubAccessor {}
适用场景:
就是对接内部其他团队或者第三方接口,我们在开发阶段是可以通过上述的方式来实现 Mock 。不至于被其他的因素阻塞自己的开发进度.
1.3.Beans
Beans模块,顾名思义就是会自动向IOC注入不同的 Bean.
1.3.1.事件扩展
在默认情况下 Spring 的事件机制是同步的,除非自定义 多播器 且设置了线程池。
如果我们需要实现异步怎么办?
扩展一个 NotifyCenter 来实现事件的发布,支持异步。
示例如下:
java
// 同步事件
this.infrasBeanEngine.notifyEngine().notifyCenter().publishEvent(new EmployeeQueryEvent(employee));
// 异步事件
this.infrasBeanEngine.notifyEngine().notifyCenter().publishAsyncEvent(new EmployeeQueryEvent(employee));
// 异步事件统一采用名为 `notifyAsyncExecutor` 的线程池, 在 Starter 模块,会尝试自动配置
1.3.2.Engine 模式
Engine 设计模式的设计初衷就是来简化 大量的 依赖注入 ,且在一定程度上可有助于解决循环依赖的问题(将 依赖注入 变为 依赖查找)。
Engine- 根接口
AbstractEngine- 实现了
Engine的部分接口抽象
- 实现了
EngineAware- 实现
Engine自动注入的根接口
- 实现
EngineAwareBeanPostProcessor- 实现
Engine自动注入的具体实现 AbstractEngineAwareBeanPostProcessor- 对公共方法做抽象
- 实现
示例: spring-project-infras-bean-example
java
// 定义一个项目级别的 Engine
public interface InfrasBeanEngine extends Engine {
RepositoryEngine repositoryEngine();
ServiceEngine serviceEngine();
NotifyEngine notifyEngine();
ThreadPoolTaskExecutor taskExecutor();
InfrasBeanProperties infrasBeanProperties();
}
Service
java
// 服务层 Engine - 实际开发中也可以直接交由 项目级别的 Engine(InfrasBeanEngine) 实现
public interface ServiceEngine extends Engine {
EmployeeService employeeService();
}
Repository
java
// 仓储 Engine - 实际开发中也可以直接交由 项目级别的 Engine(InfrasBeanEngine) 实现
public interface RepositoryEngine extends Engine {
EmployeeRepository employeeRepository();
}
java
// 基于 Spring 的 Aware 接口定于 注入 InfrasBeanEngine 的根接口
public interface InfrasBeanEngineAware extends EngineAware {
void setInfrasBeanEngine(InfrasBeanEngine infrasBeanEngine);
}
java
// 通过 BeanPostProcessor 实现 InfrasBeanEngine 自动注入
public class InfrasBeanEngineAwareBeanPostProcessor extends AbstractEngineAwareBeanPostProcessor<InfrasBeanEngine> {
@Override
public boolean supports(Object bean) {
return bean instanceof InfrasBeanEngineAware;
}
@Override
public InfrasBeanEngine tryAcquireEngine(ConfigurableListableBeanFactory beanFactory) {
return beanFactory.getBean(InfrasBeanEngine.class);
}
@Override
public void inject(InfrasBeanEngine engine, Object bean) {
((InfrasBeanEngineAware) bean).setInfrasBeanEngine(engine);
}
}
java
public interface InfrasBeanEngineGetter {
InfrasBeanEngine infrasBeanEngine();
}
java
@Component
public class InfrasBeanEngineImpl extends AbstractEngine implements InfrasBeanEngine {
// 依赖注入方式
@Autowired
private RepositoryEngine repositoryEngine;
// 依赖查找方式
@Override
public RepositoryEngine repositoryEngine() {
return this.beanFactory().getBean(RepositoryEngine.class);
}
@Override
public ServiceEngine serviceEngine() {
return this.beanFactory().getBean(ServiceEngine.class);
}
@Override
public NotifyEngine notifyEngine() {
return this.beanFactory().getBean(NotifyEngine.class);
}
@Override
public ThreadPoolTaskExecutor taskExecutor() {
return this.beanFactory().getBean(NotifyCenter.NOTIFY_EXECUTOR_BEAN_NAME, ThreadPoolTaskExecutor.class);
}
@Override
public InfrasBeanProperties infrasBeanProperties() {
return this.beanFactory().getBean(InfrasBeanProperties.class);
}
}
上述的设计我们可以学到如下:
- 另一种依赖管理的方式
- 另一种依赖注入的方式
- 另一种在一定程度上解决循环依赖的方式
- ...
Engine 设计模式的优缺点:
-
优点
- 解决大量依赖注入问题
- 解决在一个
Service或者Controller中由于业务场景复杂带来的大量的依赖注入,不管是@Autowired还是 构造函数 注入
- 解决在一个
- 一定程度上可以解决循环依赖依赖问题
- 解决大量依赖注入问题
-
缺点
-
系统复杂时
Engine可能会很庞大, 这个问题我们可以采用 分而治之 的思想来解决ServiceEngineRepositroyEngineCommonEngineCompinentEngine- ...
-
调用链变长
-
传统
this.xxxService.sayHello()
-
Engine模式-
this.xxxEngine.xxxService().sayHello() -
this.xxxEngine.serviceEngine().xxxService().sayHello() -
这个问题我们可以采用上文提到的
Getter设计模式来抽象-
我们构造一些
XxxServiceGetter -
在
default方法实现this.xxxEngine.serviceEngine.xxxService()
javadefault XxxService xxxService() { return this.xxxEngine().serviceEngine().xxxService() } -
-
-
-
1.4.Web
SpringWeb模块
抽象了一些在 spring-web 模块中用到的组件
AOPReaderFileSystemfile:///opt/xxx
Classpathclasspath://dev/xxx/x.crt
Remoteurl://https://httpbin.org/get
- ...
1.5.Starter
Spring Boot框架下的 starter 实现,支持 Spring Boot1.x,2.x以及3.x其中
3.x需要引入单独的包
Spring Boot2.x3.x
Starter 模块主要是,帮我们自动配置一些常用到的基础组件。
其中 2.x 在 2.7.x 之前的之后会加载不同的 SPI 文件,实现自动注入。
2.总结
设计哲学: Less is More
- 框架做的事情多
- 开发者做的事情少
2.1.设计目标
- 优雅
- 通过
Engine设计模式让代码更简洁简单
- 通过
- 灵活
- 通过
Getter模式和 策略管道 让代码更灵活性
- 通过
3.Next
WebAOP- 权限
- 日志
- ...
SMSOSS- ...
如果您觉得本项目对您有帮助或者感兴趣, 赶紧 run 起来吧。
4.单元测试
本项目并未对 单元测试 做抽象或扩展. 感兴趣的小伙伴可以查看 spring-boot-mock-tester
可查看往期文章或示例工程 spring-boot-mock-tester-examples