首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Spring的启发

Spring的启发

原创
作者头像
兰亭集
修改2026-05-23 22:02:22
修改2026-05-23 22:02:22
380
举报
文章被收录于专栏:软件工程软件工程

命名

Spring在它的问题域内,通过准确的命名区分定义了不同的领域概念,我们在开发时可以多多借鉴参考

  • 拦截器:XxxInterceptor,比如HandlerInterceptor
  • 处理器:XxxProcessor、XxxHandler,比如BeanPostProcessor
  • 解析器:XxxResolver、XxxParser,比如PropertyResolver、BeanDefinitionParser
  • 访问器:XxxAccessor,比如PropertyAccessor
  • 适配器:XxxAdapter,比如HandlerAdapter
  • 组合器:CompositeXxx,比如CompositeHandlerAdapter
  • 转换器:XxxConverter,比如DateToInstantConverter
  • 评估器:XxxEvaluator,比如ConditionEvaluator,语义同 Predicate.evaluate() 方法
  • 工厂:XxxFactory,比如BeanFactory
  • 源:XxxSource,比如PropertySource
  • 上下文:XxxContext,比如ApplicationContext
  • 注册中心:XxxRegistry,比如BeanDefinitionRegistry
  • 配置中心:XxxConfiguration
  • 读写分离接口:Xxx 和 ConfigurableXxx,比如ApplicationContext, ConfigurableApplicationContext
  • 可读接口:XxxCapable,比如EnvironmentCapable
  • 可写接口:XxxAware,比如ApplicationContextAware
  • 事件侦听:XxxEvent、XxxListener、XxxEventPublisher
  • 工具类:XxxUtils、XxxHelper

设计模式和设计原则的应用

从上面的命名中也可以看见Spring运用了大量的设计模式和设计原则,这里着重提及一二。

极具扩展性的拦截器

拦截器应用广泛,扩展性强。先看下Spring的HandlerInceptor定义:

代码语言:java
复制
public interface HandlerInterceptor {
    // handler执行前操作
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }

    // handler执行后操作
    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
    }

    // handler执行完成后操作
    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
    }
}

应用拦截器一般如下(实际调用逻辑参考HandlerExecutionChain类):

代码语言:java
复制
public class Business {
    private Interceptor[] interceptors;

    public void handleBusiness(BusinessContext context) {
        interceptors.forEach(interceotpr -> interceotpr.preHandle(context));
        Result result = action();
        interceptors.forEach(interceotpr -> interceotpr.postHandle(context, result));
    }
}

这里可以看到,通过拦截器我们可以把核心业务逻辑与扩展业务逻辑拆分开来,对于Spring MVC而言,处理request的handler是它的核心业务逻辑,扩展业务逻辑像鉴权、日志、request header校验、限流等等都可以放在Interceptor中。

  • 从可读性上来说,理清并拆分核心业务逻辑与扩展业务逻辑,有助于核心业务逻辑的干净清晰。
  • 从扩展性上来说,扩展逻辑的改动只需添加一个Interceptor就好,符合开闭原则,比如业务逻辑完成后发送一条kafka消息,就可以加一个Interceptor实现postHandle。

其他一些例子:

  1. Spring的BeanPostProcessor也是interceptor,@Autowire注解的支持就是通过AutowiredAnnotationBeanPostProcessor来扩展实现的。
  2. Tomcat的Filter、Dubbo的Filter,具体的实现方式各稍有不同,但都是interceptor模式,同样在鉴权、校验等方面有诸多用途。

接口隔离

Spring对不同的业务概念定义了不同的接口,一个接口下可能会对应多个不同的实现类,比如读写分离的接口设计ApplicationContext和ConfigurableApplicationContext:

代码语言:java
复制
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver {

    String getApplicationName();

    ...  // 省略
}

public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {

    void addBeanFactoryPostProcessor(BeanFactoryPostProcessor postProcessor);

    void refresh() throws BeansException, IllegalStateException;

    ...  // 省略
}

Java开发者最频繁接触到的应该就是Spring框架了,平时开发中就可以多进入源码中翻翻看看,有助于培养良好的代码风格。

注意接口的使用有个常见的误区:无脑先定义一个接口后,再写一个对应的实现类,尽管实际上并没有多态的需求。这种代码形式常见于Spring MVC分层架构里,比如定义接口XxxService,然后一对一配对实现XxxServiceImpl。大量一对一的接口和实现类反而带来很多无效的工作量,没什么意义。相反,应该采取的方法是:1. 有深入的业务知识并且仔细思考后自上而下地定义好业务接口 2. 持续重构,按业务发展的需要自下而上地创建良好的业务接口。

