springcloud feign多参数调用解析处理方法

源码地址:https://gitee.com/ttx_urey/feign-multiple-param

背景:网上也有很多处理方案的描述,但是基本都缺少demo和完整代码,所以只能自己重新写一份用来记录,顺便理解feign内部处理机制

基本原理:自定义参数注解,实现消费端(服务调用端)feign的处理器和生产端(服务提供端)webmvc参数解析

代码

首先是注解 @CustomRequestBody,写法参照@RequestParam

package org.urey.fmp.commom.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.core.annotation.AliasFor;
import org.springframework.web.bind.annotation.ValueConstants;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomRequestBody {

    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";

    boolean required() default true;

    String defaultValue() default ValueConstants.DEFAULT_NONE;

}

直接标注在service的方法参数上

package org.urey.fmp.commom.service;

import java.util.Date;
import java.util.List;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.urey.fmp.commom.annotation.CustomRequestBody;
import org.urey.fmp.commom.entity.EnumType;
import org.urey.fmp.commom.entity.User;

public interface TestService {

    @PostMapping("/findOne")
    public User findOne(@RequestParam Long id);
    
    @PostMapping("/findList")
    public List<User> findList(@CustomRequestBody List<Long> ids);

    @PostMapping("/update1")
    public void update(@CustomRequestBody User user1,@CustomRequestBody User user2);
    
    @PostMapping("/update2")
    public void update(@CustomRequestBody User user,@CustomRequestBody Date date);

    @PostMapping("/update3")
    public void update(@CustomRequestBody List<User> users,@CustomRequestBody Date date,@RequestParam String name);
    
    @PostMapping("/update4/{id}")
    public void update(@CustomRequestBody List<User> users,@CustomRequestBody List<EnumType> enums,@RequestParam String name,@PathVariable Long id);
    
}

基本上都能和spring-web的其他参数注解(@RequestParam,@PathVariable)一起使用,因为spring都有对应的处理器

还有一个ObjectMapper,这里不重要,就不贴代码了

消费端(服务调用端)

使用feign调用远程服务,其他就不赘述,主要是将自己写的处理器加入feign的处理器(feign.Contract)中

package org.urey.fmp.consumer.config;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
import org.springframework.cloud.openfeign.support.SpringMvcContract;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.urey.fmp.consumer.processor.CustomRequestBodyParameterProcessor;

import feign.Contract;

@Configuration
public class FeignSupportConfig {

    @Autowired
    private CustomRequestBodyParameterProcessor customRequestBodyParameterProcessor;

    @Bean
    public Contract feignContract() {
        List<AnnotatedParameterProcessor> annotatedArgumentResolvers = new ArrayList<>();
        annotatedArgumentResolvers.add(customRequestBodyParameterProcessor);
        return new SpringMvcContract(annotatedArgumentResolvers);
    }

}

SpringMvcContract里面已经内置了其他注解的处理器

...
    public SpringMvcContract(
            List<AnnotatedParameterProcessor> annotatedParameterProcessors) {
        this(annotatedParameterProcessors, new DefaultConversionService());
    }
    public SpringMvcContract(
            List<AnnotatedParameterProcessor> annotatedParameterProcessors,
            ConversionService conversionService) {
        Assert.notNull(annotatedParameterProcessors,
                "Parameter processors can not be null.");
        Assert.notNull(conversionService, "ConversionService can not be null.");

        List<AnnotatedParameterProcessor> processors = getDefaultAnnotatedArgumentsProcessors();
        processors.addAll(annotatedParameterProcessors);

        this.annotatedArgumentProcessors = toAnnotatedArgumentProcessorMap(processors);
        this.conversionService = conversionService;
        this.convertingExpanderFactory = new ConvertingExpanderFactory(conversionService);
    }

...
    private List<AnnotatedParameterProcessor> getDefaultAnnotatedArgumentsProcessors() {

        List<AnnotatedParameterProcessor> annotatedArgumentResolvers = new ArrayList<>();

        annotatedArgumentResolvers.add(new MatrixVariableParameterProcessor());
        annotatedArgumentResolvers.add(new PathVariableParameterProcessor());
        annotatedArgumentResolvers.add(new RequestParamParameterProcessor());
        annotatedArgumentResolvers.add(new RequestHeaderParameterProcessor());
        annotatedArgumentResolvers.add(new QueryMapParameterProcessor());
        annotatedArgumentResolvers.add(new RequestPartParameterProcessor());

        return annotatedArgumentResolvers;
    }
...

CustomRequestBodyParameterProcessor处理的是参数的模板化,然后使用自己定义的Expander去序列化参数

package org.urey.fmp.consumer.processor;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.urey.fmp.commom.annotation.CustomRequestBody;

import feign.MethodMetadata;

@Component
public class CustomRequestBodyParameterProcessor implements AnnotatedParameterProcessor {

    protected final Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private CustomJsonExpander customJsonExpander;

    private static final Class<CustomRequestBody> ANNOTATION = CustomRequestBody.class;

    @Override
    public Class<? extends Annotation> getAnnotationType() {
        return ANNOTATION;
    }

    @Override
    public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
        int parameterIndex = context.getParameterIndex();
        Class<?> parameterType = method.getParameterTypes()[parameterIndex];
        MethodMetadata data = context.getMethodMetadata();

        if (Map.class.isAssignableFrom(parameterType)) {
            Assert.state(data.queryMapIndex() == null, "Query map can only be present once.");
            data.queryMapIndex(parameterIndex);

            return true;
        }

        CustomRequestBody requestParam = ANNOTATION.cast(annotation);
        String name = requestParam.value();
        Assert.state(StringUtils.isNotBlank(name), "CustomRequestParam.value() was empty on parameter " + parameterIndex);
        context.setParameterName(name);
        data.formParams().add(name);

        List<String> formParams = data.formParams();
        StringBuffer stringBuffer = new StringBuffer("%7B");
        for (int i = 0; i < formParams.size(); i++) {
            boolean isList = false;

            Set<String> types = getAllInterfaces(method.getParameterTypes()[i]);
            if (types.contains("java.util.Collection")) {
                isList = true;
            }

            String param = formParams.get(i);
            stringBuffer.append("\"" + param + "\"").append(":");
            if (isList) {
                stringBuffer.append("[");
            }
            stringBuffer.append("{").append(param).append("}");
            if (isList) {
                stringBuffer.append("]");
            }
            if (i < formParams.size() - 1) {
                stringBuffer.append(",");
            }
        }
        stringBuffer.append("%7D");

        logger.debug("feign template:{}:{}", data.configKey(), stringBuffer);

        data.template().bodyTemplate(stringBuffer.toString());
        data.template().header("content-type", "application/json");

        data.indexToExpander().put(context.getParameterIndex(), customJsonExpander);
        return true;
    }

    /**
     * 获取该对象的类,类的所有实现接口,类的父类的名称
     * 
     * @param object
     * @return
     */
    private static Set<String> getAllInterfaces(Class<?> clazz) {
        Set<String> types = new HashSet<String>();
        Stack<Class<?>> stack = new Stack<Class<?>>();
        stack.push(clazz);
        while (!stack.empty()) {
            Class<?> c = stack.pop();
            types.add(c.getName());
            Class<?> superClass = c.getSuperclass();
            if (superClass != null) {
                stack.push(superClass);
            }
            Class<?>[] cs = c.getInterfaces();
            for (Class<?> superClazzs : cs) {
                stack.push(superClazzs);
            }
        }
        return types;
    }

}

CustomJsonExpander使用ObjectMapper去序列化对象参数,如果有些特殊格式的参数可以一并处理,比如java.util.Date,直接使用ObjectMapper序列化后得到的是普通字符串而不是json字符串

package org.urey.fmp.consumer.processor;

import java.util.Date;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import feign.Param.Expander;

@Component
public class CustomJsonExpander implements Expander {

    protected final Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public String expand(Object value) {
        try {
            String s = null;
            if (value instanceof Date) {
                s = ((Date) value).getTime() + "";
            } else {
                s = objectMapper.writeValueAsString(value);
            }
            logger.debug("feign expander:{}:{}", value.toString(), s);
            return s;
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return "";
        }
    }

}

最后随便写一个Controller来调用feign

package org.urey.fmp.consumer.controller;

import java.util.Arrays;
import java.util.Date;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.urey.fmp.commom.entity.EnumType;
import org.urey.fmp.commom.entity.User;
import org.urey.fmp.consumer.service.TestServiceFeign;

@RestController
public class TestController {

    protected final Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private TestServiceFeign testServiceFeign;

    @RequestMapping(value = "/test")
    public String test() throws Exception {
        User user1 = testServiceFeign.findOne(1L);
        logger.info("findOne user1:{}", user1);

        List<User> users = testServiceFeign.findList(Arrays.asList(1L, 2L));
        logger.info("findList users:{}", users);

        testServiceFeign.update(users.get(0), users.get(1));
        testServiceFeign.update(user1, new Date());
        testServiceFeign.update(users, new Date(), "urey");
        testServiceFeign.update(users, Arrays.asList(EnumType.E1, EnumType.E2, EnumType.E3), "urey", 1L);

        return "success";
    }

}

feign的使用只需要一个注解就行了@FeignClient

package org.urey.fmp.consumer.service;

import org.springframework.cloud.openfeign.FeignClient;
import org.urey.fmp.commom.service.TestService;

@FeignClient(value = "producer")
public interface TestServiceFeign extends TestService{

}

生产端(服务提供端)

