引言:为什么需要设计模式?
在Java Web开发中,我们面对的是一个复杂的世界:用户请求、业务逻辑、数据持久化、多线程、安全、可扩展性……如果代码组织混乱,很快就会陷入“泥潭”,变得难以维护、测试和扩展。

设计模式就像是前人总结的“兵法”,它不是具体的代码,而是针对特定场景的、可复用的、优雅的解决方案,它帮助我们:
- 提高代码复用性:避免重复造轮子。
- 增强可维护性:代码结构清晰,职责分明。
- 提升可扩展性:符合“开闭原则”,易于扩展新功能。
- 促进团队协作:统一的“语言”和思想,降低沟通成本。
“道”则是在理解这些“术”(具体模式)的基础上,形成自己的设计哲学,知道何时用、为何用、如何权衡。
第一层:Java Web开发的“三驾马车”与经典模式
早期的Java Web开发,我们主要学习Servlet/JSP,它们构成了最基础的模式。
MVC (Model-View-Controller) 模式 - 核心中的核心
这是Java Web开发最基础、最重要的模式,几乎无处不在。

- 道之所在:关注点分离,将一个复杂的业务应用拆分成三个部分,每个部分专注于自己的职责,这是构建大型应用的基石。
- 结构解析:
- Model (模型):核心业务逻辑和数据,它负责处理数据、业务规则,与数据库交互,在Java中,通常是POJO(Plain Old Java Object)、Service层、DAO层。
- View (视图):负责展示数据,通常是JSP、HTML、Thymeleaf等模板引擎,或者现代的前端框架(如Vue, React)。
- Controller (控制器):接收用户请求,调用Model处理业务逻辑,然后选择合适的View进行响应,它是Model和View之间的“桥梁”。
- Java Web实现:
- 传统实现:Servlet作为Controller,JSP作为View,JavaBean作为Model。
- 现代框架实现 (Spring MVC):
@Controller或@RestController注解的类就是Controller。- Service层和Repository层构成Model。
- Thymeleaf、FreeMarker或返回JSON数据供前端渲染,都是View。
- 关键点:Controller不应该包含业务逻辑,只负责流程调度,View不应该包含业务逻辑,只负责展示,Model不应该知道View和Controller的存在。
DAO (Data Access Object) 模式 - 数据访问的抽象
当项目变大,与数据库的交互会变得复杂且分散,DAO模式应运而生。
- 道之所在:解耦,将数据访问的逻辑与业务逻辑彻底分离,这样,当数据库从MySQL切换到Oracle,或者从JDBC切换到MyBatis时,业务代码完全不需要改动。
- 结构解析:
- DAO接口:定义了操作数据库的方法,如
save(),findById(),delete()。 - DAO实现类:使用具体的持久化技术(如JDBC、Hibernate、MyBatis)来实现接口中的方法。
- Model/Service:通过调用DAO接口来操作数据,不关心具体实现。
- DAO接口:定义了操作数据库的方法,如
- Java Web实现:
- 在Spring项目中,我们通常使用Spring Data JPA或MyBatis,它们已经为我们实现了DAO模式。
UserRepository接口继承JpaRepository,Spring Data会自动为我们生成实现,Service层直接注入UserRepository来使用。
- 关键点:Service层依赖的是DAO接口,而不是其实现类。
第二层:业务逻辑层的设计模式
当业务逻辑变得复杂时,需要更精细的模式来组织代码。
单例模式 - 控制资源消耗
- 道之所在:保证唯一性,节约资源,对于一些只需要存在一个实例的对象,单例模式可以避免重复创建带来的性能开销和潜在的不一致问题。
- Java Web实现:
- Servlet容器:每个Servlet在默认情况下是单例的(由容器管理),这样避免了每次请求都创建和销毁Servlet的开销。
- Spring Bean:默认情况下,Spring容器中的Bean都是单例的(
@Scope("singleton")),这对于管理数据库连接池、配置信息等非常有效。 - 实现方式:饿汉式、懒汉式(双重检查锁)、枚举式,在Spring中,我们只需使用
@Component等注解即可,Spring容器会帮我们处理。
- 关键点:单例模式要考虑线程安全问题,尤其是在懒加载时。
工厂模式 - 创建对象的解耦
- 道之所在:将对象的创建和使用分离,当需要根据不同条件创建不同对象时,工厂模式可以避免在客户端代码中写大量的
if-else判断。 - Java Web实现:
- Spring IoC容器:Spring本身就是一个大型的工厂,我们通过
@Service,@Repository等注解定义Bean,Spring(工厂)在需要时(如注入时)负责创建和管理这些Bean的生命周期,我们无需关心new User()的具体过程。 - 策略模式与工厂结合:支付功能,我们可以定义一个
PaymentService接口,然后有AlipayService,WeChatPayService等实现,通过一个工厂类,根据传入的支付类型("alipay"或"wechat")返回对应的支付服务实例。
- Spring IoC容器:Spring本身就是一个大型的工厂,我们通过
- 关键点:工厂模式让系统更灵活,易于扩展新的产品(如新的支付方式)。
代理模式 - 无侵入地增强功能
- 道之所在:为对象提供一个代理,以控制对这个对象的访问,在不修改源代码的情况下,为目标对象添加额外的功能,如日志、事务、权限控制等。
- Java Web实现:
- Spring AOP (面向切面编程):这是代理模式最经典的应用。
- 事务管理:在方法执行前开启事务,执行后提交或回滚,我们只需要在方法上加上
@Transactional注解,Spring AOP(通过动态代理)会帮我们完成这一切。 - 日志记录:在方法执行前后打印日志。
- 权限控制:在方法执行前检查用户是否有权限。
- 事务管理:在方法执行前开启事务,执行后提交或回滚,我们只需要在方法上加上
- 实现方式:JDK动态代理(基于接口)和CGLIB动态代理(基于继承)。
- Spring AOP (面向切面编程):这是代理模式最经典的应用。
- 关键点:代理模式实现了“横向”功能的抽取,让业务代码更加纯粹。
第三层:架构与分布式模式
随着系统规模的增长,单体应用无法满足需求,我们需要更高级的模式和架构。
前端控制器模式 - Web MVC的核心
- 道之所在:统一入口,集中控制,所有请求都先经过一个中心控制器,由它进行分发、预处理和后处理,这使得整个请求流程非常清晰和易于管理。
- Java Web实现:
- Spring MVC的
DispatcherServlet:这是前端控制器的完美体现,所有Web请求都先到达DispatcherServlet,它根据请求的URL和@RequestMapping的配置,找到对应的Controller方法,并执行。 - 其他框架:Struts2的
FilterDispatcher也是类似的思想。
- Spring MVC的
- 关键点:这是现代Web框架的标配,它为拦截器、视图解析、异常处理等提供了统一的挂载点。
拦截器/过滤器模式 - 横切关注点的处理
- 道之所在:在请求到达目标之前或之后执行特定逻辑,它们是实现横切关注点(如日志、认证、编码转换)的非侵入式方式。
- Java Web实现:
- 过滤器:工作在Servlet容器级别,依赖于Servlet API,在请求进入
Servlet之前和响应离开Servlet之后执行,字符编码过滤器CharacterEncodingFilter。 - 拦截器:工作在Spring MVC框架内部,依赖于Spring的IoC容器,更灵活,可以获取到
HandlerMethod、ModelAndView等Spring MVC的上下文信息,登录拦截器。
- 过滤器:工作在Servlet容器级别,依赖于Servlet API,在请求进入
- 关键点:过滤器在Web应用中更通用,而拦截器与Spring MVC结合更紧密,功能更强大。
策略模式 - 算法的灵活切换
- 道之所在:定义一系列算法,并将每个算法封装起来,使它们可以互相替换,策略模式让算法的变化独立于使用算法的客户端。
- Java Web实现:
- 支付:如前所述,
AlipayStrategy,WeChatPayStrategy,客户端代码(订单服务)通过一个统一的支付接口调用,具体使用哪种策略由外部配置或上下文决定。 - 优惠券计算:满减券、折扣券、无门槛券,每种都是一种计算策略。
- 支付:如前所述,
- 关键点:策略模式与工厂模式经常一起使用,工厂负责创建策略对象,策略对象负责执行具体算法。
观察者模式 - 事件驱动
- 道之所在:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新,它实现了松耦合的交互。
- Java Web实现:
- Spring事件机制:Spring Application Context是事件源,我们可以自定义事件和监听器。
- 场景:用户注册成功后,需要发送欢迎邮件、记录积分、初始化用户资料,可以定义一个
UserRegisteredEvent,然后写三个监听器分别处理这三个任务,注册业务逻辑只需发布事件,无需关心后续操作。
- 场景:用户注册成功后,需要发送欢迎邮件、记录积分、初始化用户资料,可以定义一个
- MQ (消息队列):这是分布式系统中的观察者模式,生产者发布消息到Topic,多个消费者订阅该Topic并处理消息,实现系统间的解耦和异步通信。
- Spring事件机制:Spring Application Context是事件源,我们可以自定义事件和监听器。
- 关键点:观察者模式非常适合处理异步、解耦的场景,尤其是在微服务架构中。
第四层:现代Java Web开发的“道”
掌握了具体模式后,我们需要上升到更高的层次,理解现代开发的“道”。
依赖注入 - 控制反转的核心思想
- 道之所在:不是主动创建依赖,而是被动接收依赖,这是IoC(控制反转)的一种实现方式,它让组件之间的依赖关系由容器来管理,而不是由组件自身来控制。
- 实践:
- 构造器注入:推荐方式,依赖在对象创建时就必须提供,保证了对象创建后就是完整的、不可变的。
- Setter注入:可选依赖,允许对象在创建后改变其依赖。
- 字段注入:简洁但不推荐,使得依赖关系不透明,难以进行单元测试。
- 关键点:DI是Spring框架的灵魂,它极大地提升了代码的可测试性和灵活性。
面向切面编程 - 模块化横切关注点
- 道之所在:将那些与业务逻辑无关,但又贯穿于多个模块的功能(如日志、事务、安全)抽取出来,形成独立的“切面”,AOP是实现这一思想的工具。
- 实践:Spring AOP是AOP思想的轻量级实现,它通过动态代理,在目标方法执行前后植入切面逻辑。
- 关键点:AOP与DI相辅相成,共同构建了松耦合、高内聚的应用程序。
响应式编程 - 面对高并发
- 道之所在:一种非阻塞的、异步的编程模型,它使用数据流和变化传播来构建应用,能更高效地利用系统资源,尤其适合I/O密集型的高并发场景。
- 实践:Spring WebFlux是Spring框架对响应式编程的支持,它基于Reactor或RxJava等项目,其核心是
Mono(0-1个元素) 和Flux(0-N个元素)。 - 关键点:响应式编程是未来高并发Web开发的一个重要方向,但它引入了编程模型的学习曲线。
从“术”到“道”的升华
| 模式/思想 | 核心问题 | 解决方案 | 在Java Web中的体现 |
|---|---|---|---|
| MVC | 关注点混乱 | 分离模型、视图、控制器 | Spring MVC, Servlet/JSP |
| DAO | 数据访问与业务耦合 | 抽象数据访问层 | Spring Data JPA, MyBatis |
| 单例 | 资源浪费和不一致 | 保证对象唯一性 | Servlet, Spring Bean (默认) |
| 工厂 | 对象创建复杂 | 解耦对象的创建与使用 | Spring IoC容器 |
| 代理 | 功能增强侵入性强 | 无侵入地添加额外功能 | Spring AOP (事务、日志) |
| 前端控制器 | 请求流程分散 | 统一请求入口和分发 | DispatcherServlet |
| 拦截器/过滤器 | 横切关注点处理 | 请求/响应链路上的拦截 | HandlerInterceptor, Filter |
| 策略 | 算法多变且易变 | 封装算法,动态切换 | 支付方式、优惠券计算 |
| 观察者 | 组件间紧耦合 | 事件驱动,松耦合交互 | Spring事件、MQ |
| DI/IoC | 依赖管理复杂 | 反转控制权,由容器管理 | Spring核心 |
| AOP | 横切代码散落 | 切面化处理横切逻辑 | Spring AOP |
| 响应式 | 高并发性能瓶颈 | 非阻塞、异步编程 | Spring WebFlux |
“道”的精髓在于:
- 不要为了模式而模式:理解问题,再选择合适的模式,简单的业务用简单的模式,过度设计是万恶之源。
- 理解 SOLID 原则:设计模式是SOLID原则的具体实践,学习模式的同时,更要理解背后的设计原则。
- 拥抱框架,但不被框架束缚:Spring等框架已经为我们封装了大量的设计模式,理解这些模式能让你更好地使用框架,并在需要时能跳出框架,解决更底层的问题。
- 持续演进:从传统的MVC到微服务,再到响应式编程,设计思想和模式也在不断演进,保持学习的热情,才能跟上技术的浪潮。
设计模式之道,是化繁为简、以简驭繁的智慧,它让你在面对复杂系统时,能够游刃有余,构建出优雅、健壮、可维护的软件。
