MVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范。
官方文档https://docs.spring.io/spring/docs/5.2.0.RELEASE/spring-framework-reference/web.html#spring-web
Spring MVC的特点:
-
轻量级,简单易学
-
高效 , 基于请求响应的MVC框架
-
与Spring兼容性好,无缝结合
-
约定优于配置
-
功能强大:RESTful、数据验证、格式化、本地化、主题等
-
简洁灵活
Spring的web框架围绕DispatcherServlet [ 调度Servlet ] 设计。
简要分析执行流程
-
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根据视图解析器解析的视图结果,调用具体的视图。
-
最终视图呈现给用户。
而DispatcherServlet本质也是一个Servlet
看到这里了解流程,来个demo演示一下
准备工作
=====================================
1.pom添加依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.5.RELEASE</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.2.1-b03</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency>
2.add framework suppor 选择web支持,
3.配置web.xml,注册DispatcherServlet(注册按照官方文档的步骤说明,也就是引用他的jar包下面写好的类,方便给你用,要是水平足够可以更改jar包源文件,达到自己想要的效果)
<?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"> <!--1.注册servlet--> <servlet> <servlet-name>SpringMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--通过初始化参数指定SpringMVC配置文件的位置,进行关联--> <init-param> <param-name>contextConfigLocation</param-name> <!-- 用springmvc-servlet.xml 报错,可能因为名字多个问题或者重名等 --> <param-value>classpath:spring-mvc-servlet.xml</param-value> </init-param> <!-- 启动顺序,数字越小,启动越早 --> <load-on-startup>1</load-on-startup> </servlet> <!--所有请求都会被springmvc拦截 --> <servlet-mapping> <servlet-name>SpringMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
看完了上面的官方配置和我的注解,也该知道下面该干嘛了吧,没错,官方也说明了通过一个指定的配置文件进行关联
也就是我上面的classpath里的spring配置名字
spring-mvc-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 http://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"> <!-- 自动扫描包,让指定包下的注解生效,由IOC容器统一管理 --> <context:component-scan base-package="com.yang.controller"/> <!-- 让Spring MVC不处理静态资源 --> <mvc:default-servlet-handler /> <!-- 支持mvc注解驱动 在spring中一般采用@RequestMapping注解来完成映射关系 要想使@RequestMapping注解生效 必须向上下文中注册DefaultAnnotationHandlerMapping 和一个AnnotationMethodHandlerAdapter实例 这两个实例分别在类级别和方法级别处理。 而annotation-driven配置帮助我们自动完成上述两个实例的注入。 --> <mvc:annotation-driven /> <!-- 视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver"> <!-- 前缀 --> <property name="prefix" value="/WEB-INF/jsp/" /> <!-- 后缀 --> <property name="suffix" value=".jsp" /> </bean> </beans>
其实官方文档里只写了视图解析器的部分,上面的部分是为了注解写的(注解见下面)
有了视图解析器的部分,以后在web-inf下的jsp包(自己建的,名字随意,取666也行)就能写jsp直接用名字等跳转了和使用了
====================================
配置完了就要写我们的业务Controller,要么实现Controller接口,要么增加注解;需要返回一个ModelAndView,装数据,封视图
那我们都看一下实现接口和注解
第一实现接口方式
第二种方式(推荐)
注解
package com.yang.controller; //上面配置里面扫描了该包的 import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class HelloController { @RequestMapping("/h1") public String hello(Model model){ model.addAttribute("msg","HelloSpringMVC"); return "test"; } }
然后在我们的配置文件写了的web-inf下面的jsp包下写一个test.jsp
<body>
${msg}
</body>
配置一下tomcat
运行
===================================
值得注意的是,有时候创建方式的原因,如果没有运行成功可能是包没有导入,如果没有Lib就新建一个然后添加所有maven即可
并且最好都用最新版,web.xml或者spring的xml配置等
============================================================
看完了上面的一个demo是不是体会不到springmvc的优势
来下面再看一个
在以前是不是需要写三个servlet而现在只需要三个注解加方法即可解决
注解还有
@RestController
它与@Controller不同是它直接返回一个字符串,而不像controller返回配置了的视图解析器,
(不要与spring的@component注册bean到容器中混淆,)
@PostMapping 看名字即可知道它与@RequsetMapping是一样的设置域名,不过它是post方法,适合用于表单之类
@ResponBody这个放在@controller下面就会导致配置了@ResponBody的方法不会走视图解析器,而是也返回字符串
===============================================================
乱码问题
以前乱码问题通过过滤器解决 , 而SpringMVC给我们提供了一个过滤器 , 可以在web.xml中配置 .
修改了xml文件需要重启服务器!
<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>
但是我们发现 , 有些极端情况下.这个过滤器对get的支持不好 或者没有效果!.
处理方法 :
1、修改tomcat配置文件 :设置编码!
<Connector URIEncoding="utf-8" port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
2、自定义过滤器
然后在web.xml中配置这个过滤器即可!
package com.yang.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, FilterChainchain) 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; } }
RestFul 风格
测试
-
在新建一个类 RestFulController
@Controller
public class RestFulController {
} -
在Spring MVC中可以使用 @PathVariable 注解,让方法参数的值对应绑定到一个URI模板变量上。
@Controller public class RestFulController { //映射访问路径 @RequestMapping("/commit/{p1}/{p2}") public String index(@PathVariable int p1, @PathVariable int p2, Model model){ int result = p1+p2; //Spring MVC会自动实例化一个Model对象用于向视图中传值 model.addAttribute("msg", "结果:"+result); //返回视图位置 return "test"; } }
-
我们来测试请求查看下
- =======================================================================
====================================================================================================
虽然@Controller能返回视图,因为视图解析器,我们可以这样写,因为前面说过,DisparterServlet本质上也是Servlet,既然是Servlet就会有request和response
当然在springMVC不建议这样写,不然SpringMVC配置视图解析器的意义就没有了
==================================================================================================
在以前servlet获得前端的参数是怎样
是不是request.getParameters()这样的方式
而现在
处理提交数据
1、提交的域名称和处理方法的参数名一致
提交数据 : http://localhost:8080/hello?name=yang
处理方法 :
@RequestMapping("/hello")
public String hello(String name){
System.out.println(name);
return "hello";
}
后台输出 : yang
2、提交的域名称和处理方法的参数名不一致
提交数据 : http://localhost:8080/hello?username=yang
处理方法 :
//@RequestParam("username") : username提交的域的名称 .
@RequestMapping("/hello")
public String hello(@RequestParam("username") String name){
System.out.println(name);
return "hello";
}
后台输出 : yang
3、提交的是一个对象
要求提交的表单域和对象的属性名一致 , 参数使用对象即可
1、实体类
public class User {
private int id;
private String name;
private int age;
//构造
//get/set
//tostring()
}
2、提交数据 : http://localhost:8080/mvc04/user?name=yang&id=1&age=15
3、处理方法 :
@RequestMapping("/user")
public String user(User user){
System.out.println(user);
return "hello";
}
后台输出 : User { id=1, name='yang', age=15 }
说明:如果使用对象的话,前端传递的参数名和对象名必须一致,否则就是null。
数据显示到前端
第一种 : 通过ModelAndView
一般是用的实现接口Controller方法而不用注解
public class ControllerTest1 implements Controller {
public ModelAndView handleRequest(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse) throws Exception {
//返回一个模型视图对象
ModelAndView mv = new ModelAndView();
mv.addObject("msg","ControllerTest1");
mv.setViewName("test");
return mv;
}
}
第二种 : 通过ModelMap
ModelMap
@RequestMapping("/hello")
public String hello(@RequestParam("username") String name, ModelMap model){
//封装要显示到视图中的数据
//相当于req.setAttribute("name",name);
model.addAttribute("name",name);
System.out.println(name);
return "hello";
}
第三种 : 通过Model(推荐)
Model
@RequestMapping("/ct2/hello")
public String hello(@RequestParam("username") String name, Model model){
//封装要显示到视图中的数据
//相当于req.setAttribute("name",name);
model.addAttribute("msg",name);
System.out.println(name);
return "test";
}
===================================
json
JSON 键值对是用来保存 JavaScript 对象的一种方式,和 JavaScript 对象的写法也大同小异,键/值对组合中的键名写在前面并用双引号 "" 包裹,使用冒号 : 分隔,然后紧接着值:
{"name": "yang"}
{"age": "3"}
{"sex": "男"}
JSON 是 JavaScript 对象的字符串表示法,它使用文本表示一个 JS 对象的信息,本质是一个字符串。
var obj = {a: 'Hello', b: 'World'}; //这是一个对象,注意键名也是可以使用引号包裹的
var json = '{"a": "Hello", "b": "World"}'; //这是一个 JSON 字符串,本质是一个字符串
在java中我们一般是toString把一个对象传递给前端,而json不能tostring
这时候就需要借助jar包如jackson或Fastjson
我们就用jackson
第一步导入jackson的依赖
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.8</version> </dependency>
第二步。配置
并且在SpringMVC里面什么都帮你配置好了只需要自己用即可
如下面的的jackson乱码配置
web.xml 也就是我们前面的开启注解的里面加这些(复制用即可)
<mvc:annotation-driven> <mvc:message-converters register-defaults="true"> <bean class="org.springframework.http.converter.StringHttpMessageConverter"> <constructor-arg value="UTF-8"/> </bean> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="objectMapper"> <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"> <property name="failOnEmptyBeans" value="false"/> </bean> </property> </bean> </mvc:message-converters> </mvc:annotation-driven>
并且全局使用,不用一一配置,简直方便完美
第三步
新建一个ObjectMapper对象
如下
@Controller public class UserController { @RequestMapping("/json1") @ResponseBody //返回字符串 public String json1() throws JsonProcessingException { //创建一个jackson的对象映射器,用来解析数据 ObjectMapper mapper = new ObjectMapper(); //创建一个对象 User user = new User("阳", 3, "男"); //将我们的对象解析成为json格式 String str = mapper.writeValueAsString(user); //由于@ResponseBody注解,这里会将str转成json格式返回;十分方便 return str; } }