SpringMVC是基于Java的实现了MVC设计模式的请求驱动类型的轻量级web框架,通过把Model、View、Controller分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分。简化开发,减少出错,方便组内开发人员之间的配合。
SpringMVC执行过程:
浏览器发送请求,被DispatcherServlet中央调度器捕获,但是DispatcherServlet中央调度器并没有直接处理请求,而是把请求交给了HandlerMapping处理器映射器,处理器映射器根据请求的路径去Controller控制层中匹配对应的执行器,并将匹配结果(整个Controller)返回给DsipatcherServlet中央调度器,由DispatcherServlet中央调度器调用HandlerAdpter处理器适配器来执行控制层执行器的方法;
执行器方法执行后的返回结果,由DispatcherServlet交给视图解析器ViewResolver来处理,找到对应的结果视图,渲染视图,并将结果响应给浏览器。
视图对象中有完整的(转发/重定向)URL地址
springmvc的核心控制器,负责截获所有的请求,当截获请求后委托给HandlerMapping进行请求映射的解析工作,目的是找到哪一个Controller的方法可以处理该请求,找到后再交由给HandlerAdaptor去负责调用并返回ModelAndView对象,然后将ModelAndView对象交给相应的视图解析器(ViewResolver)解析成对应的视图(View)对象,最后由这个视图对象响应客户端。
负责解析带有@ReqeustMapping注解的方法以及类信息,并在请求到达时找到相应的HandlerMethod(一个JavaBean,封装了请求处理方法、参数信息、类信息以及IOC容器等重要的内容)。当找到相应的HandlerMethod后,如果程序中有定义拦截器,那么就会将这个HandlerMethod封装到HandlerExecutionChain的类中,这个类包含了一个拦截器的集合和一个HandlerMethod的对象。最后将这个chain返回给DispatcherServlet。DispatcherServlet从这个HandlerExecutionChain中取出HandlerMethod来匹配相应的HandlerAdapter,找到合适的可以调用HandlerMathod的请求处理适配器。接着DispatcherServlet负责调用HandlerExecutionChain中的所有拦截器中的预处理方法,如果预处理方法没有任何问题,那么就将HandlerMethod交给HandlerAdapter去调用。
DispatcherServlet将HandlerMethod传递给HandlerAdapter,由它负责调用HandlerMethod(也就是目标控制器的方法)。调用时还会使用具体的MethodArgumentResolver(方法参数解析器,RequestMappingHandlerAdapter内部会初始化一系列默认的HandlerMethodArgumentResolver)将请求中的参数解析为请求处理方法所需要的具体类型参数。最后将Controller方法返回的ModelAndView一并返回到DispatcherServlet中。接着DispatcherServlet会继续执行所有拦截器中的后置处理方法。
通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
ViewResolver负责将处理结果生成View视图,ViewResolver首先根据逻辑视图名解析成物理视图名,即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。
视图对象是由相应的视图解析器解析出来的,Spring也提供了不同的视图对象来完成不同的视图响应工作,常见的有的InternalResourceView(内部资源转发视图)等。
构建页面index.jsp发起请求,在服务器端处理请求,控制台打印处理请求成功。
springmvc依赖,会将spring的核心包一并依赖进来。
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.3.23version>
dependency>
核心请求总控器:负责接受所有的请求,并本金映射url地址将请求分发给具体的控制器的方法来处理。
<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-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>dispatcherservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
web-app>
实现步骤:
- 启动注解扫描
- 启动mvc注解处理器
- 配置视图解析器
<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="edu.nf.ch01"/>
<mvc:annotation-driven/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
bean>
beans>
@Controller
@Slf4j
public class HelloController {
@RequestMapping("/hello")
public ModelAndView hello(){
log.info("Hello,world");
// 响应视图,JSP视图解析器是基于转发的机制
ModelAndView mav = new ModelAndView("index");
return mav;
}
}
这个注解驱动注册了RequestMappingHandlerMapping(请求映射处理器)和一个RequestMappingHandlerAdapter(请求处理适配器),同时提供了@RequestBoay、@ResponseBody注解支持、数据绑定等支持。
具体的包:xmlns:mvc=“http://www.springframework.org/schema/mvc”
<mvc:annotation-driven/>
用于定义映射路径,建立请求url和控制层方法之间的对应关系。
如果去我们在这里定义了get,在页面上我们使用post请求那么就会出来403的错误。
注意:在登录的时候我们应该使用的是post请求,因为在登录的时候我们会输入相应的账号密码,然而在使用get的请求会把自己登录的账号密码暴露出来,安全性能不高。
将静态资源交给默认的DefaultServlet处理
springmvc不参与解析
常见的servlet容器如Tomcat、Jetty等都会有一个自带的DefaultServle来处理这些静态资源
在springMVC.xml配置文件中加如下配置:
<mvc:default-servlet-handler/>
指定静态资源的访问路径(灵活)
静态资源由springmvc自己来处理
mapping属性:用于映射资源的虚拟url(自定义的url)
location属性:用于指定静态资源的本地相对路径
例如:下面的配置中,当请求以page为开头的所有请求都会映射到static这个目录中去查找对应的静态资源文件
在springMVC.xml配置文件中加如下配置:
<mvc:resources mapping="/page/**" location="/static/"/>
内部资源:是放在WEB-INF目录下的,浏览器不能直接访问,但是可以通过转发机制进行访问内部资源。
表单中请求参数都是基于key=value的。SpringMVC绑定请求参数的过程是通过表单提交请求参数,作为控制器中的方法参数进行绑定的。
基本类型参数:包括基本类型和String。
POJO类似参数:包括实体类,以及关联的实体类。
数组和集合类型参数:包括List结构和Map结构的集合。
基本类型或是String类型:要求我们的参数名称必须和控制器中方法的形参名称保持一致。
POJO类型:要求表单中参数名称和POJO类的属性名称保持一致。并且控制器方法的参数类型是POJO类型。
集合类型:
第一种:要求集合类型的请求参数必须在POJO中。表单中请求参数名称要和POJO中集合属性名称相同。给List集合中的元素赋值,使用下标。给Map结合中的元素赋值,使用键值对。
第二种:接受的请求参数是json格式数据。需要借助一个注解实现。
注意:参数类型不能为List集合,可以为数组
(1)页面定义请求
<form action="../add2" method="post">
用户名:<input name="userName" type="text">
年龄:<input name="age" type="text">
<input type="submit" value="提交">
form>
(2)执行器方法绑定参数
@PostMapping("/add2")
public String test2(String userName,int age){
ModelAndView mav = new ModelAndView("index");
// 将参数保存到请求作用域中
mav.addObject("username", name);
mav.addObject("age", userAge);
return mav;
}
(3)把属性值渲染到页面中
用户名:${requestScope.username}
年 龄:${requestScope.age}
(1)页面定义请求
<form action="../add2" method="post">
用户名:<input name="userName" type="text">
年龄:<input name="age" type="text">
年龄:<input name="birth" type="text">
<input type="submit" value="提交">
form>
(2)执行器方法绑定参数
@PostMapping("/add2")
public ModelAndView add3(User user) {
ModelAndView mav = new ModelAndView("index");
// 将参数保存到请求作用域中
mav.addObject("username", user.getUsername());
mav.addObject("age", user.getAge());
mav.addObject("birth", user.getBirth());
return mav;
}
(1)页面定义请求
<form id="f1" method="post" action="../add3">
Name:<input type="text" name="username" value="admin"><br>
Age:<input type="text" name="age" value="20"><br>
Birth:<input type="text" name="birth" value="2002-12-24"><br>
Tel1:<input type="text" name="tel" value="19803201011"><br>
Tel2:<input type="text" name="tel" value="17683188311"><br>
Addr1:<input type="text" name="addresses[0].addr" value="广东广州"><br>
Addr2:<input type="text" name="addresses[1].addr" value="广东珠海"><br>
IdCard:<input type="text" name="card.cardNum" value="3546323243223546"><br>
<input type="submit" value="提交"><br>
form>
(2)执行器方法绑定参数
@PostMapping("/add3")
public ModelAndView add3(User user) {
ModelAndView mav = new ModelAndView("index");
// 将参数保存到请求作用域中
mav.addObject("username", user.getUsername());
mav.addObject("age", user.getAge());
mav.addObject("birth", user.getBirth());
mav.addObject("tel1", user.getTel().get(0));
mav.addObject("tel2", user.getTel().get(1));
mav.addObject("addr1", user.getAddresses().get(0).getAddr());
mav.addObject("addr2", user.getAddresses().get(1).getAddr());
mav.addObject("idCard", user.getCard().getCardNum());
return mav;
}
(1)页面定义请求
<form action="/hello/test5" >
爱好1:<input name="hobbies" type="text">
爱好2:<input name="hobbies" type="text">
爱好3:<input name="hobbies" type="text">
<input type="submit" value="提交">
form>
(2)执行器方法绑定参数
@RequestMapping("test5")
public String test5(String[] hobbies){
ModelAndView mav = new ModelAndView("index");
// 将参数保存到请求作用域中
for(String hobby:hobbies){
for (int i = 0; i < hobby.length(); i++) {
mav.addObject("hobbies"+i, s);
}
}
return mav;
}
请求格式:/url地址/{变量}
并且使用@PathVariable
注解来映射
@GetMapping("/user/{id}")
public ModelAndView getUser(@PathVariable("id") String uid) {
ModelAndView mav = new ModelAndView("index");
mav.addObject("uid", uid);
return mav;
}
@InitBinder
注解标注的方法会在执行任何controller的方法之前先执行,springmvc会传入一个WebBinder的参数,使用这个参数可以注册任意的Formatter(格式化器)。
时间类型转换器:
我们在这里定义了什么类型的时间格式,在页面中传递过来的日期时间格式也要与之保持一致。
/*
* binder:数据绑定器,用于注册各种格式类型化
*/
@InitBinder
public void regFormatter(WebDataBinder binder) {
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
}
Spring MVC 的模型绑定机制可以将请求参数自动绑定到控制器方法的参数或对象中,实现方便的数据传递和处理。它支持常见的数据类型,包括普通的 POJO 类、HttpSession 和 java.util.Map 等。
注意:Spring MVC 并不直接支持将 jdbc 的 Connection 对象作为控制器方法的参数绑定
(1)注解介绍
使用在方法入参位置,用于指定请求参数名称,将该请求参数绑定到注解参数位置
(2)属性
(3)注解使用案例
@PostMapping("/add2")
public ModelAndView add2(@RequestParam("username") String name,
@RequestParam("age") Integer userAge) {
ModelAndView mav = new ModelAndView("index");
// 将参数保存到请求作用域中
mav.addObject("username", name);
mav.addObject("age", userAge);
return mav;
}
路径参数绑定使用该注解来映射。
注意:请求格式:/url地址/{变量}
@GetMapping("/user/{id}")
public ModelAndView getUser(@PathVariable("id") String uid) {
ModelAndView mav = new ModelAndView("index");
mav.addObject("uid", uid);
return mav;
}
该注解标注的方法会在执行任何controller的方法之前先执行,springmvc会传入一个WebBinder的参数,使用这个参数可以注册任意的Formatter(格式化器)。
// 时间格式转换
@InitBinder
public void regFormatter(WebDataBinder binder) {
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
}
(1)注解介绍
用于方法入参位置,获取请求体内容。直接使用得到是key=value&key=value…结构的数据。get请求方式不适用。通常用于将json格式字符串绑定到bean对象中。
如果我们提交的不是规范的请求体格式而是一个json字符串,那么需要使用@RequestBody注解来映射。这样springmvc就会将json字符串反序列化为实体对象。
(2)注解使用案列
@PostMapping("/add")
public String add(@RequestBody User user) {
log.info(user.getUserName());
return "success";
}
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script src="js/jquery-1.8.2.js">script>
head>
<body>
<script>
$(function () {
// 构建一个js对象
let user = {'userName': 'user2'}
// 将js对象序列化为json字符串
user = JSON.stringify(user);
$.ajax({
url: '/add',
type: 'post',
data: user,
// 以json字符串提交时,需要将请求头的类型设置为application/json
contentType: 'application/json;charset=utf-8',
success: function (result) {
alert(result);
}
})
})
script>
body>
html>
返回值为字符串:用于指定返回的逻辑视图名称
@GetMapping("/forward2")
public String forward2() {
log.info("转发视图");
return "index";
}
void类型:通常使用原始servlet处理请求时,返回该类型
ModelAndView:用于绑定参数和指定返回视图名称
@PostMapping("/add2")
public ModelAndView add2(@RequestParam("username") String name,
@RequestParam("age") Integer userAge) {
ModelAndView mav = new ModelAndView("index");
// 将参数保存到请求作用域中
mav.addObject("username", name);
mav.addObject("age", userAge);
return mav;
}
表示将方法返回值以输出流(IO)的方法写回客户端,这样springmvc就会将序列化好的json数据放入响应体中并写回。
通俗的说:controller控制层中的方法返回值是一个实体的时候,我们需要使用到@ResponseBody注解,把页面中的需要传递的json对象反序列化为实体对象返回至客户端
@GetMapping("/user")
// @ResponseBody
public User getUser() {
User user = new User();
user.setUserName("user1");
return user;
}
@RestController注解是在spring4.0后新加入的一个注解, 同样用于标注为控制器的组件,如果当前Controller中所有请求方法都需要使用@ResponseBody注解来响应,那么就可以使用标注在类上,而不需要再内一个方法上标注@ResponseBody。
@RestController
public class UserController(){
@GetMapping("/getUser")
public User getUser(){
User user = new User();
// ...
return user;
}
}
@RestController 注解等价于@Controller 和 @ResponseBody 。
转发请求url,必须使用“forward:”前缀。
注意:转发视图是通过InternalResourceViewResolver解析器来完成的。而转发请求的url是一个地址,并被视图,因此需要加上forward:前缀,并且url的路径必须写完整。
@GetMapping("/forward3")
public String forward3() {
return "forward:forward2";
}
如果需要重定向一个视图或者请求地址可以结合“redirect:”这个前缀,并且重定向的地址和视图必须是完整的url路径,因为重定向是不会走内部资源视图解析器的。
重定向不能使用ModelAndView,因为ModelAndView是访问内部资源的,只能进行转发。
RedirectAttributes是springmvc3.1版本提供的一个类,注意用于在重定向时可以重写url,在重定向的url后面加入参数这样就可以变相在重定向的西苑中获取相关的参数信息
@GetMapping("/redirect2")
public String redirect2(RedirectAttributes attributes,Model model) {
model.addAttribute("name","zs");
attributes.addAttribute("name","zs");
return "redirect:redirect.jsp";
}
注意:重定向携带参数,需要使用对象RedirectAttributes,该对象提供两个方法封装参数 addAttribute()。
Hibernate Validator 的目标是提供一种方便、易于使用的验证机制,以确保数据的完整性和一致性。它可以用于验证表单输入、数据传输对象(DTO)、实体对象等各种类型的数据。
<dependency>
<groupId>org.hibernate.validatorgroupId>
<artifactId>hibernate-validatorartifactId>
<version>6.0.13.finalversion>
dependency>
不能为空,这里的空是指空字符串
不能为null
该字段的值只能大于或等于该值
该字段的值只能小于或等于该值
检查是否是一个有效的email地址
设置日期格式。
一般不建议使用。
@Valid注解标识的参数要参与Bean验证
第一:配置pom.xml文件添加依赖
<dependency>
<groupId>org.hibernate.validatorgroupId>
<artifactId>hibernate-validatorartifactId>
<version>6.0.13.finalversion>
dependency>
第二:编写web.xml文件
第三:编写dispatcher-servlet.xml文件配置资源信息以及配置校验器
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basenames">
<list>
<value>classpath:messagevalue>
list>
property>
<property name="defaultEncoding" value="UTF-8"/>
bean>
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
<property name="validationMessageSource" ref="messageSource"/>
bean>
第四:编写message.properties文件
user.userName.notEmpty = 请输入用户名
user.age.notNull = 请输入年龄
user.age.main = 年龄不得小于18岁
user.birth.notNull = 请填写出生年月日
user.email.notEmpty = 请填写邮箱地址
user.email.legal = 请填写合法的Email地址
第五:创建User类
@Data
public class User {
/**
* @NotEmpty注解表示内容不能为空字符串 验证空串
*/
@NotEmpty(message = "{user.userName.notEmpty}")
private String username;
/**
* 验证空值
*/
@NotNull(message = "{user.age.notNull}")
/**
* 最小值范围
*/
@Min(value = 18, message = "{user.age.main}")
private Integer age;
@NotNull(message = "{user.birth.notNull}")
/**
* 不推荐使用
*/
// @DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birth;
@NotEmpty(message = "{user.email.notEmpty}")
@Email(message = "{user.email.legal}")
private String email;
}
第六:创建ResultVO类响应数据
@Data
public class ResultVO<T> {
/**
* 响应状态码,默认200
*/
private Integer code = HttpStatus.OK.value();
private String message;
private T data;
}
第七:创建UserController
@RestController
public class UserController {
@InitBinder
public void refFormatter(WebDataBinder binder){
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
}
/**
* @param user
* @param result 这个结果集里面存放了验证信息,如果校验未通过就从这个结果集中获取验证信息
* @return
* @Valid注解标识的参数实体要参与Bean验证
*/
@PostMapping("/add")
public ResultVO add(@Valid User user, BindingResult result) throws JsonProcessingException {
ResultVO vo = new ResultVO();
// 先判断是否校验通过,如果存在错误消息则表示未通过
if (result.hasErrors()) {
Map<String, String> errors = new HashMap<>();
// 循环遍历所有的信息
result.getFieldErrors().forEach(fieldError -> {
// 以字段名作为key,错误信息作为value保存到map中
errors.put(fieldError.getField(), fieldError.getDefaultMessage());
});
// 设置响应状态码
vo.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
// 方式一:将map序列化成json字符串
// 因为我们在ResultVO中定义的message是String类型的
// 那么我们需要把map对象序列化成json字符串保存在message字符串中
String messages = new ObjectMapper().writeValueAsString(errors);
vo.setMessage(messages);
// 方式二:直接将errors保存到data中
// vo.setData(errors);
return vo;
}
return vo;
}
}
注意:BindingResult结果集里面存放了验证信息,如果校验未通过就可以从这个结果集中获取验证信息。
第八:编写页面测试验证组件
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script src="js/jquery-1.8.2.js">script>
head>
<body>
<h1>用户注册h1>
<div id="msg" style="color: red">div>
<form id="f1">
Name:<input type="text" name="username"/><br>
Age:<input type="text" name="age"/><br>
Birth:<input type="text" name="birth"/><br>
Email:<input type="text" name="email"/><br>
<input type="button" value="注册"/><br>
form>
<script>
$(function () {
$(':button').on('click', function () {
$.ajax({
url: '../add',
type: 'post',
data: $('#f1').serialize(),
success: function (result) {
if (result.code == 500) {
$('#msg').empty();
let errors = result.message;
alert(errors)
// 将字符串转换为json对象
errors = $.parseJSON(errors);
// 循环遍历错误消息放入div中
$.each(errors, function (key, val) {
$('#msg').append(val + '
')
})
} else if (result.code == 200) {
alert('注册成功!')
}
}
})
})
})
script>
body>
html>
json字符串:“{”username“:“zs”,“age”:“20”…}”
**json对象(js对象):[**object,object],是一个数组或者集合。展开后{username:“zs”,age:“20”…}
注意:在页面中传递数据的时候,我们是以请求体格式(username=zs&age=20…)进行传递的。如果不使用serialize()
这个属性进行序列化,那么我们传递的数据应该是我们传递的数据应该是{‘userName’:‘zs’,‘age’:20…}。
1)添加上传组件依赖
<dependency>
<groupId>commons-fileuploadgroupId>
<artifactId>commons-fileuploadartifactId>
<version>1.3.1version>
dependency>
2)编写xml文件装配上传附件解析器
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="104857600"/>
<property name="defaultEncoding" value="utf-8"/>
bean>
3)编写ProductVO类
商品vo对象,用于保存页面提交的数据,后续将这个vo拷贝到entity中
@Data
public class ProductVO {
/**
* 商品名称
*/
private String productName;
/**
* springmvc封装的上传附件对象
* 商品图片
*/
private MultipartFile[] file;
}
4)编写ResultVO响应数据
@Data
public class ResultVO<T> {
private Integer code = HttpStatus.OK.value();
private String message;
private T data;
}
5)编写ProductController实现上传功能
@Slf4j
@RestController
public class ProductController {
/**
* 添加商品,同时带有上传的附件
*
* @param productVO
* @return
*/
@PostMapping("/add")
public ResultVO add(ProductVO productVO) throws IOException {
ResultVO vo = new ResultVO();
// 获取上传的路径(绝对路径)
String uploadPath = "d://files//";
log.info(uploadPath);
// 根据路径构建上传的文件对象
File uploadFile = new File(uploadPath);
// 判断路径中的文件夹是否存在,不存在则创建出来
if (!uploadFile.exists()) {
// 将文件夹创建出来
uploadFile.mkdirs();
}
// 获取所有上传的附件 - 同时上传多个文件
MultipartFile[] file = productVO.getFile();
// 循环遍历
for (MultipartFile multipartFile : file) {
// 获取文件名
String filename = multipartFile.getOriginalFilename();
// 执行上传
Path path = FileSystems.getDefault().getPath(uploadFile.getAbsolutePath(), filename);
multipartFile.transferTo(path);
}
return vo;
}
}
6)编写product.html页面发起请求
需要把form表单的类型改为multipart/form-data
<form id="f1" enctype="multipart/form-data">form>
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script src="js/jquery-1.8.2.js">script>
head>
<body>
<h1>添加商品h1>
<form id="f1" enctype="multipart/form-data">
Name:<input type="text" name="productName"><br>
Image:<input type="file" name="file" multiple><br>
<input type="button" value="submit">
form>
<script>
$(function () {
$(':button').on('click', function () {
// FormData对象是HTML5的内置对象,用于处理form表单的数据
// 序列化只能对文本有效,而formData对象可以处理文本和二进制数据。
let formData = new FormData($('#f1')[0]);
$.ajax({
url: '../add',
type: 'post',
data: formData,
// 不设置这个属性的时候,processData默认是true,在执行时,jQuery会根据自己默认的格式去处理这些数据,默认处理的数据不会根据我们所定义的formData对象去执行
processData: false, // 告诉jQuery不要处理发送的数据类型
contentType: false, // 告诉jQuery不要设置请求头的content-Type
success: function (result) {
if (result.code == 200) {
alert('上传成功');
}
}
})
})
})
script>
body>
html>
注意:FormData对象是HTML5的内置对象,用于处理form表单的数据。然而序列化只能对文本有效,而formData对象可以处理文本和二进制数据。
/**
* 文件下载
*
* @param fileName
* @return
*/
@GetMapping ("/download/{fileName}")
public ResponseEntity<InputStreamResource> download(@PathVariable("fileName") String fileName) throws Exception {
// 文件下载路径(也是上传路径)
// String downloadPath = "d:" +File.separator + "files" + File.separator + fileName;
String downloadPath = "d://files//" + fileName;
// 构建一个文件输入流读取服务器上的文件
FileInputStream fis = new FileInputStream(downloadPath);
// 设置响应头,告诉浏览器响应流数据
HttpHeaders headers = new HttpHeaders();
// 对文件名进行编码,防止在响应头中出现乱码
fileName = URLEncoder.encode(fileName, "UTF-8");
// 设置头信息,将响应内容处理的方式设置为附件下载 - 显示下载框进行下载
headers.setContentDispositionFormData("attachment", fileName);
// 设置响应类型为流类型
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
// 创建InputStreamResource对象封装输入流,用于读取服务器文件
InputStreamResource isr = new InputStreamResource(fis);
// 创建ResponseEntity对象(封装InputStreamResource、响应头、响应状态码)
ResponseEntity<InputStreamResource> entity = new ResponseEntity<>(isr, headers, HttpStatus.CREATED);
return entity;
}
1)下载minio.exe文件
(https://min.io/download)
2)启动minio服务
1、进入 minio.exe 存放目录(D:\minio\data 为你存放静态文件的目录)
cd D:/minio/
2、执行命令
minio.exe server D:\minio\data --console-address ":9001"
3)添加minio依赖
<dependency>
<groupId>io.miniogroupId>
<artifactId>minioartifactId>
<version>8.4.3version>
dependency>
4)自定义MinioClient工厂
@Slf4j
@Setter
public class MinioFactoryBean implements FactoryBean<MinioClient> {
/**
* 注入相关的配置属性
* 访问的URL地址
*/
private String url;
/**
* 账号
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 桶
*/
private String bucket;
/**
* 创建Bean实例MinioClient
* @return
* @throws Exception
*/
@Override
public MinioClient getObject() throws Exception {
MinioClient minioClient = MinioClient.builder()
.endpoint(url)
.credentials(username, password)
.build();
//初始化桶
initBucket(minioClient);
log.info("已初始化桶:" + bucket);
return minioClient;
}
/**
* 返回Bean实例的Class对象
* @return
*/
@Override
public Class<?> getObjectType() {
return MinioClient.class;
}
/**
* 初始化桶 - 创建桶
* @param minioClient
* @throws Exception
*/
private void initBucket(MinioClient minioClient) throws Exception{
// 先判断桶是否存在,不存在则创建
if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucket).build())) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucket).build());
}
}
}
5)编写xml文件
<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="edu.nf.ch08"/>
<mvc:annotation-driven/>
<mvc:default-servlet-handler/>
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="104857600"/>
<property name="defaultEncoding" value="utf-8"/>
bean>
<bean id="minioClient" class="edu.nf.ch08.factory.MinioFactoryBean">
<property name="url" value="http://127.0.0.1:9000"/>
<property name="username" value="minioadmin"/>
<property name="password" value="minioadmin"/>
<property name="bucket" value="myapp"/>
bean>
beans>
6)编写ResultVO类响应数据
@Data
public class ResultVO<T> {
/**
* 响应编码:默认是200
*/
private Integer code = HttpStatus.OK.value();
/**
* 响应的内容
*/
private String message;
/**
* 响应的数据
*/
private T data;
}
7)编写ItemVO类封装文件信息(文件名、文件大小)
@Data
public class ItemVO {
/**
* 文件大小
*/
private String fileSize;
/**
* 文件名
*/
private String fileName;
}
8)编写MinioController控制层实现上传和下载功能
@RestController
@RequiredArgsConstructor
public class MinioController {
/**
* 注入MinioClient
*/
private final MinioClient minioClient;
/**
* 文件上传
*
* @param path
* @param files
* @return
* @throws Exception
*/
@PostMapping("/upload/{path}")
public ResultVO upload(@PathVariable("path") String path, MultipartFile[] files) throws Exception {
for (MultipartFile file : files) {
// 获取文件名
String fileName = file.getOriginalFilename();
// 获取文件的输入流
InputStream inputStream = file.getInputStream();
minioClient.putObject(PutObjectArgs.builder()
.bucket("files")
// 远程上传的路径 - 目录/文件名
.object(path + "/" + fileName)
// file.getSize():文件的大小
// 设置一个输入流,-1表示读到文件的末尾(文件有大多就会上传多大)
.stream(inputStream, file.getSize(), -1)
// 文件的类型
.contentType(file.getContentType())
.build());
}
ResultVO vo = new ResultVO();
return vo;
}
/**
* 文件下载
*
* @param path 远程文件夹
* @param fileName 文件名
* @return
*/
@GetMapping("/download/{path}/{fileName}")
public ResponseEntity<InputStreamSource> download(@PathVariable("path") String path, @PathVariable("fileName") String fileName) throws Exception {
// 根据文件名从minio中获取一个远程的输入流
InputStream inputStream = minioClient.getObject(GetObjectArgs.builder()
.bucket("files")
.object(path + "/" + fileName)
.build());
// 设置响应头
HttpHeaders headers = new HttpHeaders();
// 对文件名进行编码,防止在响应头中出现乱码
fileName = URLEncoder.encode(fileName, "UTF-8");
// 设置头信息,将响应内容处理的方式设置为附件下载
headers.setContentDispositionFormData("attachment", fileName);
// 设置响应类型为数据流类型
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
// 创建InputStreamResource对象封装输入流,用于读取服务器文件
InputStreamResource isr = new InputStreamResource(inputStream);
// 创建ResponseEntity对象(封装InputStreamResource、响应头、响应状态码)
ResponseEntity<InputStreamSource> entity = new ResponseEntity<>(isr, headers, HttpStatus.CREATED);
return entity;
}
/**
* 根据路径目录查询文件列表
*
* @param path
* @return
*/
@GetMapping("/list/{path}")
public ResultVO<List<ItemVO>> listFiles(@PathVariable("path") String path) {
List<ItemVO> list = new ArrayList<>();
// 使用minio获取列表
minioClient.listObjects(ListObjectsArgs.builder()
.bucket("files")
.prefix(path + "/")
.build())
// 遍历集合,每一个itemResult都封装了一个文件
.forEach(itemResult -> {
try {
Item item = itemResult.get();
// 获取文件大小
long size = item.size();
// 获取文件大小,把item.size()转换成字符串
// BigDecimal bigDecimal = new BigDecimal(String.valueOf(item.size()));
// 除1024
// BigDecimal divide = bigDecimal.divide(new BigDecimal("1024"));
String fileSize = getPrintSize(size);
// 获取文件名
String fileName = item.objectName();
// 去除前缀
fileName = StringUtils.getFilename(fileName);
// 把文件封装在ItemResultVO中,循环遍历出文件大小以及文件名
ItemVO itemVO = new ItemVO();
itemVO.setFileSize(fileSize);
itemVO.setFileName(fileName);
list.add(itemVO);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
ResultVO vo = new ResultVO<>();
vo.setData(list);
return vo;
}
public static String getPrintSize(long size) {
// 如果字节数少于1024,则直接以B为单位,否则先除于1024,后3位因太少无意义
double value = (double) size;
if (value < 1024) {
return String.valueOf(value) + "B";
} else {
value = new BigDecimal(value / 1024).setScale(2, BigDecimal.ROUND_DOWN).doubleValue();
}
// 如果原字节数除于1024之后,少于1024,则可以直接以KB作为单位
// 因为还没有到达要使用另一个单位的时候
// 接下去以此类推
if (value < 1024) {
return String.valueOf(value) + "KB";
} else {
value = new BigDecimal(value / 1024).setScale(2, BigDecimal.ROUND_DOWN).doubleValue();
}
if (value < 1024) {
return String.valueOf(value) + "MB";
} else {
// 否则如果要以GB为单位的,先除于1024再作同样的处理
value = new BigDecimal(value / 1024).setScale(2, BigDecimal.ROUND_DOWN).doubleValue();
return String.valueOf(value) + "GB";
}
}
9)编写页面进行测试
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script src="js/jquery-1.8.2.js">script>
head>
<body>
<h1>文件上传h1>
<form id="f1" enctype="multipart/form-data">
文件:<input type="file" name="files" multiple/>
<input type="button" value="上传">
form>
<table id="list" border="1" width="25%" cellpadding="0" cellspacing="0">
<thead>
<tr>
<th>文件名称th>
<th>大小th>
tr>
table>
<script>
// 查询文件列表
function listFiles(path) {
$.ajax({
url: '../list/' + path,
type: 'get',
success: function (result) {
if (result.code == 200) {
// 清空表格
// $('#myTable tbody').empty();
$('#list td').remove();
$.each(result.data, function (index, obj) {
let row = `
${obj.fileName}
${obj.fileSize}
`;
$('#list').append(row);
})
}
}
})
}
$(function () {
// 页面加载时,查询一次列表
listFiles('images')
$(':button').on('click', function () {
let formData = new FormData($('#f1')[0]);
$.ajax({
url: '../upload/images',
type: 'post',
data: formData,
processData: false,
contentType: false,
success: function (result) {
if (result.code == 200) {
// 上传成功后,再查询一次列表
listFiles('images')
} else {
alert('上传失败');
}
}
})
})
})
script>
body>
html>
@ExceptionHandler注解标注的方法专门用于处理请求方法产生的异常,value属性指定要处理的异常类型
注意:这个局部异常的方法只在当前Controller中有效
1)自定义异常
public class AuthException extends RuntimeException {
/**
* 异常的状态码
*/
private Integer errorCode;
public AuthException(Integer errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
/**
* 提供一个获取错误码的方法
*
* @return
*/
public Integer getErrorCode() {
return errorCode;
}
}
2)编写User类
@Data
public class User {
private String userName;
private String password;
}
3)编写dao以及dao实现类
public interface UserDao {
User getUser(String username);
}
@Repository
@Slf4j
public class UserDaoImpl implements UserDao {
@Override
public User getUser(String username) {
log.info("select * from user_info");
User user = new User();
user.setUserName(username);
user.setPassword("123456");
return user;
}
}
4)编写service以及实体类
public interface LoginService {
/**
* 验证用户
* @param userName
* @param password
* @return
*/
User auth(String userName, String password);
}
@Slf4j
@Service
@RequiredArgsConstructor
public class LoginServiceImpl implements LoginService {
/**
* 注入DAO
*/
private final UserDao userDao;
@Override
public User auth(String userName, String password) {
User user = userDao.getUser(userName);
// 用户不为null则校验密码
if (user != null){
if (password.equals(user.getPassword())){
return user;
}
}
// 抛出业务异常
throw new AuthException(10001,"账号或密码错误");
}
}
5)编写控制层
@Slf4j
@RestController
@RequiredArgsConstructor
public class UserController {
private final LoginService service;
/**
* 局部的异常处理(处理登录业务异常)
*
* @param e
* @return
* @ExceptionHandler注解标注的方法专门用于处理请求方法产生的异常,value属性指定要处理的异常类型
* 注意:这个局部异常的方法只在当前Controller中有效
*/
@ExceptionHandler(AuthException.class)
public ResultVO handleAuthException(AuthException e) {
// 验证未通过则创建提示信息
ResultVO vo = new ResultVO();
vo.setCode(e.getErrorCode());
vo.setMessage(e.getMessage());
return vo;
}
/**
* 局部的异常处理(处理非业务异常,如数据库异常等)
* @param
* @return
*/
@ExceptionHandler(RuntimeException.class)
public ResultVO handleRuntimeException(RuntimeException e) {
// 记录异常信日志
log.error(e.getMessage());
// 再将异常转换为提示信息
ResultVO vo = new ResultVO();
vo.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
vo.setMessage("服务器内部错误,请稍后尝试");
return vo;
}
@PostMapping("/auth")
public ResultVO login(String userName, String password, HttpSession session) {
User user = service.auth(userName, password);
// 将user保存到会话中
session.setAttribute("user", user);
return new ResultVO();
}
}
定义一个全局异常处理类(类似一个切面),这个类中定义所有的异常处理方法,也可以理解为全局异常通知
用于指定要捕获异常的类,对应@Controller注解的类
@RestControllerAdvice("edu.nf.ch09.controller")
public class ExceptionAdvice {
}
用于指定要捕获异常的类,对应@RestController注解的类
// @ControllerAdvice注解(对应@Controller注解的类)
// @RestControllerAdvice(对应@RestController注解的类)
@Slf4j
// value属性指定需要捕获异常的类
@RestControllerAdvice("edu.nf.ch09.controller")
public class ExceptionAdvice {
/**
* 全局异常处理方法(处理登录分业务异常)
*
* @param e
* @return
*/
@ExceptionHandler(AuthException.class)
public ResultVO handleAuthException(AuthException e) {
// 验证未通过则创建提示信息
ResultVO vo = new ResultVO();
vo.setCode(e.getErrorCode());
vo.setMessage(e.getMessage());
return vo;
}
/**
* 全局的异常处理(处理非业务异常,如数据库异常等)
*
* @param e
* @return
*/
@ExceptionHandler(RuntimeException.class)
public ResultVO handleRuntimeException(RuntimeException e) {
// 记录异常信日志
log.error(e.getMessage());
// 再将异常转换为提示信息
ResultVO vo = new ResultVO();
vo.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
vo.setMessage("服务器内部错误,请稍后尝试");
return vo;
}
}
Spring拦截器是一种基于AOP的技术,本质也是使用一种代理技术,它主要作用于接口请求中的控制器,也就是Controller。因此它可以用于对接口进行权限验证控制。
案列:用户认证
1)创建拦截器,实现HandlerInterceptor接口
拦截所有的请求,如果未登录则返回401状态码
404找不到请求的资源、500服务器错误、200成功、401未认证、403用户没有权限
@Slf4j
public class AuthInterceptor implements HandlerInterceptor {
/**
* 在调用Controller请求方法之前执行,
* 如果此方法返回false,则请求就不会继续往下执行
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("执行preHandle方法");
HttpSession session = request.getSession();;
// 如果session为null表示用户未登录
if (session.getAttribute("user") == null){
ResultVO vo = new ResultVO();
// 设置401的状态码
vo.setCode(HttpStatus.UNAUTHORIZED.value());
response.setContentType("application/json;charset=utf-8");
// 把json序列化为字符串
String json = new ObjectMapper().writeValueAsString(vo);
response.getWriter().println(json);
return false;
}
return true;
}
/**
* 在调用Controller请求放方法之后,返回(返回值)之前执行
* 注意:只要在preHandle方法返回true的情况下才会执行
*
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("执行postHandle方法");
}
/**
* 在调用Controller方法并返回之后执行
* 注意:只要在preHandle方法返回true的情况下才会执行
*
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
2)配置拦截栈
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/static/login.html"/>
<mvc:exclude-mapping path="/auth"/>
<mvc:exclude-mapping path="/static/js/**"/>
<bean class="edu.nf.ch10.interceptor.AuthInterceptor"/>
mvc:interceptor>
mvc:interceptors>
3)编写实体User类
@Data
public class User {
private String userName;
private String password;
}
4)编写LoginService接口
public interface LoginService {
/**
* 用户认证
* @param userName
* @param password
* @return
*/
User auth(String userName, String password);
}
5)编写实现类
@Service
public class UserServiceImpl implements LoginService {
@Override
public User auth(String userName, String password) {
if ("zs".equals(userName) && "123456".equals(password)){
User user = new User();
user.setUserName(userName);
user.setPassword(password);
return user;
}
throw new RuntimeException("账号或密码错误");
}
}
6)编写ResultVO响应数据
@Data
public class ResultVO<T> {
private Integer code = HttpStatus.OK.value();
private String message;
private T data;
}
7)编写Controller类
@RestController
@RequiredArgsConstructor
public class UserController {
private final LoginService service;
@PostMapping("/auth")
public ResultVO login(String userName, String password, HttpSession session) {
User user = service.auth(userName, password);
session.setAttribute("user", user);
return new ResultVO();
}
}
8)编写login.html进行测试
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script src="js/jquery-1.8.2.js">script>
head>
<body>
<h1>用户登录h1>
<form id="f1">
账号:<input type="text" name="userName"><br>
密码:<input type="password" name="password"><br>
<input type="button" value="登录">
form>
<script>
$(function () {
$(':button').on('click', function () {
let formData = $('#f1').serialize();
$.ajax({
url: '../auth',
type: 'post',
data: formData,
success: function (result) {
if (result.code == 200) {
location.href = 'index.html';
} else {
alert("登录失败")
}
}
})
})
})
script>
body>
html>
在我们使用springmvc时,每次都要去配置web.xml,dispatcher-servlet.xml,甚至和spring整合时候,还要配置spring.xml,用起来比较麻烦。
1)通过注解@Configuration声明此类为配置类
2)通过注解@ComponentScan启动包扫描
等效于
3)通过注解@EnableWebMvc启用Spring MVC,并实现WebMvcConfigurer接口
等效于
4)静态资源的处理
方式一:使用容器默认的servlet来处理,在配置类中重写configureDefaultServletHandling方法。
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
// enable()方法表示启用默认servlet处理静态资源
configurer.enable();
}
方式二:由springmvc处理静态依赖,在配置类中重写addResourceHandlers方法。
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/page/**").addResourceLocations("/static/");
}
5)装配Bean的验证器
@Override
public Validator getValidator() {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
// 使用Hibernate框架提供的Bean验证
factoryBean.setProviderClass(HibernateValidator.class);
// 指定资源文件
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:message");
messageSource.setDefaultEncoding("UTF-8");
// 装配给factoryBean
factoryBean.setValidationMessageSource(messageSource);
return factoryBean;
}
6)装配上传解析器commons-upload
@Bean
public CommonsMultipartResolver multipartResolver(CommonsMultipartResolver resolver) {
// 设置文件上传的大小
resolver.setMaxUploadSize(104857600);
// 设置编码
resolver.setDefaultEncoding("UTF-8");
return resolver;
}
7)装配拦截器,在配置类中重写addInterceptors方法。
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/static/login.html","/static/js/**","/static/css/**","/add");
}
8)跨域配置
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("*")
.allowedHeaders("*")
.exposedHeaders("*");
// 跨域时是否允许传递cooking,默认是不允许的
// .allowCredentials(true);
}
9)配置视图解析器(内部资源视图的转发)
@Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/jsp");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
通过以上步骤,就可以实现Spring MVC的零XML配置。这种方式可以减少配置文件的数量,提高开发效率,同时也更加符合现代化的开发理念。