JSF 都 2.0 了,尼玛居然还是无法识别 multipart/form-data(至少参考实现 Mojarra 如此),绑定的属性一个都读不出来,坑爹啊!!!既然官方不支持,咱就自己搞一个补丁,让它不从也得从。
说到底,JSF 的属性绑定功能不外乎是利用 FacesServlet 帮我们把参数进行转换和校验,然后拼装成受管 Bean。而 FacesServlet 必定是通过 HttpServletRequest 的相关方法来读取请求参数,因此只需要在 FacesServlet 之前增加一个过滤器,把文本类型的 Part 参数转换为普通参数就可以了。至于文件类型的 Part,则可以使用一些第三方工具来绑定,例如使用 PrimeFaces 将文件绑定到 File 对象。下图是这种思路的流程:

第二步的过滤器就是给 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 这种蛋疼的弯路来黑?