基于java的实现MVC设计模式的请求驱动类型的轻量级Web框架,通过注解,无需实现任何接口,处理请求,支持restful。
前端控制器 DispatcherServlet(不需要程序员开发)
作用:接收请求,处理结果,相当于转发器,有了DispatcherServlet 就减少了其它组件之间的耦合度。
处理器映射器 HandleMapping ( 不需要程序员开发)
作用:根据url找到 handler
处理器适配器HandlerAdapter(适配器模式)
注意:在编写Handler的时候要按照HandlerAdapter要求的规则去编写,这样适配器HandlerAdapter才可以 正确的去执行Handler。
处理器Handler(需要程序员开发)
作用:处理请求的具体过程。
视图解析器 ViewResolver(不需要程序员开发)
作用:进行视图的解析,根据视图逻辑名解析成真正的视图(view)
视图View(需要程序员开发jsp)
View是一个接口, 它的实现类支持不同的视图类型(jsp,freemarker,pdf等等)
a) 不存在
i. 再判断是否配置了mvc:default-servlet-handler
ii. 如果没配置,则控制台报映射查找不到,客户端展示404错误
iii. 如果有配置,则访问目标资源(一般为静态资源,如:JS,CSS,HTML),找不到客户端也会展示404错误
b) 存在则执行下面的流程
a) HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息
b) 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
c) 数据格式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
d) 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error
5.Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象。
6.此时将开始执行拦截器的postHandle(…)方法【逆向】。
7.根据返回的ModelAndView(此时会判断是否存在异常:如果存在异常,则执行HandlerExceptionResolver进行异常处理)选择一个适合的ViewResolver进行视图解析,根据Model和View,来渲染视图。
8.渲染视图完毕执行拦截器的afterCompletion(…)方法【逆向】。
9.将渲染结果返回给客户端。
@Controller : 用于定义控制类
@RequestMapping : 用来处理请求地址映射的注解,可以作用于类和方法上。
属性:
@ResponseBody : 该注解用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。使用时机:返回的数据不是html标签的页面,而是其他 某种格式的数据时(如json、xml等) 使用;
@RequestParam : 用于在SpringMVC后台控制层获取参数,类似一种是 request.getParameter(“name”),它有三个常用参数:defaultValue = “0”, required = false, value = “isApp”;defaultValue 表示设置默认值,required 通过boolean设置是否是必须要传入的参数,value 值表示接受的传入的参数类型。
@PathVariable : 用于将请求URL中的模板变量映射到功能处理方法的参数上,即取出uri模板中的变量作为参数。
@ModelAttribute和 @SessionAttributes :代表的是:该Controller的所有方法在调用前,先执行此@ModelAttribute方法,可用于注解和方法参数中,可以把这个@ModelAttribute特性,应用在BaseController当中,所有的Controller继承BaseController,即可实现在调用Controller时,先执行@ModelAttribute方法。
@SessionAttributes即将值放到session作用域中,写在class上面。
<form action="${pageContext.request.contextPath}/login/test1" method="get">
用户名: <input type="text" name="username"/> <br>
密 码: <input type="password" name="password"/> <br>
<input type="submit" />
form>
@Controller
@RequestMapping("/login")
public class MyController {
@RequestMapping(value = "/?est1")
public String handler1(HttpServletRequest request) {
System.out.println("处理器1");
// 通过 HttpServletRequest 获取请求参数
String username= request.getParameter("username");
String password = request.getParameter("password");
System.out.println("username:" + username + " password:" + password);
return "success";
}
<input type="text" name="username"/>
<input type="password" name="password"/>
public String handler1(String username, String password)
注意:控制器方法的形参必须和前端 name 的属性值一致,如控制器方法的形参 username 和 password 要和 input 标签中的 name 属性值 username 和 password 一致
<form action="${pageContext.request.contextPath}/login/test1" method="get">
用户名: <input type="text" name="username"/> <br>
密 码: <input type="password" name="password"/> <br>
<input type="submit" />
form>
@Controller
@RequestMapping("/login")
public class MyController {
@RequestMapping(value = "/?est1")
// 通过 形参 获取请求参数
public String handler1(String username, String password) {
System.out.println("处理器1");
System.out.println("username:" + username + " password:" + password);
return "success";
}
}
@RequestParam 注解用于将请求参数的数据映射到 Handler 方法(控制器方法)的形参上,相当于给请求参数重命名。如有时有一些特殊情况,前端的 name 属性值与后端 Handler 方法中的形参不一致,这个时候就可以通过 @RequestParam 注解来解决。
- 语法:@RequestParam(value=”参数名”,required=”true|false”,defaultValue=””)
- value:请求中传入参数的名称
- required:该参数是否为必传项,默认为 true,表示该请求路径中必须包含该参数,如果不包含就报错;若设置为 false,则表示该请求路径中不必包含该参数,若没有传输该参数,则注解所标识的形参的值为 null
- defaultValue:设置默认参数值,如果设置了该值,required=true 将失效,自动为 false,如果没有传该参数,就使用默认值
- 测试案例:
- // 普通数据:private String username;
- // 对象:private UserInfo userInfo;
- // 数组:private String hobbys[];
- // 列表:private List titles;
- // Map:private Map
companys
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录title>
head>
<body>
<form action="${pageContext.request.contextPath}/login/test1" method="get">
用户名: <input type="text" name="username" value="赵飞燕"/> <br>
年龄: <input type="text" name="userInfo.age" value="18"/> <br>
身高: <input type="text" name="userInfo.height" value="168"/> <br>
爱好: <input type="checkbox" name="hobbys" value="追剧" checked="checked">追剧
<input type="checkbox" name="hobbys" value="画画" checked="checked">画画
<input type="checkbox" name="hobbys" value="健身" checked="checked">健身<br>
头衔1: <input type="text" name="titles[0]" value="智慧女神"/> <br>
头衔2: <input type="text" name="titles[1]" value="幸运之神"/> <br>
公司1名称: <input type="text" name="companys['公司1'].companyName" value="肯德基"/> <br>
公司1市值: <input type="text" name="companys['公司1'].values" value="12亿"/> <br>
公司2名称: <input type="text" name="companys['公司2'].companyName" value="黑马"/> <br>
公司2市值: <input type="text" name="companys['公司2'].values" value="15亿"/> <br>
<input type="submit" />
form>
body>
@Controller
@RequestMapping("/login")
public class MyController {
@RequestMapping(value = "/?est1")
// 通过 形参 获取请求参数
public String handler1(User user) {
String username = user.getUsername();
int age = user.getUserInfo().getAge();
int height = user.getUserInfo().getHeight();
String hobbys[] = user.getHobbys();
List<String> titles = user.getTitles();
Map<String, Company> companys = user.getCompanys();
// 普通参数
System.out.println("用户姓名:" + username);
// 对象
System.out.println("用户年龄:" + age);
System.out.println("用户身高:" + height);
// 数组
System.out.print("用户爱好:");
for(String hobby : hobbys) {
System.out.print(" " + hobby);
}
System.out.println();
// List
System.out.print("称号:");
for(String title : titles) {
System.out.print(" " + title);
}
System.out.println();
// Map
Set<Map.Entry<String, Company>> entries = companys.entrySet();
for (Map.Entry<String, Company> entry : entries) {
System.out.println(entry.getKey() + ":" + entry.getValue().getCompanyName() + "市值:" + entry.getValue().getValues());
}
return "success";
}
}
public class Company {
private String companyName;
private String values;
// setter、getter 方法省略
}
public class UserInfo {
private int age;
private int height;
// setter、getter 方法省略
}
在SpringMVC中常用的域有以下三个:
@Controller
public class DomainController {
/*
* 使用ServletAPI向request域对象共享数据
* */
@RequestMapping("/testRequestByServletAPI")
public String testRequestByServletAPI(HttpServletRequest request){
request.setAttribute("testRequestScope","Hello ServletAPI");
return "success";
}
}
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>成功页面title>
head>
<body>
<h1>跳转成功h1>
<p th:text="${testRequestScope}"/>
body>
html>
ModelAndView有Model和View的功能
举例
@RequestMapping("/testModelAndView")
//使用这种方法必须返回ModelAndView对象
public ModelAndView testModelAndView(){
ModelAndView mav = new ModelAndView();
//处理模型数据(向请求域request共享数据)
mav.addObject("testRequestScope","Hello ModelAndView");
//设置视图名称(跳转到哪里)
mav.setViewName("success");
return mav;
}
@RequestMapping("/testModel")
public String testModel(Model model){
model.addAttribute("testRequestScope", "Hello Model");
return "success";
}
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map){
map.put("testRequestScope","Hello Map");
return "success";
}
@RequestMapping("/testModelMap")
public String testModelMap(ModelMap modelMap){
modelMap.addAttribute("testRequestScope", "Hello ModelMap");
return "success";
}
@RequestMapping("/testModel")
public String testModel(Model model){
model.addAttribute("testRequestScope", "Hello Model");
System.out.println("Model:"+model.getClass().getName());
return "success";
}
/*
* 使用Map这种就更厉害了,给一个map的形参对象
* 用法类似于之前的Model方法
* 主要通过map对象的每一次put,将数据共享到request域中
* */
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map){
map.put("testRequestScope","Hello Map");
System.out.println("Map:"+map.getClass().getName());
return "success";
}
@RequestMapping("/testModelMap")
public String testModelMap(ModelMap modelMap){
modelMap.addAttribute("testRequestScope", "Hello ModelMap");
System.out.println("ModelMap:"+modelMap.getClass().getName());
return "success";
}
SpringMVC的处理器—>拦截器,类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。
过滤器:
拦截器:
public class MyHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("MyHandlerInterceptor->preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("MyHandlerInterceptor->postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("MyHandlerInterceptor->afterCompletion");
}
}
preHandle
:预处理回调方法,实现处理器的预处理(如登录检查),第三个参数为响应的处理器返回值:true表示继续流程(如调用下一个拦截器或处理器);false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应;postHandle
:后处理回调方法,实现处理器的后处理(但==在渲染视图之前==),此时我们可以通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为null。afterCompletion
:整个请求处理完毕回调方法,即在视图渲染完毕时回调,如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finally,但仅调用处理器执行链中preHandle返回true的拦截器才会执行afterCompletion。@Controller
@RequestMapping("/index")
public class LoginControl {
@RequestMapping(value = "/login")
public String login(){
System.out.println("LoginControl->login");
return "login";
}
@RequestMapping(value = "/test")
@ResponseBody
public String test(){
System.out.println("LoginControl->test");
return "test";
}
}
<!-- 配置拦截器:-->
<mvc:interceptors>
<!-- 会拦截所有Controller类里的所有处理方法 -->
<bean class="com.lucas.controller.MyHandlerInterceptor"></bean>
</mvc:interceptors>
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**/login"/>
<bean class="com.lucas.controller.MyHandlerInterceptor">bean>
mvc:interceptor>
mvc:interceptors>
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//创建自定义的拦截器
MyHandlerInterceptor interceptor = new MyHandlerInterceptor();
//添加拦截器
registry.addInterceptor(MyHandlerInterceptor)
//添加需要拦截的路径
.addPathPatterns("");
}
}
Spring MVC 有以下 3 种处理异常的方式:
局部异常处理仅能处理指定 Controller 中的异常。
注意:@ExceptionHandler不是加在产生异常的方法上,而是加在处理异常的方法上。
@ExceptionHandler 注解定义的方法优先级问题:例如发生的是 NullPointerException,但是声明的异常有 RuntimeException 和 Exception,这时候会根据异常的最近继承关系 找到继承深度最浅的那个@ExceptionHandler 注解方法,即标记了 RuntimeException 的方法。
例子:
定义一个处理过程中可能会存在异常情况的 submit 方法,当 i=0 时会产生算术运算异常,在同一个类中定义处理异常的方法controllerExceptionHandler,捕获运算异常。
@Controller
@RequestMapping
public class ExceptionController {
@RequestMapping("/submit") // 抛错方法
public String submit(HttpServletRequest req,
HttpServletResponse resp) throws Exception {
String num = req.getParameter("num");
System.out.println(10 / Integer.valueOf(num));
return "success";
}
@ExceptionHandler({ArithmeticException.class}) //捕获运算异常
public String controllerExceptionHandler(Exception e) {
System.out.println("打印错误信息 ===> ArithmeticException:" + e);
// 跳转到指定页面
return "error";
}
}
Spring MVC 通过 HandlerExceptionResolver 处理程序异常,包括处理器异常、数据绑定异常以及控制器执行时发生的异常。HandlerExceptionResolver 仅有一个接口方法,源码如下。
public interface HandlerExceptionResolver {
ModelAndView resolveException(HttpServletRequest var1,
HttpServletResponse var2,
Object var3,
Exception var4);
}
发生异常时,Spring MVC 会调用 resolveException() 方法,并转到 ModelAndView 对应的视图中,返回一个异常报告页面反馈给用户。
创建一个 HandlerExceptionResolver 接口的实现类 MyExceptionHandler,代码如下
@Component
public class MyExceptionHandler implements HandlerExceptionResolver {
public ModelAndView resolveException(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Object o,
Exception e) {
Map<String, Object> model = new HashMap<String, Object>();
model.put("errorMessage", "程序运行出错");
//根据不同错误转向不同页面(统一处理),即异常与View的对应关系
if (e instanceof ArithmeticException) {
return new ModelAndView("error", model);
}
return new ModelAndView("other_error", model);
}
}
全局异常处理可使用 SimpleMappingExceptionResolver 来实现。它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常。
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="defaultErrorView" value="other_error">property>
<property name="exceptionAttribute" value="errorMessage">property>
<property name="exceptionMappings">
<props>
<prop key="ArithmeticException">errorprop>
props>
property>
bean>
使用@ControllerAdvice 和@ExceptionHandler 可以全局控制异常,使业务逻辑和异常处理分隔开。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MyException.class)
@ResponseBody
public ResultBean handleMyException(MyException e){
System.out.println("handleMyException....");
return new ResultBean(e.getErrorEnum().getCode(),e.getErrorEnum().getMsg());
}
}
public class MyException extends RuntimeException {
private ErrorEnum errorEnum;
public MyException(ErrorEnum errorEnum){
this.errorEnum = errorEnum;
}
public ErrorEnum getErrorEnum() {
return errorEnum;
}
}
SpringMVC上下文中默认没有装配MultipartResolver,因此默认情况下其不能处理文件上传工作。如果想使用Spring的文件上传功能,则需要在上下文中配置MultipartResolver。
前端表单要求:为了能上传文件,必须将表单的method
设置为POST
,并将enctype
设置为multipart/form-data
。只有在这样的情况下,浏览器才会把用户选择的文件以二进制数据发送给服务器。
<form action="" enctype="multipart/form-data" method="post">
<input type="file" name="file"/>
<input type="submit">
form>
表单中enctype属性的详细说明:
application/x-www=form-urlencoded
:默认方式,只处理表单域中的 value 属性值,采用这种编码方式的表单会将表单域中的值处理成 URL 编码方式。multipart/form-data
:这种编码方式会以二进制流的方式来处理表单数据,这种编码方式会把文件域指定文件的内容也封装到请求参数中,不会对字符编码。text/plain
:除了把空格转换为 “+” 号外,其他字符都不做编码处理,这种方式适用直接通过表单发送邮件。一旦设置了enctype为multipart/form-data,浏览器即会采用二进制流的方式来处理表单数据,而对于文件上传的处理则涉及在服务器端解析原始的HTTP响应。在2003年,Apache Software Foundation发布了开源的Commons FileUpload组件,其很快成为Servlet/JSP程序员上传文件的最佳选择。
【MultipartResolver】用于处理文件上传。当收到请求时,DispatcherServlet 的 checkMultipart() 方法会调用 MultipartResolver 的 isMultipart() 方法判断请求中【是否包含文件】。如果请求数据中包含文件,则调用 MultipartResolver 的 resolveMultipart() 方法对请求的数据进行解析,然后 将文件数据解析成 MultipartFile 并封装在 MultipartHttpServletRequest (继承了 HttpServletRequest) 对象 中,最后传递给 Controller。
DispatcherServlet的核心方法中第一句就是如下的代码:
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
...
注意: MultipartResolver 默认不开启,需要手动开启。
<form action="/upload" enctype="multipart/form-data" method="post">
<input type="file" name="file"/>
<input type="submit" value="upload">
form>
<dependency>
<groupId>commons-fileuploadgroupId>
<artifactId>commons-fileuploadartifactId>
<version>1.3.3version>
dependency>
注意: 这个bena的id必须为:multipartResolver , 否则上传文件会报400的错误!
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="utf-8"/>
<property name="maxUploadSize" value="10485760"/>
<property name="maxInMemorySize" value="40960"/>
bean>
String getOriginalFilename()
:获取上传文件的原名InputStream getInputStream()
:获取文件流void transferTo(File dest)
:将上传文件保存到一个目录文件中package com.wang.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
@Controller
public class FileController {
//@RequestParam("file") 将name=file控件得到的文件封装成CommonsMultipartFile 对象
//批量上传CommonsMultipartFile则为数组即可
@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";
}
}
采用file.Transto 来保存上传的文件
/*
* 采用file.Transto 来保存上传的文件
*/
@RequestMapping("/upload2")
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 "redirect:/index.jsp";
}
一共有两种文件下载方法:
@GetMapping("/download1")
@ResponseBody
public R download1(HttpServletResponse response){
FileInputStream fileInputStream = null;
ServletOutputStream outputStream = null;
try {
// 这个文件名是前端传给你的要下载的图片的id
// 然后根据id去数据库查询出对应的文件的相关信息,包括url,文件名等
String fileName = "wang.jpg";
//1、设置response 响应头,处理中文名字乱码问题
response.reset(); //设置页面不缓存,清空buffer
response.setCharacterEncoding("UTF-8"); //字符编码
response.setContentType("multipart/form-data"); //二进制传输数据
//设置响应头,就是当用户想把请求所得的内容存为一个文件的时候提供一个默认的文件名。
//Content-Disposition属性有两种类型:inline 和 attachment
//inline :将文件内容直接显示在页面
//attachment:弹出对话框让用户下载具体例子:
response.setHeader("Content-Disposition",
"attachment;fileName="+ URLEncoder.encode(fileName, "UTF-8"));
// 通过url获取文件
File file = new File("D:/upload/"+fileName);
//2、 读取文件--输入流
fileInputStream = new FileInputStream(file);
//3、 写出文件--输出流
outputStream = response.getOutputStream();
byte[] buffer = new byte[1024];
int len;
//4、执行写出操作
while ((len = fileInputStream.read(buffer)) != -1){
outputStream.write(buffer,0,len);
outputStream.flush();
}
return R.success();
} catch (IOException e) {
e.printStackTrace();
return R.fail();
}finally {
if( fileInputStream != null ){
try {
// 5、关闭输入流
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if( outputStream != null ){
try {
// 5、关闭输出流
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@GetMapping("/download2")
public ResponseEntity<byte[]> download2(){
try {
String fileName = "wang.jpg";
byte[] bytes = FileUtils.readFileToByteArray(new File("D:/upload/"+fileName));
HttpHeaders headers=new HttpHeaders();
// Content-Disposition就是当用户想把请求所得的内容存为一个文件的时候提供一个默认的文件名。
headers.set("Content-Disposition","attachment;filename="+ URLEncoder.encode(fileName, "UTF-8"));
headers.set("charsetEncoding","utf-8");
headers.set("content-type","multipart/form-data");
ResponseEntity<byte[]> entity=new ResponseEntity<>(bytes,headers, HttpStatus.OK);
return entity;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}