1. 简介

设计模式是软件开发中不可或缺的一部分。它们不仅是解决重复问题的经典方案,还能帮助开发者通过识别通用结构,深入理解框架的设计思想。

本文将深入剖析 Spring 框架中四种最核心的设计模式:

✅ Singleton(单例)
✅ Factory Method(工厂方法)
✅ Proxy(代理)
✅ Template Method(模板方法)

我们会结合源码和实际场景,看 Spring 是如何巧妙运用这些模式,把复杂操作“封装”起来,让开发者能简单粗暴地完成繁琐任务。


2. 单例模式(Singleton Pattern)

单例模式的核心目标是:确保一个类在整个应用中只存在一个实例。这在管理共享资源(如数据库连接池、日志服务)时非常关键。

2.1 Spring 中的单例 Bean

在传统实现中,单例是 JVM 全局唯一的。但 Spring 的“单例”略有不同:✅ 它限定的是“每个 Spring IoC 容器内唯一”

这意味着:

  • 同一个应用中如果有多个 ApplicationContext(比如父子容器),那么同一个类可以存在多个“单例”实例。
  • 默认情况下,Spring 创建的所有 Bean 都是单例的。

Singleton

这个设计既保证了资源复用,又保留了容器隔离的灵活性,是 Spring 实现轻量级 IoC 的基石。

2.2 Autowired 单例验证

我们通过一个简单例子验证 Spring 的单例注入机制。

先定义一个 BookRepository

@Repository
public class BookRepository {
    public Long count() {
        return 100L;
    }

    public Optional<Book> findById(long id) {
        return Optional.of(new Book(id, "Spring in Action"));
    }

    public Book create(String author) {
        return new Book(1L, "New Book", author);
    }
}

接着创建两个 Controller,都注入 BookRepository

@RestController
public class LibraryController {
    
    @Autowired
    private BookRepository repository;

    @GetMapping("/count")
    public Long findCount() {
        System.out.println(repository);
        return repository.count();
    }
}
@RestController
public class BookController {
     
    @Autowired
    private BookRepository repository;
 
    @GetMapping("/book/{id}")
    public Book findById(@PathVariable long id) {
        System.out.println(repository);
        return repository.findById(id).get();
    }
}

启动应用并调用两个接口:

curl -X GET http://localhost:8080/count
curl -X GET http://localhost:8080/book/1

输出结果:

com.baeldung.spring.patterns.singleton.BookRepository@3ea9524f
com.baeldung.spring.patterns.singleton.BookRepository@3ea9524f

⚠️ 两个 Controller 中的 repository 实例地址完全一致 —— 证明 Spring 注入的是同一个 Bean。

如果想打破单例,只需将 Bean 作用域改为 prototype

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class BookRepository {
    // ...
}

此时每次注入都会创建新实例,对象 ID 就不再相同了。


3. 工厂方法模式(Factory Method Pattern)

工厂方法模式的核心是:定义一个创建对象的接口,但由子类决定实例化哪个类。它把对象的创建延迟到子类,实现解耦。

3.1 ApplicationContext 作为工厂

Spring 的依赖注入(DI)体系本质上就是一个巨型工厂系统。它的核心接口 BeanFactory 就是典型的工厂方法模式体现:

public interface BeanFactory {

    <T> T getBean(Class<T> requiredType);
    <T> T getBean(Class<T> requiredType, Object... args);
    Object getBean(String name);

    // ...
}

✅ 所有 getBean() 方法都是工厂方法 —— 它们根据类型、名称或参数,返回对应的 Bean 实例。

ApplicationContextBeanFactory 的增强版,支持更多配置方式(如注解、XML),是实际开发中最常用的容器。

我们来看一个基于注解配置的示例:

@Configuration
@ComponentScan(basePackageClasses = ApplicationConfig.class)
public class ApplicationConfig {
}

定义两个组件:

@Component
public class Foo {
}
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Bar {
    private String name;
     
    public Bar(String name) {
        this.name = name;
    }
     
    public String getName() {
        return name;
    }
}

通过 AnnotationConfigApplicationContext 获取 Bean:

@Test
public void whenGetSimpleBean_thenReturnConstructedBean() {
    ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
    Foo foo = context.getBean(Foo.class);
    assertNotNull(foo);
}

@Test
public void whenGetPrototypeBean_thenReturnConstructedBean() {
    String expectedName = "Design Patterns";
    ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
    Bar bar = context.getBean(Bar.class, expectedName);
    assertNotNull(bar);
    assertThat(bar.getName(), is(expectedName));
}

✅ 这里 getBean(Class, args) 就是典型的工厂方法调用,Spring 负责完成实例化和依赖注入。

3.2 外部配置切换

工厂模式的强大之处在于:可以通过更换配置源,完全改变对象的创建逻辑

比如,我们可以把基于注解的配置换成 XML:

@Test 
public void givenXmlConfiguration_whenGetPrototypeBean_thenReturnConstructedBean() { 
    String expectedName = "Design Patterns";
    ApplicationContext context = new ClassPathXmlApplicationContext("context.xml");
    Bar bar = context.getBean(Bar.class, expectedName);
    assertThat(bar.getName(), is(expectedName));
}

Factory 1

只要接口不变,底层实现可以自由切换 —— 这正是工厂模式的精髓。


4. 代理模式(Proxy Pattern)

代理模式是指通过一个代理对象控制对目标对象的访问。代理可以在不修改原对象的前提下,增加额外逻辑(如权限校验、事务管理、日志等)。

Proxy class diagram

4.1 事务中的代理应用

Spring 的声明式事务就是代理模式的典型应用。

看下面这个服务类:

@Service
public class BookManager {
    
    @Autowired
    private BookRepository repository;

    @Transactional
    public Book create(String author) {
        System.out.println(repository.getClass().getName());
        return repository.create(author);
    }
}

当你调用 create() 方法时,输出可能是:

com.baeldung.patterns.proxy.BookRepository$$EnhancerBySpringCGLIB$$3dc2b55c

⚠️ 注意!类名中出现了 EnhancerBySpringCGLIB —— 这说明 BookRepository 已被 Spring 用 CGLib 动态代理增强。

Spring 通过代理拦截方法调用,在目标方法前后自动开启/提交事务,从而实现“声明式事务”。

Proxy

4.2 Spring 的两种代理机制

Spring 支持两种代理方式:

代理类型 使用场景 原理
JDK 动态代理 目标类实现了接口 基于接口生成代理,调用 Proxy.newProxyInstance()
CGLib 代理 目标类未实现接口 通过字节码技术生成子类,重写方法

默认策略是:

  • 如果目标类实现了接口,优先使用 JDK 代理;
  • 否则使用 CGLib。

你也可以通过 @EnableAspectJAutoProxy(proxyTargetClass = true) 强制使用 CGLib。

✅ 除了事务,AOP、缓存(@Cacheable)、异步(@Async)等功能也都依赖代理机制。


5. 模板方法模式(Template Method Pattern)

模板方法模式定义一个算法骨架,并将某些步骤延迟到子类实现。它能有效消除重复代码,特别适合处理“流程固定、细节可变”的场景。

5.1 模板与回调机制

比如数据库操作的典型流程:

  1. 获取连接 ✅(固定)
  2. 执行 SQL ❌(可变)
  3. 处理结果 ❌(可变)
  4. 关闭连接 ✅(固定)

前后的固定步骤可以封装在模板中,中间的变化部分通过“回调”交给用户实现。

Spring 的 JdbcTemplate 正是基于这种思想设计的。

先看一个简化版模板:

public abstract class DatabaseQuery {

    public <T> T execute(String query, ResultsMapper<T> mapper) {
        Connection connection = createConnection();
        Results results = executeQuery(connection, query);
        closeConnection(connection);
        return mapper.map(results);
    }

    protected Connection createConnection() {
        // 连接数据库
    }

    protected Results executeQuery(Connection connection, String query) {
        // 执行查询
    }

    protected void closeConnection(Connection connection) {
        // 关闭连接
    }
}

public interface ResultsMapper<T> {
    T map(Results results);
}

用户只需关注“如何映射结果”,无需操心连接管理。

5.2 JdbcTemplate 实战

Spring 的 JdbcTemplate 提供了丰富的模板方法:

public class JdbcTemplate {

    public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
        // ...
    }
}

其中 ResultSetExtractor<T> 是顶层回调接口:

@FunctionalInterface
public interface ResultSetExtractor<T> {
    T extractData(ResultSet rs) throws SQLException, DataAccessException;
}

但通常我们更关心“逐行映射”,所以 Spring 还提供了 RowMapper

@FunctionalInterface
public interface RowMapper<T> {
    T mapRow(ResultSet rs, int rowNum) throws SQLException;
}

并内置了 RowMapperResultSetExtractor 来桥接两者:

public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
    return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper)));
}

使用示例:

public class BookRowMapper implements RowMapper<Book> {
    @Override
    public Book mapRow(ResultSet rs, int rowNum) throws SQLException {
        Book book = new Book();
        book.setId(rs.getLong("id"));
        book.setTitle(rs.getString("title"));
        book.setAuthor(rs.getString("author"));
        return book;
    }
}

调用:

JdbcTemplate template = // 获取实例
List<Book> books = template.query("SELECT * FROM books", new BookRowMapper());

✅ 从此告别 try-catch-finally 和资源释放的样板代码。

其他模板应用场景

Spring 在多个模块中使用了模板模式:

  • JmsTemplate —— 简化 JMS 消息收发
  • JpaTemplate / EntityManager —— 封装 JPA 操作(旧版)
  • HibernateTemplate —— 封装 Hibernate 会话管理(已废弃)
  • TransactionTemplate —— 编程式事务控制

6. 总结

本文深入剖析了 Spring 框架背后的四大设计模式:

模式 Spring 中的应用
单例模式 默认 Bean 作用域,容器内唯一实例
工厂方法 BeanFactory / ApplicationContext 创建 Bean
代理模式 AOP、事务、缓存等非功能性需求的实现基础
模板方法 JdbcTemplate 等消除样板代码的经典工具

✅ 理解这些模式,不仅能帮你更好地使用 Spring,还能提升你对“框架设计”的认知层次。下次看到 @TransactionaljdbcTemplate.query(),你应该能立刻想到背后隐藏的“设计哲学”。

踩坑提示:
⚠️ 不要混淆 Spring 单例与 JVM 单例;
⚠️ 代理失效常见于 this 调用或方法访问权限问题;
⚠️ JdbcTemplate 虽好,但在响应式编程中已被 R2DBC 取代。

掌握这些,你离“高级 Java 开发”又近了一步。


原始标题:Design Patterns in the Spring Framework | Baeldung