Spring MVC 学习总结(三)——请求处理方法Action详解

2022-05-09 19:20:22 浏览数 (1)

目录

  • 一、Action参数类型
    • 1.1、自动参数映射
      • 1.1.1、基本数据类型
      • 1.1.2、自定义数据类型
      • 1.1.3、复杂数据类型
      • 1.1.4、数组
      • 1.1.5、List集合类型
      • 1.1.6、Map集合类型
    • 1.2、@RequestParam参数绑定
      • 1.2.1、基本数据类型绑定与注解属性
      • 1.2.2、List与数组绑定基本数据类型
      • 1.2.3、@RequestBody
      • 1.2.4、List与数组直接绑定自定义数据类型与AJAX
    • 1.3、重定向与Flash属性
    • 1.4、转发
    •  1.5、@ModelAttribute模型特性
      • 1.5.1、注解在参数上
      • 1.5.2、注解在方法上
  • 二、Action返回值类型
    • 2.1、视图中url问题
    • 2.2、返回值为String
      • 2.2.1、String作为视图名称
      • 2.2.2、String作为内容输出
    • 2.3、返回值为void
      • 2.3.1、方法名默认作为视图名
      • 2.3.2、直接响应输出结果
    • 2.4、返回值为ModelAndView
    • 2.5、返回值为Map
    • 2.6、返回值为任意类型
      •  2.6.1、返回值为基本数据类型
      • 2.6.2、当返值为自定义类型
    • 2.7、返回值为Model类型
    • 2.8、自定义输出内容
      • 2.8.1、输出Excel
      • 2.8.2、导出XLS时增加BOM头部解决乱码问题
      • 2.8.3、导出CSV格式
    • 2.8、@ResponseBody
    • 2.9、@RestController
    • 2.10、小结
  • 三、Spring MVC乱码解决方法
    • 3.1、页面编码
  • 四、示例
  • 五、视频
  • 六、作业

Spring MVC中每个控制器中可以定义多个请求处理方法,我们把这种请求处理方法简称为Action,每个请求处理方法可以有多个不同的参数,以及一个多种类型的返回结果。

一、Action参数类型

如果在请求处理方法中需要访问HttpSession对象,则可以添加HttpSession作为参数,Spring会将对象正确的传递给方法,如:public  String action(HttpSession session);若需要访问客户端语言环境和HttpServletRequest对象,则可以在方法签名上包含这样的参数,如:public String action(HttpServletRequest request,Locale locale)。可以在请求中出现的参数类型有:

org.springframework.web.context.request.WebRequest org.springframework.web.context.request.NativeWebRequest java.util.Locale 当前请求的语言环境 java.util.TimeZone 时区 java.io.InputStream或java.io.Reader java.io.OutputStream或java.io.Writer org.springframework.http.HttpMethod java.security.Principal HttpEntity <?>参数用于访问Servlet的HTTP请求的标题和内容 java.util.Map / org.springframework.ui.Model / org.springframework.ui.ModelMap 视图隐含模型 org.springframework.web.servlet.mvc.support.RedirectAttributes 重定向 命令或表单对象 基本数据类型,如int,String,double... 复杂数据类型,如自定义的POJO对象 HandlerAdapter org.springframework.validation.Errors / org.springframework.validation.BindingResult 验证结果 org.springframework.web.bind.support.SessionStatus 会话状态 org.springframework.web.util.UriComponentsBuilder @PathVariable 注解参数访问URI模板变量。 @MatrixVariable 注释参数用于访问位于URI路径段键值对对,矩阵变量。 @RequestParam 注解参数访问特定的Servlet请求参数,请求参数绑定。 @RequestHeader 注解参数访问特定的se​​rvlet请求HTTP标头,映射请求头。 @RequestBody 注解参数访问HTTP请求主体,注解映射请求体 @RequestPart 注解参数访问“的multipart / form-data的”请求部分的内容。处理客户端上传文件,多部分文件上传的支持 @SessionAttribute 注解参数会话属性 @RequestAttribute 注解参数访问请求属性

1.1、自动参数映射

1.1.1、基本数据类型

方法的参数可以是任意基本数据类型,如果方法参数名与http中请求的参数名称相同时会进行自动映射,视图foo目录下的index.jsp与示例代码如下:

代码语言:javascript复制
    // 自动参数映射
    @RequestMapping("/action0")
    public String action0(Model model, int id, String name) {
        model.addAttribute("message", "name="   name   ",id="   id);
        return "foo/index";
    }
代码语言:javascript复制
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Foo</title>
</head>
<body>
${message}
</body>
</html>

运行结果如下:

包装类型也一样,但如果参数中没有对应名称与类型的数据则会异常。

1.1.2、自定义数据类型

除了基本数据类型,也可以自定义的数据类型,如一个自定义的POJO对象,Spring MVC会通过反射把请中的参数设置到对象中,转换类型,示例代码如下:

代码语言:javascript复制
package com.zhangguo.springmvc03.entities;

import java.io.Serializable;

/** * 产品 */
public class Product implements Serializable {
    private static final long serialVersionUID = 1L;
    private int id;
    private String name;
    private double price;

    public Product() {
    }

    public Product(String name, double price) {
        super();
        this.name = name;
        this.price = price;
    }

    public Product(int id, String name, double price) {
        super();
        this.id = id;
        this.name = name;
        this.price = price;
    }

    @Override
    public String toString() {
        return "编号(id):"   this.getId()   ",名称(name):"   this.getName()   ",价格(price):"   this.getPrice();
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
}
代码语言:javascript复制
    // 自动参数映射自定义数据类型
    @RequestMapping("/action01")
    public String action01(Model model, Product product) {
        model.addAttribute("message", product);
        return "foo/index";
    }

运行结果如下:

示例中使用的是的URL中的参数,其实也可以是客户端提交的任意参数,特别是表单中的数据。

1.1.3、复杂数据类型

这里指的复杂数据类型指的是一个自定义类型中还包含另外一个对象类型,如用户类型中包含产品对象:

代码语言:javascript复制
package com.zhangguo.springmvc03.entities;

public class User {
    private String username;
    private Product product;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Product getProduct() {
        return product;
    }

