Spring MVC - 控制器 - 处理请求

知识基础

java基础
spring基础
ajax基础
spring MVC基础
psotman测试工具

版本

版本增加内容
20190804初始版本
20190805增加7、String日期参数转Date:@DateTimeFormat
201908121、修改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();
    }
}

扩展

使用spring validation完成数据后端校验

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;
	}
}

参考文章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值