集中处理application/json类型的请求拦截器

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.google.common.collect.Maps;
import com.jfinal.aop.Interceptor;
import com.jfinal.aop.Invocation;
import io.undertow.servlet.util.IteratorEnumeration;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * 前后端分离时,使用json作为数据传输格式后,jfinal的getPara系列方法失效;
 * 此拦截器预处理,将json转为Map,写入getParamerterMap, 不影响已有的getParam方法;
 * <p> @Date : 2019/11/25 </p>
 *
 */
public class JsonParamIntercept implements Interceptor {

    @Override
    public void intercept(Invocation inv) {
        /**
         * 1. 根据请求头预判json
         * 2. 分解json,写入paramterMap
         */
        WriteHttpServletRequestWrapper request = new WriteHttpServletRequestWrapper(inv.getController().getRequest());
        if (request.getMethod().equalsIgnoreCase("post") && request.getContentType().contains("application/json")) {
            Map<String, String> jsonParams = JSON.parseObject(inv.getController().getRawData(), new TypeReference<Map<String, String>>() {

            });
            request.init(jsonParams);
            inv.getController().setHttpServletRequest(request);
        }
        inv.invoke();
    }

    class WriteHttpServletRequestWrapper extends HttpServletRequestWrapper {

        Map<String, String[]> params = Maps.newHashMap();

        /**
         * Constructs a request object wrapping the given request.
         *
         * @param request the {@link HttpServletRequest} to be wrapped.
         *
         * @throws IllegalArgumentException if the request is null
         */
        public WriteHttpServletRequestWrapper(HttpServletRequest request) {
            super(request);
            params.putAll(request.getParameterMap());
        }

        public void init(Map<String, String> flatParams) {
            for (Map.Entry<String, String> e : flatParams.entrySet()) {
                params.put(e.getKey(), new String[] { e.getValue() });
            }
        }

        @Override
        public String getParameter(String name) {
            String result = null;
            for (Map.Entry<String, String[]> e : params.entrySet()) {
                if (name.equalsIgnoreCase(e.getKey())) {
                    String[] values = e.getValue();
                    if (values.length == 1) {
                        result = values[0];
                    }
                }
            }
            return result;
        }

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

        @Override
        public Enumeration<String> getParameterNames() {
            final Set<String> parameterNames = new HashSet<>(params.keySet());
            return new IteratorEnumeration<>(parameterNames.iterator());
        }

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


前后端分离项目,前后台通信已确定使用Json格式传输数据,但在Controller中无法直接使用getPara 系列方法,会有非常不方便,尝试利用拦截器集中对Json请求处理转成Map<String,Object> 放入request.paramterMap;

开发中发现,HttpServletRequest中没有对paramterMap的写操作,想必是为了达到只读的目,为了达到目的只能扩展HttpServletRequestWrapper.

不足之处:

几十行代码,外部的依赖好几个,没考虑扩展


评论区

JFinal

2019-11-26 20:59

Controller.setHttpServletRequest(request) 方法正是这么用的,看过源码,赞

cuiweieee

2019-11-27 01:12

不建议这样做,parameter和requestBody是完全不同的概念

Jieven

2019-11-27 14:52

@cuiweieee 爽就行, 其它的不重要

zzutligang

2019-11-29 09:03

@铂金蛋蛋,这个引入了import io.undertow.servlet.util.IteratorEnumeration;这个类,是只能使用undertow吗?jetty和tomcat就没法使用了吗?

铂金蛋蛋

2019-11-29 15:41

@zzutligang 因为工程作为微服务打包成fatjar,开发和部署都是用了undertow 应用服务器,你要替换的话可以找tomcat、jetty 对应的实现就行;需要翻一下具体实现的代码;

铂金蛋蛋

2019-11-29 15:57

@zzutligang tomcat 下参考org.apache.catalina.core.ApplicationHttpRequest.AttributeNamesEnumerator 类

zzutligang

2019-11-29 16:12

@铂金蛋蛋,容器依赖性太强了吧。

JFinal

2019-11-29 16:24

@zzutligang 参考 IteratorEnumeration 自己写一个类放在项目中即可,这个类里头的逻辑应该很简单

chcode

2019-11-30 12:25

@JFinal 按照以上方法处理json 数据发现在controller可以使用getBean(xxx.class,null) 获取数据 但是无法使用@Para("") 进行参数注入 ,使用表单提交是可以进行注入的,比较烦恼

JFinal

2019-11-30 12:58

@chcode 应该可以的,你用用最新版本 jfinal

或者上面的处理 json 方式还需要改进一下,看一下 com.jfinal.core.Injector.java 源码,里面用到了:
request.getParameterMap()

chcode

2019-11-30 13:28

@JFinal 用的最新版 注入时request.getParameterMap() 为空 getBean时正常获取到了

chcode

2019-11-30 13:32

@JFinal 参数注入方法执行在JsonParamIntercept 拦截器之前

JFinal

2019-11-30 13:53

@chcode 最新版本是不是可以了?

chcode

2019-11-30 13:56

@JFinal 不可以 参数注入在拦截器之前执行了,request.getParameterMap()为空,getBean在拦截器之后执行 request.getParameterMap()不为空

JFinal

2019-11-30 14:17

@chcode 使用一个 MyHandler 来实现本贴中拦截器切换 request 对象的功能,大致如下:
public void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandlec) {
if (request.getMethod().equalsIgnoreCase("post") && request.getContentType().contains("application/json")) {
WriteHttpServletRequestWrapper request = new WriteHttpServletRequestWrapper(request);
Map jsonParams = JSON.parseObject(HttpKit.readData(request), new TypeReference>() {});
request.init(jsonParams);
}

next.handle(request, response, isHandled);
}

简单来说就是将拦截器中要做的事情提前到 handler 中去做

chcode

2019-11-30 14:24

@JFinal 搞定了,谢谢波总

JFinal

2019-11-30 14:31

@chcode jfinal 几乎每一个组件都可以扩展,而且代码量极少, jfinal 是一个可以轻松完全掌控的 MVC + ORM + AOP 框架

代码在手,随心所欲

chcode

2019-12-01 15:23

@铂金蛋蛋 https://jfinal.com/share/1918 我的另一种解决方案 欢迎指点