    public void setProduct(Product product) {
        this.product = product;
    }
}

示例代码如下:

代码语言:javascript复制
    // 自动参数映射复杂数据类型
    @RequestMapping("/action02")
    public String action02(Model model, User user) {
        model.addAttribute("message", user.getUsername()   ","   user.getProduct().getName());
        return "foo/index";
    }

测试运行结果:

为了方便这里我使用的是url,这里当然可以是一个表单,如下代码所示:

代码语言:javascript复制
<form method="post" action="foo/action02">
     username:<input name="username" /><br/>
     pdctname:<input name="product.name" /><br/>
    <button>提交</button>
</form>

1.1.4、数组

方法一:

提交时使用param1=aaa&param1=bbb&param1=3

接收时使用String param1[] 这种参数既可以获取数组的值

示例:

代码语言:javascript复制
    //3.自动参数映射数组数据类型
    @RequestMapping("/act03")
    public String act03(Model model,Integer[] id){
        model.addAttribute("msg",Arrays.toString(id));
        return "hi";
    }

结果:

方法二:

提交时使用param1=aaa&param1=bbb&param1=3

接收时使用List<String> param1 这种参数既可以获取数组的值

示例:

POJO Car.java

代码语言:javascript复制
package com.zhangguo.springmvc01.entities;

import java.util.ArrayList;
import java.util.List;

/**车*/
public class Car {
    /**编号*/
    private int id;
    /**名称*/
    private String name;
    /**价格*/
    private double price;
    /**尺寸*/
    private Size size;

    private List<Integer> ids;
    public List<Integer> getIds() {
        return ids;
    }

    public void setIds(List<Integer> ids) {
        this.ids = ids;
    }

