前后端交互使用的注解
(一)请求参数注解
1.@RequestParam(value=“XX”, required=true)
① GET和POST请求传的参数会自动转换赋值到@RequestParam 所注解的变量上;
② @RequestParam不添加任何参数默认值为变量名, required=true, 因此此时前端不传该变量,会报400错误:HTTP Status 400 - Required String parameter ‘XX’ is not present;
③ @RequestParam(required=false)与不添加该注解的效果类似;
示例代码:
(1)后端接收:
//主要用于测试,代码规范性不做讨论
@PosttMapping("/leader")
@ResponseBody
//相当于setLeader(@RequestParam("parentId",required=false) String parentId, @RequestParam("userid") String userId)
public ResultBody setLeader(String parentId, @RequestParam("userid") String userId) {
return userService.setLeader(parentId, userId);
}
(2)前端传值:
setLeader:function () {
$.ajax({
url: commonUrl.userBaseUrl + "/setLeader",
type: "post",
data:{
parentId:userItem.parentId,
userid:userItem.id//此处不是userId,userid需要和注解中声明的value一致
},
dataType: "json",
success: function (result) {
if(!!result && result.success){
$.success("设置成功");
}else{
$.error(result.note);
}
}
});
}
④可用于Get请求中参数的自动装箱
示例基础代码:
//VO
public class UserQueryCondition {
private String name;
private int age;
private int ageTo;//年龄范围
/**getters and setters**/
}
//Controller
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping
public void autoBoxing(UserQueryCondition userQueryCondition, Pageable pageable){
System.out.println(ReflectionToStringBuilder.toString(userQueryCondition, ToStringStyle.SHORT_PREFIX_STYLE));
System.out.println("大小:"+pageable.getPageSize()
+",页数:"+pageable.getPageNumber()
+",排序:"+pageable.getSort());
}
}
4.1)普通html请求链接访问
请求链接: http://localhost:8080/user?name=jojo&size=15
输出结果:
从输出结果可以看出,get请求的参数传递到后台,会将这些参数自动装箱成一个含这个属性(以这个参数命名)的对象,对象中其他未接收到传递的参数值的属性则为默认值。
4.2)用Feign构造接口
@FeignClient("user-api")
@RequestMapping("/user")
public class UserClient {
@GetMapping
void autoBoxing(String name, String size);
}
入参可以是对象的所有参数或者部分参数,通过feign调用controller的时候会进行自动装箱。
2.@RequestBody
- @RequestBody注解常用来处理content-type不是默认的application/x-www-form-urlcoded编码的内容,比如说:application/json或者是application/xml等。
一般情况下来说常用其来处理application/json类型,传入后台自动转化为java对象。 - GET方式无请求体,所以使用@RequestBody接收数据时,前端不能使用GET方式提交数据,而是用POST方式进行提交。
- 在后端的同一个接收方法里,@RequestBody与@RequestParam()可以同时使用,@RequestBody最多只能有一个,而@RequestParam()可以有多个。
示例代码:
(1)后端接收:
@RequestMapping(value = "/", method = RequestMethod.POST)
@ResponseBody
public ResultBody save(@RequestBody UserInfo userInfo) {
return userService.save(userInfo);
}
//java bean
//UserInfo(userName,userId);
(2)前端传值:
$.ajax({
url: commonUrl.userBaseUrl + "/",
type: "post",
data:JSON.stringify({userName:userItem.name,userId:userItem.id}),
dataType: "json",
Content-Type:"APPLICATION/JSON",
success: function (result) {
if(result && result.success){
$.success("保存用户信息成功");
}else{
$.error(result.note);
}
}
});
3.@PathVariable
- @PathVariable是spring3.0的一个新功能:接收请求路径中占位符的值
- @PathVariable对中文URL的使用需要注意
- @PathVariable能很好适配RestFul风格API
- @PathVariable建议加上属性,例如@PathVariable(“id”)
- 默认必传,改成非必传需要修改属性,@PathVariable(required = false)
示例代码:
(1)后端接收:
@GetMapping(value = "/{id}")
@ResponseBody
public Department get(@PathVariable("id") String id) {
return departmentService.get(id);
}
(2)前端传值:
$.ajax({
url: commonUrl.departmentBaseUrl + "/" + deptId,
type: "get",
contentType: "application/json;charset=utf-8",
dataType: "text",
async: false,
success: function (result) {
dosomething();
}
});
4.@ResponseBody
- @ResponseBody这个注解通常使用在控制层(controller)的方法上,其作用是将方法的返回值以特定的格式写入到response的body区域,进而将数据返回给客户端。
- 当方法上面没有写ResponseBody,底层会将方法的返回值封装为ModelAndView对象。直接返回一个字符串,则会默认去匹配资源文件。
示例代码:
/**简单写法**/
@RequestMapping("/index")
public String index() {
return "theme/index";//最后会去配置的资源文件目录下找index文件
}
/**ModelAndView写法**/
@RequestMapping(value="/editForm")
public ModelAndView editForm(Long uId, Model model){
String title = "修改用户信息";
User user = new User();
if(uId != null){
user = userService.findById(uId);
title = "新增用户信息";
}
model.addAttribute("user", user);
model.addAttribute("title", title);
return new ModelAndView("user/form","userModel", model);
}
/**@ResponseBody将数据返回前端,或是不返回数据**/
@DeleteMapping("/{id}")
@ResponseBody
public void deleteInfo(@PathVariable String id) {
themeClient.deleteInfo(id); //无返回值,或需要返回数据给前端需配置@ResponseBody
}
5.@RequestMapping
@RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
//GetMapping
@GetMapping("/{id}")
<==>
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
//PutMapping
@PutMapping("/")
<==>
@RequestMapping(value = "/", method = RequestMethod.PUT)
//PostMapping
@PostMapping("/")
<==>
@RequestMapping(value = "/", method = RequestMethod.POST)
//DeleteMapping
@DeleteMapping("/{id}")
<==>
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
6.@Controller 与 @RestController
- “@Controller”,表明某类是一个controller,用于接收http请求。
- @RestController注解相当于@ResponseBody + @Controller。
- 如果只是使用@RestController注解Controller,则Controller中的方法无法返回jsp页面,或者html,配置的视图解析器InternalResourceViewResolver不起作用,返回的内容就是Return 里的内容。
(二)视图相关注解
@JsonView、@JsonProperty、@JsonIgnore、@JsonFormat、@PageableDefault
- @JsonView:用来过滤序列化对象的属性,可以有选择的序列化对象,首先定义一个 View 类(或者接口),里面包含我们对要序列化的字段的定义,也可以将 View 类理解为一组标识。JsonView是可继承的。
- @JsonProperty:改变序列化json后的属性名称
- @JsonIgnore:用于属性或者方法上,可使序列化过程忽略该属性,生成的 json 不包含此属性
- @JsonFormat:用于属性或者方法上,可格式化日期属性的值。
- @PageableDefault:分页相关注解,搭配Pageable使用,设定一些分页相关参数的默认值。
示例代码:
实体类-User
import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Date;
//用户
public class User {
public interface UserSimpleView{}
public interface UserDetailView extends UserSimpleView{}
private String id;
private String name;
private String password;
//不JSON 序列化此属性
@JsonIgnore
private String nickname;
//改变序列化名称
@JsonProperty("userSexStr")
private String sex;
// 格式化日期属性
@JsonFormat(pattern = "yyyy-MM-dd")
private Date birthday;
@JsonView(UserSimpleView.class)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@JsonView(UserDetailView.class)//有继承关系,因此会显示name
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
/**省略一些getters 和 setters**/
}
控制器 - UserController
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping
public List<User> query(UserQueryCondition userQueryCondition,
@PageableDefault(page = 2,size = 10,sort = {"name,asc","age,desc"}) Pageable pageable){//@PageableDefault设定一些分页相关参数的默认值
//要在log里把对象里面的一些属性打印出来,一般我们都会去改写类的toString方法
/*ReflectionToStringBuilder主要是通过java 反射实现的属性拼接,
//能方便的实现类的toString方法,还能通过参数设置toString的展示样式(ToStringStyle)*/
System.out.println(ReflectionToStringBuilder.toString(userQueryCondition, ToStringStyle.SHORT_PREFIX_STYLE));
System.out.println("大小:"+pageable.getPageSize()+",页数:"+pageable.getPageNumber()+",排序:"+pageable.getSort());
List<User> users = new ArrayList<>();
User user1 = new User();
user1.setName("u1");
user1.setPassword("1");
user1.setGraduateDate(new Date());
user1.setNickname("uu");
user1.setSex("男");
User user2 = new User();
user2.setName("u2");
user2.setPassword("2");
users.add(user1);
users.add(user2);
return users;
}
@GetMapping("/{id:\\d+}")//id根据正则匹配,为1位及以上的数字
@JsonView(User.UserDetailView.class)
public User getUserInfo(@PathVariable("id") String id){
User user = new User();
user.setId(id);
user.setName("tom");
return user;
}
测试:
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserControllerTest {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void setup() {
//伪造的MVC环境
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
@Test
public void whenQuerySuccess() throws Exception {
String result = mockMvc.perform(get("/user")
.param("name", "lili").param("age", "18").param("ageTo", "60")
//分页相关参数Pageable对象会自动转换下列分页相关参数
.param("size", "15")
//.param("page", "3")
.param("sort", "age,desc")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk())
.andExpect(jsonPath("$.length()").value(3))
.andReturn().getResponse().getContentAsString();
System.out.println(result);
}
@Test
public void whenGetInfoSuccess() throws Exception{
String result = mockMvc.perform(get("/user/1").contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("tom"))
.andReturn().getResponse().getContentAsString();
System.out.println(result);
}
}
whenQuerySuccess输出结果:
UserQueryCondition[name=lili,age=18,ageTo=60]
大小:15,页数:2,排序:age: DESC
[{"id":null,"name":"u1","password":"1","birthday":null,"graduateDate":"2019-07-31","userSexStr":"男"},
{"id":null,"name":"u2","password":"2","birthday":null,"graduateDate":null,"userSexStr":null}]
上述输出结果说明:
①ReflectionToStringBuilder.toString能将对象完整打印出来而不用重写toString方法。
②@PageableDefault(page = 2,size = 10,sort = {“name,asc”,“age,desc”})只会给未传入的参数赋对应的值。
③@JsonProperty改变了序列化输出的参数名,@JsonFormat将日期类型转换为自己设置的展示形式。
whenGetInfoSuccess输出结果:
{"name":"tom","password":null}
由于在Controller的对应方法上声明了@JsonView(User.UserDetailView.class),也继承了UserSimpleView的属性,因此会输出name和password两个属性。
(三)校验注解
校验字段相关注解 | 功能 |
@NotNull | 值不能为空 |
@Null | 值必须为空 |
@Pattern(regex=) | 字符串必须匹配正则表达式 |
@Size(min=,max=) | 集合的元素数量必须在min和max之间 |
@CreditCardNumber() | 字符串必须是信用卡号(美国标准) |
@Email | 字符串必须是Email地址 |
@Length(min=,max=) | 字符串的长度介于min和max之间 |
@NotBlank | 字符串不为null、空串和空格 |
@NotEmpty | 字符串不为null,集合有元素 |
@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指定的值,可以进行字符串比较 |
@Degits(integer=,fraction=) | 数字检查,integer指定整数部分的最大长度,fraction指定小数部分的最大长度 |
@Future | 必须是未来的时间 |
@Past | 必须是过去的时间 |
@Max(value=) | 值必须小等于value。不能判断字符串。 |
@Min(value=) | 值必须大等于value。不能判断字符串。 |
@Valid: 提供验证对象的作用,搭配BindingResult可以将验证结果进行进一步处理。
示例代码:
自定义校验:- 自定义校验注解,自定义注解回调类
自定义校验注解需要满足以下两个条件:
①必须有一个注解@Constraint(validatedBy = MyConstraintValidator.class)传入处理校验逻辑的类。
②必须有下面三个方法。
//注解
@Target(value = {ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MyConstraintValidator.class)
public @interface MyConstraint {
String message();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
//处理类
public class MyConstraintValidator implements ConstraintValidator<MyConstraint, String> {
@Override
public void initialize(MyConstraint myConstraint){
System.out.println("init");
}
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
if("close".equals(s)){
return false;
}
return true;
}
}
实体类 - User:
import org.hibernate.validator.constraints.NotBlank;
import org.hibernate.validator.constraints.NotEmpty;
import javax.validation.constraints.Past;
import java.util.Date;
public class User {
private String id;
@MyConstraint(message = "该用户被禁用")
private String name;
@NotBlank(message = "密码不能为空!")
private String password;
@Past
private Date birthday;
/**getters and setters**/
}
控制器 - UserController:
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping
public User addUser(@Valid @RequestBody User user, BindingResult bindingResult){
//如果不加BindingResult参数,若不满足校验,会报400错误
//加了会继续执行代码,将校验结果封装进BindingResult
if(bindingResult.hasErrors()){
bindingResult.getAllErrors().stream().forEach(objectError ->
System.out.println(objectError.getDefaultMessage()));
}
System.out.println(user.getBirthday());
user.setId("1");
return user;
}
@PutMapping
public User updateUser(@Valid @RequestBody User user, BindingResult bindingResult){
//如果不加BindingResult参数,若不满足校验,会报400错误
//加了会继续执行代码,将校验结果封装进BindingResult
if(bindingResult.hasErrors()){
bindingResult.getAllErrors().stream().forEach(objectError ->{
FieldError fieldError = (FieldError)objectError;
System.out.println(fieldError.getField()+" "+objectError.getDefaultMessage());
});
}
System.out.println(user.getBirthday());
return user;
}
}
测试:
@Test
public void whenUpdateUserSuccess() throws Exception{
Date date = new Date();
date.setTime(System.currentTimeMillis()+50000);
String result = mockMvc.perform(put("/user").contentType(MediaType.APPLICATION_JSON_UTF8).content("{\"id\":\"1\",\"name\":\"close\",\"password\":null,\"birthday\":"+(System.currentTimeMillis()+50000000) +"}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value("1"))
.andReturn().getResponse().getContentAsString();
System.out.println(result);
}
测试结果: