跳至主要内容

A Request Sent to Spring(2): Example

In the last blog, we have learned the overview process how a Java server with Spring handle request.

But in our daily usage, we are sometimes very confused about how to pass argument in, i.e. the conversion rule between the request string and our method parameters.

So, today, we focus on common problem we met when use Spring MVC.

Request Parameter

@RequestParam is very common in our controller, which means this method parameter should interpreted from URL.

From @RequestParam doc:

If the method parameter type is Map and a request parameter name is specified, then the request parameter value is converted to a Map assuming an appropriate conversion strategy is available.
If the method parameter is Map<String, String> or MultiValueMap<String, String> and a parameter name is not specified, then the map parameter is populated with all request parameter names and values.

@GetMapping("/foo")
public String test(@ReqestParam Map<String, String> m) {
}

Boolean

Saying we have following example, SpringMVC can correctly interprets ?bar=true, ?bar=1, or ?bar=yes as being true, and ?bar=false, ?bar=0, or ?bar=no as being false.

@RequestMapping(value = "/foo/{id}", method = RequestMethod.GET)
@ResponseBody
public Foo getFoo(
    @PathVariable("id") String id, 
    @RequestParam(value="bar", required = false, defaultValue = "true")
        boolean bar)
{ 
    ... 
}

Furthermore, True/false and yes/no values ignore case.

Array

If we need an array from request parameter, we can do like following:

url?param=value1&param=value2&param=value3

@RequestMapping(value="/schedule", method = RequestMethod.POST)
public void action(@RequestParam("param") String[] param)

Complex Object

Although it is rare, we can send an object in URL, this question show the answer:

foo?page=1&prop1=x&prop2=y&prop3=z

public @ResponseBody List<MyObject> myAction(
    @RequestParam(value = "page", required = false) int page,
    MyObject myObject)

Notice that the complex object should not be annotated, or spring can’t transform it. By the way, sending object in this may not so useful.

Object With Customized Name

In more complex cases, we can customize the name of resulted object from this answer:

search?my-val-1=foo&my-val-2=bar

public class RequestParams {

    @JsonProperty(value = "my-val-1")
    private String myVal1;
}

Request Body

@RequestBodyannotated parameter is expected to hold the entire body of the request and bind to one object.

From doc:

The body of the request is passed through an HttpMessageConverter to resolve the method argument depending on the content type of the request. Optionally, automatic validation can be applied by annotating the argument with @Valid.

The HttpMessageConverter for request body has many sub-class which implement this interfaces and we focus only on json converter because we only interact with the web browser which use js.
In the MappingJackson2HttpMessageConverter, spring will use Jackson’s ObjectMapper to finish the job of mapping from json to java class.

If you want to customize the process of deserialize json, you need to provide your own class:

@RequestMapping(...)
public Object createRole(@RequestBody Role role) {
}

@JsonDeserialize(using = RoleDeserializer.class)
public class Role {
    // ......
}

public class RoleDeserializer extends JsonDeserializer<Role> {
    @Override
    public Role deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        // .................
        return something;
    }
}

Mis

400 Bad Request Debug

Even though we can find the conversion process by reading source code as the last blog do, this process is so much time consuming and isn’t the best option.

In order to debug the situation when 400 bad request happens, we have two ways to tell which method and parameter conversion goes wrong:

  • use log level TRACE
  • use controller advice with exception handler
@ControllerAdvice
public class ControllerConfig {
    private static final Logger logger = LoggerFactory.getLogger(ControllerConfig.class);

    // exception handler class type to match
    @ExceptionHandler(TypeMismatchException.class)
    public void handle(Exception e) { // the parameter class is very essential or spring will not match this handler
        logger.warn("Returning HTTP 400 Bad Request", e);
    }
}

Valid Character in URL

http://localhost:8080/testRequest?a={one:1,two:2}&b=asdf

The parse of request parameter is done by container, in this case is embedded tomcat, which follows the RFC 3986 and RFC 7230.

java.lang.IllegalArgumentException: Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986
    at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:472) ~[tomcat-embed-core-8.5.14.jar:8.5.14]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1455) [tomcat-embed-core-8.5.14.jar:8.5.14]
    ...

Ref

Written with StackEdit.

评论

此博客中的热门博文

Spring Boot: Customize Environment

Spring Boot: Customize Environment Environment variable is a very commonly used feature in daily programming: used in init script used in startup configuration used by logging etc In Spring Boot, all environment variables are a part of properties in Spring context and managed by Environment abstraction. Because Spring Boot can handle the parse of configuration files, when we want to implement a project which uses yml file as a separate config file, we choose the Spring Boot. The following is the problems we met when we implementing the parse of yml file and it is recorded for future reader. Bind to Class Property values can be injected directly into your beans using the @Value annotation, accessed via Spring’s Environment abstraction or bound to structured objects via @ConfigurationProperties. As the document says, there exists three ways to access properties in *.properties or *.yml : @Value : access single value Environment : can access multi

Elasticsearch: Join and SubQuery

Elasticsearch: Join and SubQuery Tony was bothered by the recent change of search engine requirement: they want the functionality of SQL-like join in Elasticsearch! “They are crazy! How can they think like that. Didn’t they understand that Elasticsearch is kind-of NoSQL 1 in which every index should be independent and self-contained? In this way, every index can work independently and scale as they like without considering other indexes, so the performance can boost. Following this design principle, Elasticsearch has little related supports.” Tony thought, after listening their requirements. Leader notice tony’s unwillingness and said, “Maybe it is hard to do, but the requirement is reasonable. We need to search person by his friends, didn’t we? What’s more, the harder to implement, the more you can learn from it, right?” Tony thought leader’s word does make sense so he set out to do the related implementations Application-Side Join “The first implementation

Implement isdigit

It is seems very easy to implement c library function isdigit , but for a library code, performance is very important. So we will try to implement it and make it faster. Function So, first we make it right. int isdigit ( char c) { return c >= '0' && c <= '9' ; } Improvements One – Macro When it comes to performance for c code, macro can always be tried. #define isdigit (c) c >= '0' && c <= '9' Two – Table Upper version use two comparison and one logical operation, but we can do better with more space: # define isdigit(c) table[c] This works and faster, but somewhat wasteful. We need only one bit to represent true or false, but we use a int. So what to do? There are many similar functions like isalpha(), isupper ... in c header file, so we can combine them into one int and get result by table[c]&SOME_BIT , which is what source do. Source code of ctype.h : # define _ISbit(bit) (1 << (