生产端本身和普通的webmvc一样,也是POST调用,不过因为多对象参数的关系,body只会去读取一次,所以我们需要把body存到httpRequest中,才能多次使用参数解析器去读取参数,这里去继承RequestResponseBodyMethodProcessor然后重写读取body的代码

首先是WebMvcConfigurer配置

package org.urey.fmp.producer.config;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.urey.fmp.producer.processor.CustomMappingJackson2HttpMessageConverter;
import org.urey.fmp.producer.processor.CustomMessageConverterMethodArgumentResolver;

import com.fasterxml.jackson.databind.ObjectMapper;

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
    
    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
        converters.add(new CustomMappingJackson2HttpMessageConverter(objectMapper));
        resolvers.add(new CustomMessageConverterMethodArgumentResolver(converters));
    }

}
package org.urey.fmp.producer.processor;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.List;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;
import org.urey.fmp.commom.annotation.CustomRequestBody;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

public class CustomMessageConverterMethodArgumentResolver extends RequestResponseBodyMethodProcessor {

    private final static String CUSTOM_REQUEST_BODY_KEY = "CUSTOM_REQUEST_BODY_KEY";

    protected final Logger logger = LoggerFactory.getLogger(getClass());

    protected final static Charset CHARSET = Charset.forName("UTF-8");

    public CustomMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters) {
        super(converters);
    }

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(CustomRequestBody.class);
    }

    @Override
    protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
        CustomRequestBody parameterAnnotation = parameter.getParameterAnnotation(CustomRequestBody.class);

        String value = parameterAnnotation.value();
        if (StringUtils.isBlank(value)) {
            value = parameter.getExecutable().getParameters()[parameter.getParameterIndex()].getName();
        }

        ServletServerHttpRequest httpRequest = (ServletServerHttpRequest) inputMessage;

        String body = (String) httpRequest.getServletRequest().getAttribute(CUSTOM_REQUEST_BODY_KEY);
        if (body == null) {
            InputStream inputStream = inputMessage.getBody();
            body = IOUtils.toString(inputStream, Charset.forName("UTF-8"));
            body = URLDecoder.decode(body, CHARSET);
            httpRequest.getServletRequest().setAttribute(CUSTOM_REQUEST_BODY_KEY, body);
            logger.info("custom message read body:{}", body);
        }

        if (StringUtils.isBlank(body)) {
            return super.readWithMessageConverters(inputMessage, parameter, targetType);
        }

        JSONObject json = JSON.parseObject(body);
        String string = json.getString(value);

        logger.info("custom message converter json {}, key: {},value: {}", json, value, string);

        CustomHttpServletRequestWrapper customHttpServletRequestWrapper = new CustomHttpServletRequestWrapper(httpRequest.getServletRequest(), string);
        ServletServerHttpRequest serverHttpRequest = new ServletServerHttpRequest(customHttpServletRequestWrapper);

        return super.readWithMessageConverters(serverHttpRequest, parameter, targetType);
    }

}

继承RequestResponseBodyMethodProcessor需要重写包装流的类,也是去继承HttpServletRequestWrapper

package org.urey.fmp.producer.processor;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper {

    private byte[] body;

    private Map<String, String[]> parameterMap;

    public CustomHttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
        parameterMap = new HashMap<String, String[]>(request.getParameterMap());
        String sessionStream = getBodyString(request);
        body = sessionStream.getBytes(Charset.forName("UTF-8"));
    }

    public CustomHttpServletRequestWrapper(HttpServletRequest request, String body) {
        super(request);
        this.body = body.getBytes(Charset.forName("UTF-8"));
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream bais = new ByteArrayInputStream(body);
        return new ServletInputStream() {
            @Override
            public int read() {
                return bais.read();
            }

            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {
            }
        };
    }

    @Override
    public void setAttribute(String name, Object o) {
        super.setAttribute(name, o);
    }

    @Override
    public Enumeration<String> getParameterNames() {
        return Collections.enumeration(parameterMap.keySet());
    }

    @Override
    public String getParameter(String name) {
        String[] values = parameterMap.get(name);
        if (values != null) {
            if (values.length == 0) {
                return "";
            }
            return values[0];
        } else {
            return null;
        }
    }

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

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

    private String getBodyString(final ServletRequest request) {
        StringBuilder sb = new StringBuilder();
        try (//
                InputStream inputStream = cloneInputStream(request.getInputStream()); //
                BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")))//
        ) {
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return sb.toString();
    }

    private InputStream cloneInputStream(ServletInputStream inputStream) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len;
        try {
            while ((len = inputStream.read(buffer)) > -1) {
                byteArrayOutputStream.write(buffer, 0, len);
            }
            byteArrayOutputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
    }

}

