SSM-SpringMVC(二)-返回值、JSP、路径问题、响应码

常见返回值类型

  1. 返回给客户端的内容通常有以下

     无返回值
     文件:SpringMVC已经处理,不需要再处理
     普通文本、HTML文本
     XML
     JSON
     JSP
    

无返回值

  1. 跟之前的Servlet一样,直接写回给客户端

     @Controller
     public class VoidController {
         @RequestMapping("/void")
         public void testVoid(HttpServletRequest request,
                              HttpServletResponse response) throws Exception {
             //直接写回给客户端
             response.setContentType("text/html; charset=UTF-8");
             response.getWriter().write("ZH666");
         }
     }
    

普通文本、HTML文本

  1. 普通文本

     //produces:响应数据为普通文本,编码为UTF8
     @RequestMapping(value = "/get", produces = "text/plain; charset=UTF-8")
     @ResponseBody
     public String get() {
         return "This is xxx";
     }
    
  2. html文本

     //produces:响应数据为html格式,编码为UTF8
     @RequestMapping(value = "/get", produces = "text/html; charset=UTF-8")
     @ResponseBody
     public String get() {
         return "<h1>This is xxx</h1>";
     }
    

XML

  1. 要求返回给客户端的数据为:

     <person name="Jack" age="20">
         <car name="BMW" price="123"/>
     </person>
    

方法1:

  1. 响应方法如下:
     @RequestMapping(value = "/xml1", produces = "application/xml; charset=UTF-8")
     @ResponseBody
     public String xml1() {
         Person person = new Person();
         person.setName("小xx");
         person.setAge(20);
        
         Car car = new Car();
         car.setName("Bently");
         car.setPrice(100);
         person.setCar(car);
        
         // 用第三方库将Model对象转为XML字符串(很多这样的库)
        
         return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
                 + "<person name=\"" + person.getName() + "\" age=\"" + person.getAge() + "\">"
                     + "<car name=\"" + car.getName() + "\" price=\"" + car.getPrice() + "\"/>"
                 + "</person>";
     }
    
  2. 部署项目,客户端访问:http://localhost:8080/mvc02/xml1

方法2:

  1. 响应方法改造如下:

     @RequestMapping(value = "/xml2")
     @ResponseBody
     public Person xml2() {
         Person person = new Person();
         person.setName("Jack");
         person.setAge(20);
    
         Car car = new Car();
         car.setName("Bently");
         car.setPrice(100);
         person.setCar(car);
    
         return person;
     }
    
  2. pom.xml添加依赖

     <!-- Model转XML字符串 -->
     <dependency>
         <groupId>javax.xml.bind</groupId>
         <artifactId>jaxb-api</artifactId>
         <version>2.4.0-b180830.0359</version>
     </dependency>
    
     <dependency>
         <groupId>javax.xml</groupId>
         <artifactId>jaxb-impl</artifactId>
         <version>2.1</version>
     </dependency>
    
  3. 在applicationContext.xml中必须设置

     <mvc:annotation-driven/>
    
  4. 模型类设置

     //person
     //告诉上面那两个依赖,这个类要生成xml,根节点为person
     @XmlRootElement(name = "person")
     public class Person {
         private String name;
         private Integer age;
         private Car car;
        
         //为根节点下的另外一个标签元素
         //@XmlElement
         //为根节点的属性
         @XmlAttribute
         public String getName() {
             return name;
         }
        
         public void setName(String name) {
             this.name = name;
         }
        
         @XmlAttribute
         public Integer getAge() {
             return age;
         }
        
         public void setAge(Integer age) {
             this.age = age;
         }
        
         @XmlElement
         public Car getCar() {
             return car;
         }
        
         public void setCar(Car car) {
             this.car = car;
         }
     }
        
     //Car
     //如果不设置name,则默认是小写
     @XmlRootElement
     public class Car {
         private String name;
         private Integer price;
        
         @XmlAttribute
         public String getName() {
             return name;
         }
        
         public void setName(String name) {
             this.name = name;
         }
        
         @XmlAttribute
         public Integer getPrice() {
             return price;
         }
        
         public void setPrice(Integer price) {
             this.price = price;
         }
     }
    
  5. 部署项目,客户端访问:http://localhost:8080/mvc02/xml2

JSON

方法一:

  1. 响应方法如下:

     @RequestMapping(value = "/json1", produces = "application/json; charset=UTF-8")
     @ResponseBody
     public String json1() {
    
         // 用第三方库将Model对象转为XML字符串(很多这样的库)
         return "{\"name\":\"华仔\",\"age\":20,\"car\":{\"name\":\"Bently\",\"price\":100}}";
     }
    
  2. 部署项目,客户端访问:http://localhost:8080/mvc02/json1

