设计模式之 装饰者模式
阅读前请注意:本文章只是在学习过程中的记录,如果有什么错误的描述的,还请大佬们多多包涵,指点一下小白,多谢多谢。
阅读前请注意:本文章只是在学习过程中的记录,如果有什么错误的描述的,还请大佬们多多包涵,指点一下小白,多谢多谢。
阅读前请注意:本文章只是在学习过程中的记录,如果有什么错误的描述的,还请大佬们多多包涵,指点一下小白,多谢多谢。
一、定义
装饰者模式 在不改动对象的情况下,动态地将功能附加到对象上。若要扩展功能,应该提供一个包装器,把要扩展的对象包装起来,以提供更加强大的功能。
二、结构
- Component 被包装的组件,可单独使用。
- Decorator 包装器,扩展包装的对象。
三、使用场景
- 扩展一个类的功能(这个继承也可以做到)。
- 动态增加功能,动态撤销功能。
四、实例
开发javaweb的时候我们经常会获取 ServletRequest 这个对象,这个对象里面有一个方法叫 getInputStream(),他是用来获取用户提交在body里面的数据流,这个数据流一旦读取就没了。
如果我们在过滤器或者拦截器之类的地方直接或者间接调用了getInputStream()这个方法,那么在controller控制层就会获取不到数据。
现在假设我们有一个过滤器就是要过滤body中的数据,这该怎么处理?基于现在的 ServletRequest 的对象明显是做不了这件事的。
这时我们就可以使用 装饰者模式 动态的扩展 ServletRequest 对象,以实现我们现在的需求。
扩展步骤:
我们创建一个类 RepeatedlyRequestWrapper 继承 HttpServletRequestWrapper
(HttpServletRequestWrapper 是一个实现 ServletRequest 接口的装饰者,我们也可以直接实现ServletRequest 接口,但是那样我们要实现的方法太多了)。
- 我们在 RepeatedlyRequestWrapper 中把结果保存下来,让getInputStream()方法去获取我们保存下来的内容,而不是去调用原始的方法,这样getInputStream()就可以一直获取到内容了。
import cn.hutool.extra.servlet.ServletUtil;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* 构建可重复读取inputStream的request
*/
public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
RepeatedlyRequestWrapper(HttpServletRequest request, HttpServletResponse response) throws IOException {
super(request);
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
body = ServletUtil.getBodyBytes(request);
}
@Override
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() {
ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
}
现在我们只需要在需要获取 body 里面的内容的时候,创建一个 RepeatedlyRequestWrapper ,传入HttpServletRequest,然后调用调用getInputStream()就可以获取到body数据流了。
当然传入的HttpServletRequest对象必须要还存在body数据流,如果之前被获取了,再包装也不会有数据,所以我们应该在数据进来就立刻把HttpServletRequest进行包装,以确保数据能准确的包装。
这时我们定义一个权重最大的过滤器,在里面包装一下,让后面调用getInputStream()能一值获取到值。
import cn.hutool.core.util.StrUtil;
import org.springframework.http.MediaType;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Repeatable 过滤器
*/
public class RepeatableFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
ServletRequest requestWrapper = null;
if (request instanceof HttpServletRequest && StrUtil.equalsAnyIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE, MediaType.TEXT_PLAIN_VALUE)) {
requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, (HttpServletResponse) response);
}
if (null == requestWrapper) {
chain.doFilter(request, response);
} else {
chain.doFilter(requestWrapper, response);
}
}
}
我们现在注册这个过滤器,把权重调到最大
@Bean
public FilterRegistrationBean<RepeatableFilter> someFilterRegistration() {
FilterRegistrationBean<RepeatableFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new RepeatableFilter());
registration.addUrlPatterns("/*");
registration.setName("repeatableFilter");
// 权重调到最大值
registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
return registration;
}
五、测试
创建一个测试接口,在接口中获取2次body中的内容。
@PostMapping("/get/body")
public void testRepeatedlyRequest(HttpServletRequest request) {
System.out.println("第1次:" + ServletUtil.getBody(request));
System.out.println("第2次:" + ServletUtil.getBody(request));
}
在没有注册Repeatable过滤器前:
在注册了Repeatable过滤器后:
肥肠抱歉,这些源码是在实际项目中的,不能提供源码了,主要的代码已经贴上了,如果有什么问题可以私信或者加我微信。