什么是装饰器模式
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构,它是作为现有的类的一个包装。这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
关键词:现有的对象,添加新功能。
通过子类的方式是对父类添加新的功能,针对的是类。而装饰器模式针对的是一个现有的对象,而不是类。
解释
看下面的例子:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequestWrapper servletRequest = (HttpServletRequestWrapper) req;
AuthServletRequest request = new AuthServletRequest(servletRequest);
//...
chain.doFilter(request, req);
}
这里的servletRequest就是一个现有的对象,在这里我们要动态的扩展servletRequest的方法,通过子类继承的方式不太好实现,但通过装饰器模式就很方便的可以实现。
AuthServletRequest就是装饰类,包装了原有的servletRequest,在保证servletRequest完整的情况下,实现了额外的功能。
增加方法
项目中使用了网关,网关解析出jwt后,将userId和role通过header传到业务项目,结构图如下:
业务项目拿到userId和role后,希望能塞入HttpRequest的parameter,这样在spring项目的Controller中就能直接定义在方法上了,简化业务项目的开发。类似这样:
RequestMapping("/sysUser")
public MyResEntity sysUser(Integer userId){
SysUser sysUser = sysUserService.sysUser(userId);
return new MyResEntity(sysUser);
}
但是HttpServletRequest没有增加参数到parameter这个方法,没法实现上述需求。
这里就轮到装饰器模式派上用场了。新增AuthServletRequest类,如下:
public class AuthServletRequest extends HttpServletRequestWrapper {
// 存储request数据的Map
private Map<String, String[]> params = new HashMap<String, String[]>();
public AuthServletRequest(HttpServletRequestWrapper request) {
super(request);
//将现有parameter传递给params
this.params.putAll(request.getParameterMap());
}
//重写getParameter,代表参数从当前类中的map获取
@Override
public String getParameter(String name) {
String[] values = params.get(name);
if (values == null || values.length == 0) {
return null;
}
return values[0];
}
//重写getParameterValues,代表参数从当前类中的map获取
@Override
public String[] getParameterValues(String name) {
return params.get(name);
}
// 核心方法,新增添加parameter的方法
public void addParameter(String name, Object value) {
if (value != null) {
System.out.println(value);
if (value instanceof String[]) {
params.put(name, (String[]) value);
} else if (value instanceof String) {
params.put(name, new String[]{(String) value});
} else {
params.put(name, new String[]{String.valueOf(value)});
}
}
}
}
然后新增一个过滤器AuthFilter,代码如下:
public class AuthFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequestWrapper servletRequest = (HttpServletRequestWrapper) req;
AuthServletRequest request = new AuthServletRequest(servletRequest);
HttpServletResponse response = (HttpServletResponse) res;
// header里的值是gateway解析jwt得出的值
String userId = request.getHeader("userId");
if (userId != null && request.getParameter("userId") == null) {
request.addParameter("userId", userId);
}
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
结论
我们通过装饰器模式,在项目运行时动态的给ServletRequest增加了addParameter方法,扩展了原对象。
装饰器模式是继承的一种替代模式,其优点是可以动态扩展一个实现类的功能。装饰器模式在jdk和各大框架中都有广泛的应用,例如:
在Java中,InputStream,FileInputStream,BufferedInputStream等IO流的操作就运用了装饰器模式。
在spring使用redis实现session共享的功能里,spring也使用装饰器模式包装了HttpServletRequest,装饰类为SessionRepositoryRequestWrapper,其中重写了getSession方法,从redis中创建和获取session对象。