方法二:

  1. pom.xml添加依赖

     <!-- Model转JSON字符串 -->
     <dependency>
         <groupId>com.fasterxml.jackson.core</groupId>
         <artifactId>jackson-databind</artifactId>
         <version>2.11.0</version>
     </dependency>
    
  2. 响应方法如下:

     //如果添加了依赖,而且返回值为Model对象,那么会自动转为json返回
     @RequestMapping(value = "/json2")
     @ResponseBody
     public Student json2() {
         Student student = new Student();
         student.setName("Jack");
         student.setAge(20);
         student.setNickNames(List.of("123", "456", "789"));
    
         Dog dog = new Dog();
         dog.setName("Bently");
         dog.setPrice(100);
         student.setDog(dog);
    
         return student;
     }
    
  3. 在applicationContext.xml中必须设置<mvc:annotation-driven/>
  4. 部署项目,客户端访问:http://localhost:8080/mvc02/json2
  5. 注意:如果添加了依赖jackson-databind,一个controller的响应方法直接返回Model对象或者Model对象的集合,那么SpringMVC,会默认将这个Model对象或者Model对象的集合转化为json串

全局设置响应数据的编码

  1. 从上面的示例可以看出,Controller的每个响应方法都需要通过一下方式设置响应数据的编码

     produces = "application/json; charset=UTF-8")
    
  2. 也可以全局设置响应数据的编码,在applicationContext.xml中如下设置

     <mvc:annotation-driven>
         <mvc:message-converters>
             <!-- 影响返回值是String的内容,设置编码,上节讲过 -->
             <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                 <property name="defaultCharset" value="UTF-8"/>
             </bean>
    
             <!-- 影响返回值是Model对象(最后通过Jackson转成JSON字符串),设置编码 -->
             <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                 <property name="defaultCharset" value="UTF-8"/>
             </bean>
    
             <!-- 影响返回值是Model对象(最后通过JAXB转成XML字符串),设置编码 -->
             <bean class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter">
                 <property name="defaultCharset" value="UTF-8"/>
             </bean>
    
         </mvc:message-converters>
     </mvc:annotation-driven>
    
  3. 这样一来,针对全部所有的Controller,响应结果为 字符串、html、xml、json 的数据都设置了统一的编码格式,就不需要通过produces属性来设置了

JSP

  1. 之前讲到的流程是:客户端请求到Servlet,servelt到数据库查找数据,然后将数据转发给jsp,jsp进行数据填充,然后响应给客户端拼接好的页面
  2. 在webapp下新建一个page路径,下面存放一些jsp文件用于测试

1.返回值为ModelAndView

  1. 可以利用ModelAndView将数据和视图绑定到一起,返回给客户端

     //返回ModelAndView
     @RequestMapping("/jsp1")
     public ModelAndView jsp1(){
         ModelAndView mv = new ModelAndView();
         //如果这么创建,下面就不需要使用setViewName设置
         //ModelAndView mv = new ModelAndView("/page/jsp1.jsp");
         // 设置数据
         Dog dog = new Dog();
         dog.setName("Larry");
         dog.setPrice(200);
         // 本质就是request.setAttribute,将数据转发给jsp
         mv.addObject("dog", dog);
    
         // 设置需要转发的页面
         mv.setViewName("/page/jsp1.jsp");
         return mv;
     }
    
  2. jsp1.jsp内容:

     <%@ page contentType="text/html;charset=UTF-8" language="java" %>
     <html>
     <head>
         <title>Title</title>
     </head>
     <body>
        
     This is /page/jsp1.jsp
        
     <div>
         name:${dog.name}, price:${dog.price}
     </div>
        
     </body>
     </html>
    

2.返回值为String

  1. 不加@ResponseBody的String返回值,也代表viewName

     //返回String
     //注意:不能写@ResponseBody,一旦写@ResponseBody,意思就是将返回值直接给客户端。不写的意思就是转发
     @RequestMapping("/jsp2")
     //设置接收参数request
     public String jsp2(HttpServletRequest request){
         // 设置数据
         Dog dog = new Dog();
         dog.setName("Larry");
         dog.setPrice(200);
         // 本质就是request.setAttribute
         request.setAttribute("dog", dog);
         return "/page/jsp2.jsp";
     }
    
  2. jsp2.jsp同上(略)

