SpringMVC学习
官方文档:https://docs.spring.io/spring/docs/5.2.0.RELEASE/spring-framework-reference/web.html#spring-web
1. 介绍
MVC
Model(模型):数据模型,提供要展示的数据,因此包含数据和行为,可以认为是领域模型或JavaBean组件(包含数据和行为),不过现在一般都分离开来:Value Object(数据Dao) 和 服务层(行为Service)。也就是模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。如JavaBean。
-
取得表单数据
-
-
转向指定的页面
View(视图):负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西。如JSP。
- 页面显示
Controller(控制器):接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。也就是说控制器做了个调度员的工作。如Servlet。
-
业务逻辑
-
保存数据的状态
SpringMVC
Spring MVC是Spring Framework的一部分,是基于Java实现MVC的轻量级Web框架。Spring的web框架围绕DispatcherServlet [ 调度Servlet ] 设计。DispatcherServlet的作用是将请求分发到不同的处理器。
Spring MVC的特点:①轻量级,简单易学;②高效,基于请求响应的MVC框架;③与Spring兼容性好,无缝结合;④约定优于配置;⑤功能强大:RESTful、数据验证、格式化、本地化、主题等;⑥简洁灵活。
Spring MVC框架像许多其他MVC框架一样, 以请求为驱动,围绕一个中心Servlet分派请求及提供其他功能,DispatcherServlet实际上是一个的Servlet (它继承自HttpServlet 基类)。
2. HelloSpringMVC
2.1 实现方式
方式一:配置版
-
新建maven项目,导入公共依赖,之后只需在该项目建立子项目即可;
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.4</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.3</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> </dependencies>
-
新建子项目springmvc-01-hello,添加web框架支持;
-
配置web.xml文件,注册DispatcherServlet;
<?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"> <!-- 注册DispatcherServlet(核心,请求分发器,前端控制器) --> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 关联springmvc的配置文件(*-servlet.xml) --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-servlet.xml</param-value> </init-param> <!-- 启动级别(1) --> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
-
配置web.xml中关联的springmvc配置文件(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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 1. 处理器映射器 --> <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/> <!-- 2. 处理器适配器 --> <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/> <!-- 3. 视图解析器 --> <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <bean id="/hello" class="com.wang.controller.HelloController"/> </beans>
-
编写要操作业务的Controller类,要么实现Controller接口,要么增加注解;返回一个ModelAndView,用于装数据、封视图;
public class HelloController implements Controller { public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { ModelAndView mv = new ModelAndView(); // 业务代码 String res = "HelloSpringMVC"; mv.addObject("msg", res); // 视图跳转 mv.setViewName("hello"); return mv; } }
-
将Controller类交给SpringIOC容器,在springmvc配置文件中注册bean;
<bean id="/hello" class="com.wang.controller.HelloController"/>
-
编写要跳转的jsp页面,显示ModelAndView存放的数据;
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>hello</title> </head> <body> ${msg} </body> </html>
-
配置Tomcat,测试。
访问出现404,排查步骤:
-
查看控制台输出,看一下是不是缺少了什么jar包。
-
如果jar包存在,显示无法输出,就在IDEA的项目发布中,添加lib依赖!
-
重启Tomcat 即可解决!
方式二:注解版
-
新建子项目springmvc-02-annotation,添加web框架支持,已在父项目中添加Spring框架核心库、Spring MVC、servlet , JSTL等依赖;
-
Maven可能存在资源过滤的问题,完善配置;
<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> </resources> </build>
-
配置web.xml文件,注册DispatcherServlet,同上;
-
配置web.xml中关联的springmvc配置文件(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" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 1. 自动扫描包,让指定包下的注解生效,由IOC容器统一管理 --> <context:component-scan base-package="com.wang.controller"/> <!-- 2. 让Spring MVC不处理静态资源 --> <mvc:default-servlet-handler/> <!-- 3. 支持mvc注解驱动 在spring中一般采用@RequestMapping注解来完成映射关系,要想使@RequestMapping注解生效, 必须向上下文中注册DefaultAnnotationHandlerMapping和一个AnnotationMethodHandlerAdapter实例 这两个实例分别在类级别和方法级别处理。而annotation-driven配置帮助我们自动完成上述两个实例的注入。 --> <mvc:annotation-driven/> <!-- 4. 视图解析器 --> <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> </beans>
-
创建Controller类;
@Controller public class HelloController { // 真实访问地址:http://localhost:8080/hello @RequestMapping("/hello") public String helloSpringMVC(Model model){ // 封装数据。向模型中添加属性,可以在JSP页面中取出并渲染 model.addAttribute("msg", "HelloSpringMVC"); // 被视图解析器处理,拼接后为/WEB-INF/jsp/hello.jsp return "hello"; } }
-
创建视图层,同上;
-
配置Tomcat,测试。
使用springMVC必须配置的三大件:处理器映射器、处理器适配器、视图解析器
通常,我们只需要手动配置视图解析器,而处理器映射器和处理器适配器只需要开启注解驱动即可。
2.2 Controller
控制器提供访问应用程序的行为,通常通过接口定义或注解定义两种方法实现。控制器负责解析用户的请求并将其转换为一个模型。在Spring MVC中一个控制器类可以包含多个方法。
-
接口定义:一个控制器中只有一个方法,如果要多个方法则需要定义多个Controller,定义的方式比较麻烦,如配置版的HelloSpringMVC;
-
注解定义:@Controller注解类型用于声明Spring类的实例是一个控制器,如注解版的HelloSpringMVC。
2.3 RequestMapping
@RequestMapping注解用于映射url到控制器类或一个特定的处理程序方法。可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
-
只作用于方法:路径为http://localhost:8080/hello
@Controller public class HelloController { @RequestMapping("/hello") public String helloSpringMVC(Model model){ model.addAttribute("msg", "HelloSpringMVC"); return "hello"; } }
-
同作用于类和方法:路径为http://localhost:8080/project/hello
@Controller @RequestMapping("/project") public class HelloController { @RequestMapping("/hello") public String helloSpringMVC(Model model){ model.addAttribute("msg", "HelloSpringMVC"); return "hello"; } }
3. Spring MVC原理
当发起请求时被前置的控制器拦截到请求,根据请求参数生成代理请求,找到请求对应的实际控制器,控制器处理请求,创建数据模型,访问数据库,将模型响应给中心控制器,控制器使用模型与视图渲染视图结果,将结果返回给中心控制器,再将结果返回给请求者。
图为SpringMVC的一个较完整的流程图,实线表示SpringMVC框架提供的技术,不需要开发者实现,虚线表示需要开发者实现。
简要分析执行流程
-
DispatcherServlet表示前置控制器,是整个SpringMVC的控制中心。用户发出请求,DispatcherServlet接收请求并拦截请求。
我们假设请求的url为 : http://localhost:8080/SpringMVC/hello
如上url拆分成三部分:http://localhost:8080服务器域名、SpringMVC部署在服务器上的web站点、hello表示控制器
通过分析,如上url表示为:请求位于服务器localhost:8080上的SpringMVC站点的hello控制器。
-
HandlerMapping为处理器映射。DispatcherServlet调用HandlerMapping,HandlerMapping根据请求url查找Handler。
-
HandlerExecution表示具体的Handler,其主要作用是根据url查找控制器,如上url被查找控制器为:hello。
-
HandlerExecution将解析后的信息传递给DispatcherServlet,如解析控制器映射等。
-
HandlerAdapter表示处理器适配器,其按照特定的规则去执行Handler。
-
Handler让具体的Controller执行。
-
Controller将具体的执行信息返回给HandlerAdapter,如ModelAndView。
-
HandlerAdapter将视图逻辑名或模型传递给DispatcherServlet。
-
DispatcherServlet调用视图解析器(ViewResolver)来解析HandlerAdapter传递的逻辑视图名。
-
视图解析器将解析的逻辑视图名传给DispatcherServlet。
-
DispatcherServlet根据视图解析器解析的视图结果,调用具体的视图。
-
最终视图呈现给用户。
4. RESTful风格
Restful就是一个资源定位及资源操作的风格,不是标准也不是协议。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。
传统方式操作资源:通过不同的参数来实现不同的效果!方法单一,post 和 get
http://127.0.0.1/item/queryItem.action?id=1 查询GET
http://127.0.0.1/item/saveItem.action 新增POST
http://127.0.0.1/item/updateItem.action 更新POST
http://127.0.0.1/item/deleteItem.action?id=1 删除GET/POST
-
编写Controller类;
@Controller public class TranditionalController { @RequestMapping("/add") public String test(int a, int b, Model model){ int res = a + b; model.addAttribute("msg", "result: " + res); return "test"; } }
-
测试。
使用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类;
@Controller public class RESTfulController { @RequestMapping("add/{p1}/{p2}") public String test(@PathVariable int p1, @PathVariable int p2, Model model){ int res = p1 + p2; model.addAttribute("msg", "result: " + res); return "test"; } }
-
测试。
使用method属性指定请求类型:用于约束请求的类型,可以收窄请求范围。指定请求谓词的类型如GET、 POST、HEAD、OPTIONS、PUT、PATCH、DELETE、TRACE等。
如:
@RequestMapping(value = "/hello",method = {RequestMethod.POST})
此时,若使用浏览器地址栏请求,或报错405,因为指定的请求类型为 POST ,而所有的浏览器地址栏请求默认都会是 HTTP GET 类型的。将 RequestMethod.POST 改为 RequestMethod.GET 即可请求成功。
方法级别的注解变体:
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
其中,@GetMapping 是一个组合注解,相当于@RequestMapping(method =RequestMethod.GET) 的一个快捷方式。
5. 结果跳转方式
方式一:ModelAndView
设置ModelAndView对象 ,根据view的名称和视图解析器跳到指定的页面。
视图解析器:
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean>
Controller类:
public class HelloController implements Controller { public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { ModelAndView mv = new ModelAndView(); mv.addObject("msg", "HelloSpringMVC"); // 视图跳转(页面:{视图解析器前缀} + viewName + {视图解析器后缀}) mv.setViewName("hello"); return mv; } }
方式二:ServletAPI
不需要视图解析器。
@Controller public class ResultGo { // 通过HttpServletResponse进行输出 @RequestMapping("/result/t1") public void test1(HttpServletRequest req, HttpServletResponse rsp) throws IOException { rsp.getWriter().println("Hello,Spring BY servlet API"); } // 通过HttpServletResponse实现重定向 @RequestMapping("/result/t2") public void test2(HttpServletRequest req, HttpServletResponse rsp) throws IOException { rsp.sendRedirect("/index.jsp"); } // 通过HttpServletResponse实现转发 @RequestMapping("/result/t3") public void test3(HttpServletRequest req, HttpServletResponse rsp) throws Exception { req.setAttribute("msg","/result/t3"); req.getRequestDispatcher("/WEB-INF/jsp/test.jsp").forward(req,rsp); } }
方式三:SpringMVC
-
无需视图解析器实现转发和重定向
在web目录下创建redirectAndForward.jsp文件测试。
@Controller public class RedirectAndForward01 { @RequestMapping("/raf/t1") public String test01(){ // 转发方式一 return "/redirectAndForward.jsp"; } @RequestMapping("/raf/t2") public String test02(){ // 转发方式二 return "forward:/redirectAndForward.jsp"; } @RequestMapping("/raf/t3") public String test03(){ // 重定向 return "redirect:/redirectAndForward.jsp"; } }
-
需要视图解析器实现转发和重定向
@Controller public class RedirectAndForward02 { @RequestMapping("/raf/t1") public String test01(){ // 转发 return "redirectAndForward"; } @RequestMapping("/raf/t3") public String test03(){ // 重定向 return "redirect:/redirectAndForward.jsp"; } }
视图解析器:
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/"/> <property name="suffix" value=".jsp"/> </bean>
注:重定向是客户端的,而转发是服务端内部的。重定向是让客户端去访问重定向的地址,而WEB-INF下的文件是不能通过外部访问的!
6. 数据处理
6.1 处理提交数据
@Controller public class DataHandling { // 1. 提交的域名称和处理方法的参数名一致 // http://localhost:8080/dh/t1?name=111 @RequestMapping("/dh/t1") public String test01(String name){ System.out.println(name); return "hello"; } // 2. 提交的域名称和处理方法的参数名不一致 // http://localhost:8080/dh/t2?username=111 @RequestMapping("/dh/t2") public String test02(@RequestParam("username") String name){ System.out.println(name); return "hello"; } // 3. 提交一个对象 // http://localhost:8080/dh/t3?id=1&name=111&age=18 @RequestMapping("/dh/t3") public String test03(User user){ System.out.println(user); return "hello"; } }
@Data @AllArgsConstructor @NoArgsConstructor public class User { private int id; private String name; private int age; }
6.2 数据显示到前端
-
ModelAndView
案例参考HelloSpringMVC配置版的Controller类。
-
ModelMap
@Controller public class DataPresentation { @RequestMapping("/dp/t1") public String test01(ModelMap model){ model.addAttribute("msg", "DataPresentation01"); return "hello"; } @RequestMapping("/dp/t2") public String test01(Model model){ model.addAttribute("msg", "DataPresentation02"); return "hello"; } }
-
Model
Model 只有寥寥几个方法只适合用于储存数据,简化了新手对于Model对象的操作和理解;
ModelMap 继承了 LinkedMap,除了实现了自身的一些方法,同样的继承 LinkedMap 的方法和特性;
ModelAndView 可以在储存数据的同时,可以进行设置返回的逻辑视图,进行控制展示层的跳转。
6.3 中文乱码问题
SpringMVC过滤器
<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>
自定义过滤器
-
修改tomcat配置文件(server.xml):设置编码
<Connector URIEncoding="utf-8" port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
-
自定义过滤器
package com.kuang.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 destroy() { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 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); chain.doFilter(myrequest, response); } @Override public void init(FilterConfig filterConfig) throws ServletException { } }
//自定义request对象,HttpServletRequest的包装类 class MyRequest extends HttpServletRequestWrapper { private HttpServletRequest request; //是否编码的标记 private boolean hasEncode; //定义一个可以传入HttpServletRequest对象的构造函数,以便对其进行装饰 public MyRequest(HttpServletRequest request) { super(request);// super必须写 this.request = request; } // 对需要增强方法 进行覆盖 @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(); if (!hasEncode) { // 确保get手动编码逻辑只运行一次 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(); } //取一个值 @Override public String getParameter(String name) { Map<String, String[]> parameterMap = getParameterMap(); String[] values = parameterMap.get(name); if (values == null) { return null; } return values[0]; // 取回参数的第一个值 } //取所有值 @Override public String[] getParameterValues(String name) { Map<String, String[]> parameterMap = getParameterMap(); String[] values = parameterMap.get(name); return values; } }
-
在web.xml中注册过滤器。