文章目录
一.SpringMVC概述
1.1 什么是SpringMVC
它是基于MVC开发模式的框架,用来优化控制器.它是Spring家族的一员.它也具备IOC和AOP.
什么是MVC?
它是一种开发模式,它是模型视图控制器的简称.所有的web应用都是基于MVC开发.
- M:模型层,包含实体类,业务逻辑层,数据访问层
- V:视图层,html,javaScript,vue等都是视图层,用来显现数据
- C:控制器,它是用来接收客户端的请求,并返回响应到客户端的组件,Servlet就是组件
1.2 SpringMVC框架的优点
1)轻量级,基于MVC的框架
2)易于上手,容易理解,功能强大
3)它具备IOC和AOP
4)完全基于注解开发
1.3 SpringMVC优化的方向
1.4 SpringMVC执行的流程
1.4.1 执行的流程
具体的流程:
- 用户通过浏览器发起 HttpRequest 请求到前端控制器 (DispatcherServlet)。
- DispatcherServlet 将用户请求发送给处理器映射器 (HandlerMapping)。
- 处理器映射器 (HandlerMapping)会根据请求,找到负责处理该请求的处理器,并将其封装为处理器执行链 返回 (HandlerExecutionChain) 给 DispatcherServlet
- DispatcherServlet 会根据 处理器执行链 中的处理器,找到能够执行该处理器的处理器适配器(HandlerAdaptor) --注,处理器适配器有多个
- 处理器适配器 (HandlerAdaptoer) 会调用对应的具体的 Controller
- Controller 将处理结果及要跳转的视图封装到一个对象 ModelAndView 中并将其返回给处理器适配器 (HandlerAdaptor)
- HandlerAdaptor 直接将 ModelAndView 交给 DispatcherServlet ,至此,业务处理完毕
- 业务处理完毕后,我们需要将处理结果展示给用户。于是DisptcherServlet 调用 ViewResolver,将 ModelAndView 中的视图名称封装为视图对象
- ViewResolver 将封装好的视图 (View) 对象返回给 DIspatcherServlet
- DispatcherServlet 调用视图对象,让其自己 (View) 进行渲染(将模型数据填充至视图中),形成响应对象 (HttpResponse)
- 前端控制器 (DispatcherServlet) 响应 (HttpResponse) 给浏览器,展示在页面上。
1.4.2 SpringMVC常用组件
1)DispatcherServlet
是一种前端控制器,由框架提供。
作用:统一处理请求和响应。除此之外还是整个流程控制的中心,由 DispatcherServlet 来调用其他组件,处理用户的请求
2)HandlerMapping
处理器映射器,由框架提供。
作用:根据请求的 url、method 等信息来查找具体的 Handler(一般来讲是Controller)
3)Handler(一般来讲是Controller)
处理器,注意,这个需由工程师自己开发。
作用:在 DispatcherServlet 的控制下,Handler对具体的用户请求进行处理
4)HandlerAdapter
处理器适配器,由框架提供。
作用:根据映射器找到的处理器 Handler 信息,按照特定的规则去执行相关的处理器 Handler。
5)ViewResolver
视图解析器,由框架提供。
作用: ViewResolver 负责将处理结果生成 View 视图。 ViewResolver 首先根据逻辑视图名解析成物理图名,即具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户。
6)View
视图,工程师自己开发
作用:View接口的职责就是接收model对象、Request对象、Response对象,并渲染输出结果给Response对象。
1.5 基于注解的SpringMVC程序
SpringMVC 的注解式开发:在代码中通过对类与方法的注解,便可完成处理器在 springmvc 容器的注册。
项目案例功能:用户提交一个请求,服务端处理器在接收到这个请求后,给出一条欢迎信息,在响应页面中显示该信息。
开发的步骤:
1)新建项目,选择webapp模板.
2)修改目录,添加缺失的test,java,resources(两套),并修改目录属性
3)修改pom.xml文件,添加SpringMVC的依赖,添加Servlet的依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--添加servlet的依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
4)添加springmvc.xml配置文件,指定包扫描,添加视图解析器.
<!--添加包扫描-->
<context:component-scan base-package="com.bjpowernode.controller"></context:component-scan>
<!--添加视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--配置前缀-->
<property name="prefix" value="/admin/"></property>
<!--配置后缀-->
<property name="suffix" value=".jsp"></property>
</bean>
5)删除web.xml文件,新建web.xml
6)在web.xml文件中注册springMVC框架(所有的web请求都是基于servlet的)
<!--注册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:springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!--
指定拦截什么样的请求
http://localhost:8080/one
http://localhost:8080/index.jsp
http://localhost:8080/demo.action
<a href="${pageContext.request.contextPath}/demo.action">访问服务器</a>
-->
<url-pattern>*.action</url-pattern>
</servlet-mapping>
7)在webapp目录下新建admin目录,在admin目录下新建main.jsp页面,删除index.jsp页面,并新建,发送请求给服务器
8)开发控制器(Servlet),它是一个普通的类.
@Controller //交给Spring去创建对象
public class DemoAction {
/**
* 以前的Servlet的规范
* protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {}
* action中所有的功能实现都是由方法来完成的
* action方法的规范
* 1)访问权限是public
* 2)方法的返回值任意
* 3)方法名称任意
* 4)方法可以没有参数,如果有可是任意类型
* 5)要使用@RequestMapping注解来声明一个访问的路径(名称)
*
*/
@RequestMapping("/demo")
public String demo(){
System.out.println("服务器被访问到了.......");
return "main"; //可以直接跳到/admin/main.jsp页面上
}
}
9)添加tomcat进行测试功能
二.SpringMVC注解式开发
2.1 @RequestMapping定义请求规则
2.1.1 指定模块名称
通过@RequestMapping 注解可以定义处理器对于请求的映射规则。该注解可以注解在方法上,也可以注解在类上,但意义是不同的。value 属性值常以“/”开始。@RequestMapping 的 value 属性用于定义所匹配请求的 URI。
举例:
1)此注解可加在方法上,是为此方法注册一个可以访问的名称(路径)
提取后:
2)此注解可以加在类上,相当于是包名(虚拟路径),区分不同类中相同的action的名称
@Controller
@RequestMapping("/zar")
public class HelloSpringMvc {
//相当于一个控制器处理的方法
@RequestMapping("/hello")
public String one() {
return "main";
}
@RequestMapping("/two")
public String two() {
return "main";
}
//客户端的请求:
// <form action="${pageContext.request.contextPath}/zar/hello.action">
// <form action="${pageContext.request.contextPath}/zar/two.action">
}
2.1.2 对请求提交方式的定义
对于@RequestMapping,其有一个属性 method,用于对被注解方法所处理请求的提交
方式进行限制,即只有满足该 method 属性指定的提交方式的请求,才会执行该被注解方法。
Method 属性的取值为 RequestMethod 枚举常量。常用的为 RequestMethod.GET 与
RequestMethod.POST,分别表示提交方式的匹配规则为 GET 与 POST 提交。
@Controller
public class ReqAction {
@RequestMapping(value = "/req",method = RequestMethod.GET)
public String req(){
System.out.println("我是处理get请求的........");
return "main";
}
@RequestMapping(value = "/req" ,method = RequestMethod.POST)
public String req1(){
System.out.println("我是处理post请求的........");
return "main";
}
}
2.2 五种数据提交的方式
前述:前四种数据注入的方式,会自动进行类型转换。但无法自动转换日期类型。
2.2.1 单个提交数据
页面:
<form action="${pageContext.request.contextPath}/one.action">
姓名:<input name="myname"><br>
年龄:<input name="age"><br>
<input type="submit" value="提交">
</form>
action:
@RequestMapping("/one")
public String one(String myname,int age){ ===>自动注入,并且类型转换
System.out.println("myname="+myname+",age="+(age+100));
return "main";
}
2.2.2 对象封装提交数据
在提交请求中,保证请求参数的名称与实体类中成员变量的名称一致,则可以自动创建对象,则可以自动提交数据,自动类型转换,自动封装数据到对象中.
实体类:
public class Users {
private String name;
private int age;
}
页面:
<form action="${pageContext.request.contextPath}/two.action" method="post">
姓名:<input name="name"><br>
年龄:<input name="age"><br>
<input type="submit" value="提交">
</form>
action:
@RequestMapping("/two")
public String two(Users u){
System.out.println(u);
return "main";
}
2.2.3 映射名称不一致
提交请求参数与action方法的形参的名称不一致,使用注解 @RequestParam 来解析
/**
* 姓名:<input name="name"><br>
* 年龄:<input name="age"><br>
*/
@RequestMapping("/four")
public String four(
@RequestParam("name") ===>专门用来解决名称不一致的问题
String uname,
@RequestParam("age")
int uage){
System.out.println("uname="+uname+",uage="+(uage+100));
return "main";
}
2.2.4 动态占位符提交
仅限于超链接或地址拦提交数据.它是一杠一值,一杠一大括号,使用注解@PathVariable来解析.
页面:
<a href="${pageContext.request.contextPath}/three/张三/22.action">动态提交</a>
action:
@RequestMapping("/three/{uname}/{uage}")
public String three(
@PathVariable("uname") ===>用来解析路径中的请求参数
String name,
@PathVariable("uage")
int age){
System.out.println("name="+name+",age="+(age+100));
return "main";
}
2.2.5 手工提取数据
/**
* 姓名:<input name="name"><br>
* 年龄:<input name="age"><br>
*/
@RequestMapping("/five")
public String five(HttpServletRequest request){
String name = request.getParameter("name");
int age = Integer.parseInt(request.getParameter("age"));
System.out.println("name="+name+",age="+(age+100));
return "main";
}
2.3 中文乱码解决方案
在 web.xml 中注册字符集过滤器,即可解决 Spring 的请求参数的中文乱码问题。不过,最好将该过滤器注册在其它过滤器之前。因为过滤器的执行是按照其注册顺序进行的。
在web.xml中配置过滤器:
<filter>
<filter-name>encode</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!--
配置参数
private String encoding;
private boolean forceRequestEncoding;
private boolean forceResponseEncoding;
-->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encode</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2.4 处理器action方法的返回值
2.4.1 String
处理器方法返回的字符串可以指定逻辑视图名,通过视图解析器解析可以将其转换为物理视图地址。
当然,也可以直接返回资源的物理视图名。不过,此时就不需要再在视图解析器中再配置前辍与后辍了。
2.4.2 Object
需要使用 @ResponseBody 注解。返回json格式的对象.自动将对象或集合转为json.使用的jackson工具进行转换,必须要添加jackson依赖.一般用于ajax请求.
项目案例:使用ajax请求返回一个JSON结构的学生.
实现步骤:
A.在pom.xml文件中添加依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
B.添加jQuery的函数库,在webapp目录下,新建js目录,拷贝jquery-3.6.0.js到目录下
C.在页面添加jQuery的函数库的引用
<script src="js/jquery-3.6.0.js"></script>
D.发送ajax请求
function show() {
$.ajax({
url:"${pageContext.request.contextPath}/ajax.action",
type:"post",
dataType:"json",
success:function (stu) {
$("#oneStu").html(stu.name+"------"+stu.age);
}
});
}
E.开发action
@Controller
public class AjaxDemo {
@RequestMapping("/ajax")
@ResponseBody //此注解用来解析ajax请求
public Object ajax(){
Student stu = new Student("张三",22);
return stu;
}
}
F.在springmvc.xml文件中添加注解驱动
<mvc:annotation-driven></mvc:annotation-driven>
G.index.jsp页面
<a href="javascript:show()">ajax访问服务器,返回一个学生</a>
<br>
<div id="oneStu"></div>
2.4.3 void
对于处理器方法返回 void 的应用场景,应用在AJAX 响应处理。若处理器对请求处理后,无需跳转到其它任何资源,此时可以让处理器方法返回 void。
2.4.4 基本数据类型
用于ajax请求.
2.4.5 ModelAndView
返回数据和视图对象,现在用的很少.
2.5 SpringMVC的四种跳转方式
本质还是两种跳转:请求转发和重定向,衍生出四种是请求转发页面、转发action、重定向页面、重定向action
@RequestMapping("/one")
public String one(){
System.out.println("这是请求转发页面跳转.........");
return "main"; //默认是请求转发,使用视图解析器拼接前缀后缀进行页面跳转
}
@RequestMapping("/two")
public String two(){
System.out.println("这是请求转发action跳转.........");
// /admin/ /other.action .jsp
//forward: 这组字符串可以屏蔽前缀和后缀的拼接.实现请求转发跳转
return "forward:/other.action"; //默认是请求转发,使用视图解析器拼接前缀后缀进行页面跳转
}
@RequestMapping("/three")
public String three(){
System.out.println("这是重定向页面.......");
//redirect: 这组字符串可以屏蔽前缀和后缀的拼接.实现重定向跳转
return "redirect:/admin/main.jsp";
}
@RequestMapping("/four")
public String four(){
System.out.println("这是重定向action.......");
//redirect: 这组字符串可以屏蔽前缀和后缀的拼接.实现重定向跳转
return "redirect:/other.action";
}
@RequestMapping("/five")
public String five(){
System.out.println("这是随便跳.......");
return "forward:/fore/login.jsp";
}
2.6 SpringMVC默认的参数类型
不需要去创建,直接拿来使用即可.
1)HttpServletRequest
2)HttpServletResponse
3)HttpSession
4)Model
5)Map
6)ModelMap
示例:
@Controller
public class ParamAction {
@RequestMapping("/param")
public String param(HttpServletRequest request,
HttpServletResponse response,
HttpSession session,
Model model,
ModelMap modelMap,
Map map){
//Map ,Model,ModelMap,request都使用请求作用域进行传值,
//所以必须使用请求转发的方式进行跳转,否则丢失数据
Student stu = new Student("张三",22);
request.setAttribute("requestStu",stu);
session.setAttribute("sessionStu",stu);
modelMap.addAttribute("modelMapStu",stu);
model.addAttribute("modelStu",stu);
map.put("mapStu",stu);
return "main"; //切记请求转发跳
// return "redirect:/admin/main.jsp";//会丢失数据
}
}
注意Model,Map,ModelMap都使用的是request请求作用域,意味着只能是请求转发后,页面才可以得到值。
2.7 日期处理
2.7.1 日期的提交处理
A.在方法的参数上使用@DateTimeFormat注解
要使用注解 @DateTimeFormat,此注解必须搭配springmvc.xml文件中的<mvc:annotation-driven/>
标签
B.@InitBinder注解解决类中日期问题
注册一个注解,用来解析本类中所有的日期类型,自动转换.
@InitBinder
public void initBinder(WebDataBinder dataBinder) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
dataBinder.registerCustomEditor(Date.class, new CustomDateEditor(sdf, true));
}
2.7.2 日期的显示处理
A.JSON中的日期显示
需要在类中的成员变量的getXXX方法上加注解.
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")
public Date getDate() {
return date;
}
B.JSP页面的日期显示
步骤:
1.需要使用国际化标签,先添加依赖
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
2.导入国际化的标签库
核心库:<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
日期库:<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
3.再使用标签显示日期
<h2>学生集合</h2>
<table border="2" width="800px">
<tr>
<th>姓名</th>
<th>年龄</th>
</tr>
<c:forEach items="${list}" var="teacher">
<tr>
<td>${teacher.name}</td>
<td>${teacher.date}-------------<fmt:formatDate value="${teacher.date}" pattern="yyyy-MM-dd"></fmt:formatDate> </td>
</tr>
</c:forEach>
</table>
2.8 mvc:annotation-driven/标签的使用
<mvc:annotation-driven/>
会自动注册两个bean,分别为DefaultAnnotationHandlerMapping和AnnotationMethodHandlerAdapter。是springmvc为@controller分发请求所必须的。除了注册了这两个bean,还提供了很多支持。
1)支持使用ConversionService 实例对表单参数进行类型转换;
2)支持使用 @NumberFormat 、@DateTimeFormat;
3)注解完成数据类型的格式化;
4)支持使用 @RequestBody 和 @ResponseBody 注解;
5)静态资源的分流也使用这个标签;
2.9 资源在WEB-INF目录下
此目录下的动态资源,不可直接访问,只能通过请求转发的方式进行访问 .
重定向也无法访问!!!!!!!!!
@Controller
public class WebInfAction {
@RequestMapping("/showIndex")
public String showIndex(){
System.out.println("访问index.jsp");
return "index";
}
@RequestMapping("/showMain")
public String showMain(){
System.out.println("访问main.jsp");
return "main";
}
@RequestMapping("/showLogin")
public String showLogin(){
System.out.println("访问login.jsp");
return "login";
}
//登录的业务判断
@RequestMapping("/login")
public String login(String name, String pwd, HttpServletRequest request){
if("zar".equalsIgnoreCase(name) && "123".equalsIgnoreCase(pwd)){
return "main";
}else{
request.setAttribute("msg","用户名或密码不正确!");
return "login";
}
}
}
三.SpringMVC拦截器
3.1 拦截器的介绍
3.1.1 什么是拦截器
针对请求和响应进行的额外的处理.在请求和响应的过程中添加预处理,后处理和最终处理.
拦截的时间点:在“处理器映射器根据用户提交的请求映射出了所要执行的处理器类,并且也找到了要执行该处理器类的处理器适配器,在处理器适配器执行处理器之前”。
当然,在处理器映射器映射出所要执行的处理器类时,已经将拦截器与处理器组合为了一个处理器执行链,并返回给了中央调度器。
3.1.2 拦截器的执行原理
3.1.3 拦截器执行的时机
1)preHandle()
:在请求被处理之前进行操作
2)postHandle()
:在请求被处理之后,但结果还没有渲染前进行操作,可以改变响应结果
3)afterCompletion
:所有的请求响应结束后执行善后工作,清理对象,关闭资源
3.1.4 拦截器实现的两种方式
1)继承HandlerInterceptorAdapter
的父类
2)实现HandlerInterceptor
接口,实现的接口,推荐使用实现接口的方式
3.2 HandlerInterceptor接口分析
自定义拦截器,需要实现 HandlerInterceptor
接口。而该接口中含有三个方法:
(1) preHandle
该方法在处理器方法执行之前执行。其返回值为 boolean,若为 true,则紧接着会执行处理器方法,且会将 afterCompletion()方法放入到一个专门的方法栈中等待执行。
(2) postHandle
该方法在处理器方法执行之后执行。处理器方法若最终未被执行,则该方法不会执行。由于该方法是在处理器方法执行完后执行,且该方法参数中包含 ModelAndView,所以该方法可以修改处理器方法的处理结果数据,且可以修改跳转方向。
(3)afterCompletion
当preHandle()方法返回 true 时,会将该方法放到专门的方法栈中,等到对请求进行响应的所有工作完成之后才执行该方法。即该方法是在中央调度器渲染(数据填充)了响应页面之后执行的,此时对 ModelAndView 再操作也对响应无济于事。afterCompletion 最后执行的方法,清除资源,例如在 Controller 方法中加入数据等。
3.3 自定义拦截器实现权限验证
1.修改web.xml文件中请求路径
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
2.将所有的页面放入WEB-INF目录下
3.开发登录action
@RequestMapping("/login")
public String login(String name, String pwd, HttpServletRequest request){
if("zar".equalsIgnoreCase(name) && "123".equalsIgnoreCase(pwd)){
//在session中存储用户信息,用于进行权限验证
request.getSession().setAttribute("users",name);
return "main";
}else{
request.setAttribute("msg","用户名或密码不正确!");
return "login";
}
}
4.开发拦截器的功能.实现HandlerInterceptor接口,重写preHandle()方法
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//是否登陆过的判断
if(request.getSession().getAttribute("user")==null){
//此时就是没有登录,转发到登陆页面,并给出提示
request.setAttribute("msg","您还没有登录,请重新登录!");
request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request,response);
return false;
}
return true;
}
}
5.在springmvc.xml文件中注册拦截器
<!--注册拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<!--要拦截的请求-->
<mvc:mapping path="/**"/>
<!--设置放行的请求-->
<mvc:exclude-mapping path="/showLogin"/>
<mvc:exclude-mapping path="/login"/>
<!--配置具体的拦截器实现功能的类-->
<bean class="com.jm.interceptor.LoginInterceptor"></bean>
</mvc:interceptor>
<!--拦截器链.......-->
</mvc:interceptors>