Spring MVC
1、什么是MVC
mvc是Web层的一种设计模式,Model、View 和Controller 的缩写,分别代表 Web 应用程序中的 3 种职责。
- 模型:用于存储数据以及处理用户请求的业务逻辑。
- 视图:向控制器提交数据,显示模型中的数据。
- 控制器:根据视图提出的请求判断将请求和数据交给哪个模型处理,将处理后的有关结果交给哪个视图更新显示。
基于 Servlet 的 MVC 模式的具体实现如下:
-
模型:一个或多个 JavaBean 对象,用于存储数据(实体模型,由 JavaBean 类创建)和处理业务逻辑(业务模型,由一般的 Java 类创建)。
-
视图:一个或多个 JSP 页面,向控制器提交数据和为模型提供数据显示,JSP 页面主要使用 HTML 标记和 JavaBean 标记来显示数据。
-
控制器:一个或多个 Servlet对象,根据视图提交的请求进行控制,即将请求转发给处理业务逻辑的 JavaBean,并将处理结果存放到实体模型 JavaBean 中,输出给视图显示。
基于 Servlet 的 MVC 模式的流程如图所示:
JSP 中的 MVC 模式
spring对web层的支持主要是提供了Spring MVC
2、Spring MVC概述
简而言之,springMVC对servlet进行封装,避免繁琐的获取表单参数,多余的serlvet服务类等代码
3、Spring MVC的执行流程
4、Spring MVC的内部执行流程
Spring MVC 执行流程
Tomcat启动阶段
1、Tomcat启动加载web.xml配置文件,web.xml中配置了一个servlet,如果servlet中的<load-on-startup>1</load-on-startup>指定了1,说明servlet的实例化和初始化的方法将会直接执行
2、通过加载web.xml配置文件,实例化初始化DispatcherServlet
3、DispatcherServlet只要初始化完成会去加载SpringMVC配置文件(配置文件一加载Spring容器就启动了,在SpringMVC中配置的处理器映射器、处理器适配器、视图解析器以及Controller都将实例化完成存放到容器中)
浏览器访问阶段
例如浏览器访问http://localhost:8080/login
1、浏览器通过域名端口找到本机的tomcat,然后通过Tomcat发送访问/login这个路径的请求
2、这个请求会进入DispatcherServlet,DispatcherServlet会找到处理器映射器
3、处理器映射器找到要执行的类与方法(执行链),并把执行链返回给DispatcherServlet
4、DispatcherServlet拿到结果,找到处理器适配器
5、处理器适配器去执行对应的方法
6、对应的方法返回ModelAndView给处理器适配器
7、处理器适配器把拿到的结果(ModelAndView)返回给DispatcherServlet
8、DispatcherServlet拿到结果(ModelAndView)之后,找到视图解析器,请求进行视图解析
9、视图解析器根据ModelAndView,拼接前后缀,找到视图真正的地址(View)并把这个地址(View)返回给了DispatcherServlet
10、此时DispatcherServlet有数据(ModelAndView)和视图的真正地址(View),就会找到对应的模板引擎(JSP),通过模板引擎(使用JSP中的EL表达式等)进行视图渲染,将模型数据填充到request域
11、最后前端展示
5、Spring MVC的基本使用
5.1、创建web工程并导入相关jar包
spring-webmvc-5.1.9.RELEASE
spring-web-5.1.9.RELEASE
spring-core-5.1.9.RELEASE
spring-expression-5.1.9.RELEASE
spring-beans-5.1.9.RELEASE
5.2、创建Controller类
public class MyController implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { ModelAndView mv = new ModelAndView(); mv.addObject("msg","hello Springmvc"); mv.setViewName("success"); return mv; } }
5.3、创建success页面
在WEB-INF下创建pages文件夹,在文件夹下创建success页面
5.4、编写配置文件stringmvc.xml
<!--myController 交给Spring管理 添加到容器--> <bean name="/myController" class="top.ftime.wk.controller.MyController"/> <!-- 配置处理器映射器--> <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/> <!-- 配置处理器适配器--> <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/> <!-- 配置视图解析器--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- 配置前缀--> <property name="prefix" value="/WEB-INF/pages/"/> <!-- 配置后缀--> <property name="suffix" value=".jsp"/> </bean>
5.5、配置web.xml
<servlet> <!-- 顺便起名--> <servlet-name>flowerTime</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 初始化参数加载配置文件 注意:key是固定写法 必须写contextConfigLocation --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>flowerTime</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
6、Spring MVC视图解析器
Spring 视图解析器是 Spring MVC 中的重要组成部分,用户可以在配置文件中定义 Spring MVC 的一个视图解析器(ViewResolver),示例代码如下:
<!-- 配置视图解析器--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- 配置前缀--> <property name="prefix" value="/WEB-INF/pages/"/> <!-- 配置后缀--> <property name="suffix" value=".jsp"/> </bean>
上述视图解析器配置了前缀和后缀两个属性,因此在Spring MVC基本使用教程中的 MyController 控制器类的视图路径仅需提供ViewName("success"),视图解析器将会自动添加前缀和后缀。
InternalResourceViewResolver 是 URLBasedViewResolver 的子类,所以 URLBasedViewResolver 支持的特性它都支持。
在实际应用中 InternalResourceViewResolver 也是使用的最广泛的一个视图解析器。那么InternalResourceViewResolver有什么自己独有的特性呢?
单从字面意思来看,我们可以把 InternalResourceViewResolver 解释为内部资源视图解析器,这就是 InternalResourceViewResolver 的一个特性。
InternalResourceViewResolver 会把返回的视图名称都解析为 InternalResourceView 对象,InternalResourceView 会把 Controller 处理器方法返回的模型属性都存放到对应的 request 属性中,然后通过 RequestDispatcher 在服务器端把请求 forword 重定向到目标 URL。
比如在 InternalResourceViewResolver 中定义了 prefix=/WEB-INF/,suffix=.jsp,然后请求的 Controller 处理器方法返回的视图名称为 login,那么这个时候 InternalResourceViewResolver 就会把 login 解析为一个 InternalResourceView 对象,先把返回的模型属性都存放到对应的 HttpServletRequest 属性中,然后利用 RequestDispatcher 在服务器端把请求 forword 到 /WEB-INF/test.jsp。
这就是 InternalResourceViewResolver 一个非常重要的特性,我们都知道存放在 /WEB-INF/ 下面的内容是不能直接通过 request 请求的方式请求到的,为了安全性考虑,我们通常会把 jsp 文件放在 WEB-INF 目录下,而 InternalResourceView 在服务器端跳转的方式可以很好的解决这个问题。
7、@Controller注解、@RequestMapping注解和配置文件的优化
7.1、@Controller注解
在Spring MVC基本使用教程中,我们定义的MyController只能做一件事,因为就一个实现方法,并且每次都要单独配置一个对应的Bean,这种方式不是很优雅,在前面Spring的学习中我们学习了使用@Component注解的方式把当前类添加到容器中,此注解在Spring MVC中同样适用,但是我们不仅要让当前类添加到容器中,还要让Spring MVC指导这是一个Controller类,是一个控制器类,因此在@Component注解的延伸下有了@Controller注解
@Controller注解的使用:
首先在Spring MVC配置文件中开启包扫描
<!-- 使用扫描机制扫描控制器类,控制器类都在controller包及其子包下 --> <context:component-scan base-package="top.ftime.wk.controller"/>
7.2、@RequestMapping注解
在基于注解的控制器类中可以为每个请求编写对应的处理方法。那么如何将请求与处理方法一一对应呢?
需要使用 org.springframework.web.bind.annotation.RequestMapping 注解类型将请求与处理方法一一对应。
请求相关的注解
@GetMapping Get请求
@DeleteMapping Delete请求
@PutMapping Put请求
@PostMapping Post请求
@RequestMapping Request请求
@Controller public class FirstController { /** * GetMapping注解表示此方法只能接收get请求 参数:表示请求的路径 * @return */ @GetMapping("login") public ModelAndView login(){ ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("flowerTime","hello login"); modelAndView.setViewName("first"); return modelAndView; } /** * PostMapping注解表示此方法只能接收post请求 参数:表示请求的路径 * @return */ @PostMapping("res") public ModelAndView res() { ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("flowerTime", "hello res"); modelAndView.setViewName("first"); return modelAndView; } }
@RequestMapping注解可以标注在类上,在类级别注解的情况下,控制器类中的所有方法都将映射为类级别的请求,用户可以使用如下URL访问login方法。
http://localhost:8080/springMVCDemo02/index/login
package controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("/index") public class IndexController { @RequestMapping("/login") public String login() { return "login"; } @RequestMapping("/register") public String register() { return "register"; } }
为了方便维护程序,建议开发者采用类级别注解,将相关处理放在同一个控制器类中。例如,对商品的增、删、改、查处理方法都可以放在 GoodsOperate 控制类中。
7.3、配置文件的优化
<?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"> <!--myController 交给Spring管理 添加到容器--> <!-- <bean name="/myController" class="top.ftime.wk.controller.MyController"/>--> <!-- 配置处理器映射器--> <!-- <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>--> <!-- 配置处理器适配器--> <!-- <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>--> <!-- 配置包扫描 并在类上添加@component的延伸注解@controller 在方法上添加请求方法相关的注解,用什么方式就写什么方式的注解,参数表示的是请求的路径 常用请求相关的注解 - @GetMapping Get请求 - @DeleteMapping Delete请求 - @PutMapping Put请求 - @PostMapping Post请求 - @RequestMapping Request请求 --> <context:component-scan base-package="top.ftime.wk.controller"/> <!--开启MVC的注解驱动,这行的配置等效于配置“处理器映射器”和“处理器适配器”--> <mvc:annotation-driven> <!-- 配置视图解析器--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- 配置前缀--> <property name="prefix" value="/WEB-INF/pages/"/> <!-- 配置后缀--> <property name="suffix" value=".jsp"/> </bean> </beans>
8、Spring MVC获取参数
8.1、基本数据类型和字符串类型
基本数据类型和字符串类型参数的获取分为两种情况:
表单中input标签name属性的值,也就是请求参数的key是否与方法形参的名字一致
1:一致时,会自动把数据封装上
2:不一致时,通过@RequestParam()注解进行绑定,注解的参数要和表单中input标签name的属性值一致
/** * 基本数据类型和字符串类型 * 方法的形参与表单的name属性,也就是和请求参数的key一致时,会自动把数据封装 * 不一致时,通过@RequestParam("表单的name")注解进行绑定 * @param age * @param username * @return */ @GetMapping("basicParam") public ModelAndView basicParams(int age ,@RequestParam("name") String username){ System.out.println(age); System.out.println(username); ModelAndView modelAndView =new ModelAndView(); modelAndView.setViewName("first"); return modelAndView; }
8.2、数组类型
/** * 数组类型 * 方法的形参要与表单的name属性一致 * @param hobby * @return */ @GetMapping("arrayParam") public ModelAndView arrayParams(String [] hobby){ System.out.println(Arrays.toString(hobby)); ModelAndView modelAndView =new ModelAndView(); modelAndView.setViewName("first"); return modelAndView; }
8.3、引用类型(对象)
/** * 对象 * 表单name属性要和实体类中的属性名一致,和controller方法中的接收对象名无关 * 用实体类接收 * @param user * @return */ @PostMapping("userParam") public ModelAndView userParams(User user){ System.out.println(user); ModelAndView modelAndView =new ModelAndView(); modelAndView.setViewName("first"); return modelAndView; }
8.4、包装类型(对象作为成员变量)
/** * 包装类类型 * 前端表单中name的属性值要写 "包装类属性名.实体类的属性" * 用包装类接收 * @param admin * @return */ @GetMapping("adminParam") public ModelAndView adminParams(Admin admin){ System.out.println(admin); ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName("first"); return modelAndView; }
8.5、List集合类型
8.6、Map集合类型
8.7、josn格式字符串
json格式字符串参数的接收需要第三方的支持(例如Jackson),我们需要导入相关的第三方jar包。
注意:
json格式字符串转对象有很多库可以使用,比如:json-lib、fastjson、Gson、Jackson等,由于Spring默认使用Jackson,只需要导入Jackson的jar包就行了,不需要进行任何配置,如果使用其他的第三方支持,需要配置比较麻烦,不如直接使用Jackson省事。
/** * 接收json格式字符串 * 导入Jackson的jar包 @RequestBody 注解表示把请求体中的json格式的字符串转换成对象,依赖jackson * @param user * @return */ @PostMapping("jsonParam") public ModelAndView jsonParams(@RequestBody User user){ System.out.println(user); return null; }
Jackson相关jar包
jackson-annotations-2.9.8
jackson-databind-2.9.8
jackson-core-2.9.8
8.8、其他
通过 @PathVariable 获取 URL 中的参数,控制器类示例代码如下:
@Controllerpublic class UserController { @RequestMapping("/user") /** * 通过@PathVariable获取URL的参数 */ public String register(@PathVariable String uname,@PathVariable String upass,Model model) { if ("zhangsan".equals(uname) && "123456".equals(upass)) { logger.info("成功"); return "login"; // 注册成功,跳转到 login.jsp } else { // 在register.jsp页面上可以使用EL表达式取出model的uname值 model.addAttribute("uname", uname); return "register"; // 返回 register.jsp } } }
@RequestMapping("/register") public String register(@ModelAttribute("user") UserForm user) { if ("zhangsan".equals(uname) && "123456".equals(upass)) { logger.info("成功"); return "login"; // 注册成功,跳转到 login.jsp } else { logger.info("失败"); // 使用@ModelAttribute("user")与model.addAttribute("user",user)的功能相同 //register.jsp页面上可以使用EL表达式${user.uname}取出ModelAttribute的uname值 return "register"; // 返回 register.jsp } }
9、乱码问题
9.1、get请求接收参数乱码
- 第一种方式
String s = new String(username.getBytes("ISO-8859-1"), "utf-8");
- 第二种方式
/** 修改 comcat中的config目录下 server.xml 添加 URIEncoding="utf-8" 注意点:如果有 useBodyEncodingForURI="true" 请干掉 例如: <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" disableUploadTimeout="true" executor="tomcatThreadPool" URIEncoding="utf-8"/> */
- 第三种方式(高版本支持)
同post解决乱码方式
9.2、post请求接收参数乱码
<!--解决Post乱码问题的拦截器CharacterEncodingFilter
在高版本的Spring中是可以解决 get请求 和Post请求 、 低版本 比如说4.X中 只能解决Post请求 不能解决get请求 --> <filter> <filter-name>Charac</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>Charac</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
10、获取ServletAPI
想要在代码中得到最原始的HttpServletRequest和HttpServletREsponse可以直接在方法里写个形参就行了,然后就可以直接使用了
11、Controller的返回值类型
/** * Controller的返回值类型 * 1、ModelAndView * 2、String * JSP页面 * 普通字符串 * 转发 * 重定向 * 3、对象 * 4、void * 5、ResponseEntity */
11.1、返回ModelAndView
/** * 返回ModelAndView * ModelAndView携带了数据,返回JSP页面,在JSP页面可以使用EL表达式取出 * @return */ @PostMapping("modelAndView") public ModelAndView fun1() { ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("flowerTime", "落花无意,流水无情"); modelAndView.setViewName("first"); return modelAndView; }
11.2、返回字符串
/** * JSP页面 *不携带数据 * @return */ @PostMapping("String_1") public String fun2() {
//返回first.jsp页面 return "first"; } /** * model方式携带数据,实际上是把Model中的数据放到了请求域中 * @return */ @PostMapping("String_1") public String fun2(Model model) { model.addAttribute("flowerTime","花火");
//返回first.jsp页面 return "first"; } /** * map方式携带数据,实际上也是把Map中的数据放到了请求域中 * * @return */ @PostMapping("String_1") public String fun2(Map map) { map.put("flowerTime", "打上花火");
//返回first.jsp页面 return "first"; }
/** * 返回真正的字符串 * 注解@ResponseBody表示返回的是字符串而不是页面 * 中文乱码可以使用produces属性解决 * * @return */ @ResponseBody @GetMapping(value = "String_2", produces = "text/html;charset=utf-8") public String fun3() {
//返回到浏览器上的是字符串 return "神秘花园"; }
若要返回json格式的字符串,produces属性值为“application/json;charset=utf-8”
produces属性只能解决局部的中文乱码,如果想统一设置,需在spring.xml中配置统一的字符编码
<!--开启MVC的注解驱动,这行的配置等效于配置“处理器映射器”和“处理器适配器”--> <mvc:annotation-driven> <!-- 统一设置全局输出的字符编码--> <mvc:message-converters> <bean class="org.springframework.http.converter.StringHttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>text/html;charset=utf-8</value> <value>application/json;charset=utf-8</value> <value>*/*;charset=utf-8</value> </list> </property> </bean> </mvc:message-converters> </mvc:annotation-driven>
11.3、转发和重定向
重定向是将用户从当前处理请求定向到另一个视图(例如 JSP)或处理请求,以前的请求(request)中存放的信息全部失效,并进入一个新的 request 作用域。转发是将用户对当前处理的请求转发给另一个视图或处理请求,以前的 request 中存放的信息不会失效。转发是服务器行为,重定向是客户端行为。
1)转发过程
客户浏览器发送 http 请求,Web 服务器接受此请求,调用内部的一个方法在容器内部完成请求处理和转发动作,将目标资源发送给客户;在这里转发的路径必须是同一个 Web 容器下的 URL,其不能转向到其他的 Web 路径上,中间传递的是自己的容器内的 request。
在客户浏览器的地址栏中显示的仍然是其第一次访问的路径,也就是说客户是感觉不到服务器做了转发的。转发行为是浏览器只做了一次访问请求。
2)重定向过程
客户浏览器发送 http 请求,Web 服务器接受后发送 302 状态码响应及对应新的 location 给客户浏览器,客户浏览器发现是 302 响应,则自动再发送一个新的 http 请求,请求 URL 是新的 location 地址,服务器根据此请求寻找资源并发送给客户。
在这里 location 可以重定向到任意 URL,既然是浏览器重新发出了请求,那么就没有什么 request 传递的概念了。在客户浏览器的地址栏中显示的是其重定向的路径,客户可以观察到地址的变化。重定向行为是浏览器做了至少两次的访问请求。
在SpringMVC中,控制器类中处理方法的return语句默认就是转发实现,只不过是实现的是转发到视图。
/** * 转发 * * @return */ @GetMapping("forward") public String fun4() { System.out.println("使用了转发"); return "forward:String_2"; } /** * 重定向 * * @return */ @GetMapping("redirect") public String fun5() { System.out.println("使用了重定向"); return "redirect:String_2"; }
11.4、返回对象
返回对象 需要使用jackson的支持 是把对象转换成json了 实际返回的是json格式字符串
/** * 返回对象 * 返回对象需要使用Jackson的支持,是把对象装换成json,实际上返回的是json格式字符串 * @return */ @ResponseBody @GetMapping("object") public User fun6(){ User user = new User(); user.setName("tom"); user.setAge(12); user.setFlag(false); return user; }
11.5、返回void
无返回值并不是代表就不返回,无返回值默认是找你配置的路径下的void.JSP页面,所以一般可以在无返回值的方法里转发或者重定向
/** * 无返回值 * 无返回值默认是找你配置的路径下的void,JSP页面 * 所以一般可以在无返回值的方法里转发或者重定向‘’ */ @GetMapping("void") public void fun7(){ }
11.6、返回ResponseEntity
一般返回ResponseEntity更符合规范,因为ResponseEntity内部提供状态码,其实返回的也是一个json格式的字符串,因此也依赖Jackson
@GetMapping("requestEntity") public ResponseEntity<User> fun8(){ User user = new User(); user.setName("tom"); user.setAge(12); user.setFlag(false); return ResponseEntity.status(HttpStatus.OK).body(user); }
12、Rest风格
Rest风格的规范 我们要求知道3个
-
对于请求方式规范 不同的操作 使用不同的请求方式
-
get: 请求一般是获取数据
-
post: 登录和添加数据
-
put: 一般是修改
-
delete : 删除操作
-
-
对于不同的状态码的规范
比如 200表示成功 404 表示未找到 201 表示创建成功
-
Rest风格的请求路径(传递参数)
13、文件上传
- 提交表单(包含异步)的方式
- Base64格式的方式(一定要掌握)
13.1、提交表单的方式
有个input type=file的标签
enctype="multipart/form-data"
表单的 enctype 属性指定的是表单数据的编码方式,该属性有以下 3 个值。
- application/x-www-form-urlencoded:这是默认的编码方式,它只处理表单域里的 value 属性值。
- multipart/form-data:该编码方式以二进制流的方式来处理表单数据,并将文件域指定文件的内容封装到请求参数里。
- text/plain:该编码方式只有当表单的 action 属性为“mailto:”URL 的形式时才使用,主要适用于直接通过表单发送邮件的方式。
由上面 3 个属性的解释可知,在基于表单上传文件时 enctype 的属性值应为 multipart/form-data。
-
-
Servlet3.0之前原始的写法
-
Servlet3.0之后part的写法
-
fileupload.jar+commons.io.jar的写法
-
fileupload.jar+commons.io.jar继承SpringMVC的写法
-
-
如果是Base64上传的方式
-
写法很单一
-
13.2、提交表单之part的方式
13.2.1、单文件上传之part的方式
part方式的文件上传不需要导入任何包,也不需要进行任何视图解析器的配置
@Controller public class FileController { @PostMapping("upload") @ResponseBody public String uploadFile(HttpServletRequest request) throws IOException, ServletException { //从前端读取文件,参数是input的name属性值 Part fileName = request.getPart("fileName"); //打印文件名称,能打印就说明上传成功 System.out.println(fileName.getSubmittedFileName()); //为防止文件重名,此时可以选择重命名,重命名的方式有以下几种 //1:UUID //2:时间戳:毫秒、纳秒 //3:建多个文件夹 //4、目录打散 哈希算法 获取一个字符串的哈希值,再通过哈希值的位与运算,获得目录 //获取一个UUID String uuid = UUID.randomUUID().toString(); //获取一个毫秒级的时间戳 long time = System.currentTimeMillis(); //获取一个纳米级的时间戳 // long time = System.nanoTime(); //获取文件的后缀名 String ext = StringUtils.getFilenameExtension(fileName.getSubmittedFileName()); //获取重命名后的文件名 // String reFileName = uuid + "." + ext; String reFileName = time + "." + ext; //获取文件夹路径 String realPath = request.getServletContext().getRealPath("/uploads"); // //今天的文件夹是否创建 // //获取今天时间 // LocalDate localDate = LocalDate.now(); // //设置日期格式 // DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); // //格式化日期 // String format = dateTimeFormatter.format(localDate); // //创建文件对象 // File file = new File(realPath + "/" + format); // //判断文件夹是否存在,不存在就创建一个新的 // if (!file.exists()) { // file.mkdirs(); // } // //文件真实路径=文件夹路径+文件名 "File.separator" ==> " / " // String path = realPath + File.separator + format + File.separator + reFileName; // //写入文件 // fileName.write(path); //目录打散 //用UUID的哈希值 int hashcode = UUID.randomUUID().toString().hashCode(); //0-15 int dir1 = hashcode & 0xf; int dir2 = (hashcode & 0xf0) >> 4; String finalFilePath = realPath + "/" + dir1 + "/" + dir2 + "/"; //创建文件对象 File file = new File(finalFilePath); //判断文件夹是否存在,不存在就创建一个新的 if (!file.exists()) { file.mkdirs(); } //文件真实路径=文件 夹路径+文件名 "File.separator" ==> " / " String path = finalFilePath + reFileName; //写入文件 fileName.write(path); return "http://localhost:8080/uploads/" + dir1 + "/" + dir2 + "/" + reFileName; } }
编写前端页面
<h2>文件上传</h2> <form action="/upload" enctype="multipart/form-data" method="post"> <input type="file" name="fileName"><br> <input type="submit" value="提交"> </form>
springmvc.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"> <!-- 开启包扫描--> <context:component-scan base-package="top.ftime.wk.controller"/> <!-- 开启MVC的注解驱动,这行的配置等效于配置“处理器映射器”和“处理器适配器--> <mvc:annotation-driven /> <!-- 开启静态资源访问 --> <mvc:default-servlet-handler/> </beans>
<?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"> <servlet> <servlet-name>dispatcherServlet</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> <!-- 开启springMVC part方式上传文件--> <multipart-config> <!-- 上传文件的最大容量(以字节为单位)。默认是没有限制的。 --> <max-file-size>20848820</max-file-size> <!-- 整个multipart请求的最大容量(以字节为单位),不会关心有多少个part以及每个part的大小。默认是没有限制的 --> <max-request-size>418018841</max-request-size> <!-- 在上传的过程中,如果文件大小达到了一个指定最大容量(以字节为单位),将会写入到临时文件路径中。默认值为0 --> <file-size-threshold>1048576</file-size-threshold> </multipart-config> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
@MultipartConfig标注主要是为了辅助Servlet3.0中HttpServletRequest提供的对上传文件的支持。该标注写在Servlet类的声明之前,一表示该Servlet希望处理的请求时multipart/form-data类型的。另外,该标注还提供了若干属性用于简化对上传文件的处理。
@MultipartConfig标注属性
fileSizeThershold int型 是(可选) 描述:当前数据量大于该值时,内容将被写入文件。
location String型 是(可选) 描述:存放生成文件的地址
maxFileSize long型 是(可选) 描述:允许上传的文件最大值,默认为-1,表示没有限制
maxRequestSize long型 是(可选) 描述:针对 multipart/form-data 请求的最大数量,默认为-1,表示没有限制
编写Controller类
@Controller @MultipartConfig public class FileController { @PostMapping("uploads") @ResponseBody public String uploads(HttpServletRequest request) throws IOException, ServletException { Collection<Part> parts = request.getParts(); String realPath = request.getServletContext().getRealPath("/WEB-INF/"); parts.forEach(part -> { try { part.write(realPath+part.getSubmittedFileName()); } catch (IOException e) { e.printStackTrace(); } }); return "success"; } }
<h2>文件上传</h2> <form action="/uploads" enctype="multipart/form-data" method="post"> <input type="file" name="fileName" multiple><br> <input type="submit" value="提交"> </form>
13.3、提交表单之Spring MVC的方式
名称 | 作用 |
---|---|
byte[] getBytes() | 以字节数组的形式返回文件的内容 |
String getContentType() | 返回文件的内容类型 |
InputStream getInputStream() | 返回一个InputStream,从中读取文件的内容 |
String getName() | 返回请求参数的名称 |
String getOriginalFillename() | 返回客户端提交的原始文件名称 |
long getSize() | 返回文件的大小,单位为字节 |
boolean isEmpty() | 判断被上传文件是否为空 |
void transferTo(File destination) | 将上传文件保存到目标目录下 |
在上传文件时需要在配置文件中使用 Spring 的 org.springframework.web.multipart.commons.CommonsMultipartResolver 类配置 MultipartResolver 用于文件上传。
<!-- 配置多媒体实体解析器--> <bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
/** * * @param request * @param fileName 形参MultipartFile 的名字要和前端页面中input标签的name属性值一致 * @return * @throws IOException */ @PostMapping("uploadByFileIo") @ResponseBody public String uploadByFileIo(HttpServletRequest request, MultipartFile fileName) throws IOException { String realPath = request.getServletContext().getRealPath("/file/"); File file = new File(realPath); if (!file.exists()) { file.mkdirs(); } //重命名 long nano = System.nanoTime(); String ext = StringUtils.getFilenameExtension(fileName.getOriginalFilename()); String filePath = realPath + nano + "." + ext; //写出文件 fileName.transferTo(new File(filePath)); return filePath; }
前端页面
<h2>文件上传</h2> <form action="/uploadByFileIo" enctype="multipart/form-data" method="post"> <input type="file" name="fileName"><br> <input type="submit" value="提交"> </form>
<?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"> <!-- 开启包扫描--> <context:component-scan base-package="top.ftime.wk.controller"/> <!-- 开启MVC的注解驱动,这行的配置等效于配置“处理器映射器”和“处理器适配器--> <mvc:annotation-driven /> <!-- 开启静态资源访问 --> <mvc:default-servlet-handler/>
<!-- 配置多媒体实体解析器--> <bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
</beans>
<?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"> <servlet> <servlet-name>dispatcherServlet</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> <!-- 开启springMVC part方式上传文件--> <multipart-config/> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
14、文件下载
14.1、通过超链接实现下载
弊端:不能统计下载次数,并且对于浏览器能够解析的文件,浏览器会直接解析展示,不会下载,需要在通过右键另存为保存文件
14.2、利用程序编码实现下载
response.setHeader("Content-Type", "application/x-msdownload"); response.setHeader("Content-Disposition", "attachment;filename="+filename);
具体实现如下
package controller; import java.io.File; import java.io.FileInputStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @Controller public class FileDownController { // 得到一个用来记录日志的对象,在打印时标记打印的是哪个类的信息 private static final Log logger = LogFactory .getLog(FileDownController.class); /** * 显示要下载的文件 */ @RequestMapping("showDownFiles") public String show(HttpServletRequest request, Model model) { // 从 workspace\.metadata\.plugins\org.eclipse.wst.server.core\ // tmp0\wtpwebapps\springMVCDemo11\下载 String realpath = request.getServletContext() .getRealPath("uploadfiles"); File dir = new File(realpath); File files[] = dir.listFiles(); // 获取该目录下的所有文件名 ArrayList<String> fileName = new ArrayList<String>(); for (int i = 0; i < files.length; i++) { fileName.add(files[i].getName()); } model.addAttribute("files", fileName); return "showDownFiles"; } /** * 执行下载 */ @RequestMapping("down") public String down(@RequestParam String filename, HttpServletRequest request, HttpServletResponse response) { String aFilePath = null; // 要下载的文件路径 FileInputStream in = null; // 输入流 ServletOutputStream out = null; // 输出流 try { // 从workspace\.metadata\.plugins\org.eclipse.wst.server.core\ // tmp0\wtpwebapps下载 aFilePath = request.getServletContext().getRealPath("uploadfiles"); // 设置下载文件使用的报头 response.setHeader("Content-Type", "application/x-msdownload"); response.setHeader("Content-Disposition", "attachment; filename=" + toUTF8String(filename)); // 读入文件 in = new FileInputStream(aFilePath + "\\" + filename); // 得到响应对象的输出流,用于向客户端输出二进制数据 out = response.getOutputStream(); out.flush(); int aRead = 0; byte b[] = new byte[1024]; while ((aRead = in.read(b)) != -1 & in != null) { out.write(b, 0, aRead); } out.flush(); in.close(); out.close(); } catch (Throwable e) { e.printStackTrace(); } logger.info("下载成功"); return null; } /** * 下载保存时中文文件名的字符编码转换方法 */ public String toUTF8String(String str) { StringBuffer sb = new StringBuffer(); int len = str.length(); for (int i = 0; i < len; i++) { // 取出字符中的每个字符 char c = str.charAt(i); // Unicode码值为0~255时,不做处理 if (c >= 0 && c <= 255) { sb.append(c); } else { // 转换 UTF-8 编码 byte b[]; try { b = Character.toString(c).getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); b = null; } // 转换为%HH的字符串形式 for (int j = 0; j < b.length; j++) { int k = b[j]; if (k < 0) { k &= 255; } sb.append("%" + Integer.toHexString(k).toUpperCase()); } } } return sb.toString(); } }
2)创建文件列表页面
下载文件示例需要一个显示被下载文件的 JSP 页面 showDownFiles.jsp,代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <table> <tr> <td>被下载的文件名</td> </tr> <!--遍历 model中的 files--> <c:forEach items="${files}" var="filename"> <tr> <td> <a href="${pageContext.request.contextPath }/down?filename=${filename}">${filename}</a> </td> </tr> </c:forEach> </table> </body> </html>
快速看懂版
15、Spring MVC的静态资源
15.1、要了解的点
<servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> <!-- <url-pattern>/*</url-pattern>--> </servlet-mapping>
15.2、静态资源解决方式一
<servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/*.do</url-pattern> </servlet-mapping>
15.3、静态资源解决方式二
<!-- 告诉Spring MVC那些请求需要到那些地方找 配置静态资源映射 Mapping:表示请求匹配,支持通配符 Location:表示要资源的位置 --> <mvc:resources mapping="pages/*" location="WEB-INF"/>
15.4、静态资源解决方式三
<!-- 开启静态资源访问 --> <mvc:default-servlet-handler/>
16、Spring MVC的异常处理
我们的开发中 三层架构 dao层 、service层和 controller层的异常, 一般都会抛给SpringMVC来处理,所以我们异常处理 是针对的SpringMVC的异常处理
16.1、方式一
16.2、方式二
-
-
要求实现 HandlerExceptionResolver
-
要求加入到springioc容器中
@Component public class MyExeptionHander implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) { ModelAndView mv = new ModelAndView(); mv.addObject("msg",e.getMessage()); mv.setViewName("error"); return mv; } }
-
-
第二步:修改error.jsp
第三步: 编写Controller
@Controller public class ErrorController { @GetMapping("error") public String error(){ if(true){ throw new RuntimeException("辉哥真帅"); } return "success"; } }
特点: 虽然可以动态显示错误信息 但是只能返回jsp页面 想要返回html页面 需要依赖模板引擎
16.3、方式三(必须掌握)
这个时候 我们要返回json数据
public enum StatusEnum { NAME_ERROR(20001,"用户名错误"), PASRROR_ERROR(20002,"密码错误"), UNKNOWN_ERROR(40004,"未知错误"); private int status; private String message; StatusEnum(int status, String message) { this.status = status; this.message = message; } public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
public class DiyExecution extends RuntimeException{ private StatusEnum statusEnum; public StatusEnum getStatusEnum() { return statusEnum; } public void setStatusEnum(StatusEnum statusEnum) { this.statusEnum = statusEnum; } public DiyExecution(StatusEnum statusEnum) { this.statusEnum = statusEnum; } }
@Controller public class ExceptionController { @GetMapping("error2") public String fun2(){ System.out.println("异常二"); throw new DiyExecution(StatusEnum.NAME_ERROR); } }
@RestControllerAdvice//给Controller添加功能 public class MyCustmerExceptionHander { /** * 注解@ExceptionHandler表示你要处理的异常 * Controller层如果出了异常,并且是DiyExecution异常时,会执行这个方法 * @param diyExecution * @return */ @ExceptionHandler({DiyExecution.class}) public ResponseEntity<Map<String,Object>> hander (DiyExecution diyExecution){ StatusEnum statusEnum = diyExecution.getStatusEnum(); Map<String,Object> map = new HashMap<>(); map.put("status",statusEnum.getStatus()); map.put("message",statusEnum.getMessage()); return ResponseEntity.ok(map); } }
16.4、处理未知异常
@RestControllerAdvice//给Controller添加功能 public class MyCustmerExceptionHander { //在不知道哪里出了异常,也不知道出现了什么异常的情况下 //我们此时要处理这个异常,必须要找这些异常的父类 throwable /** * 全局解决未知异常 */ @ExceptionHandler({Throwable.class}) public ResponseEntity<Map<String,Object>> unknown (Throwable e){
StatusEnum unknownError = StatusEnum.UNKNOWN_ERROR;
unknownError.setMessage(e.getMessage());
Map<String,Object> map = new HashMap<>(); map.put("status",unknownError.getStatus()); map.put("message",unknownError.getMessage()); return ResponseEntity.ok(map); } }
17、Spring MVC拦截器
package interceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; public class TestInterceptor implements HandlerInterceptor { @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion方法在控制器的处理请求方法执行完成后执行,即视图渲染结束之后执行"); } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle方法在控制器的处理请求方法调用之后,解析视图之前执行"); } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandle方法在控制器的处理请求方法调用之后,解析视图之前执行"); return false; } }
在上述拦截器的定义中实现了 HandlerInterceptor 接口,并实现了接口中的 3 个方法。有关这 3 个方法的描述如下。
- preHandle 方法:该方法在控制器的处理请求方法前执行,其返回值表示是否中断后续操作,返回 true 表示继续向下执行,返回 false 表示中断后续操作。
- postHandle 方法:该方法在控制器的处理请求方法调用之后、解析视图之前执行,可以通过此方法对请求域中的模型和视图做进一步的修改。
- afterCompletion 方法:该方法在控制器的处理请求方法执行完成后执行,即视图渲染结束后执行,可以通过此方法实现一些资源清理、记录日志信息等工作。
第二步:配置拦截器
让自定义的拦截器生效需要在 Spring MVC 的配置文件中进行配置,配置示例代码如下:
<!-- 配置拦截器 --> <mvc:interceptors> <!-- 配置一个全局拦截器,拦截所有请求 --> <bean class="interceptor.TestInterceptor" /> <mvc:interceptor> <!-- 配置拦截器作用的路径 --> <mvc:mapping path="/**" /> <!-- 配置不需要拦截作用的路径 --> <mvc:exclude-mapping path="" /> <!-- 定义<mvc:interceptor>元素中,表示匹配指定路径的请求才进行拦截 --> <bean class="interceptor.Interceptor1" /> </mvc:interceptor> <mvc:interceptor> <!-- 配置拦截器作用的路径 --> <mvc:mapping path="/gotoTest" /> <!-- 定义在<mvc: interceptor>元素中,表示匹配指定路径的请求才进行拦截 --> <bean class="interceptor.Interceptor2" /> </mvc:interceptor> </mvc:interceptors>
<!-- 配置拦截器 可以配置多个 --> <mvc:interceptors> <!--表示配置一个--> <mvc:interceptor> <!--拦截的路径 --> <mvc:mapping path="/interceptor/*" /> <!--拦截后要走的拦截器--> <bean class="com.shangma.cn.demo1.FirstInteceptor"></bean> </mvc:interceptor> <!--表示配置一个--> <mvc:interceptor> <!--拦截的路径 --> <mvc:mapping path="/interceptor/*" /> <!--<mvc:exclude-mapping path=""/>--> <!--拦截后要走的拦截器--> <bean class="com.shangma.cn.demo1.FisrtInteceptor"></bean> </mvc:interceptor> </mvc:interceptors>
18、Spring MVC的类型转换问题
18.1、日期类型转换之Date类型
解决方式二:
新建类型转换器
//Converter 第一个泛型 表示from //第二个泛型 表示 to public class MyConvert implements Converter<String,Date> { @Override public Date convert(String s) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); try { Date parse = sdf.parse(s); return parse; } catch (ParseException e) { SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy/MM/dd"); try { return sdf1.parse(s); } catch (ParseException e1) { e1.printStackTrace(); } } return null; }
配置转换器
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <set> <bean class="com.shangma.cn.MyConvert"/> </set> </property> </bean> <mvc:annotation-driven conversion-service="conversionService"/>
18.1.2、以json的形式传参
jackson-annotations-2.9.8
jackson-core-2.9.8
jackson-databind-2.9.8
<mvc:annotation-driven> <mvc:message-converters> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="objectMapper" ref="objectMapper"/> </bean> </mvc:message-converters> </mvc:annotation-driven> <bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean" p:indentOutput="true" p:simpleDateFormat="yyyy-MM-dd HH:mm:ss" />
18.2、日期类型转换之LocalDa&LocalDateTime
public class MyConvert1 implements Converter<String,LocalDateTime> { @Override public LocalDateTime convert(String s) { return LocalDateTime.parse(s,DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); } } public class MyConvert2 implements Converter<String,LocalDate> { @Override public LocalDate convert(String s) { return LocalDate.parse(s,DateTimeFormatter.ofPattern("yyyy-MM-dd")); } }
加载Converts
<mvc:annotation-driven conversion-service="conversionService"/> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <set> <bean class="com.shangma.cn.controller.MyConvert1"/> <bean class="com.shangma.cn.controller.MyConvert2"/> </set> </property> </bean>