1、@ControllerAdvice注解说明
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] assignableTypes() default {};
Class<? extends Annotation>[] annotations() default {};
}
1.1、内部注解说明
这段代码定义了一个名为 ControllerAdvice
的注解。让我们来逐个说明它的各个部分:
-
@Target({ElementType.TYPE})
: 这表示该注解可以应用在类上。因此,ControllerAdvice
注解可以用于标记类。 -
@Retention(RetentionPolicy.RUNTIME)
: 该注解说明在运行时可以通过反射获取到这个注解的信息。 -
@Documented
: 这表示当生成 Javadoc 文档时,注解会被包含在文档中。 -
@Component
: 由于@ControllerAdvice
是一个特殊类型的组件,因此使用了 Spring Framework 中的@Component
注解,表明这是一个Spring管理的组件。 -
@AliasFor("basePackages")
: 这个注解表示value()
属性和basePackages()
属性是互相别名,它们具有相同的作用。也就是说,你可以使用其中之一来指定要扫描的基础包。 -
String[] value() default {}
: 通过@AliasFor
表明value
属性是用来指定需要扫描的基础包路径。 -
String[] basePackages() default {}
: 通过@AliasFor
表明basePackages
属性也是用来指定需要扫描的基础包路径。 -
Class<?>[] basePackageClasses() default {}
: 允许以类的方式指定需要扫描的基础包路径。 -
Class<?>[] assignableTypes() default {}
: 允许以类的方式指定适用的控制器类型。 -
Class<? extends Annotation>[] annotations() default {}
: 允许指定需要扫描的控制器注解类型。
总的来说,ControllerAdvice
注解提供了一种方便的方式来定义全局控制器建议。通过指定需要扫描的基础包路径、适用的控制器类型以及需要扫描的控制器注解类型,可以集中处理应用程序中抛出的异常,并进行全局的预处理。
1.2、联合其它注解使用
@ControllerAdvice
注解可以与以下注解联合使用:
-
@ExceptionHandler
: 用于定义全局的异常处理方法。 -
@InitBinder
: 用于自定义数据绑定规则,通常用于日期格式化等操作。 -
@ModelAttribute
: 用于添加全局范围的模型属性,以便在每个请求的视图中都可用。
这些注解的结合使用可以让我们更方便地管理全局性的异常处理、数据绑定规则和模型属性,从而提高代码的复用性和整体的灵活性。
2、实战demo
2.1、项目目录说明
2.2、测试代码[Controller]
package com.example.demo.controller;
import com.example.demo.response.MidGroundResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author fyw
* @version 1.0
* @description: TODO
* @date 2024/1/22 17:21
*/
@RestController
@RequestMapping("/test1")
public class Test1Controller {
@RequestMapping("/test")
public MidGroundResult test() {
int i = 3 / 0;
return MidGroundResult.ok("");
}
}
package com.example.demo.controller;
import com.example.demo.response.MidGroundResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author fyw
* @version 1.0
* @description: TODO
* @date 2024/1/22 17:21
*/
@RestController
@RequestMapping("/test2")
public class Test2Controller {
@RequestMapping("/test")
public MidGroundResult test() {
int i = 3 / 0;
return MidGroundResult.ok("");
}
}
package com.example.demo.controller.group1;
import com.example.demo.response.MidGroundResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author fyw
* @version 1.0
* @description: TODO
* @date 2024/1/22 17:21
*/
@RestController
@RequestMapping("/test3")
public class Test3Controller {
@RequestMapping("/test")
public MidGroundResult test() {
int i = 3 / 0;
return MidGroundResult.ok("");
}
}
package com.example.demo.controller.group1;
import com.example.demo.response.MidGroundResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
/**
* @author fyw
* @version 1.0
* @description: TODO
* @date 2024/1/22 17:22
*/
@RestController
@RequestMapping("/test4")
public class Test4Controller {
@RequestMapping("/test")
public MidGroundResult test(Date date) {
return MidGroundResult.ok(date);
}
}
package com.example.demo.controller.group1;
import cn.hutool.json.JSONObject;
import com.example.demo.response.MidGroundResult;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
/**
* @author fyw
* @version 1.0
* @description: TODO
* @date 2024/1/22 17:22
*/
@RestController
@RequestMapping("/test5")
public class Test5Controller {
@RequestMapping("/test")
public MidGroundResult test(Date date) {
return MidGroundResult.ok(date);
}
/**
*
* @param modelMap 全局参数
* @param jsonObject 具体的业务参数
* @return
*/
@PostMapping("/test1")
public MidGroundResult test(ModelMap modelMap, @RequestBody JSONObject jsonObject) {
Object globalAttr = modelMap.getAttribute("globalAttr");
Object map = modelMap.getAttribute("map");
jsonObject.set("globalAttr",globalAttr);
jsonObject.set("map",map);
return MidGroundResult.ok(jsonObject);
}
}
2.2、异常处理
2.2.1、全局异常类
package com.example.demo.exception;
import com.example.demo.response.MidGroundResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @author fyw
* @version 1.0
* @description: TODO
* @date 2024/1/23 13:35
*/
@Slf4j
@ControllerAdvice
@Order(value = 0)
public class GobalException {
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public MidGroundResult<RuntimeException> runTimeExcepetion(RuntimeException ex){
log.error("{}全局异常处理器","GobalException",ex);
return MidGroundResult.error("全局异常处理器");
}
@ExceptionHandler(Exception.class)
@ResponseBody
public MidGroundResult excetion(RuntimeException ex){
log.error("{}全局异常处理器","GobalException",ex);
return MidGroundResult.error("全局异常处理器");
}
}
2.2.2、指定包异常捕获类
package com.example.demo.exception;
import com.example.demo.response.MidGroundResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @author fyw
* @version 1.0
* @description: 仅在某一个包生效【注意order对应的值可以介于全局和指定类之间】
* basePackageClasses = {Test1Controller.class}代表这个类所在的包和子包下面的controller都会被捕获
* value/basePackages = {"com.example.demo.controller.group1"}代表这个包和子包下面的controller都会被捕获
* @date 2024/1/23 13:35
*/
@Slf4j
//@ControllerAdvice(basePackageClasses = {Test1Controller.class})
@ControllerAdvice(value = {"com.example.demo.controller.group1"})
@Order(value = 2)
public class SepcialPackagelException {
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public MidGroundResult<RuntimeException> runTimeExcepetion(RuntimeException ex){
log.error("{},{}","SepcialPackagelException",ex);
return MidGroundResult.error(ex);
}
@ExceptionHandler(Exception.class)
@ResponseBody
public MidGroundResult excetion(RuntimeException ex){
log.error("{},{}","SepcialPackagelException",ex);
return MidGroundResult.error("");
}
}
2.2.3、指定注解异常捕获类
package com.example.demo.exception;
import com.example.demo.response.MidGroundResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
/**
* @author fyw
* @version 1.0
* @description: 仅针对某一类注解生效【注意order对应的值可以介于全局和指定类之间】
* annotations = {RestController.class}将会扫描所有被 @RestController 注解标记的控制器类,然后应用在这些类上
* @date 2024/1/23 13:35
*/
@Slf4j
@ControllerAdvice(annotations = {RestController.class})
@Order(value = 101)
public class SepcialAnnotationException {
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public MidGroundResult<RuntimeException> runTimeExcepetion(RuntimeException ex){
log.error("{}注解过滤器","SepcialAnnotationException",ex);
return MidGroundResult.error("注解过滤器");
}
@ExceptionHandler(Exception.class)
@ResponseBody
public MidGroundResult excetion(RuntimeException ex){
log.error("{}注解过滤器","SepcialAnnotationException",ex);
return MidGroundResult.error("注解过滤器");
}
}
2.2.4、指定某个类的异常捕获类
package com.example.demo.exception;
import com.example.demo.controller.Test1Controller;
import com.example.demo.response.MidGroundResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @author fyw
* @version 1.0
* @description: 仅在某一个类上生效【注意order对应的值可以设置为最小】
* @date 2024/1/23 13:35
*/
@Slf4j
@ControllerAdvice(assignableTypes = {Test1Controller.class})
@Order(value = 1)
public class SepcialClasslException {
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public MidGroundResult<RuntimeException> runTimeExcepetion(RuntimeException ex){
log.error("{}针对Test1Controller类淡出捕获的异常,不会走全局异常捕获类","SepcialClasslException",ex);
return MidGroundResult.error("针对Test1Controller类淡出捕获的异常,不会走全局异常捕获类");
}
@ExceptionHandler(Exception.class)
@ResponseBody
public MidGroundResult excetion(RuntimeException ex){
log.error("{}针对Test1Controller类淡出捕获的异常,不会走全局异常捕获类","SepcialClasslException",ex);
return MidGroundResult.error("针对Test1Controller类淡出捕获的异常,不会走全局异常捕获类");
}
}
2.3、测试场景
2.3.1、将GobalException异常处理器的order顺序优先级调整为最高0,进行异常捕获测试
<span style="background-color:#f8f8f8"><span style="color:#333333">测试结果是无论在controller目录还是group1目录下的controller类都是走的全局过滤器;
</span></span>
2.3.2、注解异常捕获类测试,将SepcialAnnotationException的order调为0,GobalException调为100
<span style="background-color:#f8f8f8"><span style="color:#333333">测试结果是无论在controller目录还是group1目录下的controller类都是走的注解过滤器,因为controller上面都有@RestController注解;
</span></span>
2.3.3、指定包异常捕获类测试,将SepcialAnnotationException的order调整为101,SepcialPackagelException的order调整为0
<span style="background-color:#f8f8f8"><span style="color:#333333">测试结果test3在group1包下面吗,走的是包过滤器;test2在controller包下面走的是全局过滤器
</span></span>
2.3.4、在2.3.3的基础上测试test1应该走的是SepcialClasslException,因为SepcialClasslException上指定了具体的Test1Controller类,并且order小于全局异常捕获器和注解异常捕获器
<span style="background-color:#f8f8f8"><span style="color:#333333">由以上测试场景,就可以在项目里面随心所欲的定义异常处理器了;
</span></span>
2.4、全局数据绑定
2.4.1、全局数据绑定类
package com.example.demo.bind;
import com.example.demo.controller.group1.Test5Controller;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author fyw
* @version 1.0
* @description: 仅作用于Test5Controller类
* @date 2024/1/23 16:25
*/
@ControllerAdvice(assignableTypes = {Test5Controller.class})
public class InitBinderController {
@InitBinder
public void initBinder(WebDataBinder binder) {
System.out.println("进入initBinder方法");
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
}
}
测试结果如下:
2.5、全局模型属性
2.5.1、全局模型属性添加类
package com.example.demo.model;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
import java.util.HashMap;
import java.util.Map;
/**
* @author fyw
* @version 1.0
* @description: TODO
* @date 2024/1/23 16:37
*/
@ControllerAdvice
public class ModelVController {
@ModelAttribute
public void presetParam(Model model){
model.addAttribute("globalAttr","this is a global attribute");
}
@ModelAttribute("map")
public Map<String, String> presetParam(){
Map<String, String> map = new HashMap<String, String>();
map.put("key1", "value1");
map.put("key2", "value2");
map.put("key3", "value3");
return map;
}
}
测试结果:
2.6、公用类代码
package com.example.demo.response;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import com.example.demo.enums.MidGroundEnum;
import lombok.Data;
import java.time.LocalDateTime;
/**
* @author fyw
* @version 1.0
* @description:
* @date 2024/1/16 8:53
*/
@Data
public class MidGroundResult<T> {
//返回码,00000成功
private String errorCode;
//返回值,说明信息
private String errorMessage;
//返回数据
private T data;
//业务响应时间 2023-05-25 18:18:18
private String resultTime;
//排查id
private String traceId;
public MidGroundResult(String errorCode, String errorMessage, T data, String resultTime, String traceId) {
this.errorCode = errorCode;
this.errorMessage = errorMessage;
this.data = data;
this.resultTime = resultTime;
this.traceId = traceId;
}
public String getErrorCode() {
return errorCode;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public String getResultTime() {
return resultTime;
}
public void setResultTime(String resultTime) {
this.resultTime = resultTime;
}
public String getTraceId() {
return traceId;
}
public void setTraceId(String traceId) {
this.traceId = traceId;
}
//正常响应
public static <T> MidGroundResult ok(T data) {
return new MidGroundResult(MidGroundEnum.SUCCESS.getCode(), MidGroundEnum.SUCCESS.getMsg(), data,
DateUtil.format(LocalDateTime.now(), DatePattern.NORM_DATETIME_PATTERN),
IdUtil.getSnowflake(1, 1).nextIdStr());
}
//系统异常
public static <T> MidGroundResult error(T data) {
return new MidGroundResult(MidGroundEnum.ERROR.getCode(), MidGroundEnum.ERROR.getMsg(), data,
DateUtil.format(LocalDateTime.now(), DatePattern.NORM_DATETIME_PATTERN),
IdUtil.getSnowflake(1, 1).nextIdStr());
}
//主动性返回异常信息-校验不通过等
public static <T> MidGroundResult fail(T data, String code, String msg) {
return new MidGroundResult(code, msg, data,
DateUtil.format(LocalDateTime.now(), DatePattern.NORM_DATETIME_PATTERN),
IdUtil.getSnowflake(1, 1).nextIdStr());
}
}
package com.example.demo.enums;
/**
* @Author fyw
* @Description
* @Date 8:59 2024/1/16
* @Param
* @return
**/
public enum MidGroundEnum {
SUCCESS("00000", "成功"),
INTERNAL_CALL_ERROR("C1001", "内部服务调用错误"),
ERROR("B1001", "系统错误");
private final String code;
private final String msg;
MidGroundEnum(String code, String msg) {
this.code = code;
this.msg = msg;
}
public String getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
添加公众号了解更多,定期分享、绝对实用,绝对对你有帮助!