A preface.

Hello, everyone.

I believe you must be more or less exposed to date parameter processing in your daily work or study process. Usually springBoot will receive dates and use **@DateTimeFormat or @jsonFormat for serialization and deserialization. Some developments also use the VO layer to receive dates using string, then use a serialization strategy to convert to LocalDateTime**. A few lines of code can be done with a few fields, but if you submit large forms with a lot of logging parameters that need to be received from the front end, it can be troublesome.

There is another parameter case, get request, array type parameters passed by the front end, the back end is used to using List parameters, but have to go through a List. You look stupid.

This paper will put forward corresponding solutions for the above two daily work code optimization points, and attach the source code. If you feel it helps you, please give a thumbs up

Two. Time formatting

2.1. Problem description

Suppose you have a request for a new user and need to fill in the date of birth of the user. The value transmitted from the front end is 1999-01-01, and the data is received and processed by the back end using LocalDate. As mentioned in the introduction, there are two options, one is to receive directly with the string field, and the other is to receive with the ** @dateTimeFormat or @jsonFormat ** annotation.

The second requirement is to query user details. The birthday field stored in the backend is LocalDate, which needs to be serialized into string format to the front end. There are also two options, the first is to add a VO layer to handle the date field, and the second is to mark the field with ** @jsonFormat ** for serialization.

At first glance, there’s nothing wrong with this solution, and I’m sure many students do this in their daily development process. But if you think about it, what’s the problem? For the first single string, I need to create a VO layer to convert all fields related to time and date. The second way is to add annotations. Now there are too many fields related to time and date. If there are more than 10 fields related to a form, the annotations marked on your code will be piled up, and there are too many. Not to mention there are validation type annotations like ** @notempty **. Entity classes look bloated and unelegant.

2.2. Solution

What makes Spring such a landmark framework is its leading thinking on AOP and IOC. The independent objects are managed and operated in a unified manner. So our core requirement is to serialize and deserialize data in time and date format uniformly.

As an introduction, Spring uses the Jackson serialization framework by default. If we need to bind field values, can we change the default serialization strategy?

Of course ** @jsonFormat ** is itself parsed by Jackson serialization. This is not the focus of this article, mention a mouth, do not do analysis.

Code 2.3.

@Configuration public class CommonJacksonConfig { public static final String timeFormat = "HH:mm:ss"; public static final String dateFormat = "yyyy-MM-dd"; public static final String dateTimeFormat = "yyyy-MM-dd HH:mm:ss"; / * * * global time format * / @ Bean public Jackson2ObjectMapperBuilderCustomizer customizer () {return builder - > { builder.simpleDateFormat(dateTimeFormat); / / date serialization builder. Serializers (new LocalTimeSerializer (DateTimeFormatter. OfPattern (timeFormat))); builder.serializers(new LocalDateSerializer(DateTimeFormatter.ofPattern(dateFormat))); builder.serializers(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(dateTimeFormat))); / / date deserialization builder. Deserializers (new LocalTimeDeserializer (DateTimeFormatter. OfPattern (timeFormat))); builder.deserializers(new LocalDateDeserializer(DateTimeFormatter.ofPattern(dateFormat))); builder.deserializers(new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(dateTimeFormat))); }; }}Copy the code

Simply add an initialization strategy for Jackson serialization to your current project.

Array parameters are parsed to List

3.1. Problem description

For example, if I want to perform a user query that supports multiple users, the front end can make a request to the back end for userNames. For Get requests, parameters are placed in the request link, and arrays can only be passed by the front end and used by the back end. However, when dealing with field resolution in daily life, the List type is used more often. Another layer of transformation is required before the corresponding parameters are passed to the ORM framework for query. Very inconvenient.

3.2. Solutions

Consistent with the unified processing of time and date formatting. The array parameters passed from the front end I parse into a List to receive. However, no array can be converted to a List, which would affect the history code logic.

Spring already provides an interface for parameter parsing and binding.

Argument parsing class: ServletModelAttributeMethodProcessor

Look up step by step a class interface relations, found that implements HandlerMethodArgumentResolver, the realization of the structure

Are the parameters of the spring supported by default, PathVariableMethodArgumentResolver class, for example, will parse @ PathVariable * * * * notes describe fields.

Parameter binding classes: ExtendedServletRequestDataBinder

Code 3.3.

3.3.1. Custom annotations

Classes, attributes, and method parameters marked by this annotation lock will be resolved to a list if the arguments passed by the front end are arrays

/** * List ** @author baiyan * @date 2021/07/13 */ @target ({elementtype. FIELD, elementtype. TYPE, PARAMETER}) @Retention(RetentionPolicy.runtime) @documented Public @interface RequestParamName {/** * * * @return */ String value() default ""; }Copy the code

