Spring
相关基础组件扩展与抽象
示例工程 spring-project-infras-examples
0.前言
Spring
框架作为 Java
生态中重要的基础设施,我想每一个 Jvr
都对其非常熟悉甚至 精通。
本文也着重讨论在实际开发工作中常用到的一些基础抽象,大致分如下几个模块:
starter
Starter
模块2.x
版本系3.x
版本系
Beans
- 一些组件抽象
Core
- 一些基础设施抽象
Web
Web
模块抽象
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
函数式编程中,提供了比较多内置函数 :
Function
Function
BiFunction
Consumer
Consumer
BiConsumer
Supplier
Supplier
- ...
在实际的开发场景中,可能由于不同的需求,在定义回调函数时,会有多参数的情况。因此, 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
怎么办?- 细想我们可以通过哪些途径拿到
Bean
BeanFactory
ApplicationContext
- 细想我们可以通过哪些途径拿到
- 如果当前 对象 本身没有被
IOC
关系,怎么获取IOC
容器?
针对上面的问题,我们扩展了 Getter
设计模式,同时为了支持非 IOC
管理模块能获取到 IOC
容器,就设计了 ApplicationContextHolder
如果,引入的不是 Starter
模块,则需要开发者自行将 ApplicationContextInjector
交由 IOC
管理即可。
Getter
模式内置了如下实现:
ApplicationContextGetter
BeanFactoryGetter
EnvironmentGetter
ObjectMapperGetter
有了 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
值。- 比如:
Pre
10xx
Handle
20xx
Post
30xx
- ...
- 比如:
- 我们可以在不同模块的
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
可能会很庞大, 这个问题我们可以采用 分而治之 的思想来解决ServiceEngine
RepositroyEngine
CommonEngine
CompinentEngine
- ...
-
调用链变长
-
传统
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
Spring
Web
模块
抽象了一些在 spring-web
模块中用到的组件
AOP
Reader
FileSystem
file:///opt/xxx
Classpath
classpath://dev/xxx/x.crt
Remote
url://https://httpbin.org/get
- ...
1.5.Starter
Spring Boot
框架下的 starter 实现,支持 Spring Boot1.x
,2.x
以及3.x
其中
3.x
需要引入单独的包
Spring Boot
2.x
3.x
Starter
模块主要是,帮我们自动配置一些常用到的基础组件。
其中 2.x
在 2.7.x
之前的之后会加载不同的 SPI
文件,实现自动注入。
2.总结
设计哲学: Less is More
- 框架做的事情多
- 开发者做的事情少
2.1.设计目标
- 优雅
- 通过
Engine
设计模式让代码更简洁简单
- 通过
- 灵活
- 通过
Getter
模式和 策略管道 让代码更灵活性
- 通过
3.Next
Web
AOP
- 权限
- 日志
- ...
SMS
OSS
- ...
如果您觉得本项目对您有帮助或者感兴趣, 赶紧 run
起来吧。
4.单元测试
本项目并未对 单元测试 做抽象或扩展. 感兴趣的小伙伴可以查看 spring-boot-mock-tester
可查看往期文章或示例工程 spring-boot-mock-tester-examples