什么是装饰器模式

装饰器模式(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对象。