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

相关推荐
诸葛悠闲28 分钟前
设计模式——桥接模式
设计模式·桥接模式
智慧老师1 小时前
Spring基础分析13-Spring Security框架
java·后端·spring
hanbarger4 小时前
mybatis框架——缓存,分页
java·spring·mybatis
捕鲸叉5 小时前
C++软件设计模式之外观(Facade)模式
c++·设计模式·外观模式
小小小妮子~5 小时前
框架专题:设计模式
设计模式·框架
先睡5 小时前
MySQL的架构设计和设计模式
数据库·mysql·设计模式
龙少95437 小时前
【深入理解@EnableCaching】
java·后端·spring
啦啦右一13 小时前
Spring Boot | (一)Spring开发环境构建
spring boot·后端·spring
Damon_X13 小时前
桥接模式(Bridge Pattern)
设计模式·桥接模式
荆州克莱16 小时前
mysql中局部变量_MySQL中变量的总结
spring boot·spring·spring cloud·css3·技术