SpringMVC --> 二、Controller和RestFul风格
狂神说
文章目录
4. 控制器和RestFul风格
4.1 控制器Controller
- Controller负责提供访问应用程序的行为,通常通过接口定义或注解定义两种方法实现;
- Controller负责解析用户的请求,并将其转换为一个Model;
- 在SpringMVC中,一个Controller类可以包含多个方法;
- 在SpringMVC中,对于Controller的配置方法有很多种。
4.2 实现Controller接口
Controller接口在
org.springframework.web.servlet.mvc
包下,该接口只有一个方法:处理请求并返回一个模型与视图对象ModelAndView
@FunctionalInterface
public interface Controller {
@Nullable
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
测试:
-
新建一个Moudle,和之前一样,添加web配置;
-
确定导入相关依赖,并刷新;
-
配置
web.xml
;<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
-
添加Spring MVC配置文件
resources/springmvc-servlet.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--视图解析器--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver"> <!--前缀--> <property name="prefix" value="/WEB-INF/jsp/"/> <!--后缀--> <property name="suffix" value=".jsp"/> </bean> </beans>
-
编写一个Controller类,
ControllerTest1.java
;public class ControllerTest1 implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { ModelAndView mv = new ModelAndView(); mv.addObject("msg","Hello ControllerTestOne"); mv.setViewName("test"); return mv; } }
-
在配置文件
resources/springmvc-servlet.xml
中注册请求的bean;<bean name="/t1" class="com.ano.controller.ControllerTest1"></bean>
-
编写前端页面
WEB-INF/jsp/test.jsp
;<%-- Created by IntelliJ IDEA. User: wangjiao Date: 2022/9/13 Time: 20:22 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> ${msg} </body> </html>
-
配置Tomcat运行测试OK!
实现Controller接口定义控制器是较老的方法;
缺点:一个控制器中只能有一个方法,如果多个方法则需要定义多个控制器,比较麻烦。
4.3 使用注解@Controller
-
@Controller注解类型用于声明Spring类的实例是一个控制器;
-
Spring可以使用扫描机制来找到应用程序中基于注解的控制类;
-
为了保证Spring能找到相应控制器,需要在配置文件中声明组件扫描,即在
springmvc-servlet.xml
中声明组件扫描:<context:component-scan base-package="com.ano.controller"></context:component-scan>
-
增加一个控制器类,使用注解实现,
ControllerTest2.java
@Controller public class ControllerTest2 { @RequestMapping("/t2") public String index(Model model) { model.addAttribute("msg","ControllerTest2"); return "test"; } }
-
运行tomcat测试OK!
可以发现,两个请求都可以指向同一个视图,但是页面结果的结果是不一样的,可以看出视图是被复用的,而控制器与视图之间是弱偶合关系;
注解方式是平时使用的最多的方式。
4.4 @RequestMapping
- 注解@RequestMapping用于映射url到控制器类或一个特定的处理程序方法;
- 用于类上时,表示类中所有响应请求的方法都是以该地址作为父路径。
-
只注解在方法上,
@Controller public class ControllerTest3 { @RequestMapping("/t3") public String test3(Model model) { model.addAttribute("msg","ControllerTest3"); return "test"; } }
访问路径为:ip:port/项目名/t3 该测试示例的访问路径即 http://localhost:8080/t3
-
同时注解在类和方法上
@Controller @RequestMapping("/admin") public class ControllerTest3 { @RequestMapping("/t3") public String test3(Model model) { model.addAttribute("msg","ControllerTest3"); return "test"; } }
访问路径为:ip:port/项目名/admin/t3 该测试示例的访问路径即http://localhost:8080/admin/t3
4.5 RestFul风格
-
Restful是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格。基于这个风格设计的软件可以更简洁、更有层次、更易于实现缓存等机制。
-
功能:互联网所有的事物都可以被抽象为资源,一般使用POST、DELETE、PUT、GET等不同方法对资源进行添加、删除、修改、查询操作。
-
传统方式操作资源:通过不同参数来实现不同的操作效果。方法比较单一,如下所示:
http://127.0.0.1/item/queryUser.action?id=1 查询,GET http://127.0.0.1/item/saveUser.action 新增,POST http://127.0.0.1/item/updateUser.action 更新,POST http://127.0.0.1/item/deleteUser.action?id=1 删除,GET或POST
-
使用RestFul风格操作资源,可以通过不同的请求方式来实现不同的效果,请求地址可能相同,但功能可以不同,如下所示:
http://127.0.0.1/item/1 查询,GET http://127.0.0.1/item 新增,POST http://127.0.0.1/item 更新,PUT http://127.0.0.1/item/1 删除,DELETE
-
测试示例
@Controller public class RestFulController { /** * 原生的URL:http://localhost:8080/add?a=1&b=2 */ @RequestMapping("/add") public String addNumTest1(int a, int b, Model model) { model.addAttribute("msg",a+b); return "test"; } /** * RestFul方式1:请求方式 method = GET * @GetMapping() = RequestMapping("/add/{a}/{b}",method=requestMethod.GET) * http://localhost:8080/add/2/3 */ @GetMapping("/add/{a}/{b}") public String addNumTest2(@PathVariable int a, @PathVariable int b, Model model) { model.addAttribute("msg",a+b); return "test"; } /** * RestFul方式2:请求方式 method = POST * 复用了相同的URL * http://localhost:8080/add/2/3 */ @PostMapping("/add/{a}/{b}") public String addNumTest3(@PathVariable int a, @PathVariable int b, Model model) { model.addAttribute("msg",a+b); return "test"; } }
5. 重定向与转发(结果跳转方式)
5.1 ModelAndView
-
通过设置ModelAndView对象,并根据view的名称和视图解析器跳转到指定的页面;
-
页面:{视图解析器前缀} + viewName +{视图解析器后缀}
<!--视图解析器--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver"> <!--前缀--> <property name="prefix" value="/WEB-INF/jsp/"/> <!--后缀--> <property name="suffix" value=".jsp"/> </bean>
-
Controller类通过实现Controller接口的方式实现
public class ControllerTest1 implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { ModelAndView mv = new ModelAndView(); mv.addObject("msg","Hello ControllerTestOne"); mv.setViewName("test"); return mv; } }
5.2 ServletAPI
-
通过设置ServletAPI的方式,不需要视图解析器。
-
示例:
@Controller public class ResultServletApi { /** * 输出 */ @RequestMapping("/result/t1") public void test1(HttpServletRequest request, HttpServletResponse response) throws IOException { response.getWriter().print("Hello, SpringMVC result by ServletAPI"); } /** * 重定向 */ @RequestMapping("/result/t2") public void test2(HttpServletRequest request, HttpServletResponse response) throws IOException { response.sendRedirect("/index.jsp"); } /** * 转发 */ @RequestMapping("/result/t3") public void test3(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { request.setAttribute("msg","result/t3"); request.getRequestDispatcher("/WEB-INF/jsp/test.jsp").forward(request,response); } }
-
通过HttpServletResponse进行输出
-
通过HttpServletResponse实现重定向
-
通过HttpServletRequest实现转发
5.3 SpringMVC
无需视图解析器的情况
- 默认为转发,forward可加可不加;
- 重定向redirect必须加上。
@Controller
public class ResultSpringMvc {
/**
* 转发
*/
@RequestMapping("/resM/t1")
public String test1(Model model) {
model.addAttribute("msg","/resM/t1");
return "/WEB-INF/jsp/test.jsp";
}
/**
* 转发
*/
@RequestMapping("/resM/t2")
public String test2(Model model) {
model.addAttribute("msg","/resM/t2");
return "forward:/WEB-INF/jsp/test.jsp";
}
/**
* 重定向
*/
@RequestMapping("/resM/t3")
public String test2() {
return "redirect:/index.jsp";
}
}
有视图解析器的情况
- 默认为转发,forward不可以加;
- 重定向redirect必须加上。
@Controller
public class ResultSpringMvc2 {
/**
* 转发
*/
@RequestMapping("/resM2/t1")
public String test1(Model model) {
model.addAttribute("msg","resM2/t1");
return "test";
}
/**
* 重定向
*/
@RequestMapping("/resM2/t2")
public String test2() {
return "redirect:/index.jsp";
}
}
6. 接受请求参数及数据回显
6.1 数据处理三种情况
- 提交的域名称和处理方法的参数名一致;
- 提交的域名称和处理方法的参数名不一致;
- 提交的数据是一个对象;
测试程序
@Controller
public class DataHandle {
/**
* 提交的域名称和处理方法的参数名一致
* http://localhost:8080/test?name=ano
*/
@RequestMapping("/test")
public String test(String name, Model model){
model.addAttribute("msg",name);
return "test";
}
/**
* 提交的域名称和处理方法的参数名不一致
* http://localhost:8080/test2?username=ano2
*/
@RequestMapping("/test2")
public String test2(@RequestParam("username") String name, Model model){
model.addAttribute("msg",name);
return "test";
}
/**
* 提交的参数是一个对象时,前端传的参数必须和对象名称一致,否则会null
* http://localhost:8080/user?name=ano&id=1001&age=18
* @param user
* @param model
* @return
*/
@RequestMapping("/user")
public String userInfo(User user, Model model) {
System.out.println(user);
model.addAttribute("msg",user.toString());
return "test";
}
}
实体类User
package com.ano.pojo;
/**
* @author wangjiao
* @version 1.0
* @date 2022/9/21 17:45
*/
public class User {
private int id;
private String name;
private int age;
public User() {
}
public User(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
6.2 数据回显到前端三种方式
-
第一种方式:ModelAndView
-
第二种方式:Model
-
第三种方式:ModelMap
简单对比:
ModelAndView 可以在储存数据的同时,可以进行设置返回的逻辑视图,进行控制展示层的跳转;
Model 只有寥寥几个方法只适合用于储存数据,简化了对于Model对象的操作和理解;
ModelMap 继承了 LinkedMap ,除了实现了自身的一些方法,同样的继承 LinkedMap 的方法和特性。
7. 乱码问题
一个简单的测试程序
-
在首页编写一个表单
index.jsp
<form action="/encoding/test" method="post"> <input type="text" name="name"> <input type="submit"> </form>
-
编写响应的Controller类及处理方法
@RequestMapping("/encoding/test") public String test(String name, Model model) { model.addAttribute("msg",name); return "test"; }
-
localhost:8080进入首页表单输入中文测试,发现会乱码
一般通过配置过滤器解决乱码问题。
7.1 自定义过滤器解决乱码
- 编写过滤器类
EncodingFilter.java
;
public class EncodingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
servletRequest.setCharacterEncoding("utf-8");
servletResponse.setCharacterEncoding("utf-8");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
- 在web.xml种配置乱码过滤器EncodingFilter;
<!--配置乱码过滤-->
<filter>
<filter-name>encoding</filter-name>
<filter-class>com.ano.filter.EncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
- 再次测试,成功解决乱码。
7.2 Spring框架提供的过滤器解决乱码
- 配置
web.xml
即可
<!--配置Spring框架提供的过滤器解决乱码-->
<filter>
<filter-name>encoding</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>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
- 再次测试,成功解决乱码。
一般情况下,Spring框架默认的这个过滤器就已经能够很好的解决乱码问题,但是有一些极端情况,Spring框架提供的过滤器对GET请求的支持不友好,处理方法如下:
- 修改Tomcat配置文件,设置编码
<Connector URIEncoding="utf-8" port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
- 自定义过滤器
package com.ano.filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Map;
/**
* 通用乱码过滤器:解决get和post请求全部乱码的过滤器
*/
public class GenericEncodingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
// 处理response的字符编码
HttpServletResponse myResponse=(HttpServletResponse) response;
myResponse.setContentType("text/html;charset=UTF-8");
// 转型为与协议相关对象
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
// 对request包装增强
HttpServletRequest myrequest = new MyRequest(httpServletRequest);
filterChain.doFilter(myrequest, response);
}
@Override
public void destroy() {
}
}
/**
* 自定义request对象,HttpServletRequest的包装类
*/
class MyRequest extends HttpServletRequestWrapper {
private HttpServletRequest request;
/**
* 是否编码的标记
*/
private boolean hasEncode;
/**
* 定义一个可以传入HttpServletRequest对象的构造函数,以便对其进行装饰
* @param request
*/
public MyRequest(HttpServletRequest request) {
// super必须写
super(request);
this.request = request;
}
/**
* 对需要增强方法 进行覆盖
* @return
*/
@Override
public Map getParameterMap() {
// 先获得请求方式
String method = request.getMethod();
if (method.equalsIgnoreCase("post")) {
// post请求
try {
// 处理post乱码
request.setCharacterEncoding("utf-8");
return request.getParameterMap();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} else if (method.equalsIgnoreCase("get")) {
// get请求
Map<String, String[]> parameterMap = request.getParameterMap();
// 确保get手动编码逻辑只运行一次
if (!hasEncode) {
for (String parameterName : parameterMap.keySet()) {
String[] values = parameterMap.get(parameterName);
if (values != null) {
for (int i = 0; i < values.length; i++) {
try {
// 处理get乱码
values[i] = new String(values[i]
.getBytes("ISO-8859-1"), "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
}
hasEncode = true;
}
return parameterMap;
}
return super.getParameterMap();
}
/**
* 取一个值
* @param name
* @return
*/
@Override
public String getParameter(String name) {
Map<String, String[]> parameterMap = getParameterMap();
String[] values = parameterMap.get(name);
if (values == null) {
return null;
}
// 取回参数的第一个值
return values[0];
}
/**
* 取所有值
* @param name
* @return
*/
@Override
public String[] getParameterValues(String name) {
Map<String, String[]> parameterMap = getParameterMap();
String[] values = parameterMap.get(name);
return values;
}
}
- 然后在web.xml中配置这个通用过滤器GenericEncodingFilter即可!