最后,为了处理特殊的类(类似java.util.Date),MappingJackson2HttpMessageConverter的相关处理也要重写一下

package org.urey.fmp.producer.processor;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Date;

import org.apache.commons.io.IOUtils;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

import com.fasterxml.jackson.databind.ObjectMapper;

public class CustomMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
    
    public CustomMappingJackson2HttpMessageConverter() {
        super();
    }
    
    public CustomMappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
        super(objectMapper);
    }

    @Override
    public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        if ("java.util.Date".equals(type.getTypeName())) {
            return new Date(Long.valueOf(IOUtils.toString(inputMessage.getBody(), "UTF-8")));
        }
        return super.read(type, contextClass, inputMessage);
    }

}

最后,去实现TestService中的方法,让消费端去调用就行了

package org.urey.fmp.producer.service;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RestController;
import org.urey.fmp.commom.entity.EnumType;
import org.urey.fmp.commom.entity.User;
import org.urey.fmp.commom.service.TestService;

@RestController
public class TestServiceImpl implements TestService {

    protected final Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public User findOne(Long id) {
        logger.info("findOne id:{}", id);
        User user = new User(id, "用户" + id, new Date());
        return user;
    }

    @Override
    public List<User> findList(List<Long> ids) {
        logger.info("findList ids:{}", ids);
        List<User> users = new ArrayList<User>();
        for (Long id : ids) {
            User user = new User(id, "用户" + id, new Date());
            users.add(user);
        }
        return users;
    }

    @Override
    public void update(User user1, User user2) {
        logger.info("update1 user1:{} ,user2:{}", user1, user2);
    }

    @Override
    public void update(User user, Date date) {
        logger.info("update2 user:{} ,date:{}", user, date);
    }

    @Override
    public void update(List<User> users, Date date, String name) {
        logger.info("update3 users:{} ,date:{} ,name:{}", users, date, name);
    }

    @Override
    public void update(List<User> users, List<EnumType> enums, String name, Long id) {
        logger.info("update4 users:{} ,enums:{} ,name:{} ,id: {}", users, enums, name, id);
    }

}

测试

消费端启动时,feign的模板就会加载,日志中输出为

feign template:TestServiceFeign#update(List,Date,String):%7B"users":[{users}]%7D
feign template:TestServiceFeign#update(List,Date,String):%7B"users":[{users}],"date":{date}%7D
feign template:TestServiceFeign#update(List,List,String,Long):%7B"users":[{users}]%7D
feign template:TestServiceFeign#update(List,List,String,Long):%7B"users":[{users}],"enums":[{enums}]%7D
feign template:TestServiceFeign#update(User,User):%7B"user1":{user1}%7D
feign template:TestServiceFeign#update(User,User):%7B"user1":{user1},"user2":{user2}%7D
feign template:TestServiceFeign#update(User,Date):%7B"user":{user}%7D
feign template:TestServiceFeign#update(User,Date):%7B"user":{user},"date":{date}%7D
feign template:TestServiceFeign#findList(List):%7B"ids":[{ids}]%7D

可以看到1个方法中有多个自定义注解会多次调用

在浏览器打开http://127.0.0.1:8100/test
在生产端可以看到接收到的参数日志

findOne id:1
findList ids:[1, 2]
update1 user1:[id:1,name:用户1,date:Tue Jul 06 14:56:56 CST 2021] ,user2:[id:2,name:用户2,date:Tue Jul 06 14:56:56 CST 2021]
update2 user:[id:1,name:用户1,date:Tue Jul 06 14:56:55 CST 2021] ,date:Tue Jul 06 14:56:56 CST 2021
update3 users:[[id:1,name:用户1,date:Tue Jul 06 14:56:56 CST 2021], [id:2,name:用户2,date:Tue Jul 06 14:56:56 CST 2021]] ,date:Tue Jul 06 14:56:56 CST 2021 ,name:urey
update4 users:[[id:1,name:用户1,date:Tue Jul 06 14:56:56 CST 2021], [id:2,name:用户2,date:Tue Jul 06 14:56:56 CST 2021]] ,enums:[E1, E2, E3] ,name:urey ,id: 1

对象参数都能正确读取出来了,其中比较特别的是在对象中的java.util.Date,ObjectMapper需要添加DateFormat

package org.urey.fmp.commom.config;

import java.text.SimpleDateFormat;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.ObjectMapper;

@Configuration
public class JacksonConfig {

    @Bean
    @Primary
    @ConditionalOnMissingBean(ObjectMapper.class)
    public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
        objectMapper.setSerializationInclusion(Include.NON_NULL);
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"));
        return objectMapper;
    }
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,607评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,239评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,960评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,750评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,764评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,604评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,347评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,253评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,702评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,893评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,015评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,734评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,352评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,934评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,052评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,216评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,969评论 2 355

推荐阅读更多精彩内容