spring mvc
MVC 是一种软件架构的思想,将软件按照数据模型层(Model)、视图层(View)、控制层(Controller)来划分,使用 MVC 的目的是将 M 和 V 实现代码分离。
- 数据模型层:指工程中的 JavaBean,作用是处理数据
- 一类称为实体类Bean:专门存储业务数据的,如 Student、User 等
- 一类称为业务处理 Bean:指 Service 或 Dao,dto对象,专门用于处理业务逻辑和数据访问
- 视图层:指工程中的html或jsp等页面,作用是与用户进行交互,展示数据 thymeleaf之类
- 控制层:指工程中的servlet,作用是接收请求和响应浏览器
1.mvc的请求流程
- 用户发起请求 DispatcherServlet前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行 处理,作为统一访问点,进行全局的流程控制
- DispatcherServlet——>HandlerMapping, HandlerMapping 将会把请求映射为 HandlerExecutionChain 对象(包含一 个Handler 处理器(页面控制器)对象、多个HandlerInterceptor 拦截器)对象,通过这种策略模式,很容易添加新 的映射策略;
- DispatcherServlet——>HandlerAdapter,HandlerAdapter 将会把处理器包装为适配器,从而支持多种类型的处理器, 即适配器设计模式的应用,从而很容易支持很多类型的处理器
- HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter 将会根据适配的结果调用真正的处理器的功能处 理方法,完成功能处理;并返回一个ModelAndView 对象(包含模型数据、逻辑视图名)
- ModelAndView 的逻辑视图名——> ViewResolver,ViewResolver 将把逻辑视图名解析为具体的View,通过这种策 略模式,很容易更换其他视图技术;
- View——>渲染,View 会根据传进来的Model 模型数据进行渲染,此处的Model 实际是一个Map 数据结构,因此 很容易支持其他视图技术;
-
返回控制权给DispatcherServlet,由DispatcherServlet 返回响应给用户,到此一个流程结束
2.典型例子
2.1新建web项目
- 打开 IDEA,选择 Java Enterprise
- 模版选择 Web 应用程序
- 应用程序服务器选择 Tomcat
- 构建系统选择 Maven
- 依赖项勾选 Servlet 和 Thymeleaf
- 删除掉 index.jsp
2.2 引入依赖
<dependencies>
<!-- SpringMVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.1</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- Spring5和Thymeleaf整合包也可以采用jsp模板引擎 -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
</dependencies>
2.3 配置spring mvc
配置web.xml 在WEB-INFO目录下
<!--配置前端过滤器-->
<filter>
<filter-name>characterEncodingFilter</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>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 配置SpringMVC的前端控制器,对浏览器发送的请求统一进行处理 -->
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 通过初始化参数指定SpringMVC配置文件的位置和名称 创建spring容器 -->
<init-param>
<!-- contextConfigLocation为固定值 -->
<param-name>contextConfigLocation</param-name>
<!-- 使用classpath:表示从类路径查找配置文件,例如maven工程中的src/main/resources -->
<param-value>classpath:springMVC.xml</param-value>
</init-param>
<!--
作为框架的核心组件,在启动过程中有大量的初始化操作要做
而这些操作放在第一次请求时才执行会严重影响访问速度
因此需要通过此标签将启动控制DispatcherServlet的初始化时间提前到服务器启动时
-->
<load-on-startup>1</load-on-startup><!-- 表示容器再启动时立即加载servlet -->
</servlet>
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<!--
设置springMVC的核心控制器所能处理的请求的请求路径
/所匹配的请求可以是/login或.html或.js或.css方式的请求路径
但是/不能匹配.jsp请求路径的请求
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
在 resources 目录配置springmvc.xml 用于配置视图解析器等
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- 配置handlerMapping-->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>
<!-- 配置handlerAdapter-->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
<!-- 配置渲染器-->
<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"></property>
<property name="prefix" value="/WEB-INF/jsp/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 配置请求和处理器-->
<bean name="/hello.do" class="com.controller.HelloController"></bean>
</beans>
另一种基于注解配置(流行)
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- 把标记了@Controller注解的类转换为bean -->
<context:component-scan base-package="com.controller" />
<!-- 启动Spring MVC的注解功能,配置annotation类型的处理器映射器-->
<bean class="org.springframework.web.servlet.mvc.annotation.RequestMappingHandlerMapping" />
<!-- 启动Spring MVC的注解功能,配置annotation类型的处理器适配器-->
<bean class="org.springframework.web.servlet.mvc.annotation.RequestMappingHandlerAdapter" />
<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"/>
</beans>
整合thymeleaf时,需要在maven映入坐标,并且在springmvc.xml添加
<!-- 自动扫描包 -->
<context:component-scan base-package="com.jwt.mvc.controller"/>
<!-- 配置Thymeleaf视图解析器 -->
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<property name="order" value="1"/>
<property name="characterEncoding" value="UTF-8"/>
<property name="templateEngine">
<bean class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver">
<bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<!-- 视图前缀 -->
<property name="prefix" value="/WEB-INF/templates/"/>
<!-- 视图后缀 -->
<property name="suffix" value=".html"/>
<property name="templateMode" value="HTML5"/>
<property name="characterEncoding" value="UTF-8" />
</bean>
</property>
</bean>
</property>
</bean>
<!--
处理静态资源,例如html、js、css、jpg
若只设置该标签,则只能访问静态资源,其他请求则无法访问
此时必须设置<mvc:annotation-driven/>解决问题
-->
<mvc:default-servlet-handler/>
<!-- 开启mvc注解驱动 -->
<mvc:annotation-driven>
<mvc:message-converters>
<!-- 处理响应中文内容乱码 -->
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="defaultCharset" value="UTF-8" />
<property name="supportedMediaTypes">
<list>
<value>text/html</value>
<value>application/json</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
- 当启动Tomcat服务器的时候,因为配置了load-on-startup标签,所以会创建DispatcherServlet对象,就会加载springmvc.xml配置文件
- 开启了注解扫描,那么HelloController对象就会被创建
- 从index.jsp发送请求,请求会先到达DispatcherServlet核心控制器,根据配置@RequestMapping注解找到执行的具体方法
- 根据执行方法的返回值,再根据配置的视图解析器,去指定的目录下查找指定名称的JSP文件
- Tomcat服务器渲染页面,做出响应
2.4 使用
在控制器层根据用户请求路径,返回页面
//交给spring处理
@Controller
public class HelloController {
// @RequestMapping注解:处理请求和控制器方法之间的映射关系
// localhost:8080/springMVC/
@RequestMapping("/")
public String index() {//控制器方法
//设置视图名称
return "index";
}
}
index.html页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>首页</h1>
<h2>Hello World</h2>
</body>
</html>
3.常用注解
为了让spring识别到注解需要在springmvc.xml添加
<!--方式一-->
<bean class="com.host.app.web.controller.MyController"/>
<!--方式二-->
< context:component-scan base-package = "com.host.app.web" />//路径写到controller的上一层(扫描包详解见下面浅析)
3.1@Controller
Controller 负责处理由DispatcherServlet 分发的请求,它把用户请求的数据经过业务处理层处理之后封装成一个Model ,然后再把该Model 返回给对应的View 进行展示。
3.2@RequestMapping
@RequestMapping源码
@Target({ElementType.TYPE, ElementType.METHOD}) //描述注解能够作用的位置
@Retention(RetentionPolicy.RUNTIME) //当前被描述的注解,会保留到class字节码文件中,并被`JVM`读取到
@Documented //描述注解是否被抽取到api文档中
@Mapping
public @interface RequestMapping {
String name() default "";
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
RequestMethod[] method() default {};
String[] params() default {};
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {};
}
属性名 | 作用 | 说明 |
---|---|---|
path | 指定请求路径的url | |
value | value属性和path属性是一样的 | @RequestMapping(“/login”) 相当@RequestMapping(value=”/login”) |
method | 指定该方法的请求方式 | 包括GET,POST,PUT,OPTIONS等 RequestMethod.GET |
params | 指定限制请求参数的条件 | |
headers | 发送的请求中必须包含的请求头 |
3.3 @PathVariable
Restful风格的URL
<a href="/user/1/roles/1"></a>
@Controller
public class TestController {
@RequestMapping(value="/user/{userId}/roles/{roleId}",method = RequestMethod.GET)
public String getLogin(@PathVariable("userId") String userId,
@PathVariable("roleId") String roleId){
System.out.println("User Id : " + userId);
System.out.println("Role Id : " + roleId);
return "hello";
}
@RequestMapping(value="/product/{productId}",method = RequestMethod.GET)
public String getProduct(@PathVariable("productId") String productId){
System.out.println("Product Id : " + productId);
return "hello";
}
@RequestMapping(value="/javabeat/{regexp1:[a-z-]+}", method = RequestMethod.GET)
public String getRegExp(@PathVariable("regexp1") String regexp1){
System.out.println("URI Part 1 : " + regexp1);
return "hello";
}
}
3.4 @RequestParam
- value:请求参数中的名称
- required:请求参数中是否必须提供此参数,默认值是true,必须提供
/**
* 接收请求
* @return
*/
@RequestMapping(path="/hello")
public String sayHello(@RequestParam(value="username",required=false)String name) {
System.out.println("aaaa");
System.out.println(name);
return "success";
}
3.5 @ResponseBody
我们不需要返回一个页面,而是直接返回数据给到前端,该注解可以放在返回值前或者方法上,用于将返回值放在response体内,而不是返回一个页面
@Controller
@RequestMapping("/anno")
public class DemoAnnoController {
@RequestMapping(value = "/index", method = RequestMethod.GET, produces = MediaType.TEXT_PLAIN_VALUE)
public @ResponseBody
String index(HttpServletRequest request) {
return "url:" + request.getRequestURI() + " can access";
}
}
请求路径
http://localhost:8080/spring-action-1.0-SNAPSHOT/anno/index
也可以将
@ResponseBody
注解放在方法上 ,所以现在前后端分离返回封装类的json数据基本上用@RestController它是@Controller和@ResponseBody的集成版本
3.6 @CookieValue
用于获取指定cookie的值
@RequestMapping(path="/hello")
public String sayHello(@CookieValue(value="JSESSIONID") String cookieValue) {
System.out.println(cookieValue);
return "success";
}
3.7 @RequestBody
主要用来接收前端传递给后端的json字符串中的数据的
@PostMapping({"/login"})
public ResponseBean login(@RequestBody @Validated GaeaUserDto dto) {
return responseSuccessWithData(accessUserService.login(dto));
}
4.请求参数绑定
意思是前端发送数据在controller怎么实现,SpringMVC的参数绑定过程是把表单提交的请求参数,作为控制器中方法的参数进行绑定的
4.1绑定方式
基本类型和 String 类型作为参数
Controller的形参为Interger id,它和请求参数的key一致,所以直接绑定成功 。如果不一样,请求参数key和controller方法的形参名称不一致时,需要使用@RequestParam注解才能将请求参数绑定成功 要指明该参数名
public String findItem(@RequestParam("itemId") Integer id)
<a href="param/testParam?id=006&username=keafmd">请求参数绑定</a>
@Controller
@RequestMapping("/param")
public class ParamController {
@RequestMapping("/testParam")
public String testParam(Integer id,String username){
System.out.println("id:"+id);
System.out.println("username:"+username);
return "success";
}
}
pojo类型作为参数 实体类需要实现序列化类 要求表单中参数名称和POJO类的属性名称保持一致
package com.Keafmd.domain;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
@Data
public class Account implements Serializable {
private String username;
private String password;
private Double money;
private User user;
}
@Controller
@RequestMapping("/param")
public class ParamController {
/**
* 请求参数绑定,把数据封装到javaBean的类中
* @return
*/
@RequestMapping("/saveAccount")
public String testParam(Account account){
System.out.println(account);
return "success";
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>请求参数绑定</title>
</head>
<body>
<%--表单,把数据封装到Account类中--%>
<form action="param/saveAccount" method="post">
姓名:<input type="text" name="username"/><br/>
密码:<input type="text" name="password"/><br/>
金额:<input type="text" name="money"/><br/>
用户姓名:<input type="text" name="user.uname"/><br/>
用户年龄:<input type="text" name="user.age"/><br/>
<input type="submit" value="提交">
</form>
</body>
</html>
使用简单类型数组接收参数 使用POJO类型集合或数组接收参数 自定义参数绑定等
4.2 原始的参数绑定
Servlet中HttpServletRequest + HttpServletResponse
@RequestMapping("/httpServlet")
public void formPost(HttpServletRequest request, HttpServletResponse response) throws IOException{
String userAgent = request.getHeader("User-Agent");
String host = request.getHeader("Host");
String cacheControl = request.getHeader("Cache-Control");
PrintWriter pw = response.getWriter();
pw.println("User-Agent :"+ userAgent);
pw.println("Host :" + host);
pw.println("Cache-Control :" + cacheControl);
}
5.返回类型
5.1 ModelAndView
现在前后端分离后,后端都是以返回 JSON 数据为主,基本不用了
@RequestMapping("/book")
public ModelAndView getAllBook() {
ModelAndView mv = new ModelAndView();
List<Book> books = new ArrayList<>();
Book b1 = new Book();
b1.setId(1);
b1.setName("三国演义");
b1.setAuthor("罗贯中");
books.add(b1);
Book b2 = new Book();
b2.setId(2);
b2.setName("红楼梦");
b2.setAuthor("曹雪芹");
books.add(b2);
//指定数据模型
mv.addObject("bs", books);
mv.setViewName("book");//指定视图名
return mv;
}
5.2 void
不返回任何值
@RequestMapping("/test2")
@ResponseBody
public void test2(){
//你的代码
}
重定向
@RequestMapping("/test1")
@ResponseBody
public void test1(HttpServletResponse resp){
resp.setStatus(302);
resp.addHeader("Location","/aa/index");
}
@RequestMapping("/test1")
@ResponseBody
public void test1(HttpServletResponse resp){
resp.sendRedirect("/aa/index");
}
返回字符串
@RequestMapping("/test2")
@ResponseBody
public void test2(HttpServletResponse resp) throws IOException {
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
List<Book> books = new ArrayList<>();
Book b1 = new Book();
b1.setId(1);
b1.setName("三国演义");
b1.setAuthor("罗贯中");
books.add(b1);
Book b2 = new Book();
b2.setId(2);
b2.setName("红楼梦");
b2.setAuthor("曹雪芹");
books.add(b2);
String s = new Gson().toJson(books);
out.write(s);
out.flush();
out.close();
}
5.3 String
逻辑视图名
@RequestMapping("/hello")
public String aaa(Model model) {
model.addAttribute("username", "张三");
return "hello";
}
重定向
@RequestMapping("/test4")
public String test4() {
return "redirect:/aa/index";
}
就是字符串
@Controller
public class HelloController {
@GetMapping("/hello")
@ResponseBody
public String hello() {
return "hello provider!";
}
}
5.4 json
@GetMapping("/user")
@ResponseBody
public User getUser() {
User user = new User();
List<String> favorites = new ArrayList<>();
favorites.add("足球");
favorites.add("篮球");
user.setFavorites(favorites);
user.setUsername("zhagnsan");
user.setPassword("123");
return user;
}
@GetMapping("/users")
@ResponseBody
public List<User> getALlUser() {
List<User> users = new ArrayList<>();
for (int i = 0; i < 10; i++) {
User e = new User();
e.setUsername("zhangsan:" + i);
e.setPassword("pwd:" + i);
users.add(e);
}
return users;
}