3.重定向

  1. 在view Name前面加上“redirect:”表示重定向
    1. 比如”redirect:/person.jsp”
  2. 参考源码: org.springframework.web.servlet.view.UrlBasedViewResolver
  3. SpringMVC默认会将ModelAndView对象addbject的内容,通过请求参数的形式传递给重定向的页面
    1. 在重定向的页面中的可以通过request.getParameter${param}获取请求参数
  4. 举例使用:
    1. 返回值为String
       //返回值为String
       @RequestMapping("/jsp3")
       public String jsp3(){
           return "redirect:/page/jsp3.jsp";
       }
      
    2. 返回值为ModelAndView

       //返回值为ModelAndView
       @RequestMapping("/jsp4")
       public ModelAndView jsp4(){
           ModelAndView mv = new ModelAndView();
           // 设置数据,这么写会自动将数据拼接到url后面,等价于redirect:/page/jsp4.jsp?test=10&name=Jack&price=666
           mv.addObject("name", "Jack");
           mv.addObject("price", 666);
          
           // 设置页面
           mv.setViewName("redirect:/page/jsp4.jsp?test=10");
           return mv;
       }
      
    3. jsp4.jsp

       <body>
       This is jsp4.jsp
       <div>
           <%--两种方法取值--%>
           name:${param.name}, price:<%=request.getParameter("price")%>
       </div>
       </body>
      

4.mvc:view-controller

  1. 可以使用<mvc:view-controller>直接配置请求路径和viewName

     <mvc:view-controller path="/mv5" view-name="/person.jsp"/>
    
  2. 当没有controller处理这个path时, 才会交给<mvc:view-controller>去处理
    1. 即如果Controller也有处理代码,会覆盖标签
  3. 使用了<mvc:view-controller>后,建议加上<mvc:annotation-driven>
    1. 否则会导致controller无法处理请求
  4. 举例使用:
    1. 在applicationContext.xml中添加

       <!--为重定向-->
       <!-- view-name="redirect:/WEB-INF/page/jsp5.jsp"   -->
       <!--为转发-->
       <mvc:view-controller path="/jsp5" view-name="/WEB-INF/page/jsp5.jsp"/>
      

InternalResourceViewResolver

  1. 可以通过InternalResourceViewResolver设置视图路径的公共前缀、后缀
  2. 受InternalResourceViewResolver影响的有
    1. 通过返回值ModelAndView设置viewName
    2. 通过返回值String设置的viewName
    3. 通过<mvc:view-controller>设置的viewName
    4. 可以配置多个InternalResourceViewResolver
      1. order值越小,优先级越高
    5. 举例:
      1. applicationContext.xml

         <!-- 通过`<mvc:view-controller>`设置的viewName -->
         <!--拼接后实际转发的是:/WEB-INF/page2/jsp5.jsp-->
         <mvc:view-controller path="/jsp5" view-name="jsp5"/>
                    
         <!--order为0,优先级高于下面的-->
         <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
             <property name="order" value="0"/>
             <property name="prefix" value="/WEB-INF/page2/"/>
             <property name="suffix" value=".jsp"/>
         </bean>
                    
         <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
             <property name="order" value="1"/>
             <property name="prefix" value="/WEB-INF/page/"/>
             <property name="suffix" value=".jsp"/>
         </bean>
        
      2. 控制器监听

         @Controller
         @RequestMapping("/test")
         public class JspController2 {
             //通过返回值String设置的viewName
             @RequestMapping("/jsp1")
             public String jsp1() {
                 //拼接后实际转发的是:/WEB-INF/page2/jsp1.jsp
                 return "jsp1";
             }
                    
             //通过返回值ModelAndView设置viewName
             @RequestMapping("/jsp2")
             public ModelAndView jsp2() {
                 //拼接后实际转发的是:/WEB-INF/page2/jsp2.jsp
                 return new ModelAndView("jsp2");
             }
         }
        

忽略InternalResourceViewResolver

  1. 从上面可以看到,只要设置了InternalResourceViewResolver,所有的响应形式都会受到影响,那么如何让某些响应避免呢?
  2. 有2种方法可以忽略InternalResourceViewResolver
    1. 方法①:在viewName前面加上“forward:”或者”redirect:”
    2. 方法②:通过ModelAndView的setView方法
  3. 举例:(下面的都不会受影响)

     //不会自动拼接参数,不受InternalResourceViewResolver影响
     <mvc:view-controller path="/jsp5" view-name="forward:/WEB-INF/page/jsp5.jsp"/>
     //通过返回值String设置的viewName
     @RequestMapping("/jsp1")
     public String jsp1() {
         //没有拼接,直接转发的是 /page/jsp1.jsp
         return "forward:/page/jsp1.jsp";
     }
        
     //通过ModelAndView的setView方法
     @RequestMapping("/jsp3")
     public ModelAndView jsp3() {
         ModelAndView mv = new ModelAndView();
         /*
             InternalResourceView:转发
             JstlView:转发
             RedirectView:重定向
          */
         //转发
         //mv.setView(new InternalResourceView("/page/jsp3.jsp"));
         //注意:使用这个重定向的话不会自动拼接项目的服务路径!!!
         //比如项目访问路径为::http://localhost:8080/mvc02/test/jsp3
         //那么重定向后的路径为:http://localhost:8080/page/jsp3.jsp
         //没有mvc02这个根路径!!!,应该是http://localhost:8080/mvc02/page/jsp3.jsp才正确
         mv.setView(new RedirectView("/page/jsp3.jsp"));
         return mv;
     }
    
  4. 实际上,之前通过返回值String、ModelAndView设置viewName之后
    1. SpringMVC内部会根据具体情况创建对应的View对象,本质还是封装成一个View对象
      1. InternalResourceView、JstlView、RedirectView
  5. InternalResourceViewResolver影响的是:没有带“forward:”、”redirect:”的viewName

