Spring Boot从入门到进阶教程系列 -- SpringMVC配置(包含自定义FastJSON配置)

上一个教程我们讲解如何配置MyBatis根据数据库类型进行对应数据库方言自动分页查询,本次我们将接着以往教程的成果来整合配置SpringMVC以及配置我们自定义的FastJSON序列输出,如需看上篇教程的同学可点击链接

【Spring Boot从入门到进阶教程系列 -- MyBatis分页拦截器实现(包含数据库方言)】

下面我们直接开启代码之旅

我们通过@ResponseBody返回对象的JSON字符串,如下示例

请求方法

    @ResponseBody
    @RequestMapping("/findOne")
    public Map findOne() throws BizErrorEx {
        Map<String, Object> map = new HashMap<>();
        map.put("id", 10001);
        map.put("name", "张三");
        map.put("sex", 1);
        map.put("ctime", System.currentTimeMillis());
        return map;
    }
页面输出
{
    "sex":1,
    "name":"张三",
    "ctime":1529545916866,
    "id":10001
}

通过以上我们可以看出@ResponseBody会把对象转化成字符串输出到前端,但是这一般不能满足我们的日常开发需求,例如我们经常需要的结构如下示例,其中包含基础的业务响应状态,HTTP状态,还有异常信息,响应时间等

{
	"code":"000000",
	"data":{
		"sex":1,
		"name":"张三",
		"ctime":1529545916866,
		"id":10001
	},
	"http":{
		"message":"OK",
		"status":200
	},
	"msg":"操作成功",
	"ts":1529545916867
}


通过以上代码演示,我们如何优雅,简洁不入侵业务代码的情况下完成我们的设计改造呢?下面我们开始尝试完成以上的设计需求实现

步骤1. 编写一个返回对象的包装实体类

public class RetData<T> implements Serializable {

    public String code; // 业务状态
    public String msg; // 业务信息
    public long ts; // 响应时间
    public T data; // 业务响应数据实体
    public Map<String, Object> http = new HashMap<>(); // HTTP状态,信息参数

    public RetData() {

    }

    public RetData(String code, String msg, T data) {
        this(code, msg, System.currentTimeMillis(), data, HttpStatus.OK);
    }

    public RetData(String code, String msg, T data, HttpStatus httpStatus) {
        this(code, msg, System.currentTimeMillis(), data, httpStatus);
    }

