springboot 项目的控制器(controller端)的返回值、自定义注解、自定义异常、异常捕捉处理

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";//返回对应的名为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; //编码:1成功,0和其它数字为失败

    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){
        //清理Session中保存的当前登录员工的id
        request.getSession().removeAttribute("employee");
        return R.success("退出成功");
    }
   @PostMapping("/login")
    public R<Employee> login(HttpServletRequest request,@RequestBody Employee employee){

        //1、将页面提交的密码password进行md5加密处理
        String password = employee.getPassword();
        password = DigestUtils.md5DigestAsHex(password.getBytes());

        //2、根据页面提交的用户名username查询数据库
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Employee::getUsername,employee.getUsername());
        Employee emp = employeeService.getOne(queryWrapper);

        //3、如果没有查询到则返回登录失败结果
        if(emp == null){
            return R.error("登录失败");
        }

        //4、密码比对,如果不一致则返回登录失败结果
        if(!emp.getPassword().equals(password)){
            return R.error("登录失败");
        }

        //5、查看员工状态,如果为已禁用状态,则返回员工已禁用结果
        if(emp.getStatus() == 0){
            return R.error("账号已禁用");
        }

        //6、登录成功,将员工id存入Session并返回登录成功结果
        request.getSession().setAttribute("employee",emp.getId());
        return R.success(emp);
    }

解释:可以看到控制器的返回值类型,是根据方法最终的具体泛型,设置的返回值泛型,此处有一个优化就是用问号代替具体的泛型,这样就可以统一规范,不用动态修改了。

 @PostMapping("/logout")
    public R<?> logout(HttpServletRequest request){
        //清理Session中保存的当前登录员工的id
        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) {
//        ConstraintValidator.super.initialize(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}$");

    /**
     * 手机号码校验
     * @author LC
     * @operation add
     * @date 2:19 下午 2022/3/2
     * @param mobile
     * @return boolean
     **/
    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、自定义异常

异常定义
第一种方式:异常放到对象当中,当捕捉通过 @RestControllerAdvice + @ExceptionHandler(value = Exception.class)的方式 捕捉到异常后,通过获取异常对象当中的对象的属性,获取异常的Message。

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

第二种异常定义

第二种不需要把异常内容传给对象,而是传给运行时异常这个父类

/**
 * 自定义业务异常类
 */
public class CustomException extends RuntimeException {
    public CustomException(String message){
        super(message);
    }
}

当获取异常的时候,直接从异常类中get 异常信息

    /**
     * 异常处理方法
     * @return
     */
    @ExceptionHandler(CustomException.class)
    public R<String> exceptionHandler(CustomException ex){
        log.error(ex.getMessage());

        return R.error(ex.getMessage());
    }

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 属性里面的值做前端展示

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值