自定义InternalResourceViewResolver

  1. 上面讲过,使用order属性可以设置多个InternalResourceViewResolver,但是只会使用优先级最高的那个,一旦访问错误,并不会自动切换到其他优先级,那么如何可以设置多个,而且一旦任何一个有问题,可以自动切换其他的呢?—自定义InternalResourceViewResolver
  2. 上面说过,返回值的本质还是会自动封装层一个View对象,上面那三种view有个共同的父类AbstractUrlBasedView,这个父类决定了是否调用其他的InternalResourceViewResolver
  3. InternalResourceViewResolver内部原理:
    1. 会拿到Controller的return的返回字符串,然后将prefix、suffix前后拼接
    2. 创建一个InternalResourceView
    3. InternalResourceView有个方法checkResource,作用是检查资源是否存在,默认返回值是true是直接设置存在。
    4. InternalResourceViewResolver会调用InternalResourceView的这个方法判断资源是否存在
    5. 因此即使资源不存在,也会去执行,所以会报错404,不会自动切换
  4. 因此可以创建一个子类View,也继承AbstractUrlBasedView,然后重写父类checkResource方法,实现自动切换
  5. 举例:
    1. 自定义MyView继承自InternalResourceView

       //com.zh.view.MyView
       public class MyView extends InternalResourceView {
           //会根据返回值true活false是否切换
           @Override
           public boolean checkResource(Locale locale) throws Exception {
               // 根据实际情况来返回
               // 存在:返回true
               // 不存在:返回false
              
               // 项目部署的根路径 + /page/index.jsp
               //String path = getServletContext().getRealPath("/") + getUrl();
               //查看文件是否存在
               // 项目部署的根路径 + /page/index.jsp
               String path = getServletContext().getRealPath(getUrl());
               File file = new File(path);
               return file.exists();
           }
       }
      
    2. applicationContext.xml配置使用

       <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
           <property name="order" value="2"/>
           <property name="prefix" value="/WEB-INF/page3/"/>
           <property name="suffix" value=".html"/>
       </bean>
          
       <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
           <property name="order" value="0"/>
           <property name="prefix" value="/WEB-INF/page2/"/>
           <property name="suffix" value=".jsp"/>
           <property name="viewClass" value="com.zh.view.MyView"/>
       </bean>
          
       <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
           <property name="order" value="1"/>
           <property name="prefix" value="/WEB-INF/page/"/>
           <property name="suffix" value=".jsp"/>
           <property name="viewClass" value="com.zh.view.MyView"/>
       </bean>
      
      1. 如果Controller响应没有资源,优先级会从oder的值0~2一次切换调用查找

路径问题总结(重要!!!!)

  1. Java代码中,路径问题总结
    1. 假设请求路径是:http://IP地址:端口/context_path/path1/path2/path3
    2. 假设转发路径是:/page/test.jsp
      1. 以斜线(/)开头,参考路径是context_path
      2. 所以最终转发路径是:”http://IP地址:端口/context_path” + “/page/test.jsp”
    3. 假设转发路径是:page/test.jsp
      1. 不以斜线(/)开头,参考路径是当前请求路径的上一层路径
      2. 所以最终转发路径是:”http://IP地址:端口/context_path/path1/path2/” + “page/test.jsp”
    4. 举例:

       // 设置需要转发的页面路径
       mv.setViewName("/page/jsp1.jsp");
      
  2. jsp、html代码中,路径问题总结
    1. 假设请求路径是:http://IP地址:端口/context_path/path1/path2/path3
    2. 假设跳转路径是:/page/test.jsp
      1. 以斜线(/)开头,参考路径是”http://IP地址:端口”
      2. 所以最终转发路径是:”http://IP地址:端口” + “/page/test.jsp”
    3. 假设转发路径是:page/test.jsp,则同java
    4. 举例:

       //设置跳转路径
       <a href="test/dog/jsp1">测试</a>
      

响应码设置 @ResponseStatus

  1. 可以通过@ResponseStatus设置响应码

     @RequestMapping(value = "/json1", produces = "application/json")
     @ResponseStatus(value = HttpStatus.BAD_REQUEST,reason="这是一个错误!")
     @ResponseBody
     public String json1() {
         return "{\"name\":\"小码哥\",\"age\":20,\"car\":{\"name\":\"Bently\",\"price\":100}}";
     }
    
Table of Contents