SpringMVC @RequestMapping

参考

  • 官方文档

关于DispatcherServlet的url-pattern问题

待解决问题:
如果设定为/, 即代替容器的default Servelt. 解析规则会变得很奇怪, 深层原因有待考究.
先贴上Tomcat的默认的default Servlet配置:
apache-tomcat-8.0.32/conf/web.xml


    <servlet>
        <servlet-name>default</servlet-name>
        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>0</param-value>
        </init-param>
        <init-param>
            <param-name>listings</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- The mapping for the default servlet -->
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

@RequestMapping

可以注解在class上, 也可以注解在method上.
如果不指定@RequestMapping(method = HTTP.METHOD), 那么默认是支持所有方法.

示例:

@Controller
@RequestMapping("/echo")
public class EchoController {

    @RequestMapping(path = "/{name}", method = RequestMethod.GET)
    @ResponseBody
    public String get(@PathVariable String name){
        return "get method: " + name;
    }

    @RequestMapping(method = RequestMethod.POST)
    @ResponseBody
    public String post(){
        return "post method";
    }
}

method字段:

/**
* The HTTP request methods to map to, narrowing the primary mapping:
* GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.
* <p><b>Supported at the type level as well as at the method level!</b>
* When used at the type level, all method-level mappings inherit
* this HTTP method restriction (i.e. the type-level restriction
* gets checked before the handler method is even resolved).
* <p>Supported for Servlet environments as well as Portlet 2.0 environments.
*/

查看RequestMapping的源码, 可以发现pathvalueServlet环境下都是对方的别名, 所以是等价的.

/**
     * The primary mapping expressed by this annotation.
     * <p>In a Servlet environment this is an alias for {@link #path}.
     * For example {@code @RequestMapping("/foo")} is equivalent to
     * {@code @RequestMapping(path="/foo")}.
     * <p>In a Portlet environment this is the mapped portlet modes
     * (i.e. "EDIT", "VIEW", "HELP" or any custom modes).
     * <p><b>Supported at the type level as well as at the method level!</b>
     * When used at the type level, all method-level mappings inherit
     * this primary mapping, narrowing it for a specific handler method.
     */
    @AliasFor("path")
    String[] value() default {};

    /**
     * In a Servlet environment only: the path mapping URIs (e.g. "/myPath.do").
     * Ant-style path patterns are also supported (e.g. "/myPath/*.do").
     * At the method level, relative paths (e.g. "edit.do") are supported within
     * the primary mapping expressed at the type level. Path mapping URIs may
     * contain placeholders (e.g. "/${connect}")
     * <p><b>Supported at the type level as well as at the method level!</b>
     * When used at the type level, all method-level mappings inherit
     * this primary mapping, narrowing it for a specific handler method.
     * @see org.springframework.web.bind.annotation.ValueConstants#DEFAULT_NONE
     * @since 4.2
     */
    @AliasFor("value")
    String[] path() default {};

@PathVariable

    @RequestMapping(path = "/{name}", method = RequestMethod.GET)
    @ResponseBody
    public String get(@PathVariable String name){
        return "get method: " + name;
    }

可以绑定一个URI template variable到方法中的某个参数中.其中URI里面的变量用{}表示.
@PathVariable绑定变量的两种方式:

  1. 如果被注解的参数名和{name}一致, 那么@PathVariable可以不用指定URI中的变量名
  2. 如果不同可以使用@PathVariable("name")指定
    例如:
@RequestMapping(path="/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable("ownerId") String theOwner, Model model) {
// implementation omitted
}

方法中可以有多个@PathVariable注解,

@RequestMapping(path="/owners/{ownerId}/pets/{petId}", method=RequestMethod.GET)
public String findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
  Owner owner = ownerService.findOwner(ownerId);
  Pet pet = owner.getPet(petId);
  model.addAttribute("pet", pet);
  return "displayPet";
}

当注解到Map<String, String>类型的参数上时: map会被填充全部变量.

@PathVariable可以从typepath level的@RequestMapping注解中取得URI template:

@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {
    @RequestMapping("/pets/{petId}")
    public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
    // implementation omitted
    }
}