    public Car(int id, String name, double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    public Car() {
    }

    public Size getSize() {
        return size;
    }

    public void setSize(Size size) {
        this.size = size;
    }

    public static List<Car> getCars() {
        return cars;
    }

    public static void setCars(List<Car> cars) {
        Car.cars = cars;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{"  
                "id="   id  
                ", name='"   name   '''  
                ", price="   price  
                ", size="   size  
                ", ids="   ids  
                '}';
    }

    public static List<Car> cars=new ArrayList<>();
    static {
        cars.add(new Car(101,"朗逸",16.59));
        cars.add(new Car(102,"菲斯塔",15.50));
        cars.add(new Car(103,"雅阁",25.98));
        cars.add(new Car(104,"卡罗拉",17.58));
        cars.add(new Car(105,"轩逸",16.15));
    }
}

Action

代码语言:javascript复制
    @RequestMapping("/act02")
    public String act02(Model model,Car car){
        model.addAttribute("msg",car);
        return "hi";
    }

结果:

1.1.5、List集合类型

不能直接在action的参数中指定List<T>类型,定义一个类型包装List集合在其中,ProductList类如下所示:

代码语言:javascript复制
package com.zhangguo.springmvc03.entities;

import java.util.List;

//产品集合
public class ProductList {
    private List<Product> items;

    public List<Product> getItems() {
        return items;
    }

    public void setItems(List<Product> items) {
        this.items = items;
    }
}

定义的action代码如下所示:

代码语言:javascript复制
    // 集合类型
    @RequestMapping("/action03")
    public String action03(Model model, ProductList products) {
        model.addAttribute("message", products.getItems().get(0)   "<br/>"   products.getItems().get(1));
        return "foo/index";
    }

在url中模拟表单数据,提交后的结果如下所示:

这里同样可以使用一个表单向服务器提交数据。

TomCat高版本可能会产生如下错误:

代码语言:javascript复制
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:479)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:684)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:806)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1498)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:745)

Get请求新的安全规范要求URL中不能直接带[],如下所示:

代码语言:javascript复制
25-Oct-2018 14:12:09.277 警告 [http-nio-8080-exec-2] org.apache.tomcat.util.http.parser.HttpParser.<clinit> Character [,] is not allowed and will continue to be rejected

解决办法(四种):

1、替换url请求。不用{}[]特殊字符! * ’( ) ; : @ & = $ , / ? # [ ])

2、对请求编码解码。 UrlDecode、UrlEncode

3、配置Tomcat对字符的支持:

  3.1、更换Tomcat版本 (注,Tomcat从 7.0.73, 8.0.39, 8.5.7 版本后添加了对Url的限制。)

  3.2、配置tomcat支持|{}等字符的方法是:

在 conf/catalina.properties 中最后添加一行:

代码语言:javascript复制
org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true

tomcat.util.http.parser.HttpParser.requestTargetAllow=|{}[]

注3.2这种方法从8.5以后就被放弃的了,新的方法如下:

tomcat.util.http.parser.HttpParser.requestTargetAllow is deprecated since Tomcat 8.5: tomcat official doc.

You can use relaxedQueryChars / relaxedPathChars in the connectors definition to allow these chars: tomcat official doc.

修改conf/server.xml文件,如下所示:

代码语言:javascript复制
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" URIEncoding="utf-8" relaxedQueryChars="],["/>

1.1.6、Map集合类型

Map与List的实现方式基本一样,这里先定义了一个包装Map的类型ProductMap,代码如下所示:

代码语言:javascript复制
package com.zhangguo.springmvc03.entities;

import java.util.Map;

/**
 * * 产品字典
 */
public class ProductMap {
    private Map<String, Product> items;

    public Map<String, Product> getItems() {
        return items;
    }

    public void setItems(Map<String, Product> items) {
        this.items = items;
    }
}

Action的定义如下:

代码语言:javascript复制
    // Map类型
    @RequestMapping("/action04")
    public String action04(Model model, ProductMap map) {
        model.addAttribute("message", map.getItems().get("p1")   "<br/>"   map.getItems().get("p2"));
        return "foo/index";
    }

测试运行结果如下:

集合类型基本都一样,set也差不多,问题是如果为了获得一个集合需要刻意去包装会很麻烦,可以通过@RequestParam结合@RequestBody等注解完成。

1.2、@RequestParam参数绑定

简单的参数可以使用上一节中讲过的自动参数映射,复杂一些的需使用@RequestParam完成,虽然自动参数映射很方便,但有些细节是不能处理的,如参数是否为必须参数,名称没有办法指定,参数的默认值就没有有办法做到了。如果使用@RequestParam可以实现请求参数绑定,Spring MVC会自动查找请求中的参数转类型并将与参数进行绑定,示例代码如下:

1.2.1、基本数据类型绑定与注解属性

代码语言:javascript复制
package com.zhangguo.springmvc03.controllers;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
@RequestMapping("/foo")
public class FooController {
    @RequestMapping("/action1")
    public String action1(Model model, @RequestParam(required = false, defaultValue = "99") int id) {
        model.addAttribute("message", id);
        return "foo/index";
    }
}

@RequestParam共有4个注解属性,required属性表示是否为必须,默认值为true,如果请求中没有指定的参数会报异常;defaultValue用于设置参数的默认值,如果不指定值则使用默认值,只能是String类型的。name与value互为别名关系用于指定参数名称。

运行结果:

1.2.2、List与数组绑定基本数据类型

在上一节中我们使用自动参数映射是不能直接完成List与数组绑定的,结合@RequestParam可以轻松实现,示例代码如下所示:

代码语言:javascript复制
    // List集合与数组类型
    @RequestMapping("/action05")
    public String action05(Model model, @RequestParam("u") List<String> users) {
        model.addAttribute("message", users.get(0)   ","   users.get(1));
        return "foo/index";
    }

运行结果:

直接在URL中输入测试数据可以绑定成功,使用表单同样可行,页面脚本如下:

代码语言:javascript复制
<form action="bar/action11" method="post">
    <p>
        <label>爱好:</label> 
        <input type="checkbox" value="15" name="id" />阅读
         <input type="checkbox" value="20" name="id" />上网
         <input type="checkbox" value="73" name="id" />电游
    </p>
    <button>提交</button>
</form>

请求处理方法action代码如下:

代码语言:javascript复制
    // List与数组绑定基本数据类型
    @RequestMapping("/action11")
    public String action11(Model model, @RequestParam("id") List<Integer> ids) {
        model.addAttribute("message", Arrays.deepToString(ids.toArray()));
        return "bar/index";
    }

运行结果:

@RequestParam("id")是必须的,因为页面中的表单name的名称为id,所有服务器在收集数据时应该使用id页非ids,如果同名则可以省去。

1.2.3、@RequestBody

@RequestBody 注解将HTTP请求正文插入方法中,使用适合的 HttpMessageConverter将请求体写入某个对象。 作用: 1) 该注解用于读取Request请求的body部分数据,使用系统默认配置的HttpMessageConverter进行解析,然后把相应的数据绑定到要返回的对象上; 2) 再把HttpMessageConverter返回的对象数据绑定到 controller中方法的参数上。

使用时机:

A) GET、POST方式提时, 根据request header Content-Type的值来判断:

application/x-www-form-urlencoded, 可选(即非必须,因为这种情况的数据@RequestParam, @ModelAttribute也可以处理,当然@RequestBody也能处理); multipart/form-data, 不能处理(即使用@RequestBody不能处理这种格式的数据); 其他格式, 必须(其他格式包括application/json, application/xml等。这些格式的数据,必须使用@RequestBody来处理);

B) PUT方式提交时, 根据request header Content-Type的值来判断:

application/x-www-form-urlencoded, 必须;multipart/form-data, 不能处理;其他格式, 必须;

说明:request的body部分的数据编码格式由header部分的Content-Type指定;

例如:

代码语言:javascript复制
@RequestMapping(value = "user/login")
@ResponseBody
// 将ajax(datas)发出的请求写入 User 对象中
public User login(@RequestBody User user) { 
// 这样就不会再被解析为跳转路径,而是直接将user对象写入 HTTP 响应正文中
return user; 
}

@requestBody注解常用来处理content-type不是默认的application/x-www-form-urlcoded编码的内容,比如说:application/json或者是application/xml等。一般情况下来说常用其来处理application/json类型。

通过@requestBody可以将请求体中的JSON字符串绑定到相应的bean上,当然,也可以将其分别绑定到对应的字符串上。 例如:

代码语言:javascript复制
    $.ajax({
        url:"/login",
        type:"POST",
        data:'{"userName":"admin","pwd","admin123"}',
        content-type:"application/json charset=utf-8",
        success:function(data){
          alert("request success ! ");
        }
    });

action:

代码语言:javascript复制
    @requestMapping("/login")
    public void login(@requestBody String userName,@requestBody String pwd){
      System.out.println(userName " :" pwd);
    }

这种情况是将JSON字符串中的两个变量的值分别赋予了两个字符串,但是呢假如我有一个User类,拥有如下字段: String userName; String pwd; 那么上述参数可以改为以下形式:@requestBody User user 这种形式会将JSON字符串中的值赋予user中对应的属性上 需要注意的是,JSON字符串中的key必须对应user中的属性名,否则是请求不过去的。

1.2.4、List与数组直接绑定自定义数据类型与AJAX

上一小节中我们绑定的集合中存放的只是基本数据类型,如果需要直接绑定更加复杂的数据类型则需要使用@RequestBody与@ResponseBody注解了,先解释一下他们的作用:

@RequestBody 将HTTP请求正文转换为适合的HttpMessageConverter对象。 @ResponseBody 将内容或对象作为 HTTP 响应正文返回,并调用适合HttpMessageConverter的Adapter转换对象,写入输出流。

AnnotationMethodHandlerAdapter将会初始化7个转换器,可以通过调用AnnotationMethodHandlerAdapter的getMessageConverts()方法来获取转换器的一个集合 List<HttpMessageConverter>,7个转换器类型分别是

ByteArrayHttpMessageConverter  StringHttpMessageConverter  ResourceHttpMessageConverter  SourceHttpMessageConverter  XmlAwareFormHttpMessageConverter  Jaxb2RootElementHttpMessageConverter  MappingJacksonHttpMessageConverter

@RequestBody默认接收的Content-Type是application/json,因此发送POST请求时需要设置请求报文头信息,否则Spring MVC在解析集合请求参数时不会自动的转换成JSON数据再解析成相应的集合,Spring默认的json协议解析由Jackson完成。要完成这个功能还需要修改配置环境,具体要求如下:

a)、修改Spring MVC配置文件,启用mvc注解驱动功能,<mvc:annotation-driven />

b)、pom.xml,添加jackson依赖,添加依赖的配置内容如下:

代码语言:javascript复制
    <!--jackson-->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.7.4</version>
    </dependency>

    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.7.4</version>
    </dependency>

    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>2.7.4</version>
    </dependency>

c)、ajax请求时需要设置属性dataType 为 json,contentType 为 'application/json;charse=UTF-8',data 转换成JSON字符串,如果条件不满足有可能会出现415异常。

Action定义的示例代码如下:

代码语言:javascript复制
    // List与数组直接绑定自定义数据类型与AJAX
    @RequestMapping("/action21")
    public void action21(@RequestBody List<Product> products, HttpServletResponse response) throws IOException {
        response.setCharacterEncoding("UTF-8");
        System.out.println(Arrays.deepToString(products.toArray()));
        response.getWriter().write("添加成功");
    }

action21的参数@RequestBody List<Product> products是接收从客户端发送到服务器的产品集合,默认的请求内容并非是application/json,而是:application/x-www-form-urlencoded,在参数前增加@RequestBody的作用是让Spring MVC在收到客户端请求时将选择合适的转换器将参数转换成相应的对象。action22的返回值为List<Product>,且在方法上有一个注解@ResponseBody,系统会使用jackson将该对象自动序列化成json字符;在客户端请求时设置内容类型为application/json,定义一个myform21.jsp页面,页面的脚本如下所示:

代码语言:javascript复制
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>List与数组直接绑定自定义数据类型与AJAX</title>
</head>
<body>
    <button type="button" onclick="addPdts_click1();">向服务器发送json</button>
    <button type="button" onclick="addPdts_click2();">接收服务器返回的json</button>
    <p id="msg"></p>
    <script type="text/javascript"
        src="<c:url value="/scripts/jQuery1.11.3/jquery-1.11.3.min.js"/>"></script>
    <script type="text/javascript">
        var products = new Array();
        products.push({
            id : 1,
            name : "iPhone 6 Plus",
            price : 4987.5
        });
        products.push({
            id : 2,
            name : "iPhone 7 Plus",
            price : 5987.5
        });
        products.push({
            id : 3,
            name : "iPhone 8 Plus",
            price : 6987.5
        });
        function addPdts_click1() {
            $.ajax({
                type : "POST",
                //请求谓词类型
                url : "bar/action21",
                data : JSON.stringify(products), //将products对象转换成json字符串
                contentType : "application/json;charset=UTF-8",
                //发送信息至服务器时内容编码类型,(默认: "application/x-www-form-urlencoded")
                dataType : "text", //预期服务器返回的数据类型
                success : function(result) {
                    $("#msg").html(result);
                }
            });
        }
        function addPdts_click2() {
            $.ajax({
                type : "POST",
                //请求谓词类型
                url : "bar/action22",
                data : JSON.stringify(products), //将products对象转换成json字符串
                contentType : "application/json;charset=UTF-8",
                //发送信息至服务器时内容编码类型,(默认: "application/x-www-form-urlencoded")
                dataType : "json", //预期服务器返回的数据类型
                success : function(result) {
                    var str = "";
                    $.each(result, function(i, obj) {
                        str  = "编号:"   obj.id   ",名称:"   obj.name   ",价格:"  obj.price   "<br/>";
                    });
                    $("#msg").html(str);
                }
            });
        }
    </script>
</body>
</html>

页面中有两个方法,第一个方法是实现将一个json集合发送到服务器并映射成一个List集合;第二个方法是实现接收服务器返回的json对象。

点击按钮1时的运行结果如下:

控制台输出:

[编号(id):1,名称(name):iPhone 6 Plus,价格(price):4987.5, 编号(id):2,名称(name):iPhone 7 Plus,价格(price):5987.5, 编号(id):3,名称(name):iPhone 8 Plus,价格(price):6987.5]

点击按钮2时的运行结果如下:

1.3、重定向与Flash属性

在一个请求处理方法Action中如果返回结果为“index”字符则表示转发到视图index,有时候我们需要重定向,则可以在返回的结果前加上一个前缀“redirect:”,可以重定向到一个指定的页面也可以是另一个action,示例代码如下:

代码语言:javascript复制
    // 重定向
    @RequestMapping("/action2")
    public String action2(Model model) {
        return "foo/index";
    }

    @RequestMapping("/action3")
    public String action3(Model model) {
        model.addAttribute("message", "action3Message");
        return "redirect:action2";
    }

当请求http://localhost:8087/SpringMVC02/foo/action3时运行结果如下:

在action3中返回的结果为redirect:action2,则表示重定向到action2这个请求处理方法,所有重定向都是以当前路径为起点的,请注意路径。在action3向model中添加了名称message的数据,因为重定向到action2中会发起2次请求,为了保持action3中的数据Spring MVC自动将数据重写到了url中。为了实现重定向时传递复杂数据,可以使用Flash属性,示例代码如下:

代码语言:javascript复制
    // 接收重定向参数
    @RequestMapping("/action2")
    public String action2(Model model, Product product) {
        model.addAttribute("message", product);
        System.out.println(model.containsAttribute("product")); // true
        return "foo/index";
    } 

    //重定向属性
    @RequestMapping("/action3")
    public String action3(Model model, RedirectAttributes redirectAttributes) {
        Product product = new Product(2, "iPhone7 Plus", 6989.5);
        redirectAttributes.addFlashAttribute("product", product);
        return "redirect:action2";
    }

当访问action3时,首先创建了一个product产口对象,将该对象添加到了Flash属性中,在重定向后取出,个人猜测应该暂时将对象存入了Session中。当请求foo/action3时运行结果如下:

url地址已经发生了变化,product对象其实也已经被存入了model中,在action的视图中可以直接拿到。

1.4、转发

str=”forward : 路径”         请求转发到一个页面中 str=”forward : controller的映射”  请求转发到一个controller方法中

示例:

代码语言:javascript复制
    //11.转发
    @RequestMapping("/act11")
    public String act11(Model model){
        return "hi";
    }

    //12.转发
    @RequestMapping("/act12")
    public String act12(Model model){
        model.addAttribute("msg","act12");
        return "forward:act11";
    }

结果:

URL没有变化,数据存在可以直接使用。

 1.5、@ModelAttribute模型特性

@ModelAttribute可以应用在方法参数上或方法上,他的作用主要是当注解在方法中时会将注解的参数对象添加到Model中;当注解在请求处理方法Action上时会将该方法变成一个非请求处理的方法,但其它Action被调用时会首先调用该方法。

1.5.1、注解在参数上

当注解在参数上时会将被注解的参数添加到Model中,并默认完成自动数据绑定,示例代码如下:

代码语言:javascript复制
    @RequestMapping("/action6")
    public String action6(Model model, @ModelAttribute(name = "product", binding = true) Product entity) {
        model.addAttribute("message", model.containsAttribute("product")   "<br/>"   entity);
        return "foo/index";
    }

运行结果:

其实不使用@ModelAttribute我也样可以完成参数与对象间的自动映射,但使用注解可以设置更多详细内容,如名称,是否绑定等。

1.5.2、注解在方法上

用于标注一个非请求处理方法,通俗说就是一个非Action,普通方法。如果一个控制器类有多个请求处理方法,以及一个有@ModelAttribute注解的方法,则在调用其它Action时会先调用非请求处理的Action,示例代码如下:

代码语言:javascript复制
    @RequestMapping("/action7")
    public String action7(Model model) {
        Map<String, Object> map = model.asMap();
        for (String key : map.keySet()) {
            System.out.println(key   ":"   map.get(key));
        }
        return "foo/index";
    }

    @ModelAttribute
    public String noaction() {
        System.out.println("noaction 方法被调用!");
        String message = "来自noaction方法的信息";
        return message;
    }

当访问http://localhost:8087/SpringMVC03/foo/action7时,控制台显示结果如下:

非请求处理方法可以返回void,也可以返回一个任意对象,该对象会被自动添加到每一个要被访问的Action的Model中,key从示例中可以看出为类型名称。

二、Action返回值类型

ModelAndView Model Map 包含模型的属性 View String 视图名称 void HttpServletResponse HttpEntity<?>或ResponseEntity<?> HttpHeaders Callable<?> DeferredResult<?> ListenableFuture<?> ResponseBodyEmitter SseEmitter StreamingResponseBody 其它任意类型,Spring将其视作输出给View的对象模型

2.1、视图中url问题

新增一个action5,代码如下:

代码语言:javascript复制
    @RequestMapping("/action5")
    public String action5(Model model) {
        return "foo/action5";
    }

在foo目录下添加视图action5.jsp,内容如下:

代码语言:javascript复制
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>action5的视图</title>
</head>
<body>
    <img alt="风景" src="../../images/3.jpg">
</body>
</html>

目标结构如下:

访问结果:

这里图片访问不到的原因是因为:action5.jsp视图此时并非以它所在的目录为实际路径,他是以当前action所在的控制器为起始目录的,当前控制器的url为:http://localhost:8087/SpringMVC02/foo/,而图片的src为:../../images/3.jpg,向上2级后变成:http://localhost:8087/images/3.jpg,但我们的项目实际路径中并没有存放3.jpg这张图片,解决的办法是在视图中使用“绝对”路径;另外一个问题是我们将静态资源存放到WEB-INF下不太合理,因为该目录禁止客户端访问,修改后的视图如下:

代码语言:javascript复制
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>action5的视图</title>
</head>
<body>
    <img alt="风景" src="<c:url value="/images/3.jpg"></c:url>">
</body>
</html>

目录结构变化后如下所示:

运行结果:

小结:主要是借助了标签<c:url value="/images/3.jpg"></c:url>,将路径转换成“绝对路径”;建议在引用外部资源如js、css、图片信息时都使用该标签解析路径。

2.2、返回值为String

2.2.1、String作为视图名称

默认如果action返回String,此时的String为视图名称,会去视图解析器的设定的目录下查找,查找的规则是:URL= prefix前缀 视图名称 suffix后缀组成,示例代码如下:

代码语言:javascript复制
    //返回值为String
    @RequestMapping("/action31")
    public String action31(Model model)
    {
        model.addAttribute("message","action31");
        return "bar/action31";
    }

Spring MVC的配置文件内容如下:

代码语言:javascript复制
    <!-- 视图解析器 -->
    <bean
        class="org.springframework.web.servlet.view.InternalResourceViewResolver"
        id="internalResourceViewResolver">
        <!-- 前缀 -->
        <property name="prefix" value="/WEB-INF/views/" />
        <!-- 后缀 -->
        <property name="suffix" value=".jsp" />
    </bean>

实际url=/WEB-INF/views/bar/action31.jsp

2.2.2、String作为内容输出

如果方法声明了注解@ResponseBody ,将内容或对象作为 HTTP 响应正文返回,并调用适合HttpMessageConverter的Adapter转换对象,写入输出流。些时的String不再是路径而是内容,示例脚本如下:

代码语言:javascript复制
    @RequestMapping("/action32")
    @ResponseBody
    public String action32()
    {
        return "not <b>path</b>,but <b>content</b>";
    }

测试运行结果:

2.3、返回值为void

void在普通方法中是没有返回值的意思,但作为请求处理方法并非这样,存在如下两种情况:

2.3.1、方法名默认作为视图名

当方法没有返回值时,方法中并未指定视图的名称,则默认视图的名称为方法名,如下代码所示:

代码语言:javascript复制
    @RequestMapping("/action33")
    public void action33()
    {
    }

直接会去访问的路径是:url=/WEB-INF/views/bar/action33.jsp,bar是当前控制器映射的路径,action33是方法名,上面的代码等同于:

代码语言:javascript复制
    @RequestMapping("/action33")
    public String action33()
    {
        return "bar/action33";  //bar是控制器的路径
    }

可见URL= prefix前缀 控制器路径 方法名称  suffix后缀组成。

2.3.2、直接响应输出结果

当方法的返回值为void,但输出流中存在输出内容时,则不会去查找视图,而是将输入流中的内容直接响应到客户端,响应的内容类型是纯文本,如下代码所示:

代码语言:javascript复制
    @RequestMapping("/action34")
    public void action34(HttpServletResponse response) throws IOException
    {
        response.getWriter().write("<h2>void method</h2>");
    }

运行结果如下:

 可以看到h2标签并未渲染成标题。

2.4、返回值为ModelAndView

 在旧的Spring MVC中ModelAndView使用频率非常高,它可以同时指定须返回的模型与视图对象或名称,示例代码如下:

代码语言:javascript复制
    @RequestMapping("/action35")
    public ModelAndView action35() 
    {
        //1只指定视图
        //return new ModelAndView("/bar/index");
        
        //2分别指定视图与模型
        //Map<String, Object> model=new HashMap<String,Object>();
        //model.put("message", "ModelAndView action35");
        //return new ModelAndView("/bar/index",model);
        
        //3同时指定视图与模型
        //return new ModelAndView("/bar/index","message","action35 ModelAndView ");
        
        //4分开指定视图与模型
        ModelAndView modelAndView=new ModelAndView();
        //指定视图名称
        modelAndView.setViewName("/bar/index");
        //添加模型中的对象
        modelAndView.addObject("message", "<h2>Hello ModelAndView</h2>");
        return modelAndView;
    }

ModelAndView有个多构造方法重载,单独设置属性也很方便,运行结果如下:

2.5、返回值为Map

当返回结果为Map时,相当于只是返回了Model,并未指定具体的视图,返回视图的办法与void是一样的,即URL= prefix前缀 控制器路径 方法名称  suffix后缀组成,示例代码如下:

代码语言:javascript复制
    @RequestMapping("/action36")
    public Map<String, Object> action36()
    {
        Map<String, Object> model=new HashMap<String,Object>();
        model.put("message", "Hello Map");
        model.put("other", "more item");
        return model;
    }

实际访问的路径是:/SpringMVC03/WEB-INF/views/bar/action36.jsp,返回给客户端的map相当于模型,在视图中可以取出。

2.6、返回值为任意类型

 2.6.1、返回值为基本数据类型

当返回结果直接为int,double,boolean等基本数据类型时的状态,测试代码如下:

代码语言:javascript复制
    @RequestMapping("/action37")
    public Integer action37()
    {
        return 9527;
    }

测试运行的结果是:exception is java.lang.IllegalArgumentException: Unknown return value type异常。

如果确实需要直接将基本数据类型返回,则可以使用注解@ReponseBody。

代码语言:javascript复制
    @RequestMapping("/action38")
    @ResponseBody
    public int action38()
    {
        return 9527;
    }

运行结果:

2.6.2、当返值为自定义类型

当返回值为自定义类型时Spring会把方法认为是视图名称,与返回值为void的类似办法处理URL,但页面中获得数据比较麻烦,示例代码如下:

代码语言:javascript复制
    @RequestMapping("/action39")
    public Product action39()
    {
        return new Product(1,"iPhone",1980.5);
    }

如果存在action39对应的视图,页面还是可以正常显示。

如果在action上添加@ResponseBody注解则返回的是Product本身,而非视图,Spring会选择一个合适的方式解析对象,默认是json。示例代码如下:

代码语言:javascript复制
    @RequestMapping("/action39")
    @ResponseBody
    public Product action39()
    {
        return new Product(1,"iPhone",1980.5);
    }

运行结果:

如果是接收json值,则需要使用注解@RequestBody指定在相应参数上。

2.7、返回值为Model类型

 该接口Model定义在包org.springframework.ui下,model对象会用于页面渲染,视图路径使用方法名,与void类似。示例代码如下:

代码语言:javascript复制
    @RequestMapping("/action40")
    public Model action40(Model model)
    {
        model.addAttribute("message", "返回类型为org.springframework.ui.Model");
        return model;
    }

运行结果:

2.8、自定义输出内容

2.8.1、输出Excel

返回的类型还有许多如view等,通过view可指定一个具体的视图,如下载Excel、Pdf文档,其实它们也修改http的头部信息,手动同样可以实现,如下代码所示:

代码语言:javascript复制
    @RequestMapping("/action41")
    @ResponseBody
    public String action41(HttpServletResponse response)
    {
        response.setHeader("Content-type","application/octet-stream");         
        response.setHeader("Content-Disposition","attachment; filename=table.xls");
        return "<table><tr><td>Hello</td><td>Excel</td></tr></table>";
    }

运行结果:

Content-disposition解释:

Content-disposition 是 MIME 协议的扩展,MIME 协议指示 MIME 用户代理如何显示附加的文件。当 Internet Explorer 接收到头时,它会激活文件下载对话框,它的文件名框自动填充了头中指定的文件名。(请注意,这是设计导致的;无法使用此功能将文档保存到用户的计算机上,而不向用户询问保存位置。)

服务端向客户端游览器发送文件时,如果是浏览器支持的文件类型,一般会默认使用浏览器打开,比如txt、jpg等,会直接在浏览器中显示,如果需要提示用户保存,就要利用Content-Disposition进行一下处理,关键在于一定要加上attachment:

代码语言:javascript复制
Response.AppendHeader("Content-Disposition","attachment;filename=FileName.txt");

Content-Type解释:

MediaType,即是Internet Media Type,互联网媒体类型;也叫做MIME类型,在Http协议消息头中,使用Content-Type来表示具体请求中的媒体类型信息。

例如: Content-Type: text/html;charset:utf-8;

常见的MIME:

代码语言:javascript复制
 常见的媒体格式类型如下:

    text/html : HTML格式
    text/plain :纯文本格式      
    text/xml :  XML格式
    image/gif :gif图片格式    
    image/jpeg :jpg图片格式 
    image/png:png图片格式
   以application开头的媒体格式类型:

   application/xhtml xml :XHTML格式
   application/xml     : XML数据格式
   application/atom xml  :Atom XML聚合格式    
   application/json    : JSON数据格式
   application/pdf       :pdf格式  
   application/msword  : Word文档格式
   application/octet-stream : 二进制流数据(如常见的文件下载)
   application/x-www-form-urlencoded : <form encType=””>中默认的encType,form表单数据被编码为key/value格式发送到服务器(表单默认的提交数据的格式)
   另外一种常见的媒体格式是上传文件之时使用的:

    multipart/form-data : 需要在表单中进行文件上传时,就需要使用该格式

2.8.2、导出XLS时增加BOM头部解决乱码问题

action代码如下:

代码语言:javascript复制
//21.下载附件,导出Excel,xls
    @RequestMapping("/act21")
    @ResponseBody
    public void act21(HttpServletResponse response) throws IOException {

        //POI
        //response.setContentType("text/html;charset=utf-8");
        //response.setCharacterEncoding("utf-8");

        //问题:下载xls问题用excel打开乱码,用notepad  等工具转成UTF-8格式(带BOM)可以正常打开。
        //解决:严格来说这并不是xls文件的问题,而是Excel处理文件编码方式问题,Excel默认并不是以UTF-8来打开文件,所以在xls开头加入BOM,告诉Excel文件使用utf-8的编码方式。
        response.setHeader("Content-Type","application/octet-stream;charset=utf-8");
        response.setHeader("Content-Disposition","attachment;filename=Cars.xls");
        PrintWriter out = response.getWriter();
        //加上bom头,解决excel打开乱码问题
        byte[] bomStrByteArr = new byte[] { (byte) 0xef, (byte) 0xbb, (byte) 0xbf };
        String bomStr = new String(bomStrByteArr, "UTF-8");
        out.write(bomStr);

        StringBuffer str=new StringBuffer("");
        str.append("<table border=1 width=100%>");
        str.append("<tr><th>编号</th><th>名称</th><th>价格</th></tr>");

        for (Car car: Car.cars) {
            str.append("<tr><td>" car.getId() "</td><td>" car.getName() "</td><td>" car.getPrice() "</td></tr>");
        }

        str.append("</table>");
        out.write(str.toString());
    }

导出结果:

2.8.3、导出CSV格式

上面的方式并非Excel原生支持的,只是转换HTML的结果,转换成csv更好,占用空间更少。

逗号分隔值CSV

代码语言:javascript复制
逗号分隔值(Comma-Separated Values,CSV,有时也称为字符分隔值,因为分隔字符也可以不是逗号),其文件以纯文本形式存储表格数据(数字和文本)。纯文本意味着该文件是一个字符序列,不含必须像二进制数字那样被解读的数据。CSV文件由任意数目的记录组成,记录间以某种换行符分隔;每条记录由字段组成,字段间的分隔符是其它字符或字符串,最常见的是逗号或制表符。通常,所有记录都有完全相同的字段序列。通常都是纯文本文件。建议使用WORDPAD或是记事本(NOTE)来开启,再则先另存新档后用EXCEL开启,也是方法之一。

规则

代码语言:javascript复制
1、开头是不留空,以行为单位。
2、可含或不含列名,含列名则居文件第一行。
3、一行数据不跨行,无空行。
4、以半角逗号(即,)作分隔符,列为空也要表达其存在。
5、列内容如存在半角引号(即"),替换成半角双引号("")转义,即用半角引号(即"")将该字段值包含起来。
6、文件读写时引号,逗号操作规则互逆。
7、内码格式不限,可为 ASCII、Unicode 或者其他。
8、不支持数字
9、不支持特殊字符

示例

代码语言:javascript复制
1997,Ford,E350,"ac, abs, moon",3000.00
1999,Chevy,"Venture ""Extended Edition""","",4900.00
1999,Chevy,"Venture ""Extended Edition, Very Large""","",5000.00
1996,Jeep,Grand Cherokee,"MUST SELL!

 action代码如下:

代码语言:javascript复制
//22.下载附件,导出Excel,csv
    @RequestMapping("/act22")
    @ResponseBody
    public void act22(HttpServletResponse response) throws IOException {

        //POI
        //response.setContentType("text/html;charset=utf-8");
        //response.setCharacterEncoding("utf-8");
        response.setHeader("Content-Type","application/octet-stream;charset=utf-8");
        response.setHeader("Content-Disposition","attachment;filename=Cars.csv");
        PrintWriter out = response.getWriter();
        //加上bom头,解决excel打开乱码问题
        byte[] bomStrByteArr = new byte[] { (byte) 0xef, (byte) 0xbb, (byte) 0xbf };
        String bomStr = new String(bomStrByteArr, "UTF-8");
        out.write(bomStr);

        StringBuffer str=new StringBuffer("");
        str.append("编号,名称,价格rn");
        for (Car car: Car.cars) {
            str.append(car.getId() "," car.getName() "," car.getPrice() "rn");
        }
        response.getWriter().write(str.toString());
    }

结果:

2.8、@ResponseBody

默认情况下面Spring MVC会使用如下流程处理请求与响应结果:

代码语言:javascript复制
@ResponseBody是作用在方法上的,@ResponseBody 表示该方法的返回结果直接写入 HTTP response body 中,一般在异步获取数据时使用【也就是AJAX】,在使用 @RequestMapping后,返回值通常解析为跳转路径,但是加上 @ResponseBody 后返回结果不会被解析为跳转路径,而是直接写入 HTTP response body 中。 比如异步获取 json 数据,加上 @ResponseBody 后,会直接返回 json 数据。@RequestBody 将 HTTP 请求正文插入方法中,使用适合的 HttpMessageConverter 将请求体写入某个对象。

如下面的源代码所示,ResponseBody可以同时注解在方法与控制器上面:

代码语言:javascript复制
/*
 * Copyright 2002-2014 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.bind.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Annotation that indicates a method return value should be bound to the web
 * response body. Supported for annotated handler methods in Servlet environments.
 *
 * <p>As of version 4.0 this annotation can also be added on the type level in
 * which case it is inherited and does not need to be added on the method level.
 *
 * @author Arjen Poutsma
 * @since 3.0
 * @see RequestBody
 * @see RestController
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {

}

如果注解在控制器上面将作用与每一个方法上,每个方法都将受到影响。

2.9、@RestController

Spring 4 MVC中提供的@RestController,使用最少的代码来构建一个Restful Web Service,支持返回xml或json数据,这个可以让用户选择,通过URL后缀.xml或.json来完成。

代码语言:javascript复制
REST即表述性状态传递(英文:Representational State Transfer,简称REST)是Roy Fielding博士在2000年他的博士论文中提出来的一种软件架构风格。它是一种针对网络应用的设计和开发方式,可以降低开发的复杂性,提高系统的可伸缩性。
目前在三种主流的Web服务实现方案中,因为REST模式的Web服务与复杂的SOAP和XML-RPC对比来讲明显的更加简洁,越来越多的web服务开始采用REST风格设计和实现。例如,Amazon.com提供接近REST风格的Web服务进行图书查找;雅虎提供的Web服务也是REST风格的。

Rest风格的URL:

代码语言:javascript复制
新增: http://www.zhangguo.com/order      POST 
修改: http://www.zhangguo.com/order/1   PUT update?id=1 
获取:http://www.zhangguo.com/order/1     GET get?id=1 
删除: http://www.zhangguo.com/order/1   DELETE delete?id=1

实现方法一:

代码语言:javascript复制
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping(value="/restservice")
public class RestService {
    
    public final static String SUCCEEDD="show";
    
    
    /**
     * get请求
     * url:  http://localhost:8080/springmvc/restservice/testRestGet/12
     * @param id  
     *         查询的参数
     * @return
     */
    @RequestMapping(value="/testRestGet/{id}",method=RequestMethod.GET)
    public String testRestGet(@PathVariable("id") Integer id){
        System.out.println("rest 风格的GET请求..........id="  id);
        return SUCCEEDD;
    }
    /**
     * post新增 
     * url:  http://localhost:8080/springmvc/restservice/testRestPost
     * @return
     */
    @RequestMapping(value="/testRestPost",method=RequestMethod.POST)
    public String testRestPost(){
        System.out.println("rest 风格的POST请求.......... ");
        return SUCCEEDD;
    }
    /**
     * PUT 修改操作
     * url:  http://localhost:8080/springmvc/restservice/testRestPut/put123
     * @param name
     * @return
     */
    @RequestMapping(value="/testRestPut/{name}",method=RequestMethod.PUT)
    public String testRestPut(@PathVariable("name") String name){
        System.out.println("rest 风格的PUT请求..........name=" name);
        return SUCCEEDD;
    }
    /**
     *   DELETE删除操作
     *   url: http://localhost:8080/springmvc/restservice/testRestDelete/11
     * @param id
     * @return
     */
    @RequestMapping(value="/testRestDelete/{id}",method=RequestMethod.DELETE)
    public String testRestDelete(@PathVariable Integer id){
        System.out.println("rest 风格的DELETE请求..........id=" id);
        return SUCCEEDD;
    }
    
       
}

不难发现@RestController是继承自@Controller与@ResponseBody的,所以它同时拥有他们的特性:

代码语言:javascript复制
/*
 * Copyright 2002-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.bind.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.stereotype.Controller;

/**
 * A convenience annotation that is itself annotated with
 * {@link Controller @Controller} and {@link ResponseBody @ResponseBody}.
 * <p>
 * Types that carry this annotation are treated as controllers where
 * {@link RequestMapping @RequestMapping} methods assume
 * {@link ResponseBody @ResponseBody} semantics by default.
 *
 * <p><b>NOTE:</b> {@code @RestController} is processed if an appropriate
 * {@code HandlerMapping}-{@code HandlerAdapter} pair is configured such as the
 * {@code RequestMappingHandlerMapping}-{@code RequestMappingHandlerAdapter}
 * pair which are the default in the MVC Java config and the MVC namespace.
 * In particular {@code @RestController} is not supported with the
 * {@code DefaultAnnotationHandlerMapping}-{@code AnnotationMethodHandlerAdapter}
 * pair both of which are also deprecated.
 *
 * @author Rossen Stoyanchev
 * @author Sam Brannen
 * @since 4.0
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {

    /**
     * The value may indicate a suggestion for a logical component name,
     * to be turned into a Spring bean in case of an autodetected component.
     * @return the suggested component name, if any
     * @since 4.0.1
     */
    String value() default "";

}

可以轻松的将上面的代码修改为@RestController注解实现。

代码语言:javascript复制
package com.zhangguo.springmvc01.controller;

import com.zhangguo.springmvc01.entities.Car;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/car")
public class CarController {
    
    @GetMapping("/{id}")
    public Car getCar(@PathVariable int id){
        return null;
    }

    @DeleteMapping("/{id}")
    public Car deleteCar(@PathVariable int id){
        return null;
    }
    
    @PostMapping
    public Car addCar(Car car){
        return null;
    }

    @PutMapping
    public Car updateCar(Car car){
        return null;
    }
    
}

2.10、小结

使用 String 作为请求处理方法的返回值类型是比较通用的方法,这样返回的逻辑视图名不会和请求 URL 绑定,具有很高的灵活性,而模型数据又可以通过Model控制。

使用void,map,Model时,返回对应的逻辑视图名称真实url为:prefix前缀 控制器路径 方法名 suffix后缀组成。

使用String,ModelAndView返回视图名称可以不受请求的url绑定,ModelAndView可以设置返回的视图名称。

另外在非MVC中使用的许多办法在Action也可以使用。

三、Spring MVC乱码解决方法

3.1、页面编码

代码语言:javascript复制
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>

3.2、URL中的乱码

  改tomcat中server.xml中Connector的port=“8080”,加上一个 URIEncoding=”utf-8”

3.3、配置过滤器,指定所有请求的编码

(1)配置spring的编码过滤器,为了防止spring中post方式提交的时候中文乱码,方法:修改web.xml文件,添加spring的编码过滤器

代码语言:javascript复制
<!-- 配置编码方式过滤器,注意一点:要配置在所有过滤器的前面 -->
  <filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>utf-8</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  (2)配置编码过滤器,方法:先创建filter类,再修改web.xml文件,注意的是放在spring的编码过滤器之后

  filter类:

代码语言:javascript复制
package com.qiyuan.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class EncoidingFilter implements Filter {

    private String encoding="";
    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }

    
    //过滤方法  是否往下执行
    @Override
    public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request=(HttpServletRequest)arg0;
        HttpServletResponse response=(HttpServletResponse)arg1;
        
        request.setCharacterEncoding(encoding);
        response.setCharacterEncoding(encoding);

        //过滤通行证
        chain.doFilter(request, response);
    }

    //根据web.xml文件的配置进行初始化  
    @Override
    public void init(FilterConfig arg0) throws ServletException {
        this.encoding = arg0.getInitParameter("Encoding");
        
    }

}

  修改web.xml,添加如下配置:

代码语言:javascript复制
<!-- 配置编码过滤 -->
   <filter>
    <filter-name>EncoidingFilter</filter-name>
    <filter-class>com.qiyuan.filter.EncoidingFilter</filter-class>
    
    <init-param>
       <param-name>Encoding</param-name>
       <param-value>utf-8</param-value>
    </init-param>
  </filter>
  
  <filter-mapping>
       <filter-name>EncoidingFilter</filter-name>
       <url-pattern>/*</url-pattern>
  </filter-mapping> 

3.4、文件编码

  将文件另存为utf-8格式

3.5、数据库编码

  连接字符串指定编码格式

代码语言:javascript复制
public static String URL="jdbc:mysql://127.0.0.1:3306/mvcdb?useUnicode=true&characterEncoding=UTF-8"

  创建数据库的时候指定utf-8编码格式

3.6、IDE中文件与工程的编码

工程编码(最后一开始建立工程就设置整个工程的编码,如UTF-8)

四、示例

点击下载示例

https://zhangguo5.coding.net/public/SpringMVCDemo/SpringMVCDemo/git

五、视频

https://www.bilibili.com/video/av16991874/

六、作业

1、重现文中所有示例

2、定义一个员工实体(Employee),实现批量添加员工功能,在表单中可以一次添加多个员工,数据可以不持久化,使用JSTL渲染页面,数据要发到服务器后再响应到页面中

 3、继续完善个人项目的前后台页面

4、定义一个员工实体(Employee),实现批量添加员工功能,在表单中可以一次添加多个员工,数据可以不持久化,使用AJAX渲染页面,数据要发到服务器后再响应到页面中

5、个人项目后台至少3个页面(登录,主页,二级页面),前端至少6个页面

6、将第4题修改为CRUD,使用JSTL实现,使用集合,可以不持久化到数据库。

7、升级员工管理功能,实现如下功能:

 7.1、添加

 7.2、修改

 7.3、删除

 7.4、批量删除

 7.5、查询

 7.6、导出(XLS、CSV、DOC)

 7.7、导入(上传,POI解析,组员选作)

尝试使用Rest风格

0 人点赞