装饰器模式:InputStream家族与Spring Security过滤器链原理
装饰器模式:InputStream家族与Spring Security过滤器链原理
适读人群:中高级Java开发者 | 阅读时长:约22分钟 | 模式类型:结构型
开篇故事
我第一次真正理解装饰器模式,是在研究 Java IO 的时候。面试官问我:"BufferedInputStream、DataInputStream、GZIPInputStream 这三个类有什么共同点?"我当时回答的是"都是 InputStream 的子类",面试官摇了摇头说:"它们都是装饰器,你有没有想过,为什么 Java IO 不直接做一个 BufferedGzipInputStream 把所有功能都集成进去,而是要这样层层套?"
这个问题把我问住了,回去研究了好几天,才真正理解了装饰器模式的精髓:通过组合而非继承来扩展功能,每个装饰器专注做一件事,多个装饰器可以任意叠加组合。
如果用继承来实现"带缓冲的、压缩的、加密的文件读取",你需要 BufferedInputStream、GZipInputStream、EncryptedInputStream,以及它们的各种组合 BufferedGzipInputStream、BufferedEncryptedInputStream、GzipEncryptedInputStream、BufferedGzipEncryptedInputStream……子类数量呈指数级爆炸。
而装饰器模式只需要把这些功能类像搭积木一样叠加:
InputStream in = new BufferedInputStream(
new GZIPInputStream(
new CipherInputStream(
new FileInputStream("data.bin"),
cipher
)
)
);一、模式动机:继承的组合爆炸问题
装饰器模式(Decorator Pattern)的动机是解决功能扩展的组合爆炸问题。
核心:在不改变原有类的情况下,通过包装(wrap)的方式动态地给对象添加新功能。装饰器与被装饰的对象实现相同的接口,对使用者透明。
关键特征:
- 装饰器与组件实现相同接口(透明性)
- 装饰器持有被装饰对象的引用(组合)
- 装饰器可以叠加(可组合性)
- 运行时决定装饰方式(动态性)
二、模式结构
三、Java IO 家族的装饰器设计
3.1 InputStream 继承体系
InputStream (抽象组件)
├── FileInputStream (具体组件:从文件读取)
├── ByteArrayInputStream (具体组件:从字节数组读取)
├── PipedInputStream (具体组件:管道读取)
├── FilterInputStream (装饰器基类) ← 关键!
│ ├── BufferedInputStream (缓冲装饰器:减少系统调用)
│ ├── DataInputStream (数据类型装饰器:读取基本类型)
│ ├── PushbackInputStream (回推装饰器:支持回推字节)
│ └── LineNumberInputStream (行号装饰器,已废弃)
└── GZIPInputStream (GZip解压装饰器,直接继承InflaterInputStream)FilterInputStream 就是装饰器基类:
public class FilterInputStream extends InputStream {
// 持有被装饰的InputStream引用(组合)
protected volatile InputStream in;
protected FilterInputStream(InputStream in) {
this.in = in;
}
// 默认实现:把所有方法委托给被包装的InputStream
@Override
public int read() throws IOException {
return in.read(); // 委托
}
@Override
public int read(byte b[], int off, int len) throws IOException {
return in.read(b, off, len); // 委托
}
@Override
public void close() throws IOException {
in.close(); // 委托
}
}BufferedInputStream 重写了 read() 方法,在委托调用之前实现缓冲逻辑:
public class BufferedInputStream extends FilterInputStream {
private static final int DEFAULT_BUFFER_SIZE = 8192; // 默认8KB缓冲区
protected volatile byte buf[]; // 缓冲区
protected int count; // 缓冲区中有效字节数
protected int pos; // 下一个要读取的字节位置
public BufferedInputStream(InputStream in) {
this(in, DEFAULT_BUFFER_SIZE);
}
public BufferedInputStream(InputStream in, int size) {
super(in);
if (size <= 0) throw new IllegalArgumentException("Buffer size <= 0");
buf = new byte[size];
}
@Override
public synchronized int read() throws IOException {
if (pos >= count) {
fill(); // 缓冲区空了,从底层InputStream批量读取
if (pos >= count) return -1;
}
return getBufIfOpen()[pos++] & 0xff; // 从缓冲区读取,不需要系统调用
}
private void fill() throws IOException {
// 从底层InputStream批量读取数据到缓冲区
byte[] buffer = getBufIfOpen();
pos = 0;
count = pos;
int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
if (n > 0) count = n + pos;
}
}3.2 Spring Security 过滤器链的装饰器原理
Spring Security 的核心是一个过滤器链,每个过滤器负责一个安全功能:
DelegatingFilterProxy(Spring管理的过滤器代理)
└── FilterChainProxy(安全过滤器链管理器)
└── SecurityFilterChain(实际的过滤器链)
├── WebAsyncManagerIntegrationFilter
├── SecurityContextPersistenceFilter(加载/保存SecurityContext)
├── HeaderWriterFilter(添加安全相关HTTP头)
├── CorsFilter(跨域处理)
├── CsrfFilter(CSRF防护)
├── LogoutFilter(登出处理)
├── UsernamePasswordAuthenticationFilter(表单登录)
├── BasicAuthenticationFilter(HTTP Basic认证)
├── RequestCacheAwareFilter(请求缓存)
├── SecurityContextHolderAwareRequestFilter(包装请求)
├── AnonymousAuthenticationFilter(匿名用户)
├── SessionManagementFilter(Session管理)
├── ExceptionTranslationFilter(安全异常处理)
└── FilterSecurityInterceptor(访问控制决策)SecurityContextHolderAwareRequestFilter 是一个典型的装饰器:它将原始的 HttpServletRequest 包装成 Servlet3SecurityContextHolderAwareRequestWrapper,增加了安全相关的方法(如 isUserInRole()、authenticate() 等),但对调用者完全透明,因为包装类和原始请求类都实现了 HttpServletRequest 接口。
四、生产级代码实现
4.1 API 请求装饰器链:日志 + 限流 + 熔断
/**
* HTTP 客户端接口(Component)
*/
public interface HttpClient {
<T> ApiResponse<T> execute(ApiRequest request, Class<T> responseType);
}
/**
* 基础 HTTP 客户端(Concrete Component)
*/
@Slf4j
public class SimpleHttpClient implements HttpClient {
private final RestTemplate restTemplate;
public SimpleHttpClient(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@Override
public <T> ApiResponse<T> execute(ApiRequest request, Class<T> responseType) {
try {
ResponseEntity<T> response = restTemplate.exchange(
request.getUrl(),
request.getMethod(),
new HttpEntity<>(request.getBody(), buildHeaders(request)),
responseType
);
return ApiResponse.success(response.getBody(), response.getStatusCodeValue());
} catch (RestClientException e) {
throw new HttpClientException("HTTP request failed: " + request.getUrl(), e);
}
}
private HttpHeaders buildHeaders(ApiRequest request) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
if (request.getHeaders() != null) {
request.getHeaders().forEach(headers::set);
}
return headers;
}
}
/**
* 装饰器基类(Decorator)
*/
public abstract class HttpClientDecorator implements HttpClient {
protected final HttpClient delegate; // 持有被装饰对象
public HttpClientDecorator(HttpClient delegate) {
this.delegate = Objects.requireNonNull(delegate);
}
}
/**
* 日志装饰器(Concrete Decorator A)
*/
@Slf4j
public class LoggingHttpClientDecorator extends HttpClientDecorator {
private final boolean logRequestBody;
private final boolean logResponseBody;
public LoggingHttpClientDecorator(HttpClient delegate,
boolean logRequestBody,
boolean logResponseBody) {
super(delegate);
this.logRequestBody = logRequestBody;
this.logResponseBody = logResponseBody;
}
@Override
public <T> ApiResponse<T> execute(ApiRequest request, Class<T> responseType) {
String requestId = UUID.randomUUID().toString().substring(0, 8);
long startTime = System.currentTimeMillis();
log.info("[{}] HTTP {} {}", requestId, request.getMethod(), request.getUrl());
if (logRequestBody && request.getBody() != null) {
log.debug("[{}] Request body: {}", requestId, request.getBody());
}
try {
ApiResponse<T> response = delegate.execute(request, responseType); // 调用被装饰对象
long elapsed = System.currentTimeMillis() - startTime;
log.info("[{}] HTTP {} {} -> {} ({}ms)",
requestId, request.getMethod(), request.getUrl(),
response.getStatusCode(), elapsed);
if (logResponseBody && response.getData() != null) {
log.debug("[{}] Response body: {}", requestId, response.getData());
}
return response;
} catch (Exception e) {
long elapsed = System.currentTimeMillis() - startTime;
log.error("[{}] HTTP {} {} FAILED ({}ms): {}",
requestId, request.getMethod(), request.getUrl(), elapsed, e.getMessage());
throw e;
}
}
}
/**
* 限流装饰器(Concrete Decorator B)
*/
@Slf4j
public class RateLimitingHttpClientDecorator extends HttpClientDecorator {
private final RateLimiter rateLimiter; // Guava的令牌桶限流器
private final String rateLimiterKey;
public RateLimitingHttpClientDecorator(HttpClient delegate, double permitsPerSecond) {
super(delegate);
this.rateLimiter = RateLimiter.create(permitsPerSecond);
this.rateLimiterKey = "http-client";
}
@Override
public <T> ApiResponse<T> execute(ApiRequest request, Class<T> responseType) {
// 获取令牌,如果速率超限则等待
double waitTime = rateLimiter.acquire();
if (waitTime > 0.01) {
log.warn("Rate limiting triggered for {}, waited {}ms",
request.getUrl(), (long)(waitTime * 1000));
}
return delegate.execute(request, responseType);
}
}
/**
* 重试装饰器(Concrete Decorator C)
*/
@Slf4j
public class RetryHttpClientDecorator extends HttpClientDecorator {
private final int maxRetries;
private final long retryDelayMs;
private final Set<Integer> retryableStatusCodes;
public RetryHttpClientDecorator(HttpClient delegate, int maxRetries, long retryDelayMs) {
super(delegate);
this.maxRetries = maxRetries;
this.retryDelayMs = retryDelayMs;
this.retryableStatusCodes = Set.of(500, 502, 503, 504);
}
@Override
public <T> ApiResponse<T> execute(ApiRequest request, Class<T> responseType) {
Exception lastException = null;
for (int attempt = 0; attempt <= maxRetries; attempt++) {
try {
ApiResponse<T> response = delegate.execute(request, responseType);
// 对于某些错误状态码也进行重试
if (attempt < maxRetries && retryableStatusCodes.contains(response.getStatusCode())) {
log.warn("Retrying due to status code {}, attempt {}/{}",
response.getStatusCode(), attempt + 1, maxRetries);
Thread.sleep(retryDelayMs * (attempt + 1)); // 指数退避
continue;
}
return response;
} catch (HttpClientException e) {
lastException = e;
if (attempt < maxRetries) {
log.warn("Request failed, retrying {}/{}: {}",
attempt + 1, maxRetries, e.getMessage());
try {
Thread.sleep(retryDelayMs * (attempt + 1));
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new HttpClientException("Interrupted during retry", ie);
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new HttpClientException("Interrupted", e);
}
}
throw new HttpClientException("Max retries exceeded", lastException);
}
}
/**
* 装饰器链工厂(Builder模式)
*/
public class HttpClientBuilder {
private HttpClient client;
private HttpClientBuilder(HttpClient baseClient) {
this.client = baseClient;
}
public static HttpClientBuilder withBase(RestTemplate restTemplate) {
return new HttpClientBuilder(new SimpleHttpClient(restTemplate));
}
public HttpClientBuilder withLogging(boolean logBody) {
client = new LoggingHttpClientDecorator(client, logBody, logBody);
return this;
}
public HttpClientBuilder withRateLimit(double permitsPerSecond) {
client = new RateLimitingHttpClientDecorator(client, permitsPerSecond);
return this;
}
public HttpClientBuilder withRetry(int maxRetries, long delayMs) {
client = new RetryHttpClientDecorator(client, maxRetries, delayMs);
return this;
}
public HttpClient build() {
return client;
}
}
// 使用示例:构建装饰器链
@Configuration
public class HttpClientConfig {
@Bean
@Primary
public HttpClient productionHttpClient(RestTemplate restTemplate) {
return HttpClientBuilder
.withBase(restTemplate)
.withRateLimit(100.0) // 最外层:先限流
.withRetry(3, 500) // 再重试(重试时也受限流约束)
.withLogging(false) // 最内层:日志记录实际执行情况
.build();
}
@Bean("debugHttpClient")
public HttpClient debugHttpClient(RestTemplate restTemplate) {
return HttpClientBuilder
.withBase(restTemplate)
.withLogging(true) // 开发环境打印请求响应体
.build();
}
}五、与相关模式的对比与选型
装饰器 vs 适配器
- 装饰器:不改变接口,增强功能,装饰前后接口相同。
- 适配器:改变接口,让不兼容的接口协同工作。
装饰器 vs 代理
- 装饰器:由调用者主动叠加,关注功能增强,叠加关系对调用者可见。
- 代理:调用者通常不知道在使用代理,关注访问控制,一般只有一层代理。
装饰器 vs 责任链
- 装饰器:所有装饰器都会被执行(层层委托),关注功能叠加。
- 责任链:请求沿链传递,某个处理者可以中断链,关注请求处理的分发。
六、踩坑实录
坑一:装饰器链的顺序决定行为
装饰器的执行顺序非常重要,顺序不同会产生完全不同的结果。比如:
// 先重试再限流 vs 先限流再重试
// 先重试再限流:每次重试都消耗令牌,3次重试 = 3个令牌
new RateLimitingDecorator(new RetryDecorator(base, 3), 100)
// 先限流再重试:只有通过限流的请求才会重试,重试不消耗额外令牌
new RetryDecorator(new RateLimitingDecorator(base, 100), 3)实际踩坑:把日志装饰器放在最外层,导致重试时每次尝试都被日志记录,日志量是预期的3倍;而且日志显示的都是"失败重试",而不是最终成功的一次调用。应该把日志放在最内层,只记录真实的HTTP请求。
坑二:close() 方法的传播
在 Java IO 中使用装饰器链时,关闭最外层的装饰器应该会递归关闭里面所有的流,但如果某个中间装饰器的 close() 方法没有调用 super.close(),会导致底层资源泄漏。
检查代码时要确保装饰器的资源释放方法(close()、destroy() 等)正确传播到被装饰对象。
坑三:Spring Security 自定义 Filter 的顺序
在 Spring Security 过滤器链中添加自定义 Filter 时,位置非常关键。在 UsernamePasswordAuthenticationFilter 之前添加的 Filter 可以影响认证流程,在之后添加的才能访问到已认证的 SecurityContext。用 addFilterBefore、addFilterAfter 还是 addFilterAt,需要仔细考虑。
有一次在 JWT Token 验证 Filter 的位置放错了,导致每次请求都报"用户未认证",排查了半天才发现 JWT Filter 放在了 SecurityContextPersistenceFilter 之前,设置到 SecurityContext 的认证信息被后者覆盖了。
七、总结
装饰器模式是 Java IO 设计中最优美的体现,也是 Spring Security 过滤器链的核心机制。
核心价值:
- 比继承更灵活:可以在运行时动态叠加装饰器,而继承是编译时固定的。
- 单一职责:每个装饰器只负责一个功能,代码结构清晰。
- 开闭原则:新增功能只需新增装饰器类,不修改已有代码。
使用要点:
- 装饰器链的顺序至关重要,需要根据业务语义仔细设计。
- 资源释放要确保正确传播(
close()必须调用被装饰对象的close())。 - 避免过深的装饰器嵌套,调用栈过深会增加调试难度。
