神奇好望角 The Magical Cape of Good Hope

庸人不必自扰,智者何需千虑?
posts - 26, comments - 50, trackbacks - 0, articles - 11
  语源科技BlogJava :: 首页 ::  :: 联系 :: 聚合  :: 管理

JSF 都 2.0 了,尼玛居然还是无法识别 multipart/form-data(至少参考实现 Mojarra 如此),绑定的属性一个都读不出来,坑爹啊!!!既然官方不支持,咱就自己搞一个补丁,让它不从也得从。

说到底,JSF 的属性绑定功能不外乎是利用 FacesServlet 帮我们把参数进行转换和校验,然后拼装成受管 Bean。而 FacesServlet 必定是通过 HttpServletRequest 的相关方法来读取请求参数,因此只需要在 FacesServlet 之前增加一个过滤器,把文本类型的 Part 参数转换为普通参数就可以了。至于文件类型的 Part,则可以使用一些第三方工具来绑定,例如使用 PrimeFaces 将文件绑定到 File 对象。下图是这种思路的流程:

multipart/form-data 的处理流程

第二步的过滤器就是给 JSF 打的“补丁”:

        /**
         * 该过滤器帮助 {@link FacesServlet} 识别 {@code multipart/form-data} 格式的 POST 请求。
         */
        @WebFilter("*.xhtml")
        public class MultipartFilter implements Filter {
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {
            }

            @Override
            public void doFilter(ServletRequest request, ServletResponse response,
                    FilterChain chain) throws IOException, ServletException {
                String contentType = request.getContentType();
                // 判断请求的格式是否为 multipart/form-data。
                if (contentType != null && contentType.startsWith("multipart/form-data")) {
                    MultipartRequest req = new MultipartRequest((HttpServletRequest) request);
                    for (Part part : req.getParts()) {
                        // 如果该 Part 的内容类型不为 null, 那它是一个文件,忽略。
                        if (part.getContentType() == null) {
                            req.addParameter(part.getName(), decode(part));
                        }
                    }
                    chain.doFilter(req, response);
                } else {
                    chain.doFilter(request, response);
                }
            }

            @Override
            public void destroy() {
            }

            /**
             * 将 {@link Part} 对象解码为字符串。
             */
            private String decode(Part part) throws IOException {
                try (InputStreamReader in = new InputStreamReader(
                        part.getInputStream(), StandardCharsets.UTF_8)) {
                    char[] buffer = new char[64];
                    int nread = 0;
                    StringBuilder sb = new StringBuilder();
                    while ((nread = in.read(buffer)) != -1) {
                        sb.append(buffer, 0, nread);
                    }
                    return sb.toString();
                }
            }

            /**
             * {@link HttpServletRequest} 中的请求参数映射是只读的,所以自己封装一个。
             */
            private static class MultipartRequest extends HttpServletRequestWrapper {
                private Map<String, String[]> parameters;

                public MultipartRequest(HttpServletRequest request) {
                    super(request);
                    parameters = new HashMap<>();
                }

                private void addParameter(String name, String value) {
                    String[] oldValues = parameters.get(name);
                    if (oldValues == null) {
                        parameters.put(name, new String[] {value});
                    } else {
                        int size = oldValues.length;
                        String[] values = new String[size + 1];
                        System.arraycopy(oldValues, 0, values, 0, size);
                        values[size] = value;
                        parameters.put(name, values);
                    }
                }

                @Override
                public String getParameter(String name) {
                    String[] values = getParameterValues(name);
                    return values == null ? null : values[0];
                }

                @Override
                public Map<String, String[]> getParameterMap() {
                    return parameters;
                }

                @Override
                public Enumeration<String> getParameterNames() {
                    final Iterator<String> it = parameters.keySet().iterator();
                    return new Enumeration<String>() {
                        @Override
                        public boolean hasMoreElements() {
                            return it.hasNext();
                        }

                        @Override
                        public String nextElement() {
                            return it.next();
                        }
                    };
                }

                @Override
                public String[] getParameterValues(String name) {
                    return parameters.get(name);
                }
            }
        }
    

这儿喷一下,为什么 HttpServletRequest 里面的请求参数映射是只读的,非得要通过继承 HttpServletRequestWrapper 这种蛋疼的弯路来黑?