@valid和@validated
@Valid与@Validated注解
@Valid:常见用在方法,类中字段上进行校验
@Validated:是spring提供的对@Valid的封装,常见用在方法上进行校验
定义的校验类型
@Null 验证对象是否为null @NotNull 验证对象是否不为null, 无法查检长度为0的字符串 @NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格. @NotEmpty 检查约束元素是否为NULL或者是EMPTY. @CreditCardNumber信用卡验证 @Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。 @URL(protocol=,host=, port=,regexp=, flags=) ip地址校验
Booelan检查 @AssertTrue 验证 Boolean 对象是否为 true @AssertFalse 验证 Boolean 对象是否为 false
长度检查 @Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内 @Length(min=, max=) Validates that the annotated string is between min and max included.
日期检查 @Past 验证 Date 和 Calendar 对象是否在当前时间之前 @Future 验证 Date 和 Calendar 对象是否在当前时间之后 @Pattern 验证 String 对象是否符合正则表达式的规则
数值检查,建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为""时无法转换为int,但可以转换为Stirng为"",Integer为null @Min 验证 Number 和 String 对象是否大等于指定的值 @Max 验证 Number 和 String 对象是否小等于指定的值 @DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度 @DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度 @Digits 验证 Number 和 String 的构成是否合法 @Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
注意的几点:
(1)如果一个bean中包含第二个bean,这时要检验第二个bean中某个字段,即嵌套校验,必须要在第一个bean对象中使用@Valid标注到表示第二个bean对象的字段上,然后再第二个bean对象里面的字段上加上校验类型
(2)@Validated支持分组注解
1、先定义一个空接口:GroupA
2、对bean对象中校验类型,添加分组信息,这里我对version字段进行了分组校验信息添加
3、方法入参中进行分组信息添加
如上图所示,则shelveProject方法由于添加了分组信息会校验DeleteProjectRequest对象中的version字段是否为空,而offShelveProject方法没有添加分组信息,不会校验version是否为空。
博客原文:https://blog.csdn.net/zhenwei1994/article/details/81460419
Spring中的@Valid 和 @Validated注解你用对了吗
1.概述#
本文我们将重点介绍Spring中 @Valid和@Validated注解的区别 。
验证用户输入是否正确是我们应用程序中的常见功能。Spring提供了@Valid和@Validated两个注解来实现验证功能,下面我们来详细介绍它们。
2. @Valid和@Validate注解#
在Spring中,我们使用@Valid 注解进行方法级别验证,同时还能用它来标记成员属性以进行验证。
但是,此注释不支持分组验证。@Validated则支持分组验证。
3.例子#
让我们考虑一个使用Spring Boot开发的简单用户注册表单。首先,我们只有名称和密码属性:
Copypublic class UserAccount {
@NotNull @Size(min = 4, max = 15) private String password;
@NotBlank private String name;
// standard constructors / setters / getters / toString
}接下来,让我们看一下控制器。在这里,我们将使用带有@Valid批注的saveBasicInfo方法来验证用户输入:
Copy@RequestMapping(value = "/saveBasicInfo", method = RequestMethod.POST)public String saveBasicInfo( @Valid @ModelAttribute("useraccount") UserAccount useraccount, BindingResult result, ModelMap model) { if (result.hasErrors()) { return "error"; } return "success";}现在让我们测试一下这个方法:
Copy@Testpublic void givenSaveBasicInfo_whenCorrectInput`thenSuccess() throws Exception { this.mockMvc.perform(MockMvcRequestBuilders.post("/saveBasicInfo") .accept(MediaType.TEXT_HTML) .param("name", "test123") .param("password", "pass")) .andExpect(view().name("success")) .andExpect(status().isOk()) .andDo(print());}在确认测试成功运行之后,现在让我们扩展功能。下一步的逻辑步骤是将其转换为多步骤注册表格,就像大多数向导一样。第一步,名称和密码保持不变。在第二步中,我们将获取其他信息,例如age 和 phone。因此,我们将使用以下其他字段更新域对象:
Copypublic class UserAccount {
@NotNull @Size(min = 4, max = 15) private String password;
@NotBlank private String name;
@Min(value = 18, message = "Age should not be less than 18") private int age;
@NotBlank private String phone;
// standard constructors / setters / getters / toString
}但是,这一次,我们将注意到先前的测试失败。这是因为我们没有传递年龄和电话字段。
为了支持此行为,我们引入支持分组验证的@Validated批注。
分组验证`,就是将字段分组,分别验证,比如我们将用户信息分为两组:`BasicInfo`和`AdvanceInfo可以建立两个空接口:
Copypublic interface BasicInfo {}Copypublic interface AdvanceInfo {}第一步将具有BasicInfo接口,第二步 将具有AdvanceInfo 。此外,我们将更新UserAccount类以使用这些标记接口,如下所示:
Copypublic class UserAccount {
@NotNull(groups = BasicInfo.class) @Size(min = 4, max = 15, groups = BasicInfo.class) private String password;
@NotBlank(groups = BasicInfo.class) private String name;
@Min(value = 18, message = "Age should not be less than 18", groups = AdvanceInfo.class) private int age;
@NotBlank(groups = AdvanceInfo.class) private String phone;
// standard constructors / setters / getters / toString
}另外,我们现在将更新控制器以使用@Validated注释而不是@Valid:
Copy@RequestMapping(value = "/saveBasicInfoStep1", method = RequestMethod.POST)public String saveBasicInfoStep1( @Validated(BasicInfo.class) @ModelAttribute("useraccount") UserAccount useraccount, BindingResult result, ModelMap model) { if (result.hasErrors()) { return "error"; } return "success";}更新后,再次执行测试,现在可以成功运行。现在,我们还要测试这个新方法:
Copy@Testpublic void givenSaveBasicInfoStep1`whenCorrectInput`thenSuccess() throws Exception { this.mockMvc.perform(MockMvcRequestBuilders.post("/saveBasicInfoStep1") .accept(MediaType.TEXT_HTML) .param("name", "test123") .param("password", "pass")) .andExpect(view().name("success")) .andExpect(status().isOk()) .andDo(print());}也成功运行!
接下来,让我们看看@Valid对于触发嵌套属性验证是必不可少的。
4.使用@Valid批注标记嵌套对象#
@Valid 可以用于嵌套对象。例如,在我们当前的场景中,让我们创建一个 UserAddress 对象:
Copypublic class UserAddress {
@NotBlank private String countryCode;
// standard constructors / setters / getters / toString}为了确保验证此嵌套对象,我们将使用@Valid批注装饰属性:
Copypublic class UserAccount {
//...
@Valid @NotNull(groups = AdvanceInfo.class) private UserAddress useraddress;
// standard constructors / setters / getters / toString}5. 总结#
@Valid保证了整个对象的验证, 但是它是对整个对象进行验证,当仅需要部分验证的时候就会出现问题。
这时候,可以使用@Validated 进行分组验证。
实战:
校验分组
/** * 新增数据 Group * * @author zy */public interface AddGroup {}
/** * 更新数据 Group * * @author zy */
public interface UpdateGroup {
}校验工具类:
package com.easypark.common.utils.validator;
import javax.validation.ConstraintViolation;import javax.validation.Validation;import javax.validation.Validator;import java.util.Set;
/** * hibernate-validator校验工具类 * * 参考文档:http://docs.jboss.org/hibernate/validator/5.4/reference/en-US/html_single/ * * @author zy */public class ValidatorUtils { private static final Validator VALIDATOR;
static { VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator(); }
/** * 校验对象 * @param object 待校验对象 * @param groups 待校验的组 * @throws RuntimeException 校验不通过,则报RRException异常 */ public static void validateEntity(Object object, Class<?>... groups) throws RuntimeException { Set<ConstraintViolation<Object>> constraintViolations = VALIDATOR.validate(object, groups); if (!constraintViolations.isEmpty()) { StringBuilder msg = new StringBuilder(); for(ConstraintViolation<Object> constraint: constraintViolations){ msg.append(constraint.getMessage()).append("<br>"); } throw new RuntimeException(msg.toString()); } }}校验字段:
public class CommercialAddOrEditRequest {
@ApiModelProperty(value = "商户ID(新增的时候不用传值,修改的时候必传!)") @NotBlank(message = "id不能为空",groups = UpdateGroup.class) private String id;
@ApiModelProperty(value = "商户名称") @NotBlank(message = "商户名称不能为空",groups = {AddGroup.class,UpdateGroup.class}) private String commercialName;
@ApiModelProperty(value = "联系人姓名") @NotBlank(message = "联系人姓名不能为空",groups = {AddGroup.class,UpdateGroup.class}) private String contactName;
@ApiModelProperty(value = "电话") @NotBlank(message = "电话不能为空",groups = {AddGroup.class,UpdateGroup.class}) private String phone;}使用校验:
@ApiOperation(value = "商户新增或修改接口") @PostMapping(value = "/addOrEdit") public JsonResultVO<Object> addOrEdit(@RequestBody CommercialAddOrEditRequest request) { if(StringUtils.isNotBlank(request.getId())){ ValidatorUtils.validateEntity(request, UpdateGroup.class); }else { ValidatorUtils.validateEntity(request, AddGroup.class); } commercialService.saveOrUpdateCommercial(request); return ResponseHelper.createJsonResult(); }MVC序列化
springmvc序列化
使用SpringBoot为例,SpringBoot默认使用Jackson作为序列化工具。通过修改Jackson配置即可自定义序列化规则。
@Valid
Spring MVC采用的校验是hibernate-validate
@NotNull 值不能为空@Null 值必须为空@Pattern(regex=) 字符串必须匹配正则表达式@Size(min=,max=) 集合的元素数量必须在min和max之间@CreditNumber(ignoreNonDigitCharacters=) 字符串必须是信用卡号(按美国的标准验证)@Email 字符串必须是Email地址@Length(min=,max=) 检查字符串的长度@NotBlank 字符串必须有字符@Range(min=,max=) 数字必须大于min,小于等于max@SafeHtml 字符串是安全的html@URL 字符串是合法的URL@AssertFalse 值必须是false@AssertTrue 值必须是true@DecimalMax(value,inclusive=) 值必须小于等于(inclusive=true)/小于(inclusive=false) value属性指定的值,可以注解在字符串类型的属性上@DecimalMin(value,inclusive=) 值必须大于等于(inclusive=true)/大于(inclusive=false) value属性指定的值,可以注解在字符串类型的属性上@Digits(integer=,fraction=) 数字格式检查,integer指定整数部分的最大长度,fraction指定小数部分的最大长度@Future 值必须是未来的日志@Past 值必须是过去的日期@Max(value=) 值必须小于等于value指定的值,不能注视在字符串类型的属性上@Min(value=) 值必须大于等于value指定的值,不能注视在字符串类型的属性上Jackson 常用注解
-
@JsonNaming(SnakeCaseStrategy.class)
指定Json字段名映射策略为蛇形大小写策略。缺省则直接使用Bean属性名
可用的命名映射策略还有: KebabCaseStrategy: 肉串策略 - 单词小写,使用连字符’-‘连接 SnakeCaseStrategy: 蛇形策略 - 单词小写,使用下划线’_‘连接;即老版本中的LowerCaseWithUnderscoresStrategy LowerCaseStrategy: 小写策略 - 简单的把所有字母全部转为小写,不添加连接符 UpperCamelCaseStrategy: 驼峰策略 - 单词首字母大写其它小写,不添加连接符;即老版本中的PascalCaseStrategy
- @JsonIgnoreProperties({“id”, “created”, “steps”, “copy”, “stepList”})
类注解,指定序列化时忽略这些属性,可以用于覆盖超类中默认输出的属性
-
@JsonInclude(Include.NON_EMPTY)
仅在属性不为空时序列化此字段,对于字符串,即null或空字符串
-
@JsonIgnore
序列化时忽略此字段
-
@JsonProperty(value = “user_name”)
指定序列化时的字段名,默认使用属性名
-
@JsonFormat(pattern = “yyyy-MM-dd HH:mm
”, timezone = “GMT+8”) 指定Date类字段序列化时的格式
-
@JsonUnwrapped(prefix = “user_”)
private User user; 把成员对象中的属性提升到其容器类,并添加给定的前缀,比如上例中: User类中有name和age两个属性,不使用此注解则序列化为: … “user”: { “name”: “xxx”, “age”: 22 } … 使用此注解则序列化为: … “user_name”: “xxx”, “user_age”: 22, …
-
@JsonIgnoreType
类注解,序列化时忽略此类
-
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,property = “id”)
作用于类或属性上,被用来在序列化/反序列化时为该对象或字段添加一个对象识别码,通常是用来解决循环嵌套的问题
序列化器:
- 实现JsonSerializer
- 对应注解上加注解
- 需求是:json 字符串不想直接转给前段,而是json对象返回。
@Datapublic class Test01 { @JsonSerialize(using = JsonStringSerilizer.class) private String jsond = "[{\"url\":\"https://dftc-temps.oss-cn-shenzhen.aliyuncs.com/eQEj1SYtXmOdTqNWZlETR4CpVJieMJQI.jpg\"},{\"url\":\"https://dftc-temps.oss-cn-shenzhen.aliyuncs.com/d5ClnyPCHTuWhBqglVDtIllF2wViDteC.jpg\"},{\"url\":\"https://dftc-temps.oss-cn-shenzhen.aliyuncs.com/ZGSQX6iAxZ0tZhenH0IIvDlagYloSZLn.jpg\"},{\"url\":\"https://dftc-temps.oss-cn-shenzhen.aliyuncs.com/KFOqZenayBSNHykp546iEa5Fus6PmxMs.jpg\"}]";
private Integer age; private Date time;}/** * <p>DESC: </p> * <p>DATE: 2019/6/27</p> * <p>VERSION:1.0.0</p> * <p>@AUTHOR: ZhengYong</p> */public class JsonStringSerilizer extends JsonSerializer<String> {
@Override public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { Object o = JSONUtil.toList(new JSONArray(s),Object.class ); jsonGenerator.writeObject(o); }}反序列化
前段数据提交:
- from-data表单提交
- application/json 提交
1、 from-data
springmvc默认转换类:Convert
可以自定义实现Convert<in,out> 接口,并且注册到spring中,即可生效
package com.leyou.auth.service.config;
import org.apache.commons.lang3.StringUtils;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.convert.converter.Converter;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Date;
/** * <p>DESC: </p> * <p>DATE: 2019/6/27</p> * <p>VERSION:1.0.0</p> * <p>@AUTHOR: ZhengYong</p> */@Configurationpublic class MvcConfig implements WebMvcConfigurer { @Bean public Converter<String, Date> stringToDate(){ return new Converter<String, Date>() { @Override public Date convert(String s) { if(StringUtils.isNotBlank(s)){ SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); try { System.out.println("进入convert"); Date parse = simpleDateFormat.parse(s); return parse; } catch (ParseException e) { e.printStackTrace(); } } return null; } }; }}2、application/json
因为传入的时json,springmvc默认参数转换使用的是:facson反序列化器 JsonDeserializer
注意controller中参数 注解:@requestBody
@RequestMapping("hello")@RestControllerpublic class MvcController {
@PostMapping("/hello") public ResponseEntity hello(@RequestBody Test01 test){ Test01 test01 = new Test01(); test01.setAge(test.getAge()); test01.setTime(test.getTime()); test01.setJsond("[{\"url\":\"https://dftc-temps.oss-cn-shenzhen.aliyuncs.com/eQEj1SYtXmOdTqNWZlETR4CpVJieMJQI.jpg\"},{\"url\":\"https://dftc-temps.oss-cn-shenzhen.aliyuncs.com/d5ClnyPCHTuWhBqglVDtIllF2wViDteC.jpg\"},{\"url\":\"https://dftc-temps.oss-cn-shenzhen.aliyuncs.com/ZGSQX6iAxZ0tZhenH0IIvDlagYloSZLn.jpg\"},{\"url\":\"https://dftc-temps.oss-cn-shenzhen.aliyuncs.com/KFOqZenayBSNHykp546iEa5Fus6PmxMs.jpg\"}]"); return ResponseEntity.ok(test01); }}第一步,实现并且,重写反序列化方法。
package com.leyou.auth.service.serializer;
import com.fasterxml.jackson.core.JsonParser;import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.DeserializationContext;import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;import java.text.DateFormat;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Date;
/** * <p>DESC: </p> * <p>DATE: 2019/6/27</p> * <p>VERSION:1.0.0</p> * <p>@AUTHOR: ZhengYong</p> */public class JsonStringDesSerilizer extends JsonDeserializer<Date> {
@Override public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { String value = jsonParser.getValueAsString(); DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); try { return dateFormat.parse(value); } catch (ParseException e) { System.out.println("反序列化"); e.printStackTrace(); } return null; }}第二步,对应属性添加注解:@
@Datapublic class Test01 {
private String jsond = "";
private Integer age;
@JsonDeserialize(using = JsonStringDesSerilizer.class) private Date time;}3,springboot 配置文件,配置时间序列化
spring: datasource: url: jdbc:mysql://192.192.192.58/dftc_dev?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8 driver-class-name: com.mysql.cj.jdbc.Driver username: root password: dftc@Mysql redis: sentinel: master: master7000 nodes: 192.192.192.58:26380,192.192.192.58:26381,192.192.192.58:26382 password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8注意: 只能对Date 类型的时间 序列化和反序列化
jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+84,详细讲解
全局配置 全局配置 全局配置 Jackson时间格式化
mvc Jackson 返回时间时,默认会返回时间戳或者是 2018-08-30T12:12:12CST格式的时间字符串
spring.jackson.date-format=yyyy-MM-dd HH:mm:ssspring.jackson.time-zone=GMT+8配置了以上参数 只能对Date 类型的时间格式化 对于 LocalDateTime ,Instant 并没有什么效果只需要添加以下配置就可以解决@Configurationpublic class Java8TimeConfig {
@Value("${spring.jackson.date-format}") private String formatValue;
@Bean(name = "format") DateTimeFormatter format() { return DateTimeFormatter.ofPattern(formatValue); }
@Bean public ObjectMapper serializingObjectMapper(@Qualifier("format") DateTimeFormatter format) { JavaTimeModule javaTimeModule = new JavaTimeModule(); javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(format)); javaTimeModule.addSerializer(Instant.class, new InstantCustomSerializer(format)); javaTimeModule.addSerializer(Date.class, new DateSerializer(false, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))); ObjectMapper mapper = new ObjectMapper() .registerModule(new ParameterNamesModule()) .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) .registerModule(javaTimeModule); return mapper; }
class InstantCustomSerializer extends JsonSerializer<Instant> { private DateTimeFormatter format;
private InstantCustomSerializer(DateTimeFormatter formatter) { this.format = formatter; }
@Override public void serialize(Instant instant, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { if (instant == null) { return; } String jsonValue = format.format(instant.atZone(ZoneId.systemDefault())); jsonGenerator.writeString(jsonValue); } }
}需要引入相关jar包 下面是pom文件
<!--jackson 时间格式化--> <dependency> <groupId>com.fasterxml.jackson.module</groupId> <artifactId>jackson-module-parameter-names</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jdk8</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> </dependency> <!--jackson 时间格式化-->RequestMapping参数解析
Http请求中Content-Type讲解以及在Spring MVC注解中produce和consumes配置详解
引言: 在Http请求中,我们每天都在使用Content-type来指定不同格式的请求信息,但是却很少有人去全面了解content-type中允许的值有多少,这里将讲解Content-Type的可用值,以及在spring MVC中如何使用它们来映射请求信息。
1、Content-Type
MediaType,即是Internet Media Type,互联网媒体类型;也叫做MIME类型,在Http协议消息头中,使用Content-Type来表示具体请求中的媒体类型信息。
类型格式:type/subtype(;parameter)? type主类型,任意的字符串,如text,如果是*号代表所有;subtype 子类型,任意的字符串,如html,如果是*号代表所有;parameter 可选,一些参数,如Accept请求头的q参数, Content-Type的 charset参数。例如: Content-Type: text/html;charset
text/html : HTML格式 text/plain :纯文本格式 text/xml : XML格式 image/gif :gif图片格式 image/jpeg :jpg图片格式 image/png:png图片格式 以application开头的媒体格式类型:
application/xhtml+xml :XHTML格式 application/xml : XML数据格式 application/atom+xml :Atom XML聚合格式 application/json : JSON数据格式 application/pdf :pdf格式 application/msword : Word文档格式 application/octet-stream : 二进制流数据(如常见的文件下载) application/x-www-form-urlencoded :
multipart/form-data : 需要在表单中进行文件上传时,就需要使用该格式 以上就是我们在日常的开发中,经常会用到的若干content-type的内容格式。
2、Spring MVC中关于关于Content-Type类型信息的使用
首先我们来看看RequestMapping中的Class定义:
@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Mappingpublic @interface RequestMapping { String[] value() default {}; RequestMethod[] method() default {}; String[] params() default {}; String[] headers() default {}; String[] consumes() default {}; String[] produces() default {};}value: 指定请求的实际地址, 比如 /action/info之类。 method: 指定请求的method类型, GET、POST、PUT、DELETE等 consumes: 指定处理请求的提交内容类型(Content-Type),例如application/json, text/html; produces: 指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回 params: 指定request中必须包含某些参数值是,才让该方法处理 headers: 指定request中必须包含某些指定的header值,才能让该方法处理请求
3、使用示例
3.1 headers
@RequestMapping(value = "/test", method = RequestMethod.GET, headers="Referer=http://www.ifeng.com/") public void testHeaders(@PathVariable String ownerId, @PathVariable String petId) { // implementation omitted }这里的Headers里面可以匹配所有Header里面可以出现的信息,不局限在Referer信息。 示例2
@RequestMapping(value = "/response/ContentType", headers = "Accept=application/json")public void response2(HttpServletResponse response) throws IOException { //表示响应的内容区数据的媒体类型为json格式,且编码为utf-8(客户端应该以utf-8解码) response.setContentType("application/json;charset=utf-8"); //写出响应体内容 String jsonData = "{"username":"zhang", "password":"123"}"; response.getWriter().write(jsonData);}服务器根据请求头“Accept=application/json”生产json数据。
当你有如下Accept头,将遵守如下规则进行应用: ①Accept:text/html,application/xml,application/json 将按照如下顺序进行produces的匹配 ①text/html ②application/xml ③application/json ②Accept:application/xml;q=0.5,application/json;q=0.9,text/html 将按照如下顺序进行produces的匹配 ①text/html ②application/json ③application/xml 参数为媒体类型的质量因子,越大则优先权越高(从0到1) ③Accept:/,text/*,text/html
将按照如下顺序进行produces的匹配 ①text/html ②text/* ③*/*
即匹配规则为:最明确的优先匹配。
Requests部分 Header 解释 示例 Accept 指定客户端能够接收的内容类型 Accept: text/plain, text/html Accept-Charset 浏览器可以接受的字符编码集。 Accept-Charset: iso-8859-5 Accept-Encoding 指定浏览器可以支持的web服务器返回内容压缩编码类型。 Accept-Encoding: compress, gzip Accept-Language 浏览器可接受的语言 Accept-Language: en,zh Accept-Ranges 可以请求网页实体的一个或者多个子范围字段 Accept-Ranges: bytes Authorization HTTP授权的授权证书 Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== Cache-Control 指定请求和响应遵循的缓存机制 Cache-Control: no-cache Connection 表示是否需要持久连接。(HTTP 1.1默认进行持久连接) Connection: close Cookie HTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器。 Cookie: $Version=1; Skin=new; Content-Length 请求的内容长度 Content-Length: 348 Content-Type 请求的与实体对应的MIME信息 Content-Type: application/x-www-form-urlencoded Date 请求发送的日期和时间 Date: Tue, 15 Nov 2010 08:12:31 GMT Expect 请求的特定的服务器行为 Expect: 100-continue From 发出请求的用户的Email From: [email protected] Host 指定请求的服务器的域名和端口号 Host: www.zcmhi.com If-Match 只有请求内容与实体相匹配才有效 If-Match: “737060cd8c284d8af7ad3082f209582d” If-Modified-Since 如果请求的部分在指定时间之后被修改则请求成功,未被修改则返回304代码 If-Modified-Since: Sat, 29 Oct 2010 19:43:31 GMT If-None-Match 如果内容未改变返回304代码,参数为服务器先前发送的Etag,与服务器回应的Etag比较判断是否改变 If-None-Match: “737060cd8c284d8af7ad3082f209582d” If-Range 如果实体未改变,服务器发送客户端丢失的部分,否则发送整个实体。参数也为Etag If-Range: “737060cd8c284d8af7ad3082f209582d” If-Unmodified-Since 只在实体在指定时间之后未被修改才请求成功 If-Unmodified-Since: Sat, 29 Oct 2010 19:43:31 GMT Max-Forwards 限制信息通过代理和网关传送的时间 Max-Forwards: 10 Pragma 用来包含实现特定的指令 Pragma: no-cache Proxy-Authorization 连接到代理的授权证书 Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== Range 只请求实体的一部分,指定范围 Range: bytes=500-999 Referer 先前网页的地址,当前请求网页紧随其后,即来路 Referer: http://www.zcmhi.com/archives/71.html TE 客户端愿意接受的传输编码,并通知服务器接受接受尾加头信息 TE: trailers,deflate;q=0.5 Upgrade 向服务器指定某种传输协议以便服务器进行转换(如果支持) Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11 User-Agent User-Agent的内容包含发出请求的用户信息 User-Agent: Mozilla/5.0 (Linux; X11) Via 通知中间网关或代理服务器地址,通信协议 Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1) Warning 关于消息实体的警告信息 Warn: 199 Miscellaneous warning Responses 部分 Header 解释 示例 Accept-Ranges 表明服务器是否支持指定范围请求及哪种类型的分段请求 Accept-Ranges: bytes Age 从原始服务器到代理缓存形成的估算时间(以秒计,非负) Age: 12 Allow 对某网络资源的有效的请求行为,不允许则返回405 Allow: GET, HEAD Cache-Control 告诉所有的缓存机制是否可以缓存及哪种类型 Cache-Control: no-cache Content-Encoding web服务器支持的返回内容压缩编码类型。 Content-Encoding: gzip Content-Language 响应体的语言 Content-Language: en,zh Content-Length 响应体的长度 Content-Length: 348 Content-Location 请求资源可替代的备用的另一地址 Content-Location: /index.htm Content-MD5 返回资源的MD5校验值 Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ== Content-Range 在整个返回体中本部分的字节位置 Content-Range: bytes 21010-47021/47022 Content-Type 返回内容的MIME类型 Content-Type: text/html; charset=utf-8 Date 原始服务器消息发出的时间 Date: Tue, 15 Nov 2010 08:12:31 GMT ETag 请求变量的实体标签的当前值 ETag: “737060cd8c284d8af7ad3082f209582d” Expires 响应过期的日期和时间 Expires: Thu, 01 Dec 2010 16:00:00 GMT Last-Modified 请求资源的最后修改时间 Last-Modified: Tue, 15 Nov 2010 12:45:26 GMT Location 用来重定向接收方到非请求URL的位置来完成请求或标识新的资源 Location: http://www.zcmhi.com/archives/94.html Pragma 包括实现特定的指令,它可应用到响应链上的任何接收方 Pragma: no-cache Proxy-Authenticate 它指出认证方案和可应用到代理的该URL上的参数 Proxy-Authenticate: Basic refresh 应用于重定向或一个新的资源被创造,在5秒之后重定向(由网景提出,被大部分浏览器支持)
Refresh: 5; url=
http://www.zcmhi.com/archives/94.html
Retry-After 如果实体暂时不可取,通知客户端在指定时间之后再次尝试 Retry-After: 120
Server web服务器软件名称 Server: Apache/1.3.27 (Unix) (Red-Hat/Linux)
Set-Cookie 设置Http Cookie Set-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1
Trailer 指出头域在分块传输编码的尾部存在 Trailer: Max-Forwards
Transfer-Encoding 文件传输编码 Transfer-Encoding
@RequestMapping(value = "/test/{userId}", method = RequestMethod.GET, params="myParam=myValue") public void findUser(@PathVariable String userId) { // implementation omitted }仅处理请求中包含了名为“myParam”,值为“myValue”的请求,起到了一个过滤的作用。 3.3 consumes/produces
@Controller@RequestMapping(value = "/users", method = RequestMethod.POST, consumes="application/json", produces="application/json")@ResponseBodypublic List<User> addUser(@RequestBody User userl) { // implementation omitted return List<User> users;}方法仅处理request Content-Type为“application/json”类型的请求. produces标识==>处理request请求中Accept头中包含了”application/json”的请求,同时暗示了返回的内容类型为application/json;
4、总结
在本文中,首先介绍了Content-Type主要支持的格式内容,然后基于@RequestMapping标注的内容介绍了主要的使用方法,其中,headers, consumes,produces,都是使用Content-Type中使用的各种媒体格式内容,可以基于这个格式内容来进行访问的控制和过滤。
跨域问题
一、什么是跨域
跨域是指跨域名的访问,以下情况都属于跨域:
跨域原因说明 示例
域名不同 www.jd.com 与 www.taobao.com
域名相同,端口不同 www.jd.com:8080 与 www.jd.com:8081
二级域名不同 item.jd.com 与 miaosha.jd.com
如果域名和端口都相同,但是请求路径不同,不属于跨域,如:
而我们刚才是从manage.leyou.com去访问api.leyou.com,这属于二级域名不同,跨域了。
二.为什么有跨域问题?
跨域不一定会有跨域问题。
因为跨域问题是浏览器对于ajax请求的一种安全限制:一个页面发起的ajax请求,只能是于当前页同域名的路径,这能有效的阻止跨站攻击。
因此:跨域问题 是针对ajax的一种限制。
但是这却给我们的开发带来了不变,而且在实际生成环境中,肯定会有很多台服务器之间交互,地址和端口都可能不同,怎么办?
三.解决跨域问题的方案
目前比较常用的跨域解决方案有3种:
- Jsonp
最早的解决方案,利用script标签可以跨域的原理实现。
限制:
- 需要服务的支持
- 只能发起GET请求
- nginx反向代理 思路是:利用nginx反向代理把跨域为不跨域,支持各种请求方式 缺点:需要在nginx进行额外配置,语义不清晰
- CORS
规范化的跨域请求解决方案,安全可靠。
优势:
- 在服务端进行控制是否允许跨域,可自定义规则
- 支持各种请求方式 缺点:
- 会产生额外的请求
我们这里会采用cors的跨域方案。
四.cors解决跨域
1.什么是cors
CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。
它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
- 浏览器端: 目前,所有浏览器都支持该功能(IE10以下不行)。整个CORS通信过程,都是浏览器自动完成,不需要用户参与。
- 服务端: CORS通信与AJAX没有任何差别,因此你不需要改变以前的业务逻辑。只不过,浏览器会在请求中携带一些头信息,我们需要以此判断是否运行其跨域,然后在响应头中加入一些信息即可。这一般通过过滤器完成即可。
2.原理有点复杂
浏览器会将ajax请求分为两类,其处理方案略有差异:简单请求、特殊请求。
简单请求
只要同时满足以下两大条件,就属于简单请求。:
(1) 请求方法是以下三种方法之一:
- HEAD
- GET
- POST
(2)HTTP的头信息不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
当浏览器发现发现的ajax请求是简单请求时,会在请求头中携带一个字段:Origin.
Origin中会指出当前请求属于哪个域(协议+域名+端口)。服务会根据这个值决定是否允许其跨域。
如果服务器允许跨域,需要在返回的响应头中携带下面信息:
Access-Control-Allow-Origin: http://manage.leyou.comAccess-Control-Allow-Credentials: trueContent-Type: text/html; charset=utf-8- Access-Control-Allow-Origin:可接受的域,是一个具体域名或者*,代表任意
- Access-Control-Allow-Credentials:是否允许携带cookie,默认情况下,cors不会携带cookie,除非这个值是true
注意:
如果跨域请求要想操作cookie,需要满足3个条件:
- 服务的响应头中需要携带Access-Control-Allow-Credentials并且为true。
- 浏览器发起ajax需要指定withCredentials 为true
- 响应头中的Access-Control-Allow-Origin一定不能为*,必须是指定的域名
特殊请求
不符合简单请求的条件,会被浏览器判定为特殊请求,,例如请求方式为PUT。
预检请求
特殊请求会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
一个“预检”请求的样板:
OPTIONS /cors HTTP/1.1Origin: http://manage.leyou.comAccess-Control-Request-Method: PUTAccess-Control-Request-Headers: X-Custom-HeaderHost: api.leyou.comAccept-Language: en-USConnection: keep-aliveUser-Agent: Mozilla/5.0...与简单请求相比,除了Origin以外,多了两个头:
- Access-Control-Request-Method:接下来会用到的请求方式,比如PUT
- Access-Control-Request-Headers:会额外用到的头信息
预检请求的响应
服务的收到预检请求,如果许可跨域,会发出响应:
HTTP/1.1 200 OKDate: Mon, 01 Dec 2008 01:15:39 GMTServer: Apache/2.0.61 (Unix)Access-Control-Allow-Origin: http://manage.leyou.comAccess-Control-Allow-Credentials: trueAccess-Control-Allow-Methods: GET, POST, PUTAccess-Control-Allow-Headers: X-Custom-HeaderAccess-Control-Max-Age: 1728000Content-Type: text/html; charset=utf-8Content-Encoding: gzipContent-Length: 0Keep-Alive: timeout=2, max=100Connection: Keep-AliveContent-Type: text/plain除了Access-Control-Allow-Origin和Access-Control-Allow-Credentials以外,这里又额外多出3个头:
- Access-Control-Allow-Methods:允许访问的方式
- Access-Control-Allow-Headers:允许携带的头
- Access-Control-Max-Age:本次许可的有效时长,单位是秒,过期之前的ajax请求就无需再次进行预检了
如果浏览器得到上述响应,则认定为可以跨域,后续就跟简单请求的处理是一样的了。
3.实现非常简单
虽然原理比较复杂,但是前面说过:
- 浏览器端都有浏览器自动完成,我们无需操心
- 服务端可以通过拦截器统一实现,不必每次都去进行跨域判定的编写。
事实上,SpringMVC已经帮我们写好了CORS的跨域过滤器:CorsFilter ,内部已经实现了刚才所讲的判定逻辑,我们直接用就好了。
在ly-api-gateway中编写一个配置类,并且注册CorsFilter:
import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.cors.CorsConfiguration;import org.springframework.web.cors.UrlBasedCorsConfigurationSource;import org.springframework.web.filter.CorsFilter;
@Configurationpublic class GlobalCorsConfig { @Bean public CorsFilter corsFilter() { //1.添加CORS配置信息 CorsConfiguration config = new CorsConfiguration(); //1) 允许的域,不要写*,否则cookie就无法使用了 config.addAllowedOrigin("http://manage.leyou.com"); //2) 是否发送Cookie信息 config.setAllowCredentials(true); //3) 允许的请求方式 config.addAllowedMethod("OPTIONS"); config.addAllowedMethod("HEAD"); config.addAllowedMethod("GET"); config.addAllowedMethod("PUT"); config.addAllowedMethod("POST"); config.addAllowedMethod("DELETE"); config.addAllowedMethod("PATCH"); // 4)允许的头信息 config.addAllowedHeader("*"); // 5)设置最大生命周期 config.setMaxAge(3600L); //2.添加映射路径,我们拦截一切请求 UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource(); configSource.registerCorsConfiguration("/**", config);
//3.返回新的CorsFilter. return new CorsFilter(configSource); }}如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时