SSM-SpringMVC(二)-返回值、JSP、路径问题、响应码
常见返回值类型
-
返回给客户端的内容通常有以下
无返回值 文件:SpringMVC已经处理,不需要再处理 普通文本、HTML文本 XML JSON JSP
无返回值
-
跟之前的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文本
-
普通文本
//produces:响应数据为普通文本,编码为UTF8 @RequestMapping(value = "/get", produces = "text/plain; charset=UTF-8") @ResponseBody public String get() { return "This is xxx"; }
-
html文本
//produces:响应数据为html格式,编码为UTF8 @RequestMapping(value = "/get", produces = "text/html; charset=UTF-8") @ResponseBody public String get() { return "<h1>This is xxx</h1>"; }
XML
-
要求返回给客户端的数据为:
<person name="Jack" age="20"> <car name="BMW" price="123"/> </person>
方法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>"; }
- 部署项目,客户端访问:
http://localhost:8080/mvc02/xml1
方法2:
-
响应方法改造如下:
@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; }
-
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>
-
在applicationContext.xml中必须设置
<mvc:annotation-driven/>
-
模型类设置
//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; } }
-
部署项目,客户端访问:
http://localhost:8080/mvc02/xml2
JSON
方法一:
-
响应方法如下:
@RequestMapping(value = "/json1", produces = "application/json; charset=UTF-8") @ResponseBody public String json1() { // 用第三方库将Model对象转为XML字符串(很多这样的库) return "{\"name\":\"华仔\",\"age\":20,\"car\":{\"name\":\"Bently\",\"price\":100}}"; }
-
部署项目,客户端访问:
http://localhost:8080/mvc02/json1
方法二:
-
pom.xml添加依赖
<!-- Model转JSON字符串 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.11.0</version> </dependency>
-
响应方法如下:
//如果添加了依赖,而且返回值为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; }
- 在applicationContext.xml中必须设置
<mvc:annotation-driven/>
- 部署项目,客户端访问:
http://localhost:8080/mvc02/json2
- 注意:如果添加了依赖jackson-databind,一个controller的响应方法直接返回Model对象或者Model对象的集合,那么SpringMVC,会默认将这个Model对象或者Model对象的集合转化为json串
全局设置响应数据的编码
-
从上面的示例可以看出,Controller的每个响应方法都需要通过一下方式设置响应数据的编码
produces = "application/json; charset=UTF-8")
-
也可以全局设置响应数据的编码,在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>
-
这样一来,针对全部所有的Controller,响应结果为 字符串、html、xml、json 的数据都设置了统一的编码格式,就不需要通过produces属性来设置了
JSP
- 之前讲到的流程是:客户端请求到Servlet,servelt到数据库查找数据,然后将数据转发给jsp,jsp进行数据填充,然后响应给客户端拼接好的页面
- 在webapp下新建一个page路径,下面存放一些jsp文件用于测试
1.返回值为ModelAndView
-
可以利用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; }
-
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
-
不加@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"; }
-
jsp2.jsp同上(略)
3.重定向
- 在view Name前面加上“redirect:”表示重定向
- 比如”redirect:/person.jsp”
- 参考源码:
org.springframework.web.servlet.view.UrlBasedViewResolver
- SpringMVC默认会将ModelAndView对象addbject的内容,通过请求参数的形式传递给重定向的页面
- 在重定向的页面中的可以通过
request.getParameter
或${param}
获取请求参数
- 在重定向的页面中的可以通过
- 举例使用:
- 返回值为String
//返回值为String @RequestMapping("/jsp3") public String jsp3(){ return "redirect:/page/jsp3.jsp"; }
-
返回值为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; }
-
jsp4.jsp
<body> This is jsp4.jsp <div> <%--两种方法取值--%> name:${param.name}, price:<%=request.getParameter("price")%> </div> </body>
- 返回值为String
4.mvc:view-controller
-
可以使用<mvc:view-controller>直接配置请求路径和viewName
<mvc:view-controller path="/mv5" view-name="/person.jsp"/>
- 当没有controller处理这个path时, 才会交给
<mvc:view-controller>
去处理- 即如果Controller也有处理代码,会覆盖标签
- 使用了
<mvc:view-controller>
后,建议加上<mvc:annotation-driven>
- 否则会导致controller无法处理请求
- 举例使用:
-
在applicationContext.xml中添加
<!--为重定向--> <!-- view-name="redirect:/WEB-INF/page/jsp5.jsp" --> <!--为转发--> <mvc:view-controller path="/jsp5" view-name="/WEB-INF/page/jsp5.jsp"/>
-
InternalResourceViewResolver
- 可以通过InternalResourceViewResolver设置视图路径的公共前缀、后缀
- 受InternalResourceViewResolver影响的有
- 通过返回值ModelAndView设置viewName
- 通过返回值String设置的viewName
- 通过
<mvc:view-controller>
设置的viewName - 可以配置多个InternalResourceViewResolver
- order值越小,优先级越高
- 举例:
-
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>
-
控制器监听
@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
- 从上面可以看到,只要设置了InternalResourceViewResolver,所有的响应形式都会受到影响,那么如何让某些响应避免呢?
- 有2种方法可以忽略InternalResourceViewResolver
- 方法①:在viewName前面加上“forward:”或者”redirect:”
- 方法②:通过ModelAndView的setView方法
-
举例:(下面的都不会受影响)
//不会自动拼接参数,不受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; }
- 实际上,之前通过返回值String、ModelAndView设置viewName之后
- SpringMVC内部会根据具体情况创建对应的View对象,本质还是封装成一个View对象
InternalResourceView、JstlView、RedirectView
- SpringMVC内部会根据具体情况创建对应的View对象,本质还是封装成一个View对象
- InternalResourceViewResolver影响的是:没有带“forward:”、”redirect:”的viewName
自定义InternalResourceViewResolver
- 上面讲过,使用order属性可以设置多个InternalResourceViewResolver,但是只会使用优先级最高的那个,一旦访问错误,并不会自动切换到其他优先级,那么如何可以设置多个,而且一旦任何一个有问题,可以自动切换其他的呢?—自定义InternalResourceViewResolver
- 上面说过,返回值的本质还是会自动封装层一个View对象,上面那三种view有个共同的父类AbstractUrlBasedView,这个父类决定了是否调用其他的InternalResourceViewResolver
- InternalResourceViewResolver内部原理:
- 会拿到Controller的return的返回字符串,然后将prefix、suffix前后拼接
- 创建一个InternalResourceView
- InternalResourceView有个方法checkResource,作用是检查资源是否存在,默认返回值是true是直接设置存在。
- InternalResourceViewResolver会调用InternalResourceView的这个方法判断资源是否存在
- 因此即使资源不存在,也会去执行,所以会报错404,不会自动切换
- 因此可以创建一个子类View,也继承AbstractUrlBasedView,然后重写父类checkResource方法,实现自动切换
- 举例:
-
自定义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(); } }
-
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>
- 如果Controller响应没有资源,优先级会从oder的值0~2一次切换调用查找
-
路径问题总结(重要!!!!)
- 在Java代码中,路径问题总结
- 假设请求路径是:
http://IP地址:端口/context_path/path1/path2/path3
- 假设转发路径是:
/page/test.jsp
- 以斜线(/)开头,参考路径是context_path
- 所以最终转发路径是:”http://IP地址:端口/context_path” + “/page/test.jsp”
- 假设转发路径是:
page/test.jsp
- 不以斜线(/)开头,参考路径是当前请求路径的上一层路径
- 所以最终转发路径是:”http://IP地址:端口/context_path/path1/path2/” + “page/test.jsp”
-
举例:
// 设置需要转发的页面路径 mv.setViewName("/page/jsp1.jsp");
- 假设请求路径是:
- 在jsp、html代码中,路径问题总结
- 假设请求路径是:
http://IP地址:端口/context_path/path1/path2/path3
- 假设跳转路径是:
/page/test.jsp
- 以斜线(/)开头,参考路径是”http://IP地址:端口”
- 所以最终转发路径是:”http://IP地址:端口” + “/page/test.jsp”
- 假设转发路径是:
page/test.jsp
,则同java -
举例:
//设置跳转路径 <a href="test/dog/jsp1">测试</a>
- 假设请求路径是:
响应码设置 @ResponseStatus
-
可以通过@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}}"; }