1、@RestController 和@Controller的区别
@Controller
1.使用@Controller注解,返回对应的页面
@Controller
public class UserController {
@Resource
private IUserService userService;
@RequestMapping("/userLogin")
public String userLogin(@Param("userName") String userName){
return "success";
}
}
2.在方法上加上@ResponseBody注解,返回json格式的数据。
@Controller
public class UserController {
@Resource
private IUserService userService;
@RequestMapping("/getDepts")
@ResponseBody
public List<Department> getDepts(){
List<Department> depts=userService.findAllDepts();
return depts;
}
}
@RestController
@RestController == @Controller + @ResponseBody 所以使用@RestController 只能给前端返回json结构的数据。
2、前后端分离的协议交互
在前后端分离的项目中,无论后端返回给前端的是成功,是失败,还是包含了数据,都要求需要执行一个统一的后端返回给前端的数据结构,通常是json结构。
在此提供两种方式,但是思想相同,都需要定义的对象里 有三个属性:1、code :返回值是处理成功还是失败 2、message :成功或者失败的信息内容。 3、引用类型:通常是泛型或者Object类型,所以返回的数据可以是任意数据类型,如 String、List、实例对象等。
1、泛型类型
1、统一返回类型的定义
@Data
public class R<T> {
private Integer code;
private String msg;
private T data;
private Map map = new HashMap();
public static <T> R<T> success(T object) {
R<T> r = new R<T>();
r.data = object;
r.code = 1;
return r;
}
public static <T> R<T> error(String msg) {
R r = new R();
r.msg = msg;
r.code = 0;
return r;
}
public R<T> add(String key, Object value) {
this.map.put(key, value);
return this;
}
}
解释: 对于需要泛型的返回值类型,在定义静态类型的方法时,需要在static 关键字后面加 <T`>,普通方法不需要这样的做法,就是正常的返回值类型。
2、控制层的代码
@PostMapping("/logout")
public R<String> logout(HttpServletRequest request){
request.getSession().removeAttribute("employee");
return R.success("退出成功");
}
@PostMapping("/login")
public R<Employee> login(HttpServletRequest request,@RequestBody Employee employee){
String password = employee.getPassword();
password = DigestUtils.md5DigestAsHex(password.getBytes());
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Employee::getUsername,employee.getUsername());
Employee emp = employeeService.getOne(queryWrapper);
if(emp == null){
return R.error("登录失败");
}
if(!emp.getPassword().equals(password)){
return R.error("登录失败");
}
if(emp.getStatus() == 0){
return R.error("账号已禁用");
}
request.getSession().setAttribute("employee",emp.getId());
return R.success(emp);
}
解释: 可以看到控制器的返回值类型,是根据方法最终的具体泛型,设置的返回值泛型,此处有一个优化就是用问号代替具体的泛型,这样就可以统一规范,不用动态修改了。
@PostMapping("/logout")
public R<?> logout(HttpServletRequest request){
request.getSession().removeAttribute("employee");
return R.success("退出成功");
}
2、Object类型
object 类型是所有引用类型的父类就意味着,任何类型都可以作为该属性类型。
public class RespBean {
private long code;
private String message;
private Object object;
public static RespBean success() {
return new RespBean(RespBeanEnum.SUCCESS.getCode(), RespBeanEnum.SUCCESS.getMessage(), null);
}
public static RespBean success(Object object) {
return new RespBean(RespBeanEnum.SUCCESS.getCode(), RespBeanEnum.SUCCESS.getMessage(), object);
}
public static RespBean error(RespBeanEnum respBeanEnum) {
return new RespBean(respBeanEnum.getCode(), respBeanEnum.getMessage(), null);
}
public static RespBean error(RespBeanEnum respBeanEnum, Object object) {
return new RespBean(respBeanEnum.getCode(), respBeanEnum.getMessage(), object);
}
public RespBean(long code, String message, Object object) {
this.code = code;
this.message = message;
this.object = object;
}
public RespBean(){
}
public long getCode() {
return code;
}
public void setCode(long code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
}
控制器代码
@RestController
@RequestMapping("/order")
@Api(value = "订单", tags = "订单")
public class TOrderController {
@Autowired
private ITOrderService itOrderService;
@ApiOperation("订单")
@RequestMapping(value = "/detail", method = RequestMethod.GET)
@ResponseBody
public RespBean detail(TUser tUser, Long orderId) {
if (tUser == null) {
return RespBean.error(RespBeanEnum.SESSION_ERROR);
}
OrderDeatilVo orderDeatilVo = itOrderService.detail(orderId);
return RespBean.success(orderDeatilVo);
}
}
3、通过注解的方式限制数据格式
1、实体类的属性上,使用注解步骤 (1)pom 引入jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
(2)把注解放到需要限制的类的属性上面(@NotNull)
@Data
public class Person {
@NotNull
private String name;
private String phone;
}
(3)控制器接收对象时,使用注解(@Valid)
@Controller
@RequestMapping("/login")
@Slf4j
public class LoginController {
@RequestMapping(value = "/doLogin", method = RequestMethod.POST)
@ResponseBody
public String doLogin(@Valid @RequestBody Person loginVo, HttpServletRequest request, HttpServletResponse response) {
log.info("{}", loginVo);
return "success";
}
}
总结:完成上述的三个步骤,当请求到达控制器之后,会通过@Valid 进而通过@NotNull 会对属性值name 做限制,不能为null。
4、效仿@NotNull 自定义注解做属性值限制
效仿 就是在自己使用@NotNull 的版本里面,使用该类的修饰符@Target、@Retention、@Repeatable、@Documented、@Constraint
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(NotNull.List.class)
@Documented
@Constraint(
validatedBy = {}
)
public @interface NotNull {
String message() default "{javax.validation.constraints.NotNull.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface List {
NotNull[] value();
}
}
下面就是把需要效仿的共性东西(注解 ),写到自定义注解类上面。
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(
validatedBy = {
IsMobileValidator.class
}
)
public @interface IsPhone {
boolean required() default true;
String message() default "手机号码格式错误";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
同时自定义的注解,可以给@Constraint 填充校验处理器,实现ConstraintValidator,重写initialize、isValid方法,并且先执行initialize才会执行 isValid方法,
public class IsMobileValidator implements ConstraintValidator<IsPhone, String> {
private boolean required = false;
@Override
public void initialize(IsPhone constraintAnnotation) {
required = constraintAnnotation.required();
}
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
if (required) {
return ValidatorUtil.isMobile(s);
} else {
if (StringUtils.isEmpty(s)) {
return true;
} else {
return ValidatorUtil.isMobile(s);
}
}
}
}
public class ValidatorUtil {
private static final Pattern mobile_patten = Pattern.compile("[1]([3-9])[0-9]{9}$");
public static boolean isMobile(String mobile) {
if (StringUtils.isEmpty(mobile)) {
return false;
}
Matcher matcher = mobile_patten.matcher(mobile);
return matcher.matches();
}
}
当 在自定义方法校验失败之后,会像@NotNull 校验失败之后,抛出 绑定异常。
@NotNull 校验失败异常
2022-10-21 01:15:21.866 WARN 145400 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public java.lang.String com.wangxg.annotate.controller.LoginController.doLogin(com.wangxg.annotate.entity.Person,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse): [Field error in object 'person' on field 'name': rejected value [null]; codes [NotNull.person.name,NotNull.name,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [person.name,name]; arguments []; default message [name]]; default message [不能为null]] ]
@IsPhone 校验失败异常
2022-10-21 01:16:57.241 WARN 145400 --- [nio-8080-exec-5] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public java.lang.String com.wangxg.annotate.controller.LoginController.doLogin(com.wangxg.annotate.entity.Person,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse): [Field error in object 'person' on field 'phone': rejected value [183413496121]; codes [IsPhone.person.phone,IsPhone.phone,IsPhone.java.lang.String,IsPhone]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [person.phone,phone]; arguments []; default message [phone],true]; default message [手机号码格式错误]] ]
5、自定义异常
异常定义
public class GlobalException extends RuntimeException {
private RespBeanEnum respBeanEnum;
public RespBeanEnum getRespBeanEnum() {
return respBeanEnum;
}
public void setRespBeanEnum(RespBeanEnum respBeanEnum) {
this.respBeanEnum = respBeanEnum;
}
public GlobalException(RespBeanEnum respBeanEnum) {
this.respBeanEnum = respBeanEnum;
}
}
抛出自定义异常
@Override
public OrderDeatilVo detail(Long orderId) {
if (orderId == null) {
throw new GlobalException(RespBeanEnum.ORDER_NOT_EXIST);
}
TOrder tOrder = tOrderMapper.selectById(orderId);
GoodsVo goodsVobyGoodsId = itGoodsService.findGoodsVobyGoodsId(tOrder.getGoodsId());
OrderDeatilVo orderDeatilVo = new OrderDeatilVo();
orderDeatilVo.setTOrder(tOrder);
orderDeatilVo.setGoodsVo(goodsVobyGoodsId);
return orderDeatilVo;
}
6、异常捕捉处理
这里指的异常捕捉处理,指的是通过控制器层向上抛出的异常,如400、500这样的返回值。这样的返回值一是不友好,二是无法正确引导请求方。
{
"timestamp": "2022-10-20T17:16:57.243+00:00",
"status": 400,
"error": "Bad Request",
"path": "/login/doLogin"
}
有两个阶段的异常捕捉,进入控制器后和进入控制器前。 进入控制器前可以使用errorController来实现,这里不细说。 本次讲实际遇到的进入控制器后的异常抛出的异常处理。
这里用到的是 @RestControllerAdvice + @ExceptionHandler(value = Exception.class)的方式
@RestControllerAdvice
public class ExecptionHandler {
@ExceptionHandler(value = Exception.class)
public String exceptionHandler(Exception e) {
if (e instanceof BindException) {
return ((BindException) e).getBindingResult().getAllErrors().get(0).getDefaultMessage();
} else if (e instanceof GlobolException) {
return e.getMessage();
} else {
return "success";
}
}
}
解释: 1、@ExceptionHandler(value = Exception.class) 表示,所有的异常 都会被捕捉到,可以看到 监测到上一个小节抛出的绑定异常,BindException,并且将Message返回给客户端。 2、但是我们在前后分离场景下,要用固定的json格式来给客户端做返回,所以使用Object类型的对象 来做优化。
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public RespBean ExceptionHandler(Exception e) {
if (e instanceof GlobalException) {
GlobalException exception = (GlobalException) e;
return RespBean.error(exception.getRespBeanEnum());
} else if (e instanceof BindException) {
BindException bindException = (BindException) e;
RespBean respBean = RespBean.error(RespBeanEnum.BIND_ERROR);
respBean.setMessage("参数校验异常:" + bindException.getBindingResult().getAllErrors().get(0).getDefaultMessage());
return respBean;
}
System.out.println("异常信息" + e);
return RespBean.error(RespBeanEnum.ERROR);
}
}
RespBean 的静态方法
public static RespBean error(RespBeanEnum respBeanEnum) {
return new RespBean(respBeanEnum.getCode(), respBeanEnum.getMessage(), null);
}
这样 前端就可以直接解析message 属性里面的值做前端展示
|