SpringMVC 高级属性
SpringMvc与JSON:
1. JS对象与JSON转换:
1 2 3 4 5 6 7
| let o = {name:"test",age:18,pass:111};
let json = JSON.stringify(o); console.log(json);
let obj = JSON.parse(json); console.log(obj);
|
2. 了解 Jackson:
1. 导入依赖:
1 2 3 4 5
| <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.11.2</version> </dependency>
|
2. @ResponseBody:
- SpringMvc 中导入 Jackson 使用 @ResponseBody 标识的方法的返回值是一个对象,那么 Jackson 会自动将该对象转为 json 字符串并返回。
- @ResponseBody 标注的类则此类中所有的方法都返回 JSON 字符串。
1 2 3 4 5 6 7 8 9
| @RequestMapping(value="/hello",produces="application/json;charset=utf-8") @ResponseBody public String hello() throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); return mapper.writeValueAsString(new User("test",111)); }
|
- 对于专返回 json 字符串的 controller 类可直接在类上标注
@RestController
注解,此注解是 @Controller
与 @ResponseBody
的结合。
3. JSON编码统一处理:
- 在 SpringMvc 配置文件中对返回的字符串进行编码设置:省去对每一个方法设置编码的步骤
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <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>
|
4. @RequestBody:
- @RequestBody 注解能获取到本次请求的 请求体 ,get没有请求体所以会获取到空。
- 如果请求体是一个对象的 JSON 字符串,它能将此 JSON 映射到对应的对象上,需要为 ajax 请求中设置
contentType: "application/json"
表示内容格式为 JSON 字符串类型。
1 2 3 4
| @RequestMapping("/hello") public void hello(@RequestBody User user){ System.out.println(user); }
|
HttpEntity:
- 方法中还能使用一个参数
HttpEntity
它能同时拿到 请求体和请求头 的数据:
1 2 3 4
| @RequestMapping("/hello") public String hello(HttpEntity<String> str) { System.out.println(str); }
|
ResponseEntity:
- 方法的返回值可以是
ResponseEntity
它可以 返回响应体的同时设置响应头 :
1 2 3 4 5 6 7 8
| @RequestMapping("/test") public ResponseEntity<String> test() { MultiValueMap<String, String> headers = new HttpHeaders(); headers.add("Set-Cookie","user=root"); return new ResponseEntity<String>("{a:1}", headers, HttpStatus.OK); }
|
5. 时间对象(Date):
- SpringMvc 中默认 Date 对象转换为时间戳,可以通过设置 ObjectMapper 时间格式处理返回的 Date 对象:
1 2 3 4 5 6 7 8 9 10
| ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
mapper.setDateFormat(sdf);
return mapper.writeValueAsString(new Date());
|
- pojo 类成员上使用注解方式指定时间格式(推荐),每次 Jackson 将 pojo 类转为 JSON 时都会按照你在成员上方注解的规则进行转化:
1 2 3 4 5
| @JsonIgnore private int pass;
@JsonFormat(pattern="yyyy-MM-dd") private Date date;
|
6. 使用 Jackson:
- jackson 对象:
1
| ObjectMapper mapper = new ObjectMapper();
|
- java对象序列化为json:
1 2 3 4
| String json = mapper.writeValueAsString(new User());
mapper.writeValue(new File("json/js.json"),new User());
|
- json反序列化为java对象:
1 2 3 4
| User user = mapper.readValue(json, User.class);
User user2 = mapper.readValue(new File("json/js.json"), User.class);
|
- 通过
TypeReference
反序列化为集合(list,map):
1 2 3 4 5 6
| ArrayList<String> list = mapper.readValue(json,new TypeReference<ArrayList<String>>(){}); HashMap<String, Object> map = mapper.readValue(json,new TypeReference<HashMap<String, Object>>(){});
|
- JsonNode 对象读取json:
1 2 3 4 5 6 7 8
| JsonNode jsonNode = mapper.readTree(json);
JsonNode name = jsonNode.get("list");
String s = name.toString();
String s1 = name.asText();
|
3. 了解 fastjson:
- 导入依赖,依赖地址:
1 2 3 4 5
| <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.73</version> </dependency>
|
- fastjson常用方法:
fastjson 三个主要的类:JSONObject 代表 json 对象 ,JSONArray 代表 json 对象数组,JSON代表 JSONObject和JSONArray的转化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| User user1 = new User("秦疆1号", 3); User user2 = new User("秦疆2号", 3); List<User> list = new ArrayList<>(); list.add(user1); list.add(user2);
String str1 = JSON.toJSONString(list); String str2 = JSON.toJSONString(user1);
User jp_user1=JSON.parseObject(str2,User.class);
JSONObject jsonObject1 = (JSONObject) JSON.toJSON(user2);
jsonObject1.getString("name");
User to_java_user = JSON.toJavaObject(jsonObject1, User.class);
|
数据校验:
Java 标准提案第 303 条规定了数据校验的标准形成了 JSR303 ,它是为 Java Bean 数据合法性校验提供的标准框架已经包含在 Java EE 6.0 中。
Hibernate Validator 是 JSR303 的一种参考实现,除 jsr 标准注解外它还支持额外的一些注解:
1 2 3 4 5 6
| <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>6.1.0.Final</version> </dependency>
|
- 常用注解(所有注解都有一个message属性,自定义错误信息):
Constraint |
详细信息 |
作用类型 |
@Null |
元素必须为 null |
引用类型 |
@NotNull |
元素必须不为 null |
引用类型 |
@AssertTrue |
元素必须为 true |
boolean |
@AssertFalse |
元素必须为 false |
boolean |
@Min(value) |
必须是一个数字,其值必须大于等于指定的最小值 |
byte、short、int、long及对应的包装类型以及BigDecimal、BigInteger |
@Max(value) |
必须是一个数字,其值必须小于等于指定的最大值 |
byte、short、int、long及对应的包装类型以及BigDecimal、BigInteger |
@DecimalMin(value) |
必须是一个数字,其值必须大于等于指定的最小值 |
byte、short、int、long及对应的包装类型以及BigDecimal、BigInteger、String |
@DecimalMax(value) |
必须是一个数字,其值必须小于等于指定的最大值 |
byte、short、int、long及对应的包装类型以及BigDecimal、BigInteger、String |
@Size(max, min) |
大小必须在指定的范围内 |
String、Collection、Map和数组 |
@Digits (integer, fraction) |
必须是一个数字,其值必须在可接受的范围内 |
byte、short、int、long及各自的包装类型以及BigDecimal、BigInteger、String |
@Past |
必须是一个过去的日期 |
java.util.Date,java.util.Calendar |
@Future |
必须是一个将来的日期 |
java.util.Date,java.util.Calendar |
@Pattern(regex=) |
必须符合指定的正则表达式 |
String |
@Valid |
需要递归验证 |
引用对象 |
以下是Hibernate Validator新增的 |
|
|
@Email |
必须是电子邮箱地址 |
String |
@Length(min=下限, max=上限) |
字符串的大小必须在指定的范围内 |
String |
@NotEmpty |
必须非空并且size大于0 |
String、Collection、Map和数组 |
@NotBlank |
必须不为空且不能全部为’ ‘(空字符串) |
String |
@Range(min=最小值, max=最大值) |
必须在合适的范围内 |
byte、short、int、long及各自的包装类型以及BigDecimal、BigInteger、String |
- 为 pojo 类需要的属性添加相应校验注解后,SpringMvc 请求方法中在使用到该 pojo 的变量前使用注解声明该 pojo 自动封装为对象时需要校验:
1 2 3 4 5 6 7 8 9 10 11 12
| @RequestMapping("hello")
public String hello(@Valid User user, BindingResult result){ result.hasErrors(); List<ObjectError> allErrors =result.getAllErrors(); for (ObjectError allError : allErrors) { allError.getDefaultMessage(); allError.getObjectName(); } return "hello"; }
|
注意:校验的 pojo 类后需要紧跟一个 BindingResult
来接收前一个参数(pojo类)的校验结果,中间不能有其它的参数。
拦截器:
SpringMVC 的拦截器类似于 Servlet 中的过滤器 Filter ,区别在于 拦截器是AOP思想的具体应用。
- 自定义拦截器必须继承 HandlerInterceptor 接口。源码如下:
1 2 3 4 5 6 7 8 9 10 11 12
| public interface HandlerInterceptor { default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { } }
|
- 自定义拦截器后需要在spring-mvc.xml中配置拦截器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="ruoxijun.config.MyInterceptor"/> </mvc:interceptor> </mvc:interceptors>
|
- 拦截器可用于用户的登录等权限管理操作,常搭配 Session 与 Cookie 一同使用(详情见javawab章)。
- mvc单拦截器运行流程:
- preHandle:目标方法运行前
- 目标方法执行
- postHandle:目标方法运行后
- 页面加载完成
- afterCompletion:请求完成后
- mvc多拦截器运行流程 :(如有两个拦截器,且1配置在2之前)
- preHandle1 -> preHandle2 -> 后目标方法运行
- postHandle2 -> postHandle1 -> 后页面加载
- afterCompletion2 -> afterCompletion1
异常处理:
SpringMvc 中也能使用 SimpleMappingExceptionResolver
在配置文件中配置异常处理。
1. @ExceptionHandler:
controller 类中可以使用 @ExceptionHandler
注解来处理该 controller发生的异常,它标注在一个方法上且 value 属性值为需要拦截的异常类数组。
1 2 3 4 5
| @ExceptionHandler({Exception.class}) public String err(Exception exception){ return "err"; }
|
- 该方法参数只能是异常类为参数来接收异常,返回值与请求方法类似(可返回
ModelAndView
)
- 当一个 controller 中有多个 @ExceptionHandler 标注的方法,发生异常时异常会匹配更精确的那个方法
2. @ControllerAdvice:
被 @ControllerAdvice
标注的类被自动添加到 spring 的 ioc 容器中,该类中被 @ExceptionHandler
标注的方法就是全局异常处理方法,同时这个类也被称之为全局异常处理类。
1 2 3 4 5 6 7
| @ControllerAdvice public class Err { @ExceptionHandler(Exception.class) public String err(){ return "err"; } }
|
- controller 异常处理方法和全局异常处理类同时存在时,优先使用 controller 本类中的存在的异常处理方法。
3. @ResponseStatus:
自定义异常类注解 @ResponseStatus
当发生该类异常时会来到一个 SpringMvc 异常显示界面,当存在异常处理方法拦截到该异常时将由异常处理方法处理,不再去 SpringMvc 的异常显示界面。
1 2 3
| @ResponseStatus(reason = "自定义异常发生" , code = HttpStatus.NOT_ACCEPTABLE) public class MyException extends RuntimeException { }
|
文件上传与下载:
文件上传注意事项:
- 为保证服务器安全,上传文件应放在外界无法访问的目录下,如WEB-INF目录。
- 为防止文件覆盖现象应为上传文件产生一个唯一文件名。
- 限制上传文件的最大值,限制上传文件的类型。
上传文件具体实现:
1. 添加依赖:
添加commons-fileupload
依赖,maven会自动导入它的依赖包commons-io
。
1 2 3 4 5 6 7
|
<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.4</version> </dependency>
|
2. 表单配置:
- form 属性
method
需要设置为 post
,get
不能传输文件,但会接收到文件名。
- form 属性
enctype
设置为 multipart/form-data
(文件传输必须设置,表示已二进制流方式处理表单数据)
- input 的
type
设置为 file
1 2 3 4
| <form action="/download" method="post" enctype="multipart/form-data"> <input type="file" name="file" /> <input type="submit" value="上传" /> </form>
|
3. 配置文件上传解析器:
SpringMvc 提供了即插即用文件上传解析器 MultipartResolver(CommonsMultipartResolver类) :
1 2 3 4 5 6 7 8
| <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="defaultEncoding" value="utf-8"/> <property name="maxUploadSize" value="#{1024*1024*10}"/> <property name="maxInMemorySize" value="40960"/> </bean>
|
4. 文件上传:
- CommonsMultipartFile 的 常用方法:
String getOriginalFilename():获取上传文件的原名
InputStream getInputStream():获取文件流
void transferTo(File dest):将上传文件保存到一个目录文件中
- 使用流(getInputStream)获取保存文件:
SpringMvc 将上传的文件信息都封装在了 CommonsMultipartFile
中,在使用文件上传时一般我们只需在接收上传文件的方法参数中添加它的父类 MultipartFile
参数来接收上传的文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| @RequestMapping(value = "/file") public String file(@RequestPart(value = "file")CommonsMultipartFile file, HttpServletRequest request) throws IOException { String name = file.getName(); String fileName = file.getOriginalFilename(); File path = new File(request.getServletContext().getRealPath("/file")); if (!path.exists()){ path.mkdir(); } InputStream in = file.getInputStream(); File filePath = new File(path,fileName); OutputStream out = new FileOutputStream(filePath); int len = 0; byte[] buffer = new byte[1024]; while ((len = in.read(buffer))!=-1){ out.write(buffer,0,len); out.flush(); } out.close(); in.close(); return "redirect:/"; }
|
- 使用 **transferTo ** 方法保存文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @RequestMapping(value = "/file") public String file(@RequestPart(value = "file")CommonsMultipartFile file, HttpServletRequest request) throws IOException { String fileName = file.getOriginalFilename(); File path = new File(request.getServletContext().getRealPath("/file")); if (!path.exists()){ path.mkdir(); } File filePath = new File(path,fileName); file.transferTo(filePath); return "redirect:/"; }
|
5. 多文件上传:
SpringMvc 多文件上传时将每一个文件的信息都封装在了 MultipartFile
最终将它们组成了一个 MultipartFile[]
的数组,我们只需在接收上传文件的方法中添加此数组参数即可接收上传的文件。
HTML中表单设置:
1 2 3 4 5 6 7 8 9
| <form action="${pageContext.request.contextPath}/upload" method="post" enctype="multipart/form-data" > <%-- 文件控件同名 --%> <input type="file" name="file" /> <input type="file" name="file" /> <%-- 不同 name 需要分别处理 --%> <input type="file" name="file2" /> <input type="submit" value="上传" /> </form>
|
controller中:
1 2 3 4 5 6 7 8 9 10 11 12 13
| @ResponseBody @RequestMapping("/upload") public String upload(@RequestPart("file") MultipartFile[] file, @RequestPart("file2") MultipartFile file2) throws IOException { for (MultipartFile multipartFile : file) { if (!multipartFile.isEmpty()){ multipartFile.transferTo(new File("E:\\", multipartFile.getOriginalFilename())); } } return "ok"; }
|
@RequestPart:
@RequestPart 适用于复杂的请求域(像JSON,XML),既可以接收 JSON 字符串并封装为对象又可以接收二进制数据流(multipart/form-data)。
文件下载:
文件下载注意配置 response 响应头和一些必要配置,都是一些固定设置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @RequestMapping(value = "/download") public String file(HttpServletRequest request, HttpServletResponse response) throws IOException { response.reset(); response.setCharacterEncoding("utf-8"); response.setContentType("multipart/form-data"); response.setHeader("Content-Disposition","attachment;fileName="+ URLEncoder.encode("wallhaven-eyg6l8.jpg","utf-8")); File path = new File(request.getServletContext().getRealPath("/file")); File filePath = new File(path,"wallhaven-eyg6l8.jpg"); InputStream in = new FileInputStream(filePath); OutputStream out = response.getOutputStream(); int len = 0; byte[] buffer = new byte[1024]; while ((len = in.read(buffer))!=-1){ out.write(buffer,0,len); out.flush(); } out.close(); in.close(); return "redirect:/"; }
|
SpringMvc 运行流程:
- 发起请求,前端控制器(DispatcherServlet)接受请求,调用doDispatch进行处理
- 根据HandlerMapper中保存的请求映射信息找到,处理当前请求的处理器执行链(包含拦截器)
- 根据当前处理器找到它的HandlerAdapter(适配器)
- 拦截器的preHandle先执行
- 适配器执行目标方法,并返回ModelAndView
- ModelAttribute注解标注的方法提前运行
- 执行目标方法时确定目标方法用的参数,根据参数前有无注解和参数类型(如Model)执行不同。
- 如果是自定义类型:
- 看隐含模型中有没有,有就从隐含模型中取
- 否则再看是否为SessionAttributes标注的属性,是则拿,拿不到则报错
- 以上都不满足利用反射创建对象
- 拦截器的postHandle执行
- 处理结果;(页面渲染流程)
- 如果有异常使用异常解析器处理,处理完成后返回ModelAndView
- 调用render进行页面渲染
- 视图解析器根据视图名得到视图对象
- 视图对象调用render方法
- 执行拦截器的afterCompletion
==掌握1-7(7.1-7.3)==