Spring MVC 5 文档翻译与解析 —— 注解驱动的控制器 —— Handler方法解析

Handler方法

@RequestMapping handler方法可以有灵活的签名并且可以从支持的控制器方法形参和返回值范围内作选择

方法形参
返回值类型

@ResponseBody 返回值将通过HttpMessageConverters转换并写到response 见@ResponseBody.

HttpEntity<B>, ResponseEntity<B>

返回值指定整个response,包括Http头和body,它被HttpMessageConverters转换并写到response 见ResponseEntity

HttpHeaders 返回一个带header而没有body的response

String

一个将被ViewResolver解析的视图名,与内含model(由command对象和@ModelAttribute方法决定)一起被使用 handler方法也可通过声明一个model参数编程式地丰富model参数 见Explicit Registrations

View

一个视图实例,用于配合内含model一起渲染 此model通过command objects和@ModelAttribute方法决定 handler方法也可通过声明一个model参数编程式地丰富model 见Explicit Registrations

java.util.Map, org.springframework.ui.Model

一些将被添加到内含model的Atrributes,并附带由一个RequestToViewNameTranslator隐式决定的视图名

@ModelAttribute

一些将被添加到内含model的Atrributes,并附带由一个RequestToViewNameTranslator隐式决定的视图名

注意 @ModelAttribute 是可选的. 见下面的"Any other return value" .

ModelAndView 对象

使用的view和model attributes,可带一个response状态

void

一个具有void返回类型(或返回值为null)的方法,如果它还具有ServletResponse,或OutputStream参数或@ResponseStatus注释,则认为已完全处理该响应。

如果控制器作一个正值ETag或lastModified 时间戳检查,亦是如此(详情见Controllers).

如果上面的情况都不是,一个void的返回类型对于Rest控制器表明没有response body,对于HTML控制器表明使用默认视图名

DeferredResult<V> 从任何线程异步生成任何前面的返回值,比如一些事件或回调的结果 见Asynchronous Requests 和 Using DeferredResult.

Callable<V> 在Spring MVC管理的线程中异步处理上面任何返回值 见 Asynchronous Requests 和 Using Callable.

ListenableFuture<V>, java.util.concurrent.CompletionStage<V>, java.util.concurrent.CompletableFuture<V> 作为DeferredResult方便的替代(例如,当底层服务返回其中一个时)。

ResponseBodyEmitter, SseEmitter 通过HttpMessageConverter实现异步发出一个object的stream到response 也支持ResponseEntity中的body 见Asynchronous Requests 和 HTTP Streaming.

StreamingResponseBody 异步写数据到response OutputStream 也支持ResponseEntity中的body 见Asynchronous Requests 和 HTTP Streaming.

Reactive类型 Reactor, RxJava或其他通过ReactiveAdapterRegistry适配的类型

以multi-value streams(如Flux, Observable)代替DeferredResult,收集到list

在流化的场景下比如text/event-stream, application/json+stream,使用SseEmitter and ResponseBodyEmitter替换 在ServletOutputStream 阻塞IO在Spring MVC管理的线程执行, 并且每次写完成时使用back pressure 见 Asynchronous Requests 和 Reactive Types.

任何其他类型的返回值

任何与此表中的任何前面的值都不匹配且返回值为String或void的返回值将被视为视图名称(通过RequestToViewNameTranslator选择默认视图名称)

如果一个返回值没有匹配到上面的任何类型,默认它将作为model attribute 被添加到model,除非它是一个简单类型(由BeanUtils#isSimpleProperty决定),这种情况下将保留unresolved

类型转换

一些出现在基于String的请求输入(如@RequestParam, @RequestHeader, @PathVariable, @MatrixVariable, , @CookieValue)注解的控制器方法参数,如果声明为非String类型,可能需要类型转换

这种情况下,类型转换自动基于配置的converters来应用 默认地,简单类型(int, long, Date等)都支持 你可以通过一个WebDataBinder (见DataBinder) 自定义类型转换或在FormattingConversionService注册Formatter,见Spring Field Formatting.

Matrix 变量

RFC 3986 谈到路径段中的name-value 对。在Spring MVC中,我们称它为Matrix 变量,但也可能被称为 URI路径参数,见https://www.w3.org/DesignIssues/MatrixURIs.html

Matrix 变量可以出现在任何路径段,每个变量通过分号分隔,而变量的多个值由逗号分隔。 (如 /cars;color=red,green;year=2012) 多个值也可以通过重复指定变量值来表达.(如 color=red;color=green;color=blue)

如果希望URL包含matrix变量,控制器方法的请求映射必须使用一个URI变量去标记那个变量内容 并确保请求能不依于matrix变量的顺序和存在性被正确匹配

// GET /pets/42;q=11;r=22

@GetMapping("/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) {

    // petId == 42
    // q == 11
}

因为所有路径段都可能包含Matrix变量,您有时可能需要消除Matrix变量预期所在的路径变量的歧义。

// GET /owners/42;q=11/pets/21;q=22

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable(name="q", pathVar="ownerId") int q1,
        @MatrixVariable(name="q", pathVar="petId") int q2) {
   //使用 pathVar 限定范围
    // q1 == 11
    // q2 == 22
}

一个matrix变量可以定义为可选,并指定默认值

// GET /pets/42

@GetMapping("/pets/{petId}")
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {

    // q == 1
}

你可以使用MultiValueMap来取得所有Matrix 变量

// GET /owners/42;q=11;r=12/pets/21;q=22;s=23

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable MultiValueMap<String, String> matrixVars,
        @MatrixVariable(pathVar="petId") MultiValueMap<String, String> petMatrixVars) {

    // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
    // petMatrixVars: ["q" : 22, "s" : 23]
}

注意你需要开启Matrix 变量的使用。在MVC java config中你需要通过Path Matching设置一个UrlPathHelper 为 removeSemicolonContent=false 在MVC XML命名空间中,你可以设置 <mvc:annotation-driven enable-matrix-variables="true"/>.

使用@RequestParam

你可以使用@RequestParam注解去绑定Servelt请求参数(查询参数或表单数据)到控制器中方法的形参

如下:

@Controller
@RequestMapping("/pets")
public class EditPetForm {

    // ...

    @GetMapping
    public String setupForm(@RequestParam("petId") int petId, Model model) { 
        Pet pet = this.clinic.loadPet(petId);
        model.addAttribute("pet", pet);
        return "petForm";
    }

    // ...

}

默认地,被注解的方法形参数是required,但你可以通过设置@RequestParam注解的required标志为false或申明形参为java.util.Optional 包装,来指定它为可选的

如果目标方法形参不是String类型,类型转换将自动应用,见Type Coonversion

当一个@RequestParam注解用于Map<String, String>, MultiValueMap<String, String>形参,此map将被置入所有请求参数

注意使用@RequestParam是可选的(比如,设置它的属性) 默认地,任何不被其他参数解析器解析的简单值参数(由BeanUtils#isSimpleProperty决定),都可以视为 已经被@RequestParam注解

使用RequestHeader

你可以使用@RequestHeader注解去绑定一个请求Header到一个控制器的方法形参

考虑如下带Header的请求:

Host                    localhost:8080
Accept                  text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language         fr,en-gb;q=0.7,en;q=0.3
Accept-Encoding         gzip,deflate
Accept-Charset          ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive              300

如下的例子取得Accept-Encoding 和 Keep-Alive Header的值:

@GetMapping("/demo")
public void handle(
        @RequestHeader("Accept-Encoding") String encoding, 
        @RequestHeader("Keep-Alive") long keepAlive) { 
    //...
}

如果目标方法形参类型不是String,类型转换将自动被应用,见Type Conversion。

当一个@RequestHeader注解用于Map<String, String>, MultiValueMap<String, String>, 或HttpHeaders 形参,此map将被置入所有header的值

Spring内建支持转换一个逗号分隔的String到数组或集合或其他类型转换系统已知的类型

比如,一个方法参数注解为@RequestHeader("Accept") ,可以是String类型或String[]或List<String>

使用CookieValue

你可以使用@CookieValue注解去绑定一个Http cookie的值到一个控制器中的方法形参

考虑一个带cookie请求: JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

下例示范如何获取cookie值:

@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) { 
    //...
}

如果目标方法形参不是String类型,类型转换将自动应用,见Type Coonversion

使用ModelAttribute

你可以在方法参数中使用@ModelAttribute注解来访问一个来自model中的attribute,如果不存在,则 将其实例化。model attribute也被名称与字段名匹配的Http Servlet请求参数的值覆盖,这个称为数据绑定 ,它使你不需逐个处理解析和转换查询参数及表单字段

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { }

绑定一个Pet实例

Pet实例将如此解析:

如果使用Model添加了模型,从Model中解析

如果使用@SessionAttributes,从Http Session中解析

通过Converter,从URI路径变量中解析(见下一个例子)

通过调用默认构造器

通过调用能匹配Servlet请求参数的主构造器,参数名由JavaBean @ConstructorProperties或者 通过字节码中的运行时存留参数名决定

虽然通常使用Model来使用属性填充模型,但还有一种替代方法是基于Converter <String,T>和URI路径变量转换 的结合。下面的例子中,model attribute名,account 匹配URI路径变量account,Accout将通过注册的Converter<String, Account>从 String 数字中加载

@PutMapping("/accounts/{account}")
public String save(@ModelAttribute("account") Account account) {
    // ...
}

取得model attribute实例后,将应用数据绑定。 WebDataBinder类匹配Servlet请求参数名(查询参数和表单字段)到目标对象的字段名。 匹配字段将在类型转换应用后置入需要的地方。 详见 Validation和DataBinder.

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { 
    if (result.hasErrors()) {
        return "petForm";
    }
    // ...
}

有时你想不通过数据绑定访问一个model attribute。 你可以注入Model到控制器并直接访问它,或设置@ModelAttribute(binding=false),如下:

@ModelAttribute
public AccountForm setUpForm() {
    return new AccountForm();
}

@ModelAttribute
public Account findAccount(@PathVariable String accountId) {
    return accountRepository.findOne(accountId);
}

@PostMapping("update")
public String update(@Valid AccountUpdateForm form, BindingResult result,
        @ModelAttribute(binding=false) Account account) { 
    // ...
}

你可以通过javax.validation.Valid注解或 Spring的 @Validated(ee Bean validation 和 Spring validation)来应用自动验证


@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) { 
    if (result.hasErrors()) {
        return "petForm";
    }
    // ...
}

注意,使用@ModelAttribute是可选的(比如,设置它的属性) 默认地,任何不被其他参数解析器解析的非简单值参数(由BeanUtils#isSimpleProperty决定),都可以视为被@ModelAttribute标记

使用@SessionAttributes(带s)

@SessionAttributes用于在存储model attributes到请求间的Http Servlet session中。 它是一个类型级别的注解,它声明的Session attributes由指定的控制器使用。 此处一般列出model attributes名或model attributes 类型,这些attributes 将 被明确地存储在session model attributes中,使后面的请求可以访问

如下:

@Controller
@SessionAttributes("pet")
public class EditPetForm {
    // ...
}

第一次请求中,当一个名为pet的model attributes被添加到model,它将自动被提升并保存到Http Servlet Session中。它将一直保留直到另一个控制器方法使用SSessionStatus方法去清除存储,如下:

@Controller
@SessionAttributes("pet") 
public class EditPetForm {

    // ...

    @PostMapping("/pets/{id}")
    public String handle(Pet pet, BindingResult errors, SessionStatus status) {
        if (errors.hasErrors) {
            // ...
        }
            status.setComplete();
            // ...
        }
    }
}
使用@SessionAttribute

如果您需要访问全局管理的预先存在的Session attributes(即,在控制器外部 - 例如,通过过滤器中设置),它可能存在或不存在。可以对方法参数使用@SessionAttribute注解,如 以下示例显示:

@RequestMapping("/")
public String handle(@SessionAttribute User user) { 
    // ...
}

需要添加或移除session attributes的情况下,考虑注入org.springframework.web.context.request.WebRequest 或javax.servlet.http.HttpSession到控制器方法 要将临时存储在session中的model attributes,作为控制器工作流的一部分 请考虑使用@SessionAttributes

使用 @RequestAttribute

类似于@SessionAttribute,你可以使用 @RequestAttribute注解来访问之前创建的的请求Attribute (比如马桶盖一个Servlet Filter或HandlerInterceptor)

@GetMapping("/")
public String handle(@RequestAttribute Client client) {
    // ...
}
Redirect Attributes

默认地,所有model attributes都被考虑暴露为redirectURI的URI模板变量。 在保留的attributes中,数组或集合中的原始类型将作为查询参数被自动附加

附加的原始类型attributes作为查询参数

RequestMappingHandlerAdapter提供一个flag,称为ignoreDefaultModelOnRedirect,你可以用来 表明默认model的内容在一个控制器方法redirect时总是不可用。 作为代替,控制器方法应该声明一个RedirectAttributes类型的attribute,否则,没有attributes会置入到 RedirectView。 MVC 命名空间和Java Config都保持这个flag为false,来维持向后兼容性,但对于新的应用程序,我们 推荐设置为true。

注意在展开redirect URL时,来自当前请求的URI模板变量将自动成为可被发现的,你需要明确通过Model或RedirectAttributes添加它们

下面的例子示范如何定义一个redirect

@PostMapping("/files/{path}")
public String upload(...) {
    // ...
    return "redirect:files/{path}";
}

另一种将数据置入redirect目标的方法是使用flash attributes。 不像其他redirect attributes,flash attribute保持在Http session中 (因此不会出现在URL中) 详见Flash Attributes

Flash Attributes

Flash Attribute其实就是通过session来延伸Request attribute的生命周期 使得请求结束后,Request attribute仍然可以保留

Flash Attributes为一个请求提供了一种存储一些属性以供另一个请求使用的的方法 重定向时最常需要这种方法 - 例如,Post-Redirect-Get模式。 Flash重定向(通常在会话中)之前临时保存Flash属性,以便在重定向后使请求可用,并立即删除。

Spring MVC有两个支持flash属性的主要抽象。 FlashMap用于保存Flash属性,而FlashMapManager用于存储,检索和管理FlashMap实例。

Flash属性支持始终处于“启用”状态,无需显式启用。但是,如果不使用,它永远不会导致HTTP会话创建。在每个请求中,都有一个“input”FlashMap,其中包含从先前请求(如果有)传递的属性,以及一个“output”FlashMap,其中包含要为后续请求保存的属性。两个FlashMap实例都可以通过RequestContextUtils中的静态方法从Spring MVC中的任何位置访问。

带注解的控制器通常不需要直接使用FlashMap。 @RequestMapping方法可以接受RedirectAttributes类型的参数,并使用它为重定向行动添加flash属性。 通过RedirectAttributes添加的Flash属性会自动传播到“output”FlashMap。同样,在重定向之后,“input入”FlashMap中的属性会自动添加到为目标URL提供服务的控制器的model中。

关于映射请求到Flash Attributes Flash Attributes的概念存在于许多其他Web框架中,并且已经证明有时会出现并发问题。 因为根据定义,Flash Attributes将被存储直到下一个请求。 但是,“下一个”请求可能不是预期的接收者而是另一个异步请求(例如,轮询或资源请求),在这种情况下,会导致过早删除Flash Attributes。

为了减少此类问题的可能性,RedirectView使用目标重定向URL的路径和查询参数自动“标记”FlashMap实例。 反过来,默认的FlashMapManager在查找input FlashMap时将该信息与传入请求进行匹配。

这并不能完全消除并发问题的可能性,但会使用重定向URL中已有的信息,大大减少并发问题。 因此,我们建议您主要使用Flash属性进行重定向方案。

Multipart

当一个MultipartResolver启用后,带有multipart/form-data的POST请求内容被解析成为一个可理解的 规范的请求参数 下例访问一个规范的form字段和一个上传的文件

@Controller
public class FileUploadController {

    @PostMapping("/form")
    public String handleFormUpload(@RequestParam("name") String name,
            @RequestParam("file") MultipartFile file) {

        if (!file.isEmpty()) {
            byte[] bytes = file.getBytes();
            // store the bytes somewhere
            return "redirect:uploadSuccess";
        }

        return "redirect:uploadFailure";
    }

}

当你使用 Servlet 3.0 multipart解析,你也可以使用javax.servlet.http.Part作为方法参数来代替Spring的 MultipartFile

你可以使用将multipart 内容作为data绑定到command对象来使用。 比如,前面的form字段和文件可以作为form对象的字段 如下:

class MyForm {

    private String name;

    private MultipartFile file;

    // ...

}

@Controller
public class FileUploadController {

    @PostMapping("/form")
    public String handleFormUpload(MyForm form, BindingResult errors) {

        if (!form.getFile().isEmpty()) {
            byte[] bytes = form.getFile().getBytes();
            // store the bytes somewhere
            return "redirect:uploadSuccess";
        }

        return "redirect:uploadFailure";
    }

}

Multipart 在一个REST服务中,请求也可能从非浏览器的客户端提交. 下面是一个带JSON的文件的例子:

POST /someUrl
Content-Type: multipart/mixed

--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="meta-data"
Content-Type: application/json; charset=UTF-8
Content-Transfer-Encoding: 8bit

{
    "name": "value"
}
--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="file-data"; filename="file.properties"
Content-Type: text/xml
Content-Transfer-Encoding: 8bit
... File Data ...

你通过@RequestParam将"meta-data"部分作为一个String来访问,但你可能想让它从JSON反序列化 (类似于@RequestBody),当一个multipart由HttpMessageConverter转换以后,可使用@RequestPart注解去访问

@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata,
        @RequestPart("file-data") MultipartFile file) {
    // ...
}

你可以结合javax.validation.Valid使用@RequestPart或使用Spring的@Validated注解,这都 导致标准Bean Validation被应用。 默认地,validation错误引发一个MethodArgumentNotValidException,它将被转入一个400 (BAD_REQUEST) response。 或者,你可以通过Errors 或 BindingResult参数,在一个控制器中本地处理validation errors,如下:

@PostMapping("/")
public String handle(@Valid @RequestPart("meta-data") MetaData metadata,
        BindingResult result) {
    // ...
}
使用@ResponseBody

你可以在一个方法上使用@ResponseBody注解,以通过HttpMessageConverter将返回值序列化到response body。

见下例:

@GetMapping("/accounts/{id}")
@ResponseBody
public Account handle() {
    // ...
}

@ResponseBody也支持用在类级别,这将应用到所有控制器方法

@RestController=@Controller + @ResponseBody,不多不少

你可以对reactive类型使用@ResponseBody,详见 Asynchronous Requests 和 Reactive Types

你可以使用MVC Config的Message Converters选项来配置或自定义消息转换

你可以结合@ResponseBody方法和JSON序列化视图,详见Jackson JSON

HttpEntity

HttpEntity与使用@RequestBody多少有些相同,但它基于一个暴露请求header和body的容器对象,如下: (这里的容器不是Spring容器的意思)

@PostMapping("/accounts")
public void handle(HttpEntity<Account> entity) {
    // ...
}
ResponseEntity

ResponseEntity和@ResponseBody多少有些相似,但它基于指定请求header和body的容器对象ResponseEntity。 (这里的容器不是Spring容器的意思) 见下例:

@PostMapping("/something")
public ResponseEntity<String> handle() {
    // ...
    URI location = ... ;
    return ResponseEntity.created(location).build();
}
Jackson JSON

Spring提供对Jackson JSON库的支持

Jackson 序列化视图

Spring MVC 提供Jackson 序列化视图的内建支持,它允许呈现一个对象字段的子集

要@ResponseBody或@ResponseEntity控制器方法上使用,你可以使用 Jackson的@JsonView 去激活一个序列化视图类,如下所示:

@RestController
public class UserController {

    @GetMapping("/user")
    @JsonView(User.WithoutPasswordView.class)
    public User getUser() {
        return new User("eric", "7!jd#h23");
    }
}

public class User {

    public interface WithoutPasswordView {};
    public interface WithPasswordView extends WithoutPasswordView {};

    private String username;
    private String password;

    public User() {
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    @JsonView(WithoutPasswordView.class)
    public String getUsername() {
        return this.username;
    }

    @JsonView(WithPasswordView.class)
    public String getPassword() {
        return this.password;
    }
}

所有getter上注解有某class的属性将会,在控制器注解@JsonView(某class)时,被提取出 它允许继承,允许混合

@JsonView允许用一个view类数组,但你对一个控制器方法可以仅指定单个view class。 如果你需要激活多view,你可以使用混合接口

对于依赖视图解析的控制器,你可以添加序列化视图class到模型,如下:

@Controller
public class UserController extends AbstractController {

    @GetMapping("/user")
    public String getUser(Model model) {
        model.addAttribute("user", new User("eric", "7!jd#h23"));
        model.addAttribute(JsonView.class.getName(), User.WithoutPasswordView.class);
        return "userView";
    }
}

转载于:https://my.oschina.net/voidgeek/blog/2206638

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值