源码地址: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;
}
}