3.3.2. Custom parameter binding resolver

/** * @author baiyan * @date 2021/07/13 */ public class RenamingDataBinder extends ExtendedServletRequestDataBinder { private static final Map<Class, Map<String, String>> renamingMapCache = Maps.newHashMap(); public RenamingDataBinder(Object target) { super(target); parsingTarget(target); } public RenamingDataBinder(Object target, String objectName) { super(target, objectName); parsingTarget(target); } public RenamingDataBinder(DataBinder dataBinder) { super(dataBinder.getTarget(), dataBinder.getObjectName()); parsingTarget(dataBinder.getTarget()); } private void parsingTarget(Object target) { if (target == null || renamingMapCache.containsKey(target.getClass())) { return; } Field[] fields = target.getClass().getDeclaredFields(); Map<String, String> renamingMap = new HashMap<>(); for (Field field : fields) { RequestParamName requestParamName = field.getAnnotation(RequestParamName.class); Optional.ofNullable(requestParamName) .filter(rp -> StrUtil.isNotBlank(rp.value())).ifPresent(rp -> { renamingMap.put(rp.value(), field.getName()); }); } renamingMapCache.put(target.getClass(), renamingMap); } Override protected void doBind(MutablePropertyValues MPVS) {// rename renaming(MPVS); [] changeArrayParams(MPVS); super.doBind(mpvs); } private void changeArrayParams(MutablePropertyValues mpvs) { PropertyValue[] pvArray = mpvs.getPropertyValues(); for (PropertyValue pv : pvArray) { String fieldName = pv.getName(); if (! fieldName.endsWith("[]")) { continue; } String newFieldName = fieldName.substring(0, fieldName.length() - 2); if (getPropertyAccessor().isWritableProperty(newFieldName) && ! mpvs.contains(newFieldName)) { mpvs.add(newFieldName, pv.getValue()); } mpvs.removePropertyValue(pv); } } protected void renaming(MutablePropertyValues mpvs) { Map<String, String> renamingMap = Optional.ofNullable(this.getTarget()) .map(Object::getClass) .map(renamingMapCache::get) .orElse(Maps.newHashMap()); PropertyValue[] pvArray = mpvs.getPropertyValues(); for (PropertyValue pv : pvArray) { if (renamingMap.containsKey(pv.getName())) { String fieldName = renamingMap.get(pv.getName()); if (getPropertyAccessor().isWritableProperty(fieldName) && ! mpvs.contains(fieldName)) { mpvs.add(fieldName, pv.getValue()); } mpvs.removePropertyValue(pv); }}}}Copy the code

3.3.3. Custom parameter resolution

/** * @author baiyan * @date 2021/07/13 */ public class RenamingProcessor extends ServletModelAttributeMethodProcessor {  @Autowired private RequestMappingHandlerAdapter requestMappingHandlerAdapter; public RenamingProcessor(boolean annotationNotRequired) { super(annotationNotRequired); } @Override public boolean supportsParameter(MethodParameter parameter) { return super.supportsParameter(parameter) && (parameter.getParameterType().isAnnotationPresent(RequestParamName.class) || Lists.newArrayList(parameter.getParameterType().getDeclaredFields()).stream().anyMatch(field -> field.isAnnotationPresent(RequestParamName.class))); } @Override protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) { RenamingDataBinder renamingDataBinder = new RenamingDataBinder(binder); WebBindingInitializer webBindingInitializer = requestMappingHandlerAdapter.getWebBindingInitializer(); Assert.state(webBindingInitializer ! = null, "WebBindingInitializer is null"); webBindingInitializer.initBinder(renamingDataBinder); super.bindRequestParameters(renamingDataBinder, request); }}Copy the code

3.3.4. Webmvc configuration

/** * @author baiyan * @date 2021/07/13 */ @Configuration public class WebConfiguration implements WebMvcConfigurer { @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.add(renamingProcessor()); } @Bean public RenamingProcessor renamingProcessor() { return new RenamingProcessor(true); }}Copy the code

3.4. Effect display

Postman initiates the request

Define a query class

@data @requestParamName public class UserQuery {/** * user id */ / @requestParamName ("userIds[]") private List<Long> userIds; /** * private String keyword; }Copy the code

The effect

4. To summarize

This paper gives two solutions and ideas for unified parameter processing. Similar solutions with customized requirements for parameter processing can also be processed by referring to the above solutions to simplify the code.

Five. Contact me

If there is an incorrect place in the article, welcome to correct, writing the article is not easy, point a thumbs-up, yao yao da ~

Nailing: louyanfeng25

WeChat: baiyan_lou