依赖注入 - IOC

依赖注入 - Dependency Injection, 控制反转 - inverse of control,是Spring框架扩展性的基石。Spring框架启动过程主要的两步:

  1. 扫描classpath上所有bean,创建BeanDefinition,注册于BeanRegistry中
  2. 根据BeanDefinition创建并初始化Bean,管理于BeanFactory中

以数据存储服务迁移为例认识下IOC框架提供的扩展性,比如从数据存储Storage_A迁移到数据存储Storage_B,处理流程一般如下:

  1. 初始状态下只有数据服务Storage_A的读写
代码语言:java
复制
@Service
public class StorageService {
    public List<Record> read() {
        // 数据存储A读取
    }

    public void insert(Record t) {
        // 数据存储A写入
    }
}
  1. 重构提取存储服务接口,StorageService变成接口,Spring会自动注入实现类StorageAService,所有依赖StorageService的上游没有任何改动,代码逻辑前后等价。
代码语言:java
复制
public interface StorageService {
    List<Record> read();

    void insert(Record t);
}

@Service
public class StorageAService implements StorageService {
    public List<Record> read() {
        // 数据存储A读取
    }

    public void insert(Record t) {
        // 数据存储A写入
    }
}
  1. 添加新存储服务实现类StorageBService,新增代理类StorageProxy控制迁移状态、双写和双读等流程。依赖StorageService的上游会被Spring注入StorageProxy实现类,上游逻辑没有任何改动。
代码语言:java
复制
public interface StorageService {
    List<Record> read();

    void insert(Record t);
}

@Service
public class StorageAService implements StorageService {
    public List<Record> read() {
        // 数据存储A读取
    }

    public void insert(Record t) {
        // 数据存储A写入
    }
}

@Service
public class StorageBService implements StorageService {
    public List<Record> read() {
        // 数据存储B读取
    }

    public void insert(Record t) {
        // 数据存储B写入
    }
}

@Primary
@Service
public class StorageProxy implements StorageService {
    private StorageAService storageAService;
    private StorageBService storageBService;

    public List<Record> read() {
        // 双读控制
    }

    public void insert(Record t) {
        // 双写控制
    }
}
  1. 迁移完成后,删除StorageAService和StorageProxy,保留接口和StorageBService
代码语言:java
复制
public interface StorageService {
    List<Record> read();

    void insert(Record t);
}

@Service
public class StorageBService implements StorageService {
    public List<Record> read() {
        // 数据存储B读取
    }

    public void insert(Record t) {
        // 数据存储B写入
    }
}

如上所示,通过Spring的依赖注入和代理模式的使用,实现了存储服务的迁移逻辑,但修改逻辑干净简洁。

面向切面编程 - AOP

无侵入实现横切的扩展逻辑,同样适用于日志、鉴权、异常处理等场景。

比如,Spring @Transactional注解的实现就是依赖于AOP,如下例所示:

代码语言:java
复制
@Repository
public class FirstRepository {
    @Transactional
    public void update() {
        System.out.println("Update ...");
    }
}

public interface SecondRepository {
    void update();
}

@Repository
public class SecondRepositoryImpl implements SecondRepository {
    @Transactional
    public void update() {
        System.out.println("Update");
    }
}

@Service
public class BusinessService {
    private FirstRepository firstRepository;
    private SecondRepository secondRepository;

    @Autowired
    public void setFirstRepository(FirstRepository firstRepository) {
        this.firstRepository = firstRepository;
    }

    @Autowired
    public void setSecondRepository(SecondRepository secondRepository) {
        this.secondRepository = secondRepository;
    }
}

Spring在启动时注入的Bean:

如上图所示,Spring通过运行时注入动态代理类的方式实现AOP,代理类的创建有两种方式:

  1. CGLIB库字节码生成创建目标类的子类,目标类无接口时只能使用此方式
  2. JDK动态代理通过Proxy和InvocationHandler创建代理类,只适用于目标类实现了接口的场景

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 命名
  • 设计模式和设计原则的应用
    • 极具扩展性的拦截器
    • 接口隔离
  • 依赖注入 - IOC
  • 面向切面编程 - AOP
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档