@PathVariable参数可以是任何简单类型, 比如int, long, Date等, Spring负责转换. 如果无法转换则会报错, 如果需要更复杂的转换规则, 可以参考“Method Parameters And Type Conversion” 以及 “Customizing WebDataBinder initialization”.

URI template对正则表达式的支持.
比如说要匹配/spring-web/spring-web-3.0.5.jar. 那么可以使用下面这种规则书写正则表达式:
{变量名: 正则表达式}

@RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{extension:\\.[a-z]+}")
public void handle(@PathVariable String version, @PathVariable String extension) {
  // ...
}

Path Patterns

Ant-style path patterns:

  • /myPath/*.do
  • /owners/*/pets/{petId}

当请求的URL匹配多个模式的时候的路径选择:

  • A pattern with a lower count of URI variables and wild cards is considered more specific.

    /hotels/{hotel}/* has 1 URI variable and 1 wild card and is considered more specific than /
    hotels/{hotel}/** which as 1 URI variable and 2 wild cards.

  • If two patterns have the same count, the one that is longer is considered more specific.
    For example /
    foo/bar* is longer and considered more specific than /foo/*.

  • When two patterns have the same count and length, the pattern with fewer wild cards is considered more specific.
    For example /hotels/{hotel} is more specific than /hotels/*.

There are also some additional special rules:

  • The default mapping pattern /** is less specific than any other pattern.
    For example /api/{a}/{b}/{c} is more specific.
  • A prefix pattern such as /public/** is less specific than any other pattern that doesn’t contain double wildcards.
    For example /public/path3/{a}/{b}/{c} is more specific.

Path Patterns with Placeholders

Patterns in @RequestMapping annotations support ${...} placeholders against local properties and/or system properties and environment variables. This may be useful in cases where the path a controller is mapped to may need to be customized through configuration. For more information on placeholders, see the javadocs of the PropertyPlaceholderConfigurer class.

@RequestParam

从Query参数中获取各个参数.
@RequestParam("") 默认表示这个参数是required的.可以通过设置这个字段来设定是否是必须的: @RequestParam(name="id", required=false).

注意: 如果设定某个参数required=false, 那么参数类型请使用引用类型, 因为如果请求中没有包含这个参数, 会传递null过去, 原始类型不能被赋值为null会报错.

@RequestParam默认参数的设置方法:

// 因为defaultValue只能是String, 直接传false是不行的, 只能弄成字符串
// 如果是数字类型也是一样, 需要用引号引起来
@RequestParam(value = "echo", required = false, defaultValue = "false") Boolean echo

简单例子

    // 注意echo的类型用了Boolean
    // 因为如果RequestParam不是required的话, 那么query中没这个参数的时候
    // 会传递null
    @RequestMapping(path = "/query")
    @ResponseBody
    public String echoQuery(@RequestParam("purpose") String purpose, @RequestParam(value = "echo", required = false) Boolean echo){
        String message;
        if (echo != null && echo)
            message = purpose;
        else
            message = "set to not echo.";
        return message;
    }

测试:

curl "http://localhost:8080/app/echo/query?purpose=test&echo=false" -i
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/plain;charset=ISO-8859-1
Content-Length: 16
Date: Sun, 12 Nov 2017 11:35:21 GMT

set to not echo.
-----------------
smallfly@Ubuntu:~$ curl "http://localhost:8080/app/echo/query?purpose=test&echo=true" -i
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/plain;charset=ISO-8859-1
Content-Length: 4
Date: Sun, 12 Nov 2017 11:36:04 GMT

test
-----------------
curl "http://localhost:8080/app/echo/query" -iHTTP/1.1 400 Bad Request
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=utf-8
Content-Language: en
Content-Length: 1111
Date: Sun, 12 Nov 2017 11:36:34 GMT
Connection: close

数组参数的例子

    @RequestMapping(path = "/array")
    @ResponseBody
    public String echoArray(@RequestParam("values") String[] values){
        return Arrays.toString(values);
    }

测试:

curl "http://localhost:8080/app/echo/array?values=tomorrow&values=is&values=Monday" -i
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/plain;charset=ISO-8859-1
Content-Length: 22
Date: Sun, 12 Nov 2017 11:42:48 GMT

[tomorrow, is, Monday]

@RequestParam 用在Map参数上

When an @RequestParam annotation is used on a Map<String, String> or MultiValueMap<String, String> argument, the map is populated with all request parameters.

Java代码:

    @RequestMapping(path = "/map", method = RequestMethod.GET)
    @ResponseBody
    public String echoMap(@RequestParam Map<String, String> map){
        Set<Map.Entry<String, String>> set =  map.entrySet();
        Iterator<Map.Entry<String, String>> iterator = set.iterator();
        StringBuilder stringBuilder = new StringBuilder();
        while (iterator.hasNext()){
            Map.Entry<String, String> entry = iterator.next();
            stringBuilder.append(String.format("%s: %s\n", entry.getKey(), entry.getValue()));
        }
        return stringBuilder.toString();
    }

测试:

curl "http://localhost:8080/app/echo/map?name=xiaofu&age=22" -i
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/plain;charset=ISO-8859-1
Content-Length: 21
Date: Sun, 12 Nov 2017 11:54:20 GMT

name: xiaofu
age: 22

@RequestParam 用在MultiValueMap参数上

测试:
Java:

    @RequestMapping(path = "/multiValue", method = RequestMethod.GET)
    @ResponseBody
    public String echoMap(@RequestParam MultiValueMap<String, String> map){
        Set<Map.Entry<String, List<String>>> set =  map.entrySet();
        Iterator<Map.Entry<String, List<String>>> iterator = set.iterator();
        StringBuilder stringBuilder = new StringBuilder();
        while (iterator.hasNext()){
            Map.Entry<String, List<String>> entry = iterator.next();
            stringBuilder.append(String.format("%s: %s\n", entry.getKey(), entry.getValue()));
        }
        return stringBuilder.toString();
    }

注意URL匹配是大小写有关的

curl "http://localhost:8080/app/echo/multiValue?name=xiaofu&age=22" -i
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/plain;charset=ISO-8859-1
Content-Length: 25
Date: Sun, 12 Nov 2017 11:57:15 GMT

name: [xiaofu]
age: [22]

处理form参数

结合javax.validationhibernate-validator实现bean的验证.
首先在pom.xml中添加依赖: 注意两个依赖的版本是互相有关的, 否则可能会出现问题.
依赖:

        <!--需要注意javax.validation和Hibernate Validator的版本匹配问题-->
        <!--https://stackoverflow.com/questions/34966160/hibernate-validations-->

        <!-- https://mvnrepository.com/artifact/javax.validation/validation-api -->
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>2.0.0.Final</version>
        </dependency>

        <!-- Hibernate Validator -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.0.4.Final</version>
        </dependency>

定义Bean:

package me.xiaofud.spring101.spittr.domainobject;


import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

/**
 * Created by xiaofu on 17-11-12.
 */
