springmvc是一个基于mvc模式的web框架,springmvc是spring框架中的一个模块,可以和spring框架无缝集成 。
mvc: m:model (模型–数据) v :view (视图)–页面 c:controller (控制层)–处理器-用来跳转返回相关数据
springmvc为什么要学习 ? 90%的招聘单位要求使用springmvc,以spring框架为核心 ,在表现层提供了一套优秀的解决方案 (数据接收,上传,下载,国际化)
springmvc是一个servlet ,纯正的servlet .
使用springmvc必须先要保证有spring的环境,而且要保证spring ioc容器初始化 。
springmvc应用必须要导入spring相关的jar包 (核心jar包)–springmvc的核心jar包
spring-webmvc ----springmvc的核心包
spring-web ------spring框架对web项目的支持
作用:主要是用来拦截请求,根据相关的规则分发请求到目标处理器 。

和servlet应用关联起来
复习servlet的实现方式 : 两种方式 web.xml文件是web项目和服务器约定的最关键的文件。
/**
* servlet的第一种实现方式
*
*/
public class MyFirstServlet implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("接收到请求~~");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
web.xml文件
<!--servlet实现第一种方式配置 -->
<servlet>
<servlet-name>firstServlet</servlet-name>
<servlet-class>cn.wolfcode.servlet.MyFirstServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>firstServlet</servlet-name>
<url-pattern>*.do</url-pattern> <!-- *.do 表示所有以.do结束的请求,也就是说这类请求会和web.xml文件进行匹配。
首先会找到servlet-mapping 然后通过servlet-name交给指定的servlet-class进行处理-->
</servlet-mapping>
/**
* servlet的第二种实现方式
*/
@WebServlet("/second")
public class MySecondServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("MySecodeServlet-接收到请求");
}
}
/**
* 使用springmvcd的第一种实现方式
*/
public class FirstHandler implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("FirstHandler--接收到请求");
return null;
}
}
public class SecondHandler implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("SecondHandler-接收到请求");
return null;
}
}
web.xml
<servlet>
<servlet-name>FirstHandler</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 此处必须是一个servlet核心控制器 -->
<init-param> <!-- 初始化servlet-class 告诉其找类的时候不要去web目录下找 -->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>FirstHandler</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
applicationContext.xml
<bean id="/test.do" class="cn.wolfcode.handler.FirstHandler"></bean>
<bean id="/test2.do" class="cn.wolfcode.handler.SecondHandler"></bean>
@Controller // 有@Controller注解 就表示 这是一个处理器 相当于 实现了Controller接口
public class ThridController {
@RequestMapping("/req1.do") // 该注解 相当于在applicationContext.xml文件中配置了一个bean
public ModelAndView requestHandle(HttpServletRequest request, HttpServletResponse resp){
System.out.println("ThridController请求....");
return null;
}
// 以前配置,实现Controller的方法 只能有一个处理器(一个链接),想要多建几个就必须多new几个类。而使用注解的方式
// 想要几个处理器就加几个RequestMapping 一个RequestMapping就是一个处理器
@RequestMapping("/req2.do")
public ModelAndView requestHandle2(HttpServletRequest request, HttpServletResponse resp){
System.out.println("ThridController请求req2.do....");
return null;
}
web.xml
<servlet>
<servlet-name>FirstHandler</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param> <!-- 告诉 核心控制器 DispatcherServlet 到指定地方去找 -->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</init-param>
<!-- 启动参数:0 这个值越小 就越先初始化-->
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>FirstHandler</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
applicationContext.xml(note:要使context标签在xml文件中可用还需要在头文件中配置相关的请求头)
<!-- @Controller注解生效 Controller注解 只是把上面配置bean的步骤省了 浏览器来的请求也是从 web中的web.xml中去找 然后逐次跳转到此处再到具体的类 -->
<context:component-scan base-package="cn.wolfcode.handler"/>
@RequestMapping("/req1.do") // 该注解 相当于在applicationContext.xml文件中配置了一个bean
public ModelAndView requestHandle(HttpServletRequest request, HttpServletResponse resp){
System.out.println("ThridController请求....");
//转发操作
try {
//设置作用域数据(注意:先设置数据后跳转页面,因为跳转页面后再设置数据已经没有意义了)
request.setAttribute("info","hello");
//跳转 转发 跳转到jsp/fj.jsp这个页面
request.getRequestDispatcher("jsp/fj.jsp").forward(request,resp);
//后续执行的代码 需要和跳转的代码无关系
} catch (ServletException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@RequestMapping("/req2.do")
public ModelAndView requestHandle2(HttpServletRequest request, HttpServletResponse resp){
System.out.println("ThridController请求req2.do....");
//实现转发
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("jsp/fj.jsp"); // 跳转到 jsp/fj.jsp这个页面
//设置一个值
modelAndView.addObject("info","world");
return modelAndView;
}
@RequestMapping("/req1.do") // 该注解 相当于在applicationContext.xml文件中配置了一个bean
public ModelAndView requestHandle(HttpServletRequest request, HttpServletResponse resp){
System.out.println("ThridController请求....");
// 执行重定向
try {
// request.setAttribute("info","重定向携带数据"); // 一次请求生效
request.getSession().setAttribute("info","session重定向携带数据"); // 重定向携带数据 .getSession().
// resp.sendRedirect("jsp/fj.jsp");
// 重定向到baidu.com
resp.sendRedirect("https://www.baidu.com/");
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@RequestMapping("/req2.do")
public ModelAndView requestHandle2(HttpServletRequest request, HttpServletResponse resp){
System.out.println("ThridController请求req2.do....");
//实现重定向
ModelAndView mav=new ModelAndView() ;
mav.setViewName("redirect:jsp/fj.jsp");
// mav.addObject("info","springmvccho重定向");
//不能够使用addObject这个方法
// spring使用传统方式实现重定向携带数据
request.getSession().setAttribute("info","spring使用传统方式实现重定向携带数据");
return mav;
}
note:WEB-INF里的页面是一个非常重要的核心资源,不可能轻易在外部被访问,故重定向不可以访问到(由用户决定),而转发可以访问到(由程序决定),这样也是出于安全考虑。
配置:在application.xml文件中配置 自己的视图解析器
<!--配置视图解析器简化页面跳转 该class为视图解析器的核心类型 id可配可不配-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- suffix表示页面视图的后缀 -->
<property name="suffix" value=".html"></property>
<!-- prefix表示页面视图的前缀 -->
<property name="prefix" value="WEB-INF/html/userhtml/"></property>
</bean>
配置以后实现转发:
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("user");
此时会转发到 WEB-INF/html/userhtml/.html页面,配置文件会自动将前缀后缀拼接上。
note:视图解析器和重定向不能同时使用,即redirect关键字与视图解析器不能同时使用!
可以,但是必须省略转发的关键字forward!其实转发和重定向都有一个关键字:
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("forward:user"); // 转发 转发的关键字可以省略
modelAndView.setViewName("redirect:user"); // 重定向
@RequestMapping("/req4.do")
public String requestHandle4(HttpServletRequest request, HttpServletResponse resp) {
return "user" ; // 返回了一个对应的视图 (配置了上面所提的视图解析器)
return "/req1.do"; // 返回了一个当前系统对应的请求路径(前面的代码有) (未配置上面所提的视图解析器)
return "WEB-INF/html/userhtml/user.html"; // 完整资源的路径,找对应的资源 (未配置上面所提的视图解析器)
}
}
当配置了视图解析器的时候,返回String类型只能表示访问对应的视图
当没有配置视图解析器的时候,返回String类型表示当前系统对应的请求路径(处理器的请求路径/完整资源的路径)
eg:
@RequestMapping("/getjson.do")
public String getjson1(HttpServletRequest req, HttpServletResponse resp){
//实现将对象转换成json字符串
//先创建一个ObjectMapper对象
ObjectMapper objectMapper = new ObjectMapper();
//创建一个待转换的对象
Map<String,Object> resultMap=new HashMap<>() ;
resultMap.put("code","1111") ;
//转换
String json_str=null ;
try {
json_str = objectMapper.writeValueAsString(resultMap);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
req.setAttribute("info",json_str);
return "jsp/fj.jsp";
}
@ResponseBody
@RequestMapping("/getjson2.do")
public Map<String,Object> getjson2(HttpServletRequest req, HttpServletResponse resp){
//先存放用于保存键值对的map集合
Map<String,Object> resultMap=new HashMap<String,Object>() ;
resultMap.put("code","9999");
resultMap.put("msg","system error");
return resultMap ;
}
note:此处的@ResponseBody注解作用是将java对象转换为json格式的数据,同时也需要在applicationContext.xml配置文件中加入(以下两个都是为了启用@ResponseBody注解)
添加mv约束:(因为mvc标签用不了)
xmlns:mvc="http://www.springframework.org/schema/mvc"
mvc标签文件:
<mvc:annotation-driven />
eg: 满足json且满足restful风格
<url-pattern>/*
/**
*要求返回json
* 同时满足restful风格
* @param req
* @param resp
* @return
*/
@ResponseBody
@RequestMapping("/getjson3")
public Map<String,Object> getjson3(HttpServletRequest req, HttpServletResponse resp){
//先存放用于保存键值对的map集合
Map<String,Object> resultMap=new HashMap<String,Object>() ;
resultMap.put("code","9999");
resultMap.put("msg","system error");
return resultMap ;
}
/* 表示所有的请求都会发送到前端处理器进行请求!实际开发中并不会这样使用,因为这样会导致所有的静态资源都交给了前端处理器进行处理,解决这个问题十分的麻烦。 一般使用:
<url-pattern>/</url-pattern>
note:SpringMVC自带了静态资源处理器,故想直接在浏览器访问静态资源是不可能的。若想访问静态资源,则需要增加配置:
<mvc:default-servlet-handler/>
该行配置表示不使用mvc自带的静态资源处理器,而是使用tomcat自带的资源处理器。
#### SpringMVC接收简单参数类型
/**
* 传统方式接收参数
* @param req
* @param resp
* @return
*/
@ResponseBody
@RequestMapping("/request1")
public Map<String,Object> requestHandle1(HttpServletRequest req, HttpServletResponse resp){
//先存放用于保存键值对的map集合
Map<String,Object> resultMap=new HashMap<String,Object>() ;
//从保存请求信息的对象中获取req
String name = req.getParameter("name");
//打印
System.out.println("接收到的数据:"+name);
//设置返回值
resultMap.put("data",name);
//返回
return resultMap ;
}
在请求头中 + ?name=luhua 的结果

多个参数就用&进行拼接!name=…&name=…&name=…
/**
* 使用springmvc接收简单参数类型
* 而且要求实际传参-实参数名称和际使用的参数名称一致
* 如果不一致,会导致无法接收到参数,此时会默认该值为NULL
* 注意:
* @param name
* @param age
* @return
*/
@ResponseBody
@RequestMapping("/request2")
public Map<String,Object> requestHandle2(String name,Integer age){
//先存放用于保存键值对的map集合
Map<String,Object> resultMap=new HashMap<String,Object>() ;
//直接从请求参数中获取并打印
System.out.println("接收到的数据:"+name+"---"+age);
//设置返回值
resultMap.put("name",name);
resultMap.put("age",age);
//返回
return resultMap ;
}

/**
* 接收参数参数名称和实际使用的参数名称不一致
* @RequestParam()中的属性 name=value --表示形参的别名(处理器/接口对外暴露的参数名称)
* required =true 必传 =false 非必传 默认必传
* @param uname
* @param uage
* @return
*/
@ResponseBody
@RequestMapping("/request3")
public Map<String,Object> requestHandle3(@RequestParam(value ="nn")String uname,
@RequestParam(value="a",required = true)Integer uage){
//先存放用于保存键值对的map集合
Map<String,Object> resultMap=new HashMap<String,Object>() ;
//直接从请求参数中获取并打印
System.out.println("接收到的数据:"+uname+"---"+uage);
//设置返回值
resultMap.put("name",uname);
resultMap.put("age",uage);
//返回
return resultMap ;
}
<%-- action表示请求后端的请求的具体的接口是什么--%>
<form method="post" action="../request4">
<input type="text" name="uname"/>
<input type="text" name="uage"/>
<input type="submit" value="提交" />
</form>
/**
* 接收post请求
* 1.form表单中action属性值../相对路径,解决了404问题
* 2.使用处理器可以接收到post请求
* 3.接收到post请求出现了乱码
* 4.乱码问题怎么解决?
* 回顾之前的get请求并没有出现乱码情况
* 现在post请求为什么就出现乱码了?
* 5.get请求实际上也出现了乱码只是被tomcat8.0+解决了 tomcat8一下版本没有解决
* 6.如果项目上要求使用tomcat 7 ,get请求的乱码无法解决了嘛?
* 怎么解决?在tomcat 7中添加了URIEncoding="utf-8"
* 7.post请求乱码怎么办?
springmvc提供了解决方案,在web.xml文件中配置一个解决乱码的配置文件 配置一个过滤器
* 8.通用的乱码解决方案(get/post)
*
* @param uname
* @param uage
* @return
*/
@ResponseBody
@RequestMapping("/request4")
public Map<String,Object> requestHandle4(@RequestParam(value ="uname")String uname,
@RequestParam(value="uage")Integer uage){
//先存放用于保存键值对的map集合
Map<String,Object> resultMap=new HashMap<String,Object>() ;
//直接从请求参数中获取并打印
System.out.println("接收到的数据:"+uname+"---"+uage);
//将接收到的乱码的数据进行转换
try { // 如果直接在浏览器中输入 /request4这个地址 会出现400错误 原因在于uname此时一直为null值 故不可能存在.getBytes
// 故出现报错: Required request parameter 'uname' for method parameter type String is not present]
byte[] bytes = uname.getBytes("ISO-8859-1");
//将字节数组进行重新编码
String str=new String(bytes,"utf-8") ;
System.out.println("转码之后的字符串:"+str);
resultMap.put("name",str);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//设置返回值
resultMap.put("age",uage);
//返回
return resultMap ;
}
出现乱码的原因:数据在传输的过程中,编码发生改变,导致前后编码不一样故出现了乱码。
方式1:在web.xml文件中配置一个过滤器 /* 表示所有的请求
<!-- springMVC提供的过滤器-->
<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>/*
方式2:无SpringMVC框架的情况下解决乱码问题! 通用的乱码解决方案
//此处接收到的数据是 uname
//将接收到的乱码的数据进行转换 此处的ISO-8859-1为已知的数据的编码格式
byte[] bytes = uname.getBytes("ISO-8859-1"); // 将其转换为其对应的编码格式的字符数组
//将字节数组进行重新编码
String str=new String(bytes,"utf-8") ;
数组这种复杂的数据
<form method="post" action="../request5"> <%-- action使其对应到一个具体的 处理器 --%>
游泳:<input type="checkbox" name="hobby" value="游泳"/>
下棋:<input type="checkbox" name="hobby" value="下棋"/>
登山:<input type="checkbox" name="hobby" value="登山"/>
打球:<input type="checkbox" name="hobby" value="打球"/>
<input type="submit" value="提交" />
</form>
@ResponseBody
@RequestMapping("/request5")
public Map<String,Object> requestHandle5(@RequestParam(value = "hobby")String[] hobby){
//先存放用于保存键值对的map集合
Map<String,Object> resultMap=new HashMap<String,Object>() ;
//打印接收到的参数
System.out.println(Arrays.toString(hobby));
//设置返回值
resultMap.put("code",9999);
//返回
return resultMap ;
}
用数组的类型去接收hobby这些数据。
1.需求: 接收一个人的信息(注册)– 用户在网页上填写个人信息
jsp前端页面:
```java
定义一个对应的实体类,去跟请求参数匹配:
/**
* 跟request6中的请求参数匹配
*/
public class UserInfo {
private String uname ;
//private Integer uage ;
private String idcard ;
private String uphone ;
@DateTimeFormat(pattern ="yyyy-MM-dd" )
private Date birthday ;
..................................................
}
@ResponseBody
@RequestMapping("/request6")
public Map<String,Object> requestHandle6(UserInfo userInfo){
//先存放用于保存键值对的map集合
Map<String,Object> resultMap=new HashMap<String,Object>() ;
//打印接收到的参数
System.out.println(userInfo);
Date birthday = userInfo.getBirthday();
//设置返回值
resultMap.put("code",9999);
//返回
return resultMap;
}
note:
需求:注册用户后,需要展示用户的信息
<form method="post" action="../request6">
姓名:<input type="text" name="uname" /> <br/>
生日:<input type="date" name="birthday" /><br/>
身份证:<input type="text" name="idcard"/><br/>
手机号码:<input type="text" name="uphone"/><br/>
<input type="submit" value="提交"/>
</form>
<hr/>
<h2>${user.uname}</h2>
<h2>${user.idcard}</h2>
<h2>${user.uphone}</h2>
<h2>${user.birthday}</h2>
<hr/>
@RequestMapping("/request6")
public String requestHandle6(@ModelAttribute("user") UserInfo userInfo){
//先存放用于保存键值对的map集合
Map<String,Object> resultMap=new HashMap<String,Object>() ;
//打印接收到的参数
System.out.println(userInfo);
Date birthday = userInfo.getBirthday();
//设置返回值
resultMap.put("code",9999);
//返回
return "jsp/fj.jsp" ;
}
note:@ModelAttribute(“user”) 该注解的意思将参数userInfo简化为user(故在上面的前端代码中使用的是user而不是userInfo)(自我感觉是内部简化因为是在代码内部调用的)!
实际操作:IO流 读数据写数据的过程
<h2>文件上传</h2>
<form action="../uploadfile" method="post" enctype="multipart/form-data">
<input type="file" name="myfile" value="选择文件"/>
<input type="submit" value="上传提交"/>
</form>
1.所有文件上传都是采用post的方式;2.enctype="multipart/form-data"在使用包含文件上传控件的表单时必须使用该值
/**
* 文件上传
* @param req
* @return
*/
@ResponseBody // 不加该注解表示返回一个Map集合的这种类型的视图,但是没有就会报错,故需加该注解
@RequestMapping("/uploadfile")
public Map<String, Object> uploadFile(HttpServletRequest req) {
//定义map集合
Map<String,Object> resultMap=new HashMap<>() ;
ServletInputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = req.getInputStream();
// 此处写死了
outputStream = new FileOutputStream(new File("C:\\Users\\LuoTianYang\\Desktop\\uploadDir\\xx.jpg"));
//定义一个byte数组
byte[] b = new byte[1024];
//读取到的长度
int len = 0;
//判断 如果read为-1那么表示数据读取完成
while ((len = inputStream.read(b)) != -1) {
//写的过程
outputStream.write(b, 0, len);
}
//关闭流
outputStream.close();
inputStream.close();
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
resultMap.put("code","success") ;
return resultMap;
}
以上是传统的方式实现文件的上传,会出现实际上传保存后的文件会比原来的文件更大,因为在使用req.getInputStream();流的时候会将前端的一些标签流也获得,从而造成文件更大而无法正常打开。
<h2>文件上传</h2>
<form action="../uploadfile" method="post" enctype="multipart/form-data">
<input type="file" name="myfile" value="选择文件"/>
<input type="submit" value="上传提交"/>
</form>
@ResponseBody
@RequestMapping("/uploadfile") // MultipartFile 使用文件上传的对应的那个部件 myfile是与控件中的名称保持一致
public Map<String, Object> uploadFile(MultipartFile myfile) {
//定义map集合
Map<String,Object> resultMap=new HashMap<>() ;
//获取文件写文件到本地磁盘
try {
InputStream inputStream = myfile.getInputStream();
// myfile.getOriginalFilename() 上传的文件是什么名称,它保存后的文件就是什么名称
OutputStream outputStream=new FileOutputStream(new File("C:\\Users\\LuoTianYang\\Desktop\\uploadDir\\"+myfile.getOriginalFilename())) ;
//边读边写的
byte[] b=new byte[1024];
//定义长度
int len= 0 ;
while( (len=inputStream.read(b))!=-1 ){
outputStream.write(b,0,len);
}
outputStream.flush();
outputStream.close();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
resultMap.put("code","success") ;
return resultMap;
}
note:上述代码中用了MultipartFile多媒体请求,该请求需要加一个额外的配置
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
</bean>
<h2>文件下载</h2>
<a href="../download?filename=xx.jpg">下载图片a</a>
<a href="../download?filename=短视频_俏.mp4">下载视频b</a>
/**
* 下载
* @param filename
* @param req
* @param resp
* @return
*/
@ResponseBody
@RequestMapping("/download")
public void downloadFile(String filename,HttpServletRequest req, HttpServletResponse resp){
//定义一个返回值
Map<String,Object> resultMap=new HashMap<>() ;
//设置响应的名称,文件叫什么名字
resp.setHeader("Content-Disposition","filename="+filename);
//设置文件类型
resp.setHeader("Content-Type","application/mp4");
//从服务器端磁盘中找到需要下载的文件
//目前在服务端提供的下载的资源目录
String dir="C:\\Users\\LuoTianYang\\Desktop\\uploadDir\\" ;
//拼接一个需要下载的文件的路径
String fileurl=dir+filename ;
//创建一个文件对象
File file=new File(fileurl) ;
//判定file是否存在
if(file.exists()){
//存在--就下载
try {
//读取这个文件到内存中
InputStream input=new FileInputStream(file);
//创建一个输出流
ServletOutputStream output = resp.getOutputStream();
//边读边写
byte[] b=new byte[2048];
//定义长度
int len=0 ;
while( (len=input.read(b)) != -1){
output.write(b,0,len);
}
//关闭流
output.flush();
output.close();
input.close();
//设置返回值
resultMap.put("code",1000);
resultMap.put("msg","下载成功");
} catch (FileNotFoundException e) {
e.printStackTrace();
resultMap.put("code",1001);
resultMap.put("msg","文件不存在");
} catch (IOException e) {
e.printStackTrace();
resultMap.put("code",1002);
resultMap.put("msg","输入输出异常");
}
}else{
//不存在
resultMap.put("code",1001);
resultMap.put("msg","文件没找到");
}
}
note: