前后端交互使用的注解

(一)请求参数注解

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

输出结果:

Spring 前端页面如何找到后端 spring前后端交互_序列化

从输出结果可以看出,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);
  }

测试结果:

Spring 前端页面如何找到后端 spring前后端交互_Spring 前端页面如何找到后端_02