public class Post {

    // 设置max为10, 方便调试
    @NotNull
    @NotEmpty
    @Size(min = 1, max = 10)
    private String title;

    @NotNull
    @NotEmpty
    @Size(min = 1, max = 140)
    private String content;


    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

}

Controller:
addPost()方法的参数都会被注入. 其中Errors对象可以得知验证bean过程中是否出现了错误, 以及具体的错误原因.

@Controller
public class PostController {

    @RequestMapping(value = "/post/add", method = RequestMethod.POST)
    @ResponseBody
    public String addPost(@Valid Post post, Errors errors){
        if (errors.hasErrors()){
            return errors.getAllErrors().toString();
        }
        return "saved:" + post.getTitle();
    }

}

测试:

curl -i -X POST "http://localhost:8080/app/post/add" --data "title=123456789" --data "content=Hello!"
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/plain;charset=ISO-8859-1
Content-Length: 15
Date: Sun, 12 Nov 2017 13:42:42 GMT

saved:123456789

# 不合法参数
curl -i -X POST "http://localhost:8080/app/post/add" --data "title=12345678901" --data "content=Hello"
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/plain;charset=ISO-8859-1
Content-Length: 339
Date: Sun, 12 Nov 2017 13:45:39 GMT

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

推荐阅读更多精彩内容