SpringMVC
1、概述
1.1、什么是MVC
MVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范。
- 是将业务逻辑、数据、显示分离的方法来组织代码。
- MVC主要作用是降低了视图与业务逻辑间的双向偶合。
- MVC不是一种设计模式,是一种架构模式。 当然不同的MVC存在差异。
Model(模型):数据模型,提供要展示的数据,包括数据和业务。
View(视图):负责进行模型的展示,用户界面。
Controller(控制器):控制器做调度员的工作,处理用户请求,将数据返回给视图进行展示。
最典型的MVC就是JSP + servlet + javabean的模式。
什么是SpringMVC
1.2、什么是SpringMVC
基于Java实现MVC的轻量级Web框架(底层是Servlet)。
Spring MVC的特点
- 轻量级,简单易学
- 高效 , 基于请求响应的MVC框架
- 与Spring兼容性好,无缝结合
- 约定优于配置
- 功能强大:RESTful、数据验证、格式化、本地化、主题等
- 简洁灵活
- 最重要的一点还是用的人多,使用的公司多 。
SpringMVC围绕DispatcherServlet [ 调度Servlet ] 设计。
DispatcherServlet的作用是将请求分发到不同的处理器。
2、SpringMVC处理流程
- 主要分为三步:
- 处理映射器,找到对应的Handler
- 处理适配器:对Handler封装,找到对应的Controller类
- 视图解析器:解析视图
- 用户的所有请求都交给前端控制器DispatcherServlet来处理,它会负责调用系统的其他模块来真正处理用户的请求。
- DispatcherServlet收到请求后,将根据请求的信息(包括URL、HTTP协议方法、请求头、请求参数、Cookie等)以及HandlerMapping处理器映射的配置找到处理该请求的Handler
- 在这个地方Spring会通过HandlerAdapter对该处理器进行封装。
- HandlerAdapter处理器适配器,适配到对应的Controller层,Java类。
- Controller执行对应的业务方法,返回对应的ModelAndView对象给DispatcherServlet,ModelAndView包含了数据和视图信息。
- ModelAndView是逻辑视图,DispatcherServlet还要通过ViewResolver视图解析器完成从逻辑视图到真实视图对象的解析工作。
- 当得到真正的视图对象后,DispatcherServlet通过视图对象对模型数据进行渲染,返回给用户。
- 客户端得到响应,可能是一个普通的HTML页面,也可以是XML或JSON字符串,还可以是一张图片或者一个PDF文件。
3、第一个SpringMVC程序
导入依赖
<!--SpringMVC依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.1</version>
</dependency>
<!--将复杂对象转化成Json格式,只需要导入这个依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.2</version>
</dependency>
配置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">
<!--1.注册servlet-->
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--通过初始化参数指定SpringMVC配置文件的位置,进行关联-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
<!-- 启动顺序,数字越小,启动越早 -->
<load-on-startup>1</load-on-startup>
</servlet>
<!--所有请求都会被springmvc拦截
/ :匹配所有的请求;(不包括.jsp)
/* :匹配所有的请求;(包括.jsp)
-->
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--字符编码有问题,配置过滤器-->
<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>/</url-pattern>
</filter-mapping>
</web-app>
配置springmvc-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 自动扫描包,让指定包下的注解生效,由IOC容器统一管理 -->
<context:component-scan base-package="com.lvboaa.controller"/>
<!-- 过滤静态资源,不需要走视图解析器,可直接访问静态资源 -->
<mvc:default-servlet-handler />
<!--支持mvc注解驱动,完成处理映射器和处理适配器的自动装配-->
<!--<mvc:annotation-driven />-->
<!--处理乱码-->
<mvc:annotation-driven >
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg value="UTF-8"/>
</bean>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
<property name="failOnEmptyBeans" value="false"/>
</bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
id="internalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/WEB-INF/jsp/" />
<!-- 后缀 -->
<property name="suffix" value=".jsp" />
</bean>
</beans>
编写Controller类
//@RestController将返回的对象转成JSON格式,不走视图解析器
@RestController
@RequestMapping("/")
public class HelloController {
@RequestMapping("/hello")
public String hello(){
return "hello";
}
}
可能遇到的问题:访问路径出现404错误
- 查看编译目录是否缺少lib文件夹(没有对于的依赖)
- 在项目结构的Artifacts添加lib目录导入依赖
- 重启Tomcat
4、Controller和RestFul
4.1、Controller实现
实现Controller接口
- 不推荐使用,一个请求就需要实现一个类
注解实现@Controller
- 更简洁地实现servlet
- 如果需要使用视图,多个请求都可以指向一个视图,但是页面结果可以不一样,视图可被复用,控制器与视图之间是弱偶合关系。
4.2、RestFul风格
Restful就是一个资源定位及资源操作的风格。不是标准也不是协议。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。
功能
- 通过不同的请求方法实现不同的效果,请求路径url可以一样,实现的功能不同
http://127.0.0.1/item/1 查询,GET
http://127.0.0.1/item 新增,POST
http://127.0.0.1/item 更新,PUT
http://127.0.0.1/item/1 删除,DELETE
注意
@RestController
=@ResponseBody
(返回字符串)+@Controller
- 将删除的id这些参数放到url,
@PathVariable
表示url地址栏的参数 - 传入对象的话直接在形参方写成一个对象
@RequestParam
表示必须传的参数,用在形参
实现
//RequestMapping默认get和post方法都可访问
//@RequestMapping(path = "/add/{a}/{b}",method = RequestMethod.POST)
@PostMapping("/{a}/{b}")
public String add1(@PathVariable int a,@PathVariable int b){
String str = "实现添加业务";
return "add";
}
//等于RequestMapping的method为get
@GetMapping("/{a}")
public String add3(@PathVariable int a){
String str = "执行查询业务";
return "add";
}
注意:网页状态码错误
- 100+:服务器收到请求,需要请求者执行操作
- 200+:成功
- 300+:客户端请求重定向
- 400+:客户端请求错误(找不到文件)
- 500+:服务器代码错误
转发和重定向的区别
- 转发是服务器收到请求后为了完成响应跳转到一个新的地址,请求一次;重定向是完成响应后需要请求新的地址,至少请求两次
- 转发url不会变化,重定向会发生变化
- 重定向不会共享数据,转发共享
- 重定向可跳转至任意页面,转发只能本站页面
- 转发是服务器端行为,重定向是客户端行为
4.3、使用JSON交互前后端
导入依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.2</version>
</dependency>
实现
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/t1")
public String add(@RequestParam("username") String name){
System.out.println(name);
return name;
}
@GetMapping("/t2")
public User add2(User user){
System.out.println(user);
return user;
}
}
解决JSON中文编码问题(springmvc-servlet.xml)
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg value="UTF-8"/>
</bean>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
<property name="failOnEmptyBeans" value="false"/>
</bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
5、AJAX异步请求
实现页面的异步局部更新。
导入
jquery-3.5.1.js
- 异步请求代码
<script src="/js/jquery-3.5.1.js"></script>
<script>
function a1() {
//还有get和ajax方法
$.post({
url: "/a3",
data: {
name: $('#name').val()
},
success: function (data) {
console.log(data);
}
});
}
</script>
6、拦截器
6.1、简介
拦截器类似于过滤器
过滤器与拦截器的区别:拦截器是AOP思想的具体应用。
不改变源码拦截请求
- 过滤器
- servlet规范中的一部分,任何java web工程都可以使用
- 在url-pattern中配置了/*之后,可以对所有要访问的资源进行过滤(包括jsp页面,/不包括)
- 拦截器
- 拦截器是SpringMVC框架自己的,只有使用SpringMVC才能使用
- 拦截器只会拦截访问的控制器请求,默认静态资源过滤,访问静态资源不会拦截
6.2、拦截器实现
实现接口
public class MyInterceptor implements HandlerInterceptor {
//在请求处理的方法之前执行
//如果返回true执行下一个拦截器,放行
//如果返回false就不执行下一个拦截器,阻塞在这
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("=========处理前=========");
return true;
}
//postHandle和afterCompletion相当于是日志拦截
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("=========处理后=========");
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("=========清理=========");
}
}
配置springmvc.xml文件
<mvc:interceptors>
<mvc:interceptor>
<!--/**表示过滤/下面的所有请求-->
<mvc:mapping path="/**"/>
<bean class="com.lvboaa.config.MyInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
实现用户登录拦截器
- 原理:根据cookie判断是否登录
- 控制器
@Controller
@RequestMapping("/user")
public class UserController {
//跳转到登陆页面
@RequestMapping("/tologin")
public String toLogin() throws Exception {
return "login";
}
//跳转到成功页面
@RequestMapping("/toSuccess")
public String jumpSuccess() throws Exception {
return "success";
}
//登陆提交
@RequestMapping("/login")
public String login(HttpSession session, String username, String pwd) throws Exception {
// 向session记录用户身份信息
System.out.println("接收前端==="+username);
session.setAttribute("user", username);
return "success";
}
//退出登陆
@RequestMapping("logout")
public String logout(HttpSession session) throws Exception {
//session 过期
session.invalidate();
return "login";
}
}
- 拦截器类
public class UserInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("uri: " + request.getRequestURI());
//请求包含login字符串直接放行
if (request.getRequestURI().contains("login")) {
return true;
}
HttpSession session = request.getSession();
// 如果用户已登陆也放行
if(session.getAttribute("user") != null) {
return true;
}
// 用户没有登陆跳转到登陆页面
request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);
return false;
}
}
- 配置拦截器
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/user/**"/>
<bean class="com.lvboaa.config.UserInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
7、文件上传下载
导入依赖
<!--文件上传-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
<!--servlet-api导入高版本的-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
配置springmvc.xml配置文件
<!--文件上传配置-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 请求的编码格式,必须和jSP的pageEncoding属性一致,以便正确读取表单的内容,默认为ISO-8859-1 -->
<property name="defaultEncoding" value="utf-8"/>
<!-- 上传文件大小上限,单位为字节(10485760=10M) -->
<property name="maxUploadSize" value="10485760"/>
<property name="maxInMemorySize" value="40960"/>
</bean>
控制器实现上传下载代码
@RestController
public class FileController {
@RequestMapping("/upload")
//方式一
public String fileUpload(@RequestParam("file") CommonsMultipartFile file , HttpServletRequest request) throws IOException {
//获取文件名 : file.getOriginalFilename();
String uploadFileName = file.getOriginalFilename();
//如果文件名为空,直接回到首页!
if ("".equals(uploadFileName)){
return "redirect:/index.jsp";
}
System.out.println("上传文件名 : "+uploadFileName);
//上传路径保存设置,
String path = request.getServletContext().getRealPath("/upload");
//如果路径不存在,创建一个
File realPath = new File(path);
if (!realPath.exists()){
realPath.mkdir();
}
System.out.println("上传文件保存地址:"+realPath);
InputStream is = file.getInputStream(); //文件输入流
OutputStream os = new FileOutputStream(new File(realPath,uploadFileName)); //文件输出流
//读取写出
int len=0;
byte[] buffer = new byte[1024];
while ((len=is.read(buffer))!=-1){
os.write(buffer,0,len);
os.flush();
}
os.close();
is.close();
return "redirect:/index.jsp";
}
@RequestMapping("/upload2")
//方式2
public String fileUpload2(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException {
//上传路径保存设置
String path = request.getServletContext().getRealPath("/upload");
File realPath = new File(path);
if (!realPath.exists()){
realPath.mkdir();
}
//上传文件地址
System.out.println("上传文件保存地址:"+realPath);
//通过CommonsMultipartFile的方法直接写文件(注意这个时候)
file.transferTo(new File(realPath +"/"+ file.getOriginalFilename()));
return "上传成功";
}
@RequestMapping("/download")
public String downloads(HttpServletResponse response , HttpServletRequest request) throws Exception{
//要下载的图片地址
String path = request.getServletContext().getRealPath("/upload");
String fileName = "需求文档.txt";
//1、设置response 响应头
response.reset(); //设置页面不缓存,清空buffer
response.setCharacterEncoding("UTF-8"); //字符编码
response.setContentType("multipart/form-data"); //二进制传输数据
//设置响应头
response.setHeader("Content-Disposition",
"attachment;fileName="+ URLEncoder.encode(fileName, "UTF-8"));
File file = new File(path,fileName);
//2、 读取文件--输入流
InputStream input=new FileInputStream(file);
//3、 写出文件--输出流
OutputStream out = response.getOutputStream();
byte[] buff =new byte[1024];
int index=0;
//4、执行 写出操作
while((index= input.read(buff))!= -1){
out.write(buff, 0, index);
out.flush();
}
out.close();
input.close();
return "success";
}
}
前端使用表单文件上传组件
- 也可以使用链接a标签实现下载文件