1. 简介
设计模式是软件开发中不可或缺的一部分。它们不仅是解决重复问题的经典方案,还能帮助开发者通过识别通用结构,深入理解框架的设计思想。
本文将深入剖析 Spring 框架中四种最核心的设计模式:
✅ Singleton(单例)
✅ Factory Method(工厂方法)
✅ Proxy(代理)
✅ Template Method(模板方法)
我们会结合源码和实际场景,看 Spring 是如何巧妙运用这些模式,把复杂操作“封装”起来,让开发者能简单粗暴地完成繁琐任务。
2. 单例模式(Singleton Pattern)
单例模式的核心目标是:确保一个类在整个应用中只存在一个实例。这在管理共享资源(如数据库连接池、日志服务)时非常关键。
2.1 Spring 中的单例 Bean
在传统实现中,单例是 JVM 全局唯一的。但 Spring 的“单例”略有不同:✅ 它限定的是“每个 Spring IoC 容器内唯一”。
这意味着:
- 同一个应用中如果有多个
ApplicationContext
(比如父子容器),那么同一个类可以存在多个“单例”实例。 - 默认情况下,Spring 创建的所有 Bean 都是单例的。
这个设计既保证了资源复用,又保留了容器隔离的灵活性,是 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 实例。
而 ApplicationContext
是 BeanFactory
的增强版,支持更多配置方式(如注解、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));
}
只要接口不变,底层实现可以自由切换 —— 这正是工厂模式的精髓。
4. 代理模式(Proxy Pattern)
代理模式是指通过一个代理对象控制对目标对象的访问。代理可以在不修改原对象的前提下,增加额外逻辑(如权限校验、事务管理、日志等)。
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 通过代理拦截方法调用,在目标方法前后自动开启/提交事务,从而实现“声明式事务”。
4.2 Spring 的两种代理机制
Spring 支持两种代理方式:
代理类型 | 使用场景 | 原理 |
---|---|---|
JDK 动态代理 | 目标类实现了接口 | 基于接口生成代理,调用 Proxy.newProxyInstance() |
CGLib 代理 | 目标类未实现接口 | 通过字节码技术生成子类,重写方法 |
默认策略是:
- 如果目标类实现了接口,优先使用 JDK 代理;
- 否则使用 CGLib。
你也可以通过 @EnableAspectJAutoProxy(proxyTargetClass = true)
强制使用 CGLib。
✅ 除了事务,AOP、缓存(@Cacheable
)、异步(@Async
)等功能也都依赖代理机制。
5. 模板方法模式(Template Method Pattern)
模板方法模式定义一个算法骨架,并将某些步骤延迟到子类实现。它能有效消除重复代码,特别适合处理“流程固定、细节可变”的场景。
5.1 模板与回调机制
比如数据库操作的典型流程:
- 获取连接 ✅(固定)
- 执行 SQL ❌(可变)
- 处理结果 ❌(可变)
- 关闭连接 ✅(固定)
前后的固定步骤可以封装在模板中,中间的变化部分通过“回调”交给用户实现。
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,还能提升你对“框架设计”的认知层次。下次看到 @Transactional
或 jdbcTemplate.query()
,你应该能立刻想到背后隐藏的“设计哲学”。
踩坑提示:
⚠️ 不要混淆 Spring 单例与 JVM 单例;
⚠️ 代理失效常见于 this
调用或方法访问权限问题;
⚠️ JdbcTemplate
虽好,但在响应式编程中已被 R2DBC
取代。
掌握这些,你离“高级 Java 开发”又近了一步。