参考
- 官方文档
关于DispatcherServlet的url-pattern问题
待解决问题:
如果设定为/
, 即代替容器的default
Servelt. 解析规则会变得很奇怪, 深层原因有待考究.
先贴上Tomcat的默认的default
Servlet配置:
apache-tomcat-8.0.32/conf/web.xml
default
org.apache.catalina.servlets.DefaultServlet
debug
0
listings
false
1
default
/
@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.
*Supported at the type level as well as at the method level!
* 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).
*Supported for Servlet environments as well as Portlet 2.0 environments.
*/
查看RequestMapping的源码, 可以发现path
和value
在Servlet
环境下都是对方的别名, 所以是等价的.
/**
* The primary mapping expressed by this annotation.
* In a Servlet environment this is an alias for {@link #path}.
* For example {@code @RequestMapping("/foo")} is equivalent to
* {@code @RequestMapping(path="/foo")}.
*
In a Portlet environment this is the mapped portlet modes
* (i.e. "EDIT", "VIEW", "HELP" or any custom modes).
*
Supported at the type level as well as at the method level!
* 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}")
*
Supported at the type level as well as at the method level!
* 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绑定变量的两种方式:
- 如果被注解的参数名和
{name}
一致, 那么@PathVariable可以不用指定URI
中的变量名 - 如果不同可以使用@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
类型的参数上时: map
会被填充全部变量.
@PathVariable可以从type
和path
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
or MultiValueMap
argument, the map is populated with all request parameters.
Java代码:
@RequestMapping(path = "/map", method = RequestMethod.GET)
@ResponseBody
public String echoMap(@RequestParam Map map){
Set> set = map.entrySet();
Iterator> iterator = set.iterator();
StringBuilder stringBuilder = new StringBuilder();
while (iterator.hasNext()){
Map.Entry 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 map){
Set>> set = map.entrySet();
Iterator>> iterator = set.iterator();
StringBuilder stringBuilder = new StringBuilder();
while (iterator.hasNext()){
Map.Entry> 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.validation
与hibernate-validator
实现bean的验证.
首先在pom.xml
中添加依赖: 注意两个依赖的版本是互相有关的, 否则可能会出现问题.
依赖:
javax.validation
validation-api
2.0.0.Final
org.hibernate
hibernate-validator
6.0.4.Final
定义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]]