权限校验和日志审计是典型的横切关注点,它们和业务逻辑本身没有直接关系,却往往被硬编码进业务方法里。时间一长,一个下单方法里可能混杂着角色判断、参数校验、日志拼装、异常记录,核心逻辑反而被淹没在辅助代码中。装饰器模式提供了一条更干净的路:业务类只负责业务,权限和日志作为装饰器动态套在外面,想用就包一层,不想用就拆掉,原有代码一行不用改。

装饰器模式的核心结构是什么
装饰器模式属于结构型设计模式,它的本质是“用组合代替继承”。整个模式由四个角色构成:抽象构件(Component)、具体构件(ConcreteComponent)、抽象装饰器(Decorator)和具体装饰器(ConcreteDecorator)。抽象构件定义业务接口,具体构件实现真正的业务逻辑,抽象装饰器持有一个构件的引用并把请求转发给它,具体装饰器在转发前后追加自己的增强逻辑。
和继承相比,装饰器的优势在于灵活性。如果用继承来做权限校验,每种权限级别都要派生一个子类,业务类和权限类组合起来会出现类爆炸。而装饰器是运行时组装的,同一个业务对象可以被多个装饰器层层包裹,包裹的顺序、数量都可以在代码里动态决定。比如先包一层权限校验装饰器,再包一层日志装饰器,调用时日志先记录,然后做权限判断,最后才执行业务——顺序完全由装饰链的组装顺序决定。
需要注意的一点是,装饰器和代理模式在代码结构上非常相似,都是持有一个目标对象并做转发。区别主要在意图:代理模式控制对对象的访问,强调“管理”;装饰器模式给对象增强功能,强调“添加”。理解这个区别有助于在团队沟通时准确表达设计意图。
用装饰器实现权限校验与日志审计的完整案例
下面用一个订单服务做例子。先定义抽象构件接口,再写核心业务实现,然后分别实现权限校验装饰器和日志审计装饰器。为了演示方便,代码用 Java 编写,其他面向对象语言思路完全一致。
// 抽象构件:订单服务接口
public interface OrderService {
void createOrder(String userId, String orderNo, double amount);
}
// 具体构件:真正的业务逻辑,不含任何权限和日志代码
public class BaseOrderService implements OrderService {
@Override
public void createOrder(String userId, String orderNo, double amount) {
System.out.println("订单已创建:" + orderNo + ",金额:" + amount);
}
}
// 抽象装饰器:持有构件引用并转发调用
public abstract class OrderServiceDecorator implements OrderService {
protected final OrderService delegate;
public OrderServiceDecorator(OrderService delegate) {
this.delegate = delegate;
}
@Override
public void createOrder(String userId, String orderNo, double amount) {
delegate.createOrder(userId, orderNo, amount);
}
}
// 具体装饰器一:权限校验
public class AuthDecorator extends OrderServiceDecorator {
public AuthDecorator(OrderService delegate) {
super(delegate);
}
@Override
public void createOrder(String userId, String orderNo, double amount) {
if (!hasPermission(userId, "ORDER_CREATE")) {
throw new SecurityException("用户无下单权限:" + userId);
}
super.createOrder(userId, orderNo, amount);
}
private boolean hasPermission(String userId, String action) {
// 实际项目中这里会查询权限表或缓存
return "admin".equals(userId) || "vip001".equals(userId);
}
}
// 具体装饰器二:日志审计
public class AuditLogDecorator extends OrderServiceDecorator {
public AuditLogDecorator(OrderService delegate) {
super(delegate);
}
@Override
public void createOrder(String userId, String orderNo, double amount) {
long start = System.currentTimeMillis();
System.out.println("[审计] 用户 " + userId + " 发起下单 " + orderNo);
try {
super.createOrder(userId, orderNo, amount);
System.out.println("[审计] 下单成功,耗时 " + (System.currentTimeMillis() - start) + "ms");
} catch (Exception e) {
System.out.println("[审计] 下单失败:" + e.getMessage());
throw e;
}
}
}
使用时的组装方式非常直观,调用方根据场景决定要包几层。比如内部管理后台需要严格审计,就同时包上日志和权限两个装饰器;而系统内部定时任务调用的场景,可以直接用裸的 BaseOrderService,完全绕开这些增强逻辑。
public class Client {
public static void main(String[] args) {
// 完整能力:日志审计在外层,权限校验在内层,核心业务在最里层
OrderService service = new AuditLogDecorator(
new AuthDecorator(
new BaseOrderService()));
service.createOrder("vip001", "ORD-2024-0001", 299.00);
}
}
这段代码的执行顺序是:日志装饰器先记录请求,然后权限装饰器校验用户身份,校验通过后才真正执行下单,最后日志装饰器补记执行结果和耗时。整个链路中 BaseOrderService 对权限和日志一无所知,二者彻底解耦。如果某天权限规则变了,只改 AuthDecorator 一个类;要换成基于注解的鉴权框架,业务类同样不用动。
装饰器与拦截器、AOP方案的对比及注意事项
装饰器并不是实现横切逻辑的唯一方案,实际项目里要结合技术栈做选择。下面的表格从几个维度做了对比:
| 维度 | 装饰器模式 | 拦截器 | AOP框架 |
|---|---|---|---|
| 侵入性 | 低,需接口抽象 | 低,框架层生效 | 极低,注解即可 |
| 控制粒度 | 对象级,可精细组装 | 请求或方法级 | 方法级,支持切点表达式 |
| 依赖程度 | 无外部依赖 | 依赖容器或框架 | 依赖AOP框架 |
| 调试难度 | 调用链清晰 | 中等 | 较难,逻辑分散在切面 |
从表格可以看出,装饰器最大的价值在于不依赖任何框架,适合类库开发、跨项目复用或者无法引入AOP的场景。而在已经使用Spring的项目里,AOP配合自定义注解往往写起来更快;拦截器则更适合Web请求级别的统一控制,比如登录态检查。三者并不互斥,一个成熟的系统里常常是拦截器挡住未认证请求、AOP处理通用鉴权、装饰器服务于需要精细组装的复杂场景。
使用装饰器也要警惕两个坑。第一是类膨胀问题,如果每个业务方法都有不同的增强组合,具体装饰器的数量会快速增长,这时可以考虑把装饰器做成通用的函数式包装,用一个接受 Runnable 或函数接口的通用装饰器替代多个具体类。第二是多层装饰会让调用栈变深、排查问题变难,建议控制装饰层数在三层以内,并在装饰器入口处打印统一的调试日志,方便定位链路。另外,装饰器必须和原对象实现同一接口,如果业务类没有抽象出接口,强行套装饰器就需要先做一次重构,这个前置成本要提前评估。
总结一下,装饰器模式把“做什么”和“怎么增强”拆成两条独立的演化路径。业务类专注于领域逻辑,权限校验和日志审计以可插拔的方式挂在调用链上,新增、移除、调整顺序都不触碰原有代码。对于追求可维护性和可测试性的系统来说,这种动态增强能力是值得投入的设计。