    public RetData(String code, String msg, long ts, T data, HttpStatus httpStatus) {
        this.code = code;
        this.msg = msg;
        this.ts = ts;
        this.data = data;
        httpStatus = httpStatus == null ? HttpStatus.OK : httpStatus;
        http.put("status", httpStatus.value());
        http.put("message", httpStatus.getReasonPhrase());
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public long getTs() {
        return ts;
    }

    public void setTs(long ts) {
        this.ts = ts;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}


步骤2. 通过查看FastJSON源码并改造writeInternal方法,加入响应实体包装业务实体的操作

核心代码

protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        ByteArrayOutputStream outnew = new ByteArrayOutputStream();
        try {
            HttpHeaders headers = outputMessage.getHeaders();
            SerializeFilter[] globalFilters = this.fastJsonConfig.getSerializeFilters();
            List<SerializeFilter> allFilters = new ArrayList(Arrays.asList(globalFilters));
            boolean isJsonp = false;
            Object value = this.strangeCodeForJackson(object);
            if (value instanceof FastJsonContainer) {
                FastJsonContainer fastJsonContainer = (FastJsonContainer) value;
                PropertyPreFilters filters = fastJsonContainer.getFilters();
                allFilters.addAll(filters.getFilters());
                value = fastJsonContainer.getValue();
            }
            if (value instanceof MappingFastJsonValue) {
                if (!StringUtils.isEmpty(((MappingFastJsonValue) value).getJsonpFunction())) {
                    isJsonp = true;
                }
            } else if (value instanceof JSONPObject) {
                isJsonp = true;
            }
            RetData data = new RetData(RetCode.OK_STATUS, RetCode.OK_MESSAGE, value);
            int len = JSON.writeJSONString(outnew, this.fastJsonConfig.getCharset(), data, this.fastJsonConfig.getSerializeConfig(), (SerializeFilter[]) allFilters.toArray(new SerializeFilter[allFilters.size()]), this.fastJsonConfig.getDateFormat(), JSON.DEFAULT_GENERATE_FEATURE, this.fastJsonConfig.getSerializerFeatures());
            if (isJsonp) {
                headers.setContentType(APPLICATION_JAVASCRIPT);
            }
            if (this.fastJsonConfig.isWriteContentLength()) {
                headers.setContentLength((long) len);
            }
            outnew.writeTo(outputMessage.getBody());
        } catch (JSONException var14) {
            throw new HttpMessageNotWritableException("Could not write JSON: " + var14.getMessage(), var14);
        } finally {
            outnew.close();
        }
    }

完整代码 FastJsonHttpUTF8MessageConverter.java

public class FastJsonHttpUTF8MessageConverter extends AbstractHttpMessageConverter<Object> implements GenericHttpMessageConverter<Object> {
    public static final MediaType APPLICATION_JAVASCRIPT = new MediaType("application", "javascript");
    /**
     * @deprecated
     */
    @Deprecated
    protected SerializerFeature[] features = new SerializerFeature[0];
    /**
     * @deprecated
     */
    @Deprecated
    protected SerializeFilter[] filters = new SerializeFilter[0];
    /**
     * @deprecated
     */
    @Deprecated
    protected String dateFormat;
    private FastJsonConfig fastJsonConfig = new FastJsonConfig();

    public FastJsonConfig getFastJsonConfig() {
        return this.fastJsonConfig;
    }

    public void setFastJsonConfig(FastJsonConfig fastJsonConfig) {
        this.fastJsonConfig = fastJsonConfig;
    }

    public FastJsonHttpUTF8MessageConverter() {
        super(MediaType.ALL);
        this.fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
        List<MediaType> fastMediaTypes = new ArrayList<>();
        fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
        this.setSupportedMediaTypes(fastMediaTypes);
    }

    /**
     * @deprecated
     */
    @Deprecated
    public Charset getCharset() {
        return this.fastJsonConfig.getCharset();
    }

    /**
     * @deprecated
     */
    @Deprecated
    public void setCharset(Charset charset) {
        this.fastJsonConfig.setCharset(charset);
    }

    /**
     * @deprecated
     */
    @Deprecated
    public String getDateFormat() {
        return this.fastJsonConfig.getDateFormat();
    }

    /**
     * @deprecated
     */
    @Deprecated
    public void setDateFormat(String dateFormat) {
        this.fastJsonConfig.setDateFormat(dateFormat);
    }

    /**
     * @deprecated
     */
    @Deprecated
    public SerializerFeature[] getFeatures() {
        return this.fastJsonConfig.getSerializerFeatures();
    }

    /**
     * @deprecated
     */
    @Deprecated
    public void setFeatures(SerializerFeature... features) {
        this.fastJsonConfig.setSerializerFeatures(features);
    }

    /**
     * @deprecated
     */
    @Deprecated
    public SerializeFilter[] getFilters() {
        return this.fastJsonConfig.getSerializeFilters();
    }

    /**
     * @deprecated
     */
    @Deprecated
    public void setFilters(SerializeFilter... filters) {
        this.fastJsonConfig.setSerializeFilters(filters);
    }

    /**
     * @deprecated
     */
    @Deprecated
    public void addSerializeFilter(SerializeFilter filter) {
        if (filter != null) {
            int length = this.fastJsonConfig.getSerializeFilters().length;
            SerializeFilter[] filters = new SerializeFilter[length + 1];
            System.arraycopy(this.fastJsonConfig.getSerializeFilters(), 0, filters, 0, length);
            filters[filters.length - 1] = filter;
            this.fastJsonConfig.setSerializeFilters(filters);
        }
    }

    protected boolean supports(Class<?> clazz) {
        return true;
    }

    public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
        return super.canRead(contextClass, mediaType);
    }

    public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
        return super.canWrite(clazz, mediaType);
    }

    public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return this.readType(this.getType(type, contextClass), inputMessage);
    }

    public void write(Object o, Type type, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        super.write(o, contentType, outputMessage);
    }

    protected Object readInternal(Class<? extends Object> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return this.readType(this.getType(clazz, (Class) null), inputMessage);
    }

    private Object readType(Type type, HttpInputMessage inputMessage) throws IOException {
        try {
            InputStream in = inputMessage.getBody();
            return JSON.parseObject(in, this.fastJsonConfig.getCharset(), type, this.fastJsonConfig.getFeatures());
        } catch (JSONException var4) {
            throw new HttpMessageNotReadableException("JSON parse error: " + var4.getMessage(), var4);
        } catch (IOException var5) {
            throw new HttpMessageNotReadableException("I/O error while reading input message", var5);
        }
    }

    protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        ByteArrayOutputStream outnew = new ByteArrayOutputStream();

        try {
            HttpHeaders headers = outputMessage.getHeaders();
            SerializeFilter[] globalFilters = this.fastJsonConfig.getSerializeFilters();
            List<SerializeFilter> allFilters = new ArrayList(Arrays.asList(globalFilters));
            boolean isJsonp = false;
            Object value = this.strangeCodeForJackson(object);
            if (value instanceof FastJsonContainer) {
                FastJsonContainer fastJsonContainer = (FastJsonContainer) value;
                PropertyPreFilters filters = fastJsonContainer.getFilters();
                allFilters.addAll(filters.getFilters());
                value = fastJsonContainer.getValue();
            }

            if (value instanceof MappingFastJsonValue) {
                if (!StringUtils.isEmpty(((MappingFastJsonValue) value).getJsonpFunction())) {
                    isJsonp = true;
                }
            } else if (value instanceof JSONPObject) {
                isJsonp = true;
            }
            RetData data = new RetData(RetCode.OK_STATUS, RetCode.OK_MESSAGE, value);
            int len = JSON.writeJSONString(outnew, this.fastJsonConfig.getCharset(), data, this.fastJsonConfig.getSerializeConfig(), (SerializeFilter[]) allFilters.toArray(new SerializeFilter[allFilters.size()]), this.fastJsonConfig.getDateFormat(), JSON.DEFAULT_GENERATE_FEATURE, this.fastJsonConfig.getSerializerFeatures());
            if (isJsonp) {
                headers.setContentType(APPLICATION_JAVASCRIPT);
            }

            if (this.fastJsonConfig.isWriteContentLength()) {
                headers.setContentLength((long) len);
            }

            outnew.writeTo(outputMessage.getBody());
        } catch (JSONException var14) {
            throw new HttpMessageNotWritableException("Could not write JSON: " + var14.getMessage(), var14);
        } finally {
            outnew.close();
        }

    }

    private Object strangeCodeForJackson(Object obj) {
        if (obj != null) {
            String className = obj.getClass().getName();
            if ("com.fasterxml.jackson.databind.node.ObjectNode".equals(className)) {
                return obj.toString();
            }
        }

        return obj;
    }

    protected Type getType(Type type, Class<?> contextClass) {
        return FastJsonHttpUTF8MessageConverter.Spring4TypeResolvableHelper.hasClazzResolvableType ? FastJsonHttpUTF8MessageConverter.Spring4TypeResolvableHelper.getType(type, contextClass) : type;
    }

    private static class Spring4TypeResolvableHelper {
        private static boolean hasClazzResolvableType;

        private Spring4TypeResolvableHelper() {
        }

        private static boolean isSupport() {
            return hasClazzResolvableType;
        }

        private static Type getType(Type type, Class<?> contextClass) {
            if (contextClass != null) {
                ResolvableType resolvedType = ResolvableType.forType(type);
                if (type instanceof TypeVariable) {
                    ResolvableType resolvedTypeVariable = resolveVariable((TypeVariable) type, ResolvableType.forClass(contextClass));
                    if (resolvedTypeVariable != ResolvableType.NONE) {
                        return resolvedTypeVariable.resolve();
                    }
                } else if (type instanceof ParameterizedType && resolvedType.hasUnresolvableGenerics()) {
                    ParameterizedType parameterizedType = (ParameterizedType) type;
                    Class<?>[] generics = new Class[parameterizedType.getActualTypeArguments().length];
                    Type[] typeArguments = parameterizedType.getActualTypeArguments();

                    for (int i = 0; i < typeArguments.length; ++i) {
                        Type typeArgument = typeArguments[i];
                        if (typeArgument instanceof TypeVariable) {
                            ResolvableType resolvedTypeArgument = resolveVariable((TypeVariable) typeArgument, ResolvableType.forClass(contextClass));
                            if (resolvedTypeArgument != ResolvableType.NONE) {
                                generics[i] = resolvedTypeArgument.resolve();
                            } else {
                                generics[i] = ResolvableType.forType(typeArgument).resolve();
                            }
                        } else {
                            generics[i] = ResolvableType.forType(typeArgument).resolve();
                        }
                    }

                    return ResolvableType.forClassWithGenerics(resolvedType.getRawClass(), generics).getType();
                }
            }

            return type;
        }

        private static ResolvableType resolveVariable(TypeVariable<?> typeVariable, ResolvableType contextType) {
            ResolvableType resolvedType;
            if (contextType.hasGenerics()) {
                resolvedType = ResolvableType.forType(typeVariable, contextType);
                if (resolvedType.resolve() != null) {
                    return resolvedType;
                }
            }

            ResolvableType superType = contextType.getSuperType();
            if (superType != ResolvableType.NONE) {
                resolvedType = resolveVariable(typeVariable, superType);
                if (resolvedType.resolve() != null) {
                    return resolvedType;
                }
            }

            ResolvableType[] var4 = contextType.getInterfaces();
            int var5 = var4.length;

            for (int var6 = 0; var6 < var5; ++var6) {
                ResolvableType ifc = var4[var6];
                resolvedType = resolveVariable(typeVariable, ifc);
                if (resolvedType.resolve() != null) {
                    return resolvedType;
                }
            }

            return ResolvableType.NONE;
        }

        static {
            try {
                Class.forName("org.springframework.core.ResolvableType");
                hasClazzResolvableType = true;
            } catch (ClassNotFoundException var1) {
                hasClazzResolvableType = false;
            }

        }

    }
}


步骤3. 增加SpringMVC配置初始化

@Configuration
public class MvcWebConfig implements WebMvcConfigurer {

    @Bean
    public FilterRegistrationBean testFilterRegistration() {
        return new FilterRegistrationBean();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        List<HandlerInterceptor> interceptors = new ArrayList<>(); // TODO 加入你的自定义拦截器集合
        for (HandlerInterceptor interceptor : interceptors) {
            InterceptorRegistration addInterceptor = registry.addInterceptor(interceptor);
            addInterceptor.excludePathPatterns("/static/**"); // 排除配置
            addInterceptor.addPathPatterns("/**"); // 拦截配置
        }
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new FastJsonHttpUTF8MessageConverter()); // 设置HTTP UTF-8 JSON对象模型
    }

}


总结,通过@ResponseBody返回对象的JSON字符串是我们最常用的一种数据返回方式,如有需要订制响应对象,不妨参考我提供的该方式进行改造你的代码设计

如果喜欢我的教程文章,请点下赞或收藏,如有疑惑可随时私信或评论区@我,感激不尽

猜你喜欢

转载自blog.csdn.net/shadowsick/article/details/80755856