持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第20天,点击查看活动详情
ControllerAdvice使用问题
ControllerAdvice、RestControllerAdvice是用注解方式捕获并处理异常,
问题
之前用着一直都没问题, 直到有个小伙伴(当然不是我啦!!!)写出了这样的代码
问题代码 TestController
@Slf4j
@RequestMapping("/test")
@RestController
@Import(value = {SpringUtil.class, DefaultExceptionHandlerAdviceBeanDefinitionRegistrar.class})
@RestControllerAdvice
public class TestController {
@Autowired
private HttpServletRequest httpServletRequest;
@ExceptionHandler(MyException.class)
public String handlerException(MyException e) {
log.error("error:{}", e.getMessage());
return "error";
}
@RequestMapping("/hello")
public void hello() {
// 这里引入了hutool的SpringUtil方便获取bean
TestController bean = SpringUtil.getBean(TestController.class);
System.out.println(bean);
System.out.println(httpServletRequest.getServletPath());
}
}
就是一个简单的Controller代码, 但是里面同时加了ExceptionHandler异常处理, 然后诡异的事情发生了, TestController的httpServletRequest属性是null, 访问http://localhost:8081/test/hello报空指针异常了
可以看出我还在这个类上加了aop的, 正常情况这个类应该是一个代理类, 但是这里可以看到实际在Spring容器中获取到的TestController是一个普通的bean
公司内部的封装的项目基础框架中有注册一个默认异常处理器DefaultExceptionHandlerAdviceBeanDefinitionRegistrar的代码, 大致如下:
DefaultExceptionHandlerAdviceBeanDefinitionRegistrar
public class DefaultExceptionHandlerAdviceBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
private static final Logger log = LoggerFactory.getLogger(DefaultExceptionHandlerAdviceBeanDefinitionRegistrar.class);
public DefaultExceptionHandlerAdviceBeanDefinitionRegistrar() {
}
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Map<String, Object> beansWithAnnotation = ((DefaultListableBeanFactory) registry).getBeansWithAnnotation(ControllerAdvice.class);
boolean anyMatch = beansWithAnnotation.entrySet().stream()
.flatMap((e) -> Arrays.stream(ReflectUtil.getMethods(e.getValue().getClass())))
.anyMatch((e) -> Optional.ofNullable(e.getDeclaredAnnotation(ExceptionHandler.class))
.map(ExceptionHandler::value)
.map((val) -> val[0])
.map((val) -> val == Exception.class)
.orElse(false));
if (anyMatch) {
log.info("加载自定义异常处理器");
} else {
log.info("加载默认异常处理器");
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
Class<DefaultExceptionHandlerAdvice> beanClass = DefaultExceptionHandlerAdvice.class;
beanDefinition.setBeanClass(beanClass);
registry.registerBeanDefinition(Introspector.decapitalize(ClassUtils.getShortName(beanClass)), beanDefinition);
}
}
}
DefaultExceptionHandlerAdvice
@RestControllerAdvice(annotations = {RestController.class, Controller.class})
public class DefaultExceptionHandlerAdvice {
private static final Logger log = LoggerFactory.getLogger(DefaultExceptionHandlerAdvice.class);
public DefaultExceptionHandlerAdvice() {
}
@ExceptionHandler({Exception.class})
public String defaultExceptionHandler(Exception ex) {
log.error(ex.getMessage(), ex);
// ...... 略
return "";
}
}
然而小伙伴并不知道有这个类的存在, 所以一脸懵逼, 我们把RestControllerAdvice注释掉再看一下
一切都正常了, 分析DefaultExceptionHandlerAdviceBeanDefinitionRegistrar的代码, 可以看出, 这个类中改变了ControllerAdvice标记的异常处理类的beanDefinition, 由于TestController有@RestControllerAdvice标记, 导致TestController的beanDefinition异常了
总结
在开发过程中, 我们不应该将异常处理和controller混在一起, 异常处理应该使用单独的类来处理