Spring 相关基础组件扩展与抽象

Spring 相关基础组件扩展与抽象

项目 spring-project-infras

示例工程 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

JavaLambda 函数式编程中,提供了比较多内置函数 :

  • 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
    • 支持
    • 模仿 FastjsonAPI 风格,设计 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
  • 在管道之间可以通过 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()

          java 复制代码
          default 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 Boot 1.x,2.x 以及 3.x

其中 3.x 需要引入单独的包

  • Spring Boot
    • 2.x
    • 3.x

Starter 模块主要是,帮我们自动配置一些常用到的基础组件。

其中 2.x2.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

相关推荐
程序猿零零漆3 小时前
SpringCloud系列教程:微服务的未来(二十)Seata快速入门、部署TC服务、微服务集成Seata
java·spring·spring cloud·微服务
小林up7 小时前
【C语言设计模式学习笔记1】面向接口编程/简单工厂模式/多态
c语言·设计模式
大耳朵土土垚10 小时前
【Linux】日志设计模式与实现
linux·运维·设计模式
Miketutu13 小时前
Spring MVC消息转换器
java·spring
小王子102413 小时前
设计模式Python版 组合模式
python·设计模式·组合模式
小小虫码15 小时前
项目中用的网关Gateway及SpringCloud
spring·spring cloud·gateway
linwq815 小时前
设计模式学习(二)
java·学习·设计模式
带刺的坐椅20 小时前
无耳科技 Solon v3.0.7 发布(2025农历新年版)
java·spring·mvc·solon·aop
精通HelloWorld!1 天前
使用HttpClient和HttpRequest发送HTTP请求
java·spring boot·网络协议·spring·http
LUCIAZZZ1 天前
基于Docker以KRaft模式快速部署Kafka
java·运维·spring·docker·容器·kafka