目录
4.6.1 SpringMVC
4.6.1.1 SpringMVC概述
SpringMVC 是一种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级 Web 框架,属于SpringFrameWork 的后续产品,已经融合在 Spring Web Flow 中。
SpringMVC 已经成为目前最主流的MVC框架之一。它通过一套注解,让一个简单的 Java 类成为处理请求的控制器,而无须实现任何接口。
使用servlet进行控制层处理,每一个请求都需要书写一个servlet,每个servlet只修改处理请求的方法与名称,其余没有修改,springMVC可以看做提供了一个中央控制器对这些servlet进行统一的分发管理并简化servlet的书写
4.6.1.2 手动实现简单SpringMVC
4.6.1.2.1 分析
我们先来回顾以下servlet,如图所示, Servlet是由tomcat容器来管理。
我们再来思考,在之前,我们把很多类都交由Spring来管理,那么,servlet我们是不是也可以交由Spring管理呢?
思路:
将所有的方法书写在一个类中,再创建一个核心控制器,用于处理所有请求的servlet,通过反射调用相应的方法。最后将书写的自定义类交由spring容器管理。
步骤
- 导入SpringMVC相关坐标
- 配置SpringMVC核心控制器DispathcerServlet
- 创建Controller类和视图页面
- 使用注解配置Controller类中业务方法的映射地址
- 配置SpringMVC核心文件 spring-mvc.xml
- 客户端发起请求测试
<!--Spring坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!--SpringMVC坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!--Jsp坐标-->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
</dependency>
<!-- Servlet坐标 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
Controller类
@Controller
public class MyController {
//与之前的servlet:一个servlet只能写一个方法
//现在可以写多个方法,但是需要每个方法对应url
//创建HashMap集合保存 key:请求的url,value:方法名
public static HashMap<String, Method> map = new HashMap<>();
//放在静态块中spring加载时能知道
static {
Class<MyController> mc = MyController.class;
try {
//正常的这个路径需要拼接,但是是简单实现,就不拼接了
map.put("/day1121/a", mc.getDeclaredMethod("a"));
map.put("/day1121/b", mc.getDeclaredMethod("b"));
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
//书写执行方法
public void a() {
System.out.println("a方法执行");
}
public void b() {
System.out.println("a方法执行");
}
}
核心控制器DispathcerServlet
@WebServlet(name = "TestServlet", value = "/*")
public class DispathcerServlet extends HttpServlet {
@SneakyThrows
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取请求的路径
String requestURI = request.getRequestURI();
// System.out.println(requestURI); 格式为: /day1121/a
// String contextPath = request.getServletContext().getContextPath();
// System.out.println(contextPath); /day1121
//获取spring容器controller对象中的requestMapping属性进行比对
HashMap<String, Method> requestMapping = MyController.map;
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());
MyController bean = webApplicationContext.getBean(MyController.class);
if (requestMapping.containsKey(requestURI)) {
//如果请求的路径与配置中key相同 说明有与之匹配的方法执行
//通过反射执行对应方法
Method method = requestMapping.get(requestURI);
method.setAccessible(true);
method.invoke(bean);
} else {
//循环结束没有匹配url 说明请求找不到 返回404
response.getWriter().append("<h1>404</h1>");
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
以上就是我们搭建的简单的SpringMVC,我们再来总结下:
前台请求会先到servlet,servlet会根据请求路径找集合映射中是否有对应方法,有就处理,通过反射执行,没有就报错。
4.6.1.3 搭建SpringMVC
4.6.1.3.1 快速搭建
导入Spring和SpringMVC的坐标、导入Servlet和Jsp的坐标
<!--Spring坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!--SpringMVC坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!--Jsp坐标-->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
</dependency>
<!-- Servlet坐标 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
web.xml配置SpringMVC的核心控制器与spring加载监听器
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!-- 配置springmvc前端控制器 -->
<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:spring.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!--注意这里的*.do-->
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
创建Controller和业务方法
package com.bl.Controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/select.do")
public ModelAndView select(ModelAndView mv,HttpServletRequest request){
request.setAttribute("msg","查找返回的数据");
mv.setViewName("/index.jsp");
return mv;
}
@RequestMapping("/update.do")
public String update(ModelAndView mv,HttpServletRequest request){
request.setAttribute("msg","更新返回的数据");
return "/index.jsp";
}
}
spring.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置注解扫描-->
<context:component-scan base-package="com.bl"/>
</beans>
4.6.1.3.2 SpringMVC的执行流程
流程图:
- 用户发送请求至前端控制器DispatcherServlet。
- DispatcherServlet收到请求调用HandlerMapping处理器映射器。
- 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
- DispatcherServlet调用HandlerAdapter处理器适配器。
- HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
- Controller执行完成返回ModelAndView。
- HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。 DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
- ViewReslover解析后返回具体View。
- DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。DispatcherServlet响应用户。
4.6.1.3.3 SpringMVC组件
-
前端控制器:DispatcherServlet 用户请求到达前端控制器,它就相当于 MVC 模式中的 C,DispatcherServlet 是整个流程控制的中心,由它调用其它组件处理用户的请求,DispatcherServlet 的存在降低了组件之间的耦合性。
-
处理器映射器:HandlerMapping HandlerMapping 负责根据用户请求找到 Handler 即处理器,SpringMVC 提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
-
处理器适配器:HandlerAdapter 通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
-
处理器:Handler 它就是我们开发中要编写的具体业务控制器。由 DispatcherServlet 把用户请求转发到 Handler。由Handler 对具体的用户请求进行处理。
-
视图解析器:View Resolver View Resolver 负责将处理结果生成 View 视图,View Resolver 首先根据逻辑视图名解析成物理视图名,即具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户。
-
视图:View SpringMVC 框架提供了很多的 View 视图类型的支持,包括:jstlView、freemarkerView、pdfView等。最常用的视图就是 jsp。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面
4.6.1.3.4 SpringMVC注解
@RequestMapping
作用:用于建立请求 URL 和处理请求方法之间的对应关系
用在类上,请求URL 的第一级访问目录。此处不写的话,就相当于应用的根目录
用在方法上,请求 URL 的第二级访问目录,与类上的使用@RequestMapping标注的一级目录一起组成访问虚拟路径
属性:
value:用于指定请求的URL。它和path属性的作用是一样的
method:用于指定请求的方式
params:用于指定限制请求参数的条件。它支持简单的表达式。要求请求参数的key和value必须和配置的一模一样
4.6.1.3.5 组件扫描
SpringMVC基于Spring容器,所以在进行SpringMVC操作时,需要将Controller存储到Spring容器中,如果使用@Controller注解标注的话,就需要使用
<context:component-scan base-package=“com.bl"/>进行组件扫描。
SpringMVC有默认组件配置,默认组件都是DispatcherServlet.properties配置文件中配置的,该配置文件地址org/springframework/web/servlet/DispatcherServlet.properties,该文件中配置了默认的视图解析器
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
解析器的默认设置:
REDIRECT_URL_PREFIX = "redirect:" --重定向前缀
FORWARD_URL_PREFIX = "forward:" --转发前缀(默认值)
prefix = ""; --视图名称前缀
suffix = ""; --视图名称后缀
4.6.1.3.6 视图解析器
如果我们把页面放在WEB-INF/view/中,那么controller中的关于页面的都要手动添加路径。如下所示
public class UserController {
@RequestMapping("/select.do")
public ModelAndView select(ModelAndView mv,HttpServletRequest request){
request.setAttribute("msg","查找返回的数据");
mv.setViewName("/WEB-INF/view/index.jsp");
return mv;
}
@RequestMapping("/update.do")
public String update(ModelAndView mv,HttpServletRequest request){
request.setAttribute("msg","更新返回的数据");
return "/WEB-INF/view/index.jsp";
}
}
是不是很麻烦,所以我们可以通过属性注入的方式修改视图的的前后缀。
配置解析器
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/select.do")
public ModelAndView select(ModelAndView mv,HttpServletRequest request){
request.setAttribute("msg","查找返回的数据");
mv.setViewName("/index");
return mv;
}
@RequestMapping("/update.do")
public String update(ModelAndView mv,HttpServletRequest request){
request.setAttribute("msg","更新返回的数据");
return "/index";
}
}
4.6.1.4 SpringMVC的数据响应
SpringMVC数据响应方式:
1、页面跳转
①直接返回字符串
②通过ModelAndView(HttpServletRequest)对象返回
2、回写数据
①直接返回字符串
②返回对象或集合
4.6.1.4.1 页面跳转
(1)直接返回字符串跳转页面
方法的返回值为字符串类型 返回的是要跳转的页面名称,会自动与视图解析器配置的前后缀进行组合找到对应的页面 ,默认使用请求转发的形式访问指定页面
如果想使用重定向需要在页面名称前添加 redirect:
@RequestMapping("/u1.do")
public String u1(HttpServletRequest request) {
request.setAttribute("msg", "u1");
return "index"; //重定向 return "redirect:index";
}
(2)通过ModelAndView对象跳转页面
ModelAndView对象代表当前执行后的结果 存储的数据与视图对象 ,前端控制器会自动取出对应的数据进行使用
Model:模型 作用封装数据
View:视图 作用展示数据
@RequestMapping("/u2.do")
public ModelAndView u2(ModelAndView mv) {
mv.setViewName("index");
mv.addObject("msg","u2");
return mv;
}
4.6.1.4.2 回写数据
(1)直接返回字符串
将方法返回的字符直接发送至客户端浏览器。
@ResponseBody注解:表示该方法返回的字符串不被视图解析器解析,而是直接通过响应体发送给前台。
@ResponseBody
@RequestMapping("/u3.do")
public String res4(HttpServletRequest request) {
return "return msg data";
}
(2)返回对象或集合
后台返回到前台的数据一般使用JSON格式
需要将java对象或集合转化为json字符串返回 ,显然如果每个方法都需要我们手动转化,太繁琐,SpringMVC提供了自动转化的方式 ,只需要在配置结束后,定义方法时修改返回值类型即可。
导入jackson坐标
<!-- jackson依赖 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.0</version>
</dependency>
在spring.xml中配置
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
</list>
</property>
</bean>
返回对象
@ResponseBody
@RequestMapping("/u4.do")
public Student u4(){
Student student=new Student(1,"tom","18","18732553866");
return student;
}
返回集合
@ResponseBody
@RequestMapping("/u5.do")
public ArrayList<Student> u5(){
ArrayList<Student> list=new ArrayList<>();
Student student1=new Student(1,"tom","18","18732553866");
Student student2=new Student(2,"cat","26","12345465677");
Student student3=new Student(3,"jery","48","18123124246");
Student student4=new Student(4,"jim","28","112233455666");
list.add(student1);
list.add(student2);
list.add(student3);
list.add(student4);
return list;
}
注意
也可以通过注解驱动代替上述在spring.xml中的配置
<mvc:annotation-driven/>
在 SpringMVC 的各个组件中,处理器映射器、处理器适配器、视图解析器称为 SpringMVC 的三大组件。
使用
<mvc:annotation-driven />
自动加载 RequestMappingHandlerMapping(处理映射器)和RequestMappingHandlerAdapter( 处 理 适 配 器 ),可用在Spring-xml.xml配置文件中使用<mvc:annotation-driven />
替代注解处理器和适配器的配置。同时使用<mvc:annotation-driven />
默认底层就会集成jackson进行对象或集合的json格式字符串的转换
4.6.1.4.3 补充
在实际开发中前后台进行数据交互JSON,一般格式为: code msg data
我们可以创建一个用于响应数据的类 ,然后进行封装赋值,并使用jackson返回
用于封装响应数据
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<E> {
private int code;
private String msg;
private List<E> data;
}
模拟查询
//模拟查询
@ResponseBody
@RequestMapping("/u6.do")
public Result<Student> findStudent(){
Student student1=new Student(1,"tom","18","18732553866");
Student student2=new Student(2,"cat","26","12345465677");
ArrayList<Student> list=new ArrayList<>();
list.add(student1);
list.add(student2);
Result<Student> result=new Result<>();
result.setCode(200);
result.setMsg("成功");
result.setData(list);
//jackson会自动进行解析 返回json字符串
return result;
}
4.6.1.5 SpringMVC的数据请求
4.6.1.5.1 基本类型参数获取
基本类型(常用类型)参数的获取
只需要在方法声明对应类型的行参, springmvc就会自动进行赋值,数据在web传输获取都是字符串也会自动转换为对应类型,只需要保证形参名称与传递参数的name相同即可。
注册页面
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<body>
<form action="r1.do">
账号:<input type="text" name="sid"><br>
姓名:<input type="text" name="sname"><br>
年龄:<input type="password" name="age"><br>
手机号:<input type="text" name="phone"><br>
<input type="submit" value="注册">
</form>
</body>
</html>
@Controller
public class RequestController {
@ResponseBody
@RequestMapping("/r1.do")
public String r1(int sid,String sname,String age,String phone) {
return sid+sname+age+phone;
}
}
Controller中的业务方法的POJO参数的属性名与请求参数的name一致,参数值会自动映射匹配。
使用RequestParam注解
@RequestParam有以下三个参数: value:参数名字,即入参的请求参数名字,如username表示请求的参数区中的名字为username的参数的值将传入; required:是否必须,默认是true,表示请求中一定要有相应的参数,否则将抛出异常; defaultValue:默认值,表示如果请求中没有同名参数时的默认值,设置该参数时,自动将required设为false。
4.6.1.5.2 类类型参数的获取
SpringMVC会自动将传递的参数赋值给对象的相应属性,但要求属性名与传递参数名一致。
@ResponseBody
@RequestMapping("/r2.do")
public User r2(Student student) {
return student;
}
4.6.1.5.3 数组类型参数的获取
场景:注册时选择多选框
此类场景会出现name相同情况,如果使用类类型参数获取的方法仅能获取第一个数据,
可以通过声明数组保存数据,进而获取请求中name相同的多个数据。
@ResponseBody
@RequestMapping("/r3.do")
public String r3(String[] arr) {
return Arrays.toString(arr);
}
4.6.1.5.4 集合类型参数的获取
SpringMVC会自动将对个name相同的数据解析为数组,所以在进行集合获取时需要将集合参数包装到一个POJO类中。
public class Handle{
private List<Student> studentList;
public List<student> getstudentList() {
return studentList;
}
public void setstudentList(List<Student> studentList) {
this.studentList = studentList;
}
@Override
public String toString() {
return "Handle:{" +
"studentList=" + studentList +
'}';
}
}
@RequestMapping("/r4.do")
@ResponseBody
public void r4(Handle handle) throws IOException {
System.out.println(handle);
}
对于不同的pojo类需要创建对应的集合类 ,太繁琐
使用ajax提交json数据获取集合类型参数数据
当使用ajax提交时,可以指定contentType为json形式,那么在方法参数位置使用@RequestBody可以直接接收集合数据而无需使用POJO进行包装 。
@ResponseBody
@RequestMapping(value="/r4.do")
public void r4(@RequestBody List<Student> studentList) throws IOException {
System.out.println(studentList);
}
4.6.1.6 SpringMVC 处理访问静态资源
在前面的学习,web.xml配置的servlet映射中放行url格式为 *.do,
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
方法的映射路径都要加 .do后缀
@ResponseBody
@RequestMapping("/r1.do")
public String r1(int sid,String sname,String age,String phone) {
return sid+sname+age+phone;
}
如果我们修改为/: <url-pattern>/*</url-pattern>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
<!-- *.do只管理.do结尾的请求
/* 所有的请求都会处理 包括页面
/ 仅处理请求 对页面处理放行
-->
</servlet-mapping>
方法映射路径就可以省去.do后缀
@ResponseBody
@RequestMapping("/r1")
public String r1(int sid,String sname,String age,String phone) {
return sid+sname+age+phone;
}
/与/*区别
现有如下结构的项目
background.css
.background{
background-color: blue;
}
hello.png
static1.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>Title</title>
<link rel="stylesheet" href="../css/background.css">
<style>
div {
height: 200px;
width: 200px;
}
</style>
</head>
<body>
${msg}
<div class="bg"></div>
<img src="../img/hello.png">
</body>
</html>
@Controller
@RequestMapping("/r")
public class StaticController {
@RequestMapping("/s1")
public String s1(HttpServletRequest request){
request.setAttribute("msg","s1 data");
return "static1";
}
}
运行结果
F12进入开发者,发现页面中有引用了background.css的div模块,但是在页面中却没有显示,同样图片也加载不出来。
这就是加载静态资源出现的问题
先来查看资源请求的路径,发现直接在项目路径下直接请求css或imgm,但是我们实际路径是WEB-INF/css或WEB-INF/img
为了解决资源路径的问题,我们需要在spring.xml添加资源路径映射
<!--springmvc路径配置/会尽量对资源进行放行
但是静态资源在页面请求的路径与实际路径不一致导致无法加载(页面相对路径在web中相对的是服务器)
所以需要手动的配置 静态资源服务器请求路径与实际路径的映射
-->
<mvc:resources mapping="/css/**" location="/WEB-INF/css/"/>
<mvc:resources mapping="/img/**" location="/WEB-INF/img/"/>
如果所有的静态资源都手动添加映射,太过于繁琐。
因此,SpringMVC提供了 默认的servlet处理器,当遇到HandlerMapping 无法处理的请求时使用默认的servlet处理器进行处理。
<mvc:default-servlet-handler/>
注意:
默认配置是处理webapp下的静态资源 类似于默认配置
<mvc:resources mapping="/css/**" location="/css/"/>
如果静态资源放在WEB-INF下 那么还是需要手动进行配置
成功运行
4.6.1.7 编码过滤器
编码不一致时会出现乱码问题
<!--配置全局过滤的filter-->
<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>
如果出现JSON传值乱码
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
4.6.1.8 SpringMVC配置参数绑定
请求的参数名称与Controller业务方法参数名称不一致时,就需要通过@RequestParam注解显式的绑定。
如页面传过来的是名称是username,但是我们业务方法里是name
@RequestMapping("/p1")
@ResponseBody
//获取传递中指定name的数据赋值给形参
public String p1(@RequestParam(value = "username") String name){
return "name:"+name;
}
但是如果页面中username不传参数,那页面报错400,这时候有两种解决办法:
法一
参数绑定注解required属性默认为true,没有数据就报错
@RequestParam(value = " ",required = false)
法二
设置默认值当没有传递数据时使用默认值赋值,这样没有传参数就会使用默认值作为参数,
@RequestParam(value = " ",defaultValue = " ")
@RequestMapping("/p1")
@ResponseBody
//设置默认值 当没有传递数据时 使用默认值赋值
public String p1(@RequestParam(value = "name",defaultValue = "admin") String name){
return "name:"+name;
}
4.6.1.9 SpringMVC配置请求类型转换器
SpringMVC 默认已经提供了一些常用的类型转换器 ,但不是所有的数据类型都提供了转换器,没有提供的需要自定义转换器 。
日期类型的数据需要自定义转换器
由于没有提供转换器,日期数据在web以字符串形式传输
自定义日期转换器
//通过实现转换器接口来自定义日期转化器 实现字符串转化为日期对象
@Component
public class DateConverter implements Converter<String, Date> {
//转化方法 参数传入的就是获取实际数据的字符串
//返回值就是转化后的结果
@Override
public Date convert(String source) {
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
Date parse=null;
try {
parse = sdf.parse(source);
} catch (ParseException e) {
e.printStackTrace();
}
return parse;
}
}
注意导包
spring.xml中配置转换器服务
<mvc:annotation-driven conversion-service="conversionService"/>
将自定义转换器交由SpringMVC管理
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<ref bean="dateConverter"/>
</set>
</property>
</bean>
测试
//以字符串形式获取可以
@RequestMapping("/date")
@ResponseBody
public String date(Date time){
System.out.println(time);
return null;
}
以上是字符串类型转换为日期类型 ,通过自定义转换器,我们可以定制很多其他的转换器,具体方法如上。
4.6.1.10 SpringMVC请求头信息信息的获取
@RequestHeader可以获得请求头信息,相当于web阶段学习的request.getHeader(name)
@RequestHeader(value = "请求头名称",required = true/false 是否必须携带此请求头 默认:true)
@RequestMapping("/h")
@ResponseBody
public String h(@RequestHeader(value = "User-Agent",required = true) String header){
return header;
}
请求头cookie数据的获取
@CookieValue可以获得指定Cookie的值
@CookieValue(value = "cookie名称 ",required=true/false 默认:true)
4.6.1.11 SpringMVC使用RESTful风格
RESTful是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。主要用于客户端和服务器交互类的软件。
Restful风格的请求是使用“url+请求方式”表示一次请求目的,HTTP 协议里面四个表示操作方式的动词如下:
GET:用于获取资源
POST:用于新建资源
PUT:用于更新资源
DELETE:用于删除资源
4.6.1.11.1 获取URL占位符
RESTful可以通过通配符获取url地址形式的参数 在参数列表使用@PathVariable进行绑定
@Controller
@RequestMapping("/restful")
public class RestfulController {
@RequestMapping("/path/{value}")
@ResponseBody
public String path(@PathVariable("value") String value){
return value;
}
}
4.6.1.11.2 RESTful风格的增删改查
对于http请求包括get与post请求,但是并没有put与delete请求。这两个请求本质还是使用post请求,以ajax为例额外添加了_method:"put/delete"
<!DOCTYPE html>
<%@ page contentType="text/html;charset=utf-8" %>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
$(function() {
$("button").eq(0).click(function() {
$.ajax({
async: false, //使用同步Ajax请求
type: "POST",
url: "/restful/user/1", //为id为1 添加信息
data: {
"username": "zhangsan",
"password": 24,
"phone": "12312312321"
},
dataType: "json", //省略智能判断类型
success: function(msg) {
console.log(msg)
},
error: function(msg) {
console.log(msg)
}
});
})
$("button").eq(1).click(function() {
$.ajax({
async: false, //使用同步Ajax请求
type: "DELETE",//直接书写delete后台可以执行 但是无法获取请求数据
url: "/restful/user/1",
dataType: "text", //省略智能判断类型
success: function(msg) {
console.log(msg)
},
error: function(msg) {
console.log(msg)
}
});
})
$("button").eq(2).click(function() {
$.ajax({
async: false, //使用同步Ajax请求
type: "POST",//直接书写put后台可以执行 但是无法获取请求数据
url: "/restful/user/1",
dataType: "text", //省略智能判断类型
data: {
"_method":"PUT",
"username":"zhangsan"
,"password":"123456"
},
success: function(msg) {
console.log(msg)
},
error: function(msg) {
console.log(msg)
}
});
})
$("button").eq(3).click(function() {
$.ajax({
async: false, //使用同步Ajax请求
type: "GET",
url: "/restful/user/1",
dataType: "text", //省略智能判断类型
success: function(msg) {
console.log(msg)
},
error: function(msg) {
console.log(msg)
}
});
})
})
</script>
</head>
<body>
<button>添加</button>
<button>删除</button>
<button>修改</button>
<button>查询</button>
</body>
</html>
@Controller
@RequestMapping("/restful")
public class RestfulController {
}
//restful风格就是一种处理请求的方式
//将相同的url设置不同的请求方式 实现相同url不同的内容执行
//get请求用于查询
//post请求用于添加
//delete请求用于删除
//put请求用于修改
@RequestMapping(value = "/user/{id}",method = RequestMethod.GET)
@ResponseBody
public String select(@PathVariable("id") int id){
return "select"+id;
}
@RequestMapping(value = "/user/{id}",method = RequestMethod.PUT)
@ResponseBody
public String update(@PathVariable("id") int id,String username,String password){
return "update"+id+username+password;
}
@RequestMapping(value = "/user/{id}",method = RequestMethod.DELETE)
@ResponseBody
public String delete(@PathVariable("id") int id){
return "delete="+id;
}
@RequestMapping(value = "/user",method = RequestMethod.POST)
@ResponseBody
public User insert(User u){
return u;
}
}
4.6.1.12 SpringMVC拦截器
4.6.1.12.1 过滤器与拦截器
SpringMVC的处理器拦截器,类似于Servlet开发中的过滤器Filter
过滤器:
依赖于servlet容器。在实现上基于函数回调,可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次。使用过滤器的目的是用来做一些过滤操作,比如:在过滤器中修改字符编码。
请求到服务器,执行servlet之前。
拦截器:
依赖于web框架,在实现上基于Java的反射机制,属于面向切面编程(AOP)的一种运用。由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。
拦截器本质是AOP(面向切面编程),也就是说符合横切关注点的所有功能都可以放入拦截器实现。
已经找到服务器和执行的方法,在方法前后执行
常见应用场景
1)日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等。
2)权限检查:如登录检测,进入处理器检测是否登录,如果没有直接返回到登录页面;
3)性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间(如果有反向代理,如apache可以自动记录);
4)通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个Controller中的处理方法都需要的,我们就可以使用拦截器实现。
5)OpenSessionInView:如Hibernate,在进入处理器打开Session,在完成后关闭Session。
4.6.1.12.2 拦截器
拦截器一个有3个回调方法,而一般的过滤器Filter才两个:
preHandle:预处理回调方法,实现处理器的预处理(如登录检查),第三个参数为响应的处理器返回值:true表示继续流程(如调用下一个拦截器或处理器);false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应;
postHandle:后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为null。
afterCompletion:整个请求处理完毕回调方法,即在视图渲染完毕时回调,如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finally,但仅调用处理器执行链中preHandle返回true的拦截器才会执行afterCompletion。
SpringMVC提供的拦截器接口:HandlerInterceptor
拦截器适配器:HandlerInterceptorAdapter
有时我们可能只需要实现三个回调方法中的某一个,如果实现HandlerInterceptor接口的话,三个方法必须实现,此时spring提供了一个HandlerInterceptorAdapter适配器(一种适配器设计模式的实现),允许我们只实现需要的回调方法
1、拦截所有Controller类
自定义拦截器
@Component
public class MyHandlerInterceptor extends HandlerInterceptorAdapter {
//前置拦截 返回值true继续执行 false不执行
//在controller方法执行前执行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyHandlerInterceptor前置拦截");
return true;
}
//后置拦截
//在controller方法执行后 将modelAndView返回给前端控制器时执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyHandlerInterceptor后置拦截");
}
//最终拦截
//是在前端控制器 渲染完视图页面返回前台时执行
//代码当前请求结束
@Override
public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyHandlerInterceptor最终拦截");
}
}
将拦截器交由Spring管理
<mvc:interceptors>
<ref bean="myHandlerInterceptor"></ref>
<bean class="com.bl.Controller.MyHandlerInterceptor"></bean>
</mvc:interceptors>
测试类
@Controller
@RequestMapping("/interceptor")
public class InterceptorController {
@RequestMapping("/i1")
@ResponseBody
public void i1() {
System.out.println("i1方法执行");
}
}
运行结果
你会发现,这怎么和前置通知、后置通知很相似。这是因为拦截器本质还是AOP。
2、只拦截某个请求路径的处理方法
通过定义定义path属性来指定拦截,如果不定义path,默认拦截所有controller类
<mvc:mapping path="拦截路径"/>
指定拦截路径
<mvc:interceptors>
<mvc:interceptor>
<!-- 只拦截该路径 -->
<mvc:mapping path="/interceptor/i2"/>
<bean class="com.bl.Controller.MyHandlerInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
@Controller
@RequestMapping("/interceptor")
public class InterceptorController {
@RequestMapping("/i1")
@ResponseBody
public void i1() {
System.out.println("i1方法执行");
}
@RequestMapping("/i2")
@ResponseBody
public void i2(){
System.out.println("i2方法执行");
}
}
i1方法正常执行,i2方法被拦截
3、拦截器高级配置
<mvc:interceptors>
<mvc:interceptor>
<!-- 拦截所有请求 -->
<mvc:mapping path="/interceptor/**"/>
<!-- 指定的请求 不拦截-->
<mvc:exclude-mapping path="/interceptor/i3"/>
<mvc:exclude-mapping path="/interceptor/i2"/>
<bean class="com.bl.Controller.MyHandlerInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
4.6.2 SSM整合
4.6.2.1 SSM概述
SSM:Spring+SpringMVC+MyBatis
SSM框架集由Spring、MyBatis两个开源框架整合而成(SpringMVC是Spring中的部分内容),常作为数据源较简单的web项目的框架。
4.6.2.2 整合步骤
- 导入相应的依赖坐标
- 创建相应的包
- 书写mybatis核心配置文件
- 书写log4j与数据连接配置文件
- 配置spring相关核心配置文件
- 配置web.xml
- 书写相应的代码测试ssm整合是否成功