文章目录
知识基础
java基础
spring基础
ajax基础
spring MVC基础
psotman测试工具
版本
版本 | 增加内容 |
---|---|
20190804 | 初始版本 |
20190805 | 增加7、String日期参数转Date:@DateTimeFormat |
20190812 | 1、修改6、请求校验 对单个请求参数进行校验 中的错误,2、在 6、请求校验 处理错误消息 统一异常处理类处理 中增加对多种异常类型的处理 |
1、@Controller
作用
标记一个类是控制层类,Spring会通过包扫描找到该类,并为其创建对象。
包路径
import org.springframework.stereotype.Controller;
属性
value:用以给controller指定名字。
controller(value=”aController”)
public class LoginController{
}
不加此属性,默认为类名首字母小写,上述例子就是loginController
2、@RequestMapping
作用
用来处理请求地址映射的注解,可用于类或方法上。
用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
包路径
import org.springframework.web.bind.annotation.RequestMapping;
属性
value
指定请求的实际地址。有多种写法:
普通值:
@RequestMapping("/appointments")
含有通配符*的值(通配符表示可以匹配任何字符):
@RequestMapping ( "/myTest" )
public class MyController {
@RequestMapping ( "*/wildcard" )
public String testWildcard() {
含有某变量的值(URI Template Patterns with Path Variables):
在方法的参数类型前需要加@PathVariable。
@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable String ownerId, Model model) {
Owner owner = ownerService.findOwner(ownerId);
model.addAttribute("owner", owner);
return "displayOwner";
}
@RequestMapping ( "/test/{variable1}" )
public class MyController {
@RequestMapping ( "/showView/{variable2}" )
public ModelAndView showView( @PathVariable String variable1, @PathVariable ( "variable2" ) int variable2) {
// ...
}
}
含正则表达式的一类值( URI Template Patterns with Regular Expressions):
@RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\d\.\d\.\d}.{extension:\.[a-z]}")
public void handle(@PathVariable String version, @PathVariable String extension) {
// ...
}
}
method
指定请求的method类型, GET、POST、PUT、DELETE等;
@RequestMapping(method = RequestMethod.GET)
@RequestMapping(method = RequestMethod.GET)
@RequestMapping(value="/new", method = RequestMethod.GET)
consumes
指定处理的请求的编码格式(Content-Type),非指定类型不处理。例如application/json, text/html;
@RequestMapping(value = "/pets", method = RequestMethod.POST, consumes="application/json")
application/x-www-form-urlencoded
是浏览器默认的编码格式。对于Get请求,是将参数转换?key=value&key=value格式,连接到url后
ps:可以在这个网址测试表单:http://www.runoob.com/try/try.php?filename=tryhtml_form_submit
multipart/form-data
上传文件的编码格式。
在开发者工具中可以看出multipart/form-data不会对参数编码,使用的boundary(分割线),相当于&,boundary的值是----Web**AJv3。
如果是SpringMVC项目,要服务器能接受multipart/form-data类型参数,还要在spring上下文配置以下内容,SpringBoot项目则不需要。
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="utf-8"></property>
</bean>
application/json
待写
produces
1、指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回;
@RequestMapping(value = "/pets", method = RequestMethod.GET, produces="application/json")
方法仅处理request请求中Accept头中包含了"application/json"的请求,同时暗示了返回的内容类型为application/json;
当使用了@ResponseBody时可省略(@ResponseBody表示返回json):
@RequestMapping(value = "/pets", method = RequestMethod.GET)
@ResponseBody
2、指定返回的编码格式
@RequestMapping(value = "/pets", method = RequestMethod.GET, produces="application/json;charset=utf-8")
params
指定request中必须包含某些参数值时,才让该方法处理。
@RequestMapping(value = "/pets", method = RequestMethod.GET, params="myParam=myValue")
仅处理请求中包含了名为“myParam”,值为“myValue”的请求;
headers
指定request中必须包含某些指定的header值,才能让该方法处理请求。
@RequestMapping(value = "/pets", method = RequestMethod.GET, headers="Referer=http://www.ifeng.com/")
仅处理request的header中包含了指定“Refer”请求头和对应值为“http://www.ifeng.com/” 的请求;
简化
@GetMapping:是@RequestMapping(method = RequestMethod.GET)的缩写
@PostMapping:是@RequestMapping(method = RequestMethod.POST)的缩写
@PutMapping
@DeleteMapping
@PatchMapping
3、请求参数的接受方式
Java Servlet的方式
Servlet可以通过request.getParameter(“参数名”)的方式来逐个获取参数。
Spring的方式
由于Servlet接受参数的方式比较繁琐,因此Spring做出了简化。
用局部变量接受
@RequestMapping("param")
public void param(int age,String name){
System.out.println(age);
System.out.println(name);
return null;
}
请求参数的名称必须和页面中定义的参数的名称相同。在方法的括号里直接写所要的参数,并且声明它对应的类型。
用对象接受
多个属性时,用上述方式接收不方便,可以用对象来接受。
@RequestMapping("param")
public void param(User user){
System.out.println(user);
return null;
}
此时,对象中的成员属性必须与前端传过来的参数名称相同,此时Spring会通过调用User类的set方法为其属性赋值:
public class User {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
4、@RequestParam
作用
用来处理Content-Type为 application/x-www-form-urlencoded编码的内容。提交方式为get或post。
(Http协议中,如果不指定Content-Type,则默认传递的参数就是application/x-www-form-urlencoded类型)
原理
RequestParam实质是将Request.getParameter() 中的Key-Value参数Map利用Spring的转化机制ConversionService配置,转化成参数接收对象或字段。get方式中query String的值,和post方式中body data的值都会被Servlet接受到并转化到Request.getParameter()参数集中,所以@RequestParam可以获取的到。
value(必配属性):设置请求参数的名称
如果前端传入的参数与后端的接受的参数名称不一致,可以用此属性来绑定。
@RequestParam("") 或 @RequestParam(value="")
required(选配属性)
设置请求参数是否是必需的,默认为 true,即:请求中必须包含该参数,如果没有包含,将会抛出异常。
如果写成false,表示此参数非必须,如果没有传就不会报错,而是显示null。
defaultValue(选配属性)
如果请求中没有value名字的参数,局部变量的值就为defaultValue属性的值。
设置请求参数的默认值,如果设置了该值,required 将自动设为 false,
或
自动数字类型转换
如果请求参数中的 userId 是纯数字,那么使用 @RequestParam时,可以根据自己的需求将方法参数类型设置为 Long、Integer、String,它将自动进行类型转换
接受get请求同名参数
5、@RequestBody
作用
用来处理Content-Type为 application/json编码的内容。提交方式为get或post。
原理
Http传递请求体信息,最终会被封装进com.fasterxml.jackson.core.json.UTF8StreamJsonParser中(提示:Spring采用CharacterEncodingFilter设置了默认编码为UTF-8),然后在public class BeanDeserializer extends BeanDeserializerBase implements java.io.Serializable中,通过 public Object deserializeFromObject(JsonParser p, DeserializationContext ctxt) throws IOException方法进行解析。
用String类型接受前端的json数据
返回为json格式的字符串。
用对象来接受前端的json数据
用一个对象接收参数
用多个对象接受参数
@RequestParam和@RequestBody同时使用
6、请求校验
校验原则
用户提交的数据可能是不正确的。比如手机号写成10位数。
用户提交的数据可能是不安全的。比如提交了一段代码,这段代码可以注入开发者的代码,对系统产生威胁。
用户可以在浏览器提交数据,也可以可以绕过浏览器,使用http工具直接向后端提交数据。因此,后端的校验是必须的。而前端的校验可以减轻服务器的负担,加快对用户的反馈。
因此一般前后端都会进行校验。
通过if判断进行参数校验
使用if判断是最原始的方式,但是效率很低:
if(userName == null | username ==””)){
return new Result("用户名不能为空");
}
if(password == null | password == “”){
return new Result("密码不能为空");
}
if(userName.length() > 10){
return new Result("用户名长度不能超过10位");
}
……
使用自定义注解进行请求校验
见 https://blog.csdn.net/m0_37499059/article/details/81431562
使用自定义工具类进行请求校验
见 https://blog.csdn.net/m0_37499059/article/details/81431562
使用Spring Validation进行请求校验
Spring Validation概述
JSR 303(Java Specification Requests 规范提案)是JAVA EE 6中的一项子规范,一套JavaBean参数校验的标准,叫做Bean Validation。JSR 303用于对Java Bean中的字段的值进行验证,位于javax.validation.constraints包。
JSR-349是其的升级版本,位于org.hibernate.validator.constraints包。并且hibernate validation实现了这些标准。
Spring Validation在上述基础上又做了扩展。
添加依赖
<!--jsr 303-->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<!-- hibernate validator-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.4.1.Final</version>
</dependency>
对单个请求参数进行校验
JSR和Hibernate validator不能对单个的参数进行校验,spring 在此基础上进行了扩展,添加了MethodValidationPostProcessor拦截器,可以实现对方法参数的校验。
只要在类上加@Validated,请求的参数前加上校验注解如@Min(18)即可。(校验注解详见下下节)
@RestController
@Validated
public class BarController {
@RequestMapping("/bar")
public String bar(@Min(18) Integer age) {
System.out.println("age : " + age);
return "";
}
}
对多个请求参数进行校验
在Controller层方法的参数前面加上@Validated或@Valid注解
import org.springframework.validation.annotation.Validated;
@Controller
public class FooController {
@RequestMapping("/foo")
public String foo(@Validated LoginVO vo) {
return "success";
}
}
参数Foo前加上@Validated注解,表明spring会对其进行校验。
@Validated和@Valid的区别如下:
@Valid | @Validated | |
---|---|---|
提供者 | javax(标准JSR-303规范) | Spring |
分组校验 | 不支持 | 支持 |
嵌套检验 | 能用在成员属性上实现嵌套验证 | 不能用在成员属性上实现嵌套验证 |
分组校验和嵌套校验的概念,见后文解释。
在VO类属性上加上校验注解
@Data
public class LoginVO extends BaseEntity {
private static final long serialVersionUID = -8369349317874841387L;
@NotEmpty(message = "手机号为空")
@Length(min = 11,message = "手机号不正确")
private String mobile;
@NotEmpty(message = "密码为空")
private String password;
@Valid
private List<User> user;
}
由于该类注入(嵌套)着另一个对象user,因此需要用@ Valid来进行嵌套校验。
校验的注解
校验的注解可以分为2类:非自定义注解(JSR303规范提供的注解,Hibernate Validator提供的注解,Spring提供的注解),自定义注解。
javax(JSR303)提供的注解
@Null 被注释的元素必须为 null
@NotNull 被注释的元素不能为null,可以为empty("”),可以有空格(" ")
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max=, min=) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(regexp=,flag=) 被注释的元素必须符合指定的正则表达式
@Pattern(regexp="^[0-9]{4}-[0-9]{2}-[0-9]{2}$",message="出生日期格式不正确")
private String birthday;
Hibernate Validator提供的注解
@Range(min=,max=) 被注释的元素必须在合适的范围内
@NotEmpty 被注释的字符串不能为null,不可以为empty(“”),可以有空格(" ")
@NotBlank 验证字符串不能为null,不可以为empty(“”),而且调用trim()后,长度必须大于0,只能作用在String上
@Email 被注释的元素必须是电子邮箱地址
@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
@Length(min = 5, max = 20, message = "{user.name.length.illegal}")
private String name;
Spring提供的注解,分组校验
Spring Validation在以上两者的基础上,加入了@Validated和groups属性用来进行分组校验。
首先我们声明2个空接口,用来标记不同的校验场景。
public class User implements Serializable {
/**注册校验标记*/
public interface UserRegisterValidView {}
/**登录校验标记*/
public interface UserLoginValidView {}
}
然后,在需要区分场景校验的属性的校验注解上加上groups属性:
public class User implements Serializable {
/**注册校验标记*/
public interface UserRegisterValidView {}
/**登录校验标记*/
public interface UserLoginValidView {}
@Past(groups = { UserRegisterValidView.class }, message = "出生日期不符合要求")
private Date birthday;
@AssertTrue(groups = { UserRegisterValidView.class, UserLoginValidView.class }, message = "标记必须为true")
private boolean flag;
}
如此,在注册时对birthday和flag进行校验,在登录时只对flag进行校验。
最后,在controller的@Validated上加上value属性,值为使用的校验规则。
@RequestMapping(value = "/register", method = RequestMethod.POST)
public CommonResponse register(@Validated(value = { UserRegisterValidView.class }) User user) {
CommonResponse response = new CommonResponse();
return response;
}
使用javax.validation自定义注解和校验器
如果我们要校验手机号,我们需要定义一个@IsMobile:
import javax.validation.Constraint;
import javax.validation.constraints.Pattern;
import javax.validation.ReportAsSingleViolation;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.validation.Payload;
//@Constraint是定义校验注解必备的注解,其中属性validatedBy中可以填写指定的检验器类型
@Constraint(validatedBy = {})
@Pattern(regexp = "1[3|4|5|7|8][0-9]\\d{8}")
//@ReportAsSingleViolation表示这个注解会和@pattern注解组合在一起,只引发一次错误。
@ReportAsSingleViolation
@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IsMobile {
String regexp() default "";
String message() default "手机号格式不正确";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
上面的@Constraint(validatedBy = {})没有指定校验器,因为我们使用了@Pattern直接进行正则匹配。
下面我们指定一个校验器来校验:
@Constraint(validatedBy = {IsMobile.MobileValidate.class})
@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IsMobile {
String regexp() default "";
String message() default "手机号格式不正确";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* ConstraintValidator接口,有两个泛型,
* 第一个是自定义的注解类,第二个是要验证的数据的类型(例如写了String类型的数据,
* 那么这个注解就要放在String类型的字段上才会起作用,
* 最简便的写成Object,那么它可以接收任何数据类型的数据)。
*/
class MobileValidate implements ConstraintValidator<IsMobile,String>{
//初始化方法
@Override
public void initialize(IsMobile constraintAnnotation) {
}
//验证方法,返回true,验证通过,否则不通过。
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
Pattern p = Pattern.compile("1[3|4|5|7|8][0-9]\\d{8}");
Matcher m = p.matcher(value);
return m.matches();
}
}
}
最后,我们就可以使用这个注解了:
@IsMobile
private String mobile;
处理错误消息
默认情况下,如果spring validation校验失败会抛javax.validation.ConstraintViolationException异常。
可以用BindingResult处理,还可以用统一异常处理类来处理。详见下文:
使用BindingResult处理
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
@Controller
public class FooController {
@RequestMapping("/foo")
public String foo(@Validated Foo foo, BindingResult bindingResult) {
if(bindingResult.hasErrors()){
for (ObjectError error : bindingResult.getAllErrors()) {
return new Result("8888",error);
}
}
return new Result("0000","处理成功");;
}
}
如上,校验的信息会存放到其后的BindingResult中。
如果有多个参数需要校验,形式如下:
@RequestMapping("/foo")
public String foo(@Validated Foo foo, BindingResult fooBindingResult ,@Validated Bar bar, BindingResult barBindingResult){
}
即一个校验类对应一个校验结果。
统一异常处理类处理
不同的处理方式,系统会抛出不同的异常类型,因此如果要对这些异常进行分别处理,就要进行分别的捕获:
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
//处理Get请求中,使用@Valid 验证路径中请求实体校验失败后抛出的异常
@ExceptionHandler(BindException.class)
public Result handleMethodArgNotValidException(BindException e) {
log.error(e.getMessage(), e);
BindingResult bindingResult = e.getBindingResult();
StringBuffer sb = new StringBuffer();
for (FieldError fieldError : bindingResult.getFieldErrors()) {
sb.append(fieldError.getDefaultMessage());
}
return new Result("5000","参数错误: " + sb.toString());
}
//@RequestBody上validate失败后抛出的异常是MethodArgumentNotValidException
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result handleMethodArgNotValidException(MethodArgumentNotValidException e) {
log.error(e.getMessage(), e);
BindingResult bindingResult = e.getBindingResult();
StringBuffer sb = new StringBuffer();
for (FieldError fieldError : bindingResult.getFieldErrors()) {
sb.append(fieldError.getDefaultMessage());
}
return new Result("5000","参数错误: " + sb.toString());
}
//@RequestParam上validate失败后抛出的异常是ConstraintViolationException
@ExceptionHandler(ConstraintViolationException.class)
public Result handleMethodArgNotValidException(ConstraintViolationException e) {
log.error(e.getMessage(), e);
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
StringBuffer sb = new StringBuffer();
for (ConstraintViolation violation : violations) {
sb.append(violation.getMessage());
}
return new Result("5000","参数错误: " + sb.toString());
}
//
}
取消试错机制
spring validation不会在第一个错误发生后立即停止,而是继续试错,然后将错误信息一起返回,但很多时候不需要这样,一个校验失败了,其它就不必校验了。
因此可以做出如下配置:
@Configuration
public class WebConfig {
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
.failFast(true)//只要校验失败就立即结束,不再进行后续校验。
.buildValidatorFactory();
return validatorFactory.getValidator();
}
}
扩展
7、String日期参数转Date:@DateTimeFormat
@DateTimeFormat对java的SimpleDateFormat类进行了封装,简化了开发,使用方式如下:
@Controller
public class xxController{
@RequestMappeing(/mm)
public void a(A a){
}
}
在入参的上方加上@DateTimeFormat注解,并定义转化的格式,就可以自动将规定格式的字符串转化成Date类型了:
class A{
@DateTimeFormat (pattern="yyyy/MM/dd HH:mm:ss")
private Date date;
public void setDate(Date date){
this.date = date;
}
}
参考文章
- SpringMVC强大的数据绑定(2)——第六章 注解式控制器详解——跟着开涛学SpringMVC
- Post请求的两种编码格式:application/x-www-form-urlencoded和multipart/form-data
- @RequestBody的使用
- Java 使用注解检验手机号
- JAVA参数验证 Validation(二)分组校验&自定义校验
- @Validated和@Valid区别:Spring validation验证框架对入参实体进行嵌套验证必须在相应属性(字段)加上@Valid而不是@Validated
- 总结篇-后台参数验证的几种方式
- 使用spring validation完成数据后端校验