第二十四讲-@ControllerAdvice
第二十四讲-@ControllerAdvise
@ControllerAdvice是对所有控制器做一些功能增强,它能提供哪些方面的功能增强呢?其实一共有三种:
- 一种是
@ExceptionHandler
:将来控制器抛出异常了,由@ExceptionHandler来处理-->加在方法上 - 一种是
@ModelAttribute
:这个方法的返回值会作为模型数据补充到控制器的执行过程中-->加在方法上 - 一种是
@InitBinder
:所有控制器一个自定义类型转换器的时候我们可以利用你@InitBinder补充自定义转换器
本讲我们主要讲一下@InitBinder的用法,其它两个我们在后面再探讨。
可能有人会问,这个@ControllerAdvice和我们前面分析过的AOP中的那套理论相同吗?其实,@ControllerAdvice和AOP没有半毛钱关系!@ControllerAdvice只是在名字上借鉴了AOP的那些理论而已!
接下来我们看一下@InitBinder可以用在哪里?-->可以用在@ControllerAdvice(对所有控制器都生效)和@Controller(仅对该控制器生效)中
@Configuration
public class WebConfig {
@ControllerAdvice
static class MyControllerAdvice {
@InitBinder // 作用在@ControllerAdvice中全局生效
public void binder3(WebDataBinder webDataBinder) {
webDataBinder.addCustomFormatter(new MyDateFormatter("binder3 转换器"));
}
}
@Controller
static class Controller1 {
@InitBinder
public void binder1(WebDataBinder webDataBinder) {
webDataBinder.addCustomFormatter(new MyDateFormatter("binder1 转换器"));
}
public void foo() {
}
}
@Controller
static class Controller2 {
@InitBinder // 作用在@Controller中,仅该Controller生效
public void binder21(WebDataBinder webDataBinder) {
webDataBinder.addCustomFormatter(new MyDateFormatter("binder21 转换器"));
}
@InitBinder
public void binder22(WebDataBinder webDataBinder) {
webDataBinder.addCustomFormatter(new MyDateFormatter("binder22 转换器"));
}
public void bar() {
}
}
}
其中MyDateFormatter
代码如下:
public class MyDateFormatter implements Formatter<Date> {
private static final Logger log = LoggerFactory.getLogger(MyDateFormatter.class);
private final String desc;
public MyDateFormatter(String desc) {
this.desc = desc;
}
@Override
public String print(Date date, Locale locale) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
return sdf.format(date);
}
@Override
public Date parse(String text, Locale locale) throws ParseException {
log.debug(">>>>>> 进入了: {}", desc);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy|MM|dd");
return sdf.parse(text);
}
}
编写主方法测试:
package com.cherry.a24;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.web.method.ControllerAdviceBean;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/*
@InitBinder 的来源
*/
public class A24 {
private static final Logger log = LoggerFactory.getLogger(A24.class);
public static void main(String[] args) throws Exception {
/*
@InitBinder 的来源有两个
1. @ControllerAdvice 中 @InitBinder 标注的方法,由 RequestMappingHandlerAdapter 在初始化时解析并记录
2. @Controller 中 @InitBinder 标注的方法,由 RequestMappingHandlerAdapter 会在控制器方法首次执行时解析并记录
*/
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(WebConfig.class);
RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();
handlerAdapter.setApplicationContext(context);
handlerAdapter.afterPropertiesSet();
log.debug("1. 刚开始...");
showBindMethods(handlerAdapter);
Method getDataBinderFactory = RequestMappingHandlerAdapter.class.getDeclaredMethod("getDataBinderFactory", HandlerMethod.class);
getDataBinderFactory.setAccessible(true);
log.debug("2. 模拟调用 Controller1 的 foo 方法时 ...");
getDataBinderFactory.invoke(handlerAdapter, new HandlerMethod(new WebConfig.Controller1(), WebConfig.Controller1.class.getMethod("foo")));
showBindMethods(handlerAdapter);
log.debug("3. 模拟调用 Controller2 的 bar 方法时 ...");
getDataBinderFactory.invoke(handlerAdapter, new HandlerMethod(new WebConfig.Controller2(), WebConfig.Controller2.class.getMethod("bar")));
showBindMethods(handlerAdapter);
context.close();
/*
学到了什么
a. Method 对象的获取利用了缓存来进行加速
b. 绑定器工厂的扩展点(advice 之一), 通过 @InitBinder 扩展类型转换器
*/
}
@SuppressWarnings("all")
private static void showBindMethods(RequestMappingHandlerAdapter handlerAdapter) throws NoSuchFieldException, IllegalAccessException {
Field initBinderAdviceCache = RequestMappingHandlerAdapter.class.getDeclaredField("initBinderAdviceCache");
initBinderAdviceCache.setAccessible(true);
Map<ControllerAdviceBean, Set<Method>> globalMap = (Map<ControllerAdviceBean, Set<Method>>) initBinderAdviceCache.get(handlerAdapter);
log.debug("全局的 @InitBinder 方法 {}",
globalMap.values().stream()
.flatMap(ms -> ms.stream().map(m -> m.getName()))
.collect(Collectors.toList())
);
Field initBinderCache = RequestMappingHandlerAdapter.class.getDeclaredField("initBinderCache");
initBinderCache.setAccessible(true);
Map<Class<?>, Set<Method>> controllerMap = (Map<Class<?>, Set<Method>>) initBinderCache.get(handlerAdapter);
log.debug("控制器的 @InitBinder 方法 {}",
controllerMap.entrySet().stream()
.flatMap(e -> e.getValue().stream().map(v -> e.getKey().getSimpleName() + "." + v.getName()))
.collect(Collectors.toList())
);
}
}
10:24:39.224 [main] DEBUG com.cherry.a24.A24 - 1. 刚开始...
10:24:39.226 [main] DEBUG com.cherry.a24.A24 - 全局的 @InitBinder 方法 [binder3]
10:24:39.229 [main] DEBUG com.cherry.a24.A24 - 控制器的 @InitBinder 方法 []
10:24:39.231 [main] DEBUG com.cherry.a24.A24 - 2. 模拟调用 Controller1 的 foo 方法时 ...
10:24:39.236 [main] DEBUG com.cherry.a24.A24 - 全局的 @InitBinder 方法 [binder3]
10:24:39.239 [main] DEBUG com.cherry.a24.A24 - 控制器的 @InitBinder 方法 [Controller1.binder1]
10:24:39.239 [main] DEBUG com.cherry.a24.A24 - 3. 模拟调用 Controller2 的 bar 方法时 ...
10:24:39.240 [main] DEBUG com.cherry.a24.A24 - 全局的 @InitBinder 方法 [binder3]
10:24:39.240 [main] DEBUG com.cherry.a24.A24 - 控制器的 @InitBinder 方法 [Controller1.binder1, Controller2.binder21, Controller2.binder22]
我们此外还发现了全局@InitBinder优先于局部@InitBinder执行!
分类:
Spring 高级49讲
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构