一、 SpringMVC简介
springmvc是专门做web开发的spring框架,是在spring3.0后发布的。
1.1SpringMVC的优点
1.基于 MVC 架构
基于 MVC 架构,功能分工明确。解耦合,
M:model 模型 ->Dao,业务逻辑,负责处理业务逻辑,进行数据管理和数据库设计。
V:view 视图 ->表现层,负责前端页面的显示,与用户的交互
C:Controler 控制器->接收用户的请求,将请求跳转到模型处理,模型处理完毕后,在通过控制器,返回视图中的请求。
2.SpringMVC是轻量级容器,jar包很小,不依赖特定的接口和类,符合依赖倒置原则
3.作为Spring框架的一部分,能够使用Spring的IoC和Aop,方 便 整 合Strtus,MyBatis,Hiberate,JPA 等其他框架。
4.4.SpringMVC 强化注解的使用,在控制器,Service,Dao 都可以使用注解。方便灵活。
使用@Controller 创建处理器对象,@Service 创建业务对象,@Autowired 或者@Resource
在控制器类中注入 Service, Service 类中注入 Dao。
1.2第一个SpringMVC项目
作者使用的Tomcat9,java1.8
1.新建一个空的工程
2.新建一个mavenweb模块
创建后注意等待导入maven插件,如果不等待,暂时不会出现mavenweb的文件夹等。
这个过程第一个很漫长,建议在maven的pom.xml文件中加入阿里云仓库加快导入速度。
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*,!jeecg,!jeecg-snapshots</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
mavenweb插件导入后的目录格式如下
创建项目可能出现的问题:
a.这里的webapp和ch002-springmvc没有小蓝点。
有问题,我们查看fFle下的project-structure的Modules,果然,Modules没有ch002-springmvc,我们点击加号导入项目,注意要以maven的形式导入,这样,他可以自动识别webapp。
b.只是webapp没有蓝点,只需要在Modules模块的该项目ch002-springmvc下添加Web,并更改Web名称为webapp,修改Web Resource Directory的位置和web.xml的位置。
回到目录右键点击Reload from disk,刷新目录。
3、修改web.xml文件初始信息
因为web.xml文件刚开始的版本锅底,会导致后期前端无法识别jsp语句。
故需要修改web.xml
低版本
<!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>
</web-app>
点击File-Project Sturcture -Module右边第一个Type框的Web Module Deployment Descriptor,删除,重加一下形式:
点击应用Apply,查看1web.xml为高版本web.xml。
<?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">
</web-app>
4、在pom.xml文件中添加依赖
spring-webmvc依赖,间接把spring的依赖都加入到项目
jsp,servlet依赖,junit依赖
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>ch002-springmvc</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>ch002-springmvc Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
</dependencies>
<build>
<finalName>ch002-springmvc</finalName>
</build>
</project>
注意这里的依赖导入和Tomcat版本不一致问题。如果你是Tomcat10,我建议你的版本最好是降低为Tomcat9,因为Tomcat10的servlet依赖是jarkata.servlet且用到的spring-webmvc也是高版本的。作者之前用的是Tomcat10+java17,导入的依赖不合适会导致后续很多问题,建议大家先用Tomcat9,或者寻找Tomcat10对应的依赖包版本。
5、在web.xml中注册springmvc框架的核心对象DispatcherServlet
1)DispatcherServlet叫做中央调度器, 是一个servlet, 它的父类是继承HttpServlet
2)DispatcherServlet页叫做前端控制器(front controller)
3)DispatcherServlet负责接收用户提交的请求, 调用其它的控制器对象,并把请求的处理结果显示给用户
<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>
<!--在tomcat启动后,创建Servlet对象
load-on-startup:表示tomcat启动后创建对象的顺序。它的值是整数,数值越小,
tomcat创建对象的时间越早。 大于等于0的整数。
-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
a.注册springmvc的核心对象DispatcherServlet,需要在tomcat服务器启动后,创建DispatcherServlet对象的实例。
为什么要创建DispatcherServlet对象的实例呢?
因为DispatcherServlet在他的创建过程中,会同时创建springmvc容器对象,读取springmvc的配置文件,把这个配置文件中的对象都创建好,当用户发起请求时就可以直接使用对象了。
b.注册DispactherServlet对象->执行init()方法->读取springmvc配置文件
打开 DispatcherServlet 的源码,其继承自 FrameworkServlet,而该类中有一个属性contextConfigLocation,用于设置 SpringMVC 配置文件的路径及文件名。该初始化参数的属性就来自于这里。
url路径是指用户的请求是怎样的会走中央调度器。
对于,可以写为 / ,建议写为*.do 的形式。
配置文件可能出现的问题:
启动tomcat报错,读取这个文件 /WEB-INF/springmvc-servlet.xml(/WEB-INF/myweb-servlet.xml)
Could not open ServletContext resource [/WEB-INF/myweb-servlet.xml]
springmvc创建容器对象时,读取的配置文件默认是/WEB-INF/-servlet.xml .
一般情况下,配置文件是放在类路径下,即 resources 目录下。我们需要指定配置文件的路径。
注意这里标签内的name标签内部不能写其他的值,必须写contextConfigLocation
<init-param>
<param-name>contextConfigLocation<param-name>
<param-value>classpath:springmvc.xml<param-value>
<init-param>
6.创建springmvc.xml
在工程的类路径即 src 目录下创建 SpringMVC 的配置文件 springmvc.xml。该文件名可以任意命名。
因为要使用@Controller来管理springmvc对象,也就是司令官,我们需要在司令官这个类中调配。
故用注解@Controller,需要组件扫描器
<!--声明组件扫描器-->
<context:component-scan base-package="com.link.controller" />
7、定义首页,在首页里面点击超链接发起请求
8、编写控制器类,也就是处理器
在类上与方法上添加相应注解即可。
@Controller:表示当前类为处理器
@RequestMapping:表示当前方法为处理器方法。该方法要对 value 属性所指定的 URI进行处理与响应。被注解的方法的方法名可以随意。
/**
* @Controller:创建处理器对象,对象放在springmvc容器中。
* 位置:在类的上面
* 和Spring中讲的@Service ,@Component
*
* 能处理请求的都是控制器(处理器): MyController能处理请求,
* 叫做后端控制器(back controller)
*
* 没有注解之前,需要实现各种不同的接口才能做控制器使用
*/
@Controller
public class MyController {
/*
处理用户提交的请求,springmvc中是使用方法来处理的。
方法是自定义的, 可以有多种返回值, 多种参数,方法名称自定义
*/
/**
* 准备使用doSome方法处理some.do请求。
* @RequestMapping: 请求映射,作用是把一个请求地址和一个方法绑定在一起。
* 一个请求指定一个方法处理。
* 属性: 1. value 是一个String,表示请求的uri地址的(some.do)。
* value的值必须是唯一的, 不能重复。 在使用时,推荐地址以“/”,代表是根路径。
* 位置:1.在方法的上面,常用的。
* 2.在类的上面
* 说明: 使用RequestMapping修饰的方法叫做处理器方法或者控制器方法。
* 使用@RequestMapping修饰的方法可以处理请求的,类似Servlet中的doGet, doPost
*
* 返回值:ModelAndView 表示本次请求的处理结果
* Model: 数据,请求处理完成后,要显示给用户的数据
* View: 视图, 比如jsp等等。
*/
@RequestMapping(value = "/some.do")
public ModelAndView doSome()
{
// doGet()--service请求处理
//处理some.do请求了。 相当于service调用处理完成了。 以后讲
ModelAndView mv = new ModelAndView();
//添加数据, 框架在请求的最后把数据放入到request作用域。
//request.setAttribute("msg","欢迎使用springmvc做web开发");
mv.addObject("msg","欢迎使用springmvc做web开发");
mv.addObject("fun","执行的是doSome方法");
//指定视图, 指定视图的完整路径
//框架在最后对视图执行的forward操作, request.getRequestDispather("/show.jsp).forward(...)
//mv.setViewName("/show.jsp");
mv.setViewName("/WEB-INF/show.jsp");
return mv;
}
若有多个请求路径均可匹配该处理器方法的执行,则@RequestMapping 的 value 属性中可以写上一个数组,RequestMapping注解还有一个属性Method,Method 属性的取值为 RequestMethod 枚举常量。
如果该请求路径下Method的读取请求的方式和用户发起请求的方式不同会出现405的错误。也就是前端与后端的请求方式不一致导致的。method=RequestMethod.GET
@RequestMapping可以放到类上,用于该类上请求路径的公共路径。
ModelAndView 类中的 addObject()方法用于向其 Model 中添加数据。Model 的底层为一个 HashMap。
Model 中的数据存储在 request 作用域中,SringMVC 默认采用转发的方式跳转到视图,本次请求结束,模型中的数据被销毁。
9、定义目标页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>show.jsp从request作用域获取数据</h3>
<h3>msg数据:${msg}</h3><br/>
<h3>fun数据:${fun}</h3>
</body>
</html>
二、Springmvc请求的处理流程
用户发送一个请求,会将请求传递给Tomcat(web.xml – url-pattern知道 *.do请求给DispatcherServlet来调配)------DispatcherServlet(DispatcherServlet读取配置文件找到组件扫描器,知道some.do请求路径对鸭doSome方法)----DispactherServlet把some.do转发给MyController.doSome方法—框架执行doSome()把得到的ModelAndView进行处理,转发到show.jsp
简化:some.do->DispatcherServlet–MyController
2.1中央调度器:
1、负责创建springmvc容器对象,读取xml配置文件,创建文件中的Controller对象
2、负责接收用户的请求,分派给自定义的Controller对象
2.2 Springmvc执行过程源代码分析
1、启动过程
根据<load-on-startup>指定的1
可知是在启动tomcat服务器的时候,就创建了中央调度器的实例,DispactherServlet是一个Servlet,在创建的过程中会执行init方法
//创建容器,读取类路径下的配置文件
WebApplicationContext ctx = new ClassPathXMLApplication("spring.xml");
//把容器对象放入ServletContext全局作用域中,map集合("容器id",容器对象)
getServletContext.setAttribute(key.ctx);
2、请求的处理过程
由Servlet来处理
执行Servlet的service方法–>doService(HttpServletRequest request,HttpServletResponse response)->doDisapatch(HttpServletRequest request,HttpServletResponse response)方法–>.handle()走处理器方法
执行流程的煎蛋分析:
(1)浏览器提交请求到中央调度器
(2)中央调度器直接将请求转给处理器映射器。
(3)处理器映射器会根据请求,找到处理该请求的处理器,并将其封装为处理器执行链后返回给中央调度器。
(4)中央调度器根据处理器执行链中的处理器,找到能够执行该处理器的处理器适配器。
(5)处理器适配器调用执行处理器。
(6)处理器将处理结果及要跳转的视图封装到一个对象 ModelAndView 中,并将其返回给处理器适配器。
(7)处理器适配器直接将结果返回给中央调度器。
(8)中央调度器调用视图解析器,将 ModelAndView 中的视图名称封装为视图对象。
(9)视图解析器将封装了的视图对象返回给中央调度器
(10)中央调度器调用视图对象,让其自己进行渲染,即进行数据填充,形成响应对象。
(11)中央调度器响应浏览器。
2.3 解决问题:可以直接在浏览器上输入目标地址进入目标页面
如果目标页面在/WEB-INF/目录下,就无法进行访问。
如果/WEB-INF目录下有多个.jsp,我们在控制类的setViewName中会写太多重复代码,为了使代码得到复用,我们可以采用配置视图解析器。
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view" />
<property name="suffix" value=".jsp"/>
</bean>
2.4 前端页面写,响应中文乱码问题,过滤器
之前在servlet中学过,用过滤器
现在在springmvc中同样用过滤器 这里采用的是CharacterEncodingFilter
源码分析
在web.xml文件中配置
<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>
<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>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
注意这里的<url-pattern>
标签内
- /a.do、/b.do、/dept/save。这些配置方式都是精确匹配。
- /* 匹配所有路径。
- *.do 后缀匹配。不要以 / 开始
- /dept/* 前缀匹配。
Filter的生命周期?
- 和Servlet对象生命周期一致。
- 唯一的区别:Filter默认情况下,在服务器启动阶段就实例化。Servlet不会。
三、处理器方法的参数
处理器方法可以包含以下四类参数,这些参数会在系统调用时由系统自动赋值,即程序员可在方法内直接使用。
➢ HttpServletRequest
➢ HttpServletResponse
➢ HttpSession
➢ 请求中所携带的请求参数
当用户发来的请求携带参数时,服务器需要接收请求参数(数据)。
要求:处理器方法的形参名和请求中参数名必须一致。
3.1、逐个接收参数
底层实现:
1、框架使用request对象接收请求参数,从request域中取数据赋值给处理器方法形参。
String name = request.getParameter("name");
String name = request.getParameter("name");
2、springmvc框架通过DispatcherServlet(的doDispatcher(request,response)方法内部的.handle方法)调用MyController的doSome()方法。
调用方法时,按名称对应,把接收的参数赋值给形参
doSome(name,Integer.valueOf(age))
框架会自动类型转换,无需我们程序员手动转换。
需要注意的是如果我们写的处理方法的age参数的类型为int,age在前端页面不写,也就是age的值为null,提交数据会发生400的错误。
400状态码是客户端错误,表示提交请求参数过程中,发生了问题。
包装类可以赋值为null
代码:
@RequestMapping(value = "/receiveproperty.do")
public ModelAndView doSome(String name, Integer age){
System.out.println("doSome, name="+name+" age="+age);
//可以在方法中直接使用 name , age
//处理some.do请求了。 相当于service调用处理完成了。
ModelAndView mv = new ModelAndView();
mv.addObject("myname",name);
mv.addObject("myage",Integer.valueOf(age));
//show是视图文件的逻辑名称(文件名称)
mv.setViewName("show");
return mv;
}
3.2、对象为接收参数
注意点
1、定义的对象的属性名和请求参数名是一样的
底层实现:框架会自动创建java对象,调用set方法给属性赋值。
然后传值给这个请求参数Student.。
public ModelAndView doSome3(Student student){
System.out.println("doSome, name="+student.getName()+" age="+student.getAge());
//可以在方法中直接使用 name , age
//处理some.do请求了。 相当于service调用处理完成了。
ModelAndView mv = new ModelAndView();
mv.addObject("xmyname",student.getName());
mv.addObject("xmyage",student.getAge());
mv.addObject("mystudent",student.toString());
//show是视图文件的逻辑名称(文件名称)
mv.setViewName("show");
return mv;
}
校正请求参数名@RequestParam
请求 URL 所携带的参数名称与处理方法中指定的参数名不相同时,强行赋值会出现赋值为NULL的情况。
解决方法
@RequestParam("请求参数名")
public ModelAndView doSome2(@RequestParam(value = "rname",required = false) String name,
@RequestParam(value = "rage",required = false) Integer age)
注意这个只能给逐个赋值的请求参数使用,请求参数是对象的无法使用。