Spring?MVC注解@ControllerAdvice的工作原理解讀
Spring MVC中,通過組合使用注解@ControllerAdvice和其他一些注解,我們可以為開發(fā)人員實(shí)現(xiàn)的控制器類做一些全局性的定制,具體來(lái)講,可作如下定制 :
- 結(jié)合
@ExceptionHandler使用 ==> 添加統(tǒng)一的異常處理控制器方法 - 結(jié)合
@ModelAttribute使用 ==> 使用共用方法添加渲染視圖的數(shù)據(jù)模型屬性 - 結(jié)合
@InitBinder使用 ==> 使用共用方法初始化控制器方法調(diào)用使用的數(shù)據(jù)綁定器
數(shù)據(jù)綁定器涉及到哪些參數(shù)/屬性需要/不需要綁定,設(shè)置數(shù)據(jù)類型轉(zhuǎn)換時(shí)使用的PropertyEditor,Formatter等。
那么,@ControllerAdvice的工作原理又是怎樣的呢 ?這篇文章,我們就一探究竟。
注解@ControllerAdvice是如何被發(fā)現(xiàn)的 ?
首先,容器啟動(dòng)時(shí),會(huì)定義類型為RequestMappingHandlerAdapter的bean組件,這是DispatcherServlet用于執(zhí)行控制器方法的HandlerAdapter,它實(shí)現(xiàn)了接口InitializingBean,所以自身在初始化時(shí)其方法#afterPropertiesSet會(huì)被調(diào)用執(zhí)行。
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBody advice beans
initControllerAdviceCache();
// 省略掉無(wú)關(guān)代碼
// ...
}
從以上代碼可以看出,RequestMappingHandlerAdapter bean組件在自身初始化時(shí)調(diào)用了#initControllerAdviceCache,從這個(gè)方法的名字上就可以看出,這是一個(gè)ControllerAdvice相關(guān)的初始化函數(shù),而#initControllerAdviceCache具體又做了什么呢?
我們繼續(xù)來(lái)看 :
private void initControllerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
// 找到所有使用了注解 @ControllerAdvice 的bean組件
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
// 排序
AnnotationAwareOrderComparator.sort(adviceBeans);
// this. requestResponseBodyAdvice :
// 用于記錄所有 @ControllerAdvice + RequestBodyAdvice/ResponseBodyAdvice bean
// this.modelAttributeAdviceCache :
// 用于記錄所有 @ControllerAdvice bean組件中的 @ModuleAttribute 方法
// this.initBinderAdviceCache :
// 用于記錄所有 @ControllerAdvice bean組件中的 @InitBinder 方法
// 用于臨時(shí)記錄所有 @ControllerAdvice + RequestResponseBodyAdvice bean
List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();
// 遍歷每個(gè)使用了注解 @ControllerAdvice 的 bean 組件
for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
// 獲取當(dāng)前 ControllerAdviceBean 中所有使用了 @ModelAttribute 注解的方法
Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
if (!attrMethods.isEmpty()) {
this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
}
// 獲取當(dāng)前 ControllerAdviceBean 中所有使用了 @InitMethod 注解的方法
Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
if (!binderMethods.isEmpty()) {
this.initBinderAdviceCache.put(adviceBean, binderMethods);
}
// 如果當(dāng)前 ControllerAdviceBean 繼承自 RequestBodyAdvice,將其登記到 requestResponseBodyAdviceBeans
if (RequestBodyAdvice.class.isAssignableFrom(beanType)) {
requestResponseBodyAdviceBeans.add(adviceBean);
}
// 如果當(dāng)前 ControllerAdviceBean 繼承自 ResponseBodyAdvice,將其登記到 requestResponseBodyAdviceBeans
if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
requestResponseBodyAdviceBeans.add(adviceBean);
}
}
if (!requestResponseBodyAdviceBeans.isEmpty()) {
this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
}
if (logger.isDebugEnabled()) {
int modelSize = this.modelAttributeAdviceCache.size();
int binderSize = this.initBinderAdviceCache.size();
int reqCount = getBodyAdviceCount(RequestBodyAdvice.class);
int resCount = getBodyAdviceCount(ResponseBodyAdvice.class);
if (modelSize == 0 && binderSize == 0 && reqCount == 0 && resCount == 0) {
logger.debug("ControllerAdvice beans: none");
}
else {
logger.debug("ControllerAdvice beans: " + modelSize + " @ModelAttribute, " + binderSize +
" @InitBinder, " + reqCount + " RequestBodyAdvice, " + resCount + " ResponseBodyAdvice");
}
}
}
從以上#initControllerAdviceCache方法的實(shí)現(xiàn)邏輯來(lái)看,它將容器中所有使用了注解@ControllerAdvice的bean或者其方法都分門別類做了統(tǒng)計(jì),記錄到了RequestMappingHandlerAdapter實(shí)例的三個(gè)屬性中 :
requestResponseBodyAdvice
- 用于記錄所有
@ControllerAdvice+RequestBodyAdvice/ResponseBodyAdvicebean組件
modelAttributeAdviceCache
- 用于記錄所有
@ControllerAdvicebean組件中的@ModuleAttribute方法
initBinderAdviceCache
- 用于記錄所有
@ControllerAdvicebean組件中的@InitBinder方法
到此為止,我們知道,使用注解@ControllerAdvice的bean中的信息被提取出來(lái)了,但是,這些信息又是怎么使用的呢 ?我們繼續(xù)來(lái)看。
@ControllerAdvice定義信息的使用
requestResponseBodyAdvice的使用
/**
* Return the list of argument resolvers to use including built-in resolvers
* and custom resolvers provided via {@link #setCustomArgumentResolvers}.
*/
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
// ... 省略無(wú)關(guān)代碼
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),
this.requestResponseBodyAdvice));
// ... 省略無(wú)關(guān)代碼
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(),
this.requestResponseBodyAdvice));
// ... 省略無(wú)關(guān)代碼
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(),
this.requestResponseBodyAdvice));
// ... 省略無(wú)關(guān)代碼
return resolvers;
}
#getDefaultArgumentResolvers方法用于準(zhǔn)備RequestMappingHandlerAdapter執(zhí)行控制器方法過程中缺省使用的HandlerMethodArgumentResolver,從上面代碼可見,requestResponseBodyAdvice會(huì)被傳遞給RequestResponseBodyMethodProcessor/RequestPartMethodArgumentResolver/HttpEntityMethodProcessor這三個(gè)參數(shù)解析器,不難猜測(cè),它們?cè)诠ぷ鲿r(shí)會(huì)使用到該requestResponseBodyAdvice,但具體怎么使用,為避免過深細(xì)節(jié)影響理解,本文我們不繼續(xù)展開。
方法#getDefaultArgumentResolvers也在RequestMappingHandlerAdapter初始化方法中被調(diào)用執(zhí)行,如下所示 :
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBody advice beans
initControllerAdviceCache();
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); // <==
this.argumentResolvers = new HandlerMethodArgumentResolverComposite()
.addResolvers(resolvers);
}
// 省略無(wú)關(guān)代碼
}
modelAttributeAdviceCache的使用
private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
Class<?> handlerType = handlerMethod.getBeanType();
Set<Method> methods = this.modelAttributeCache.get(handlerType);
if (methods == null) {
// 獲取當(dāng)前控制器類中使用了 @ModelAttribute 的方法
methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);
this.modelAttributeCache.put(handlerType, methods);
}
List<InvocableHandlerMethod> attrMethods = new ArrayList<>();
// Global methods first
// 遍歷@ControllerAdvice bean中所有使用了 @ModelAttribute 的方法,
// 將其包裝成 InvocableHandlerMethod 放到 attrMethods
// ********* 這里就是 modelAttributeAdviceCache 被使用到的地方了 ************
this.modelAttributeAdviceCache.forEach((clazz, methodSet) -> {
if (clazz.isApplicableToBeanType(handlerType)) {
Object bean = clazz.resolveBean();
for (Method method : methodSet) {
attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
}
}
});
// 遍歷當(dāng)前控制器類中中所有使用了 @ModelAttribute 的方法,
// 也將其包裝成 InvocableHandlerMethod 放到 attrMethods
for (Method method : methods) {
Object bean = handlerMethod.getBean();
attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
}
// 此時(shí) attrMethods 包含了兩類 InvocableHandlerMethod, 分別來(lái)自于 :
// 1. @ControllerAdvice bean 中所有使用了 @ModelAttribute 的方法
// 2. 當(dāng)前控制器類中中所有使用了 @ModelAttribute 的方法
return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
}
// 從指定 bean 的方法 method ,其實(shí)是一個(gè)使用了注解 @ModelAttribute 的方法,
// 構(gòu)造一個(gè) InvocableHandlerMethod 對(duì)象
private InvocableHandlerMethod createModelAttributeMethod(WebDataBinderFactory factory,
Object bean, Method method) {
InvocableHandlerMethod attrMethod = new InvocableHandlerMethod(bean, method);
if (this.argumentResolvers != null) {
attrMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
attrMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
attrMethod.setDataBinderFactory(factory);
return attrMethod;
}
從此方法可以看到,#getModelFactory方法使用到了modelAttributeAdviceCache,它會(huì)根據(jù)其中每個(gè)元素構(gòu)造成一個(gè)InvocableHandlerMethod,最終傳遞給要?jiǎng)?chuàng)建的ModelFactory對(duì)象。而#getModelFactory又在什么時(shí)候被使用呢 ? 它會(huì)在RequestMappingHandlerAdapter執(zhí)行一個(gè)控制器方法的準(zhǔn)備過程中被調(diào)用,如下所示 :
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
// 構(gòu)造調(diào)用 handlerMethod 所要使用的數(shù)據(jù)綁定器工廠
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
// 構(gòu)造調(diào)用 handlerMethod 所要使用的數(shù)據(jù)模型工廠
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
// 省略無(wú)關(guān)代碼 ...
}
initBinderAdviceCache的使用
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
Class<?> handlerType = handlerMethod.getBeanType();
Set<Method> methods = this.initBinderCache.get(handlerType);
if (methods == null) {
// 獲取當(dāng)前控制器類中使用了 @InitBinder 的方法
methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
this.initBinderCache.put(handlerType, methods);
}
List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();
// Global methods first
// 遍歷@ControllerAdvice bean中所有使用了 @InitBinder 的方法,
// 將其包裝成 InvocableHandlerMethod 放到 initBinderMethods
// ********* 這里就是 initBinderAdviceCache 被使用到的地方了 ************
this.initBinderAdviceCache.forEach((clazz, methodSet) -> {
if (clazz.isApplicableToBeanType(handlerType)) {
Object bean = clazz.resolveBean();
for (Method method : methodSet) {
initBinderMethods.add(createInitBinderMethod(bean, method));
}
}
});
// 遍歷當(dāng)前控制器類中所有使用了 @InitBinder 的方法,
// 將其包裝成 InvocableHandlerMethod 放到 initBinderMethods
for (Method method : methods) {
Object bean = handlerMethod.getBean();
initBinderMethods.add(createInitBinderMethod(bean, method));
}
// 此時(shí) initBinderMethods 包含了兩類 InvocableHandlerMethod, 分別來(lái)自于 :
// 1. @ControllerAdvice bean 中所有使用了 @InitBinder 的方法
// 2. 當(dāng)前控制器類中中所有使用了 @InitBinder 的方法
return createDataBinderFactory(initBinderMethods);
}
// 從指定 bean 的方法 method ,其實(shí)是一個(gè)使用了注解 @InitBinder 的方法,
// 構(gòu)造一個(gè) InvocableHandlerMethod 對(duì)象
private InvocableHandlerMethod createInitBinderMethod(Object bean, Method method) {
InvocableHandlerMethod binderMethod = new InvocableHandlerMethod(bean, method);
if (this.initBinderArgumentResolvers != null) {
binderMethod.setHandlerMethodArgumentResolvers(this.initBinderArgumentResolvers);
}
binderMethod.setDataBinderFactory(new DefaultDataBinderFactory(this.webBindingInitializer));
binderMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
return binderMethod;
}
/**
* Template method to create a new InitBinderDataBinderFactory instance.
* <p>The default implementation creates a ServletRequestDataBinderFactory.
* This can be overridden for custom ServletRequestDataBinder subclasses.
* @param binderMethods {@code @InitBinder} methods
* @return the InitBinderDataBinderFactory instance to use
* @throws Exception in case of invalid state or arguments
*/
protected InitBinderDataBinderFactory createDataBinderFactory(
List<InvocableHandlerMethod> binderMethods)
throws Exception {
return new ServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer());
}
從此方法可以看到,#getDataBinderFactory方法使用到了initBinderAdviceCache,它會(huì)根據(jù)其中每個(gè)元素構(gòu)造成一個(gè)InvocableHandlerMethod,最終傳遞給要?jiǎng)?chuàng)建的InitBinderDataBinderFactory對(duì)象。而#getDataBinderFactory又在什么時(shí)候被使用呢 ? 它會(huì)在RequestMappingHandlerAdapter執(zhí)行一個(gè)控制器方法的準(zhǔn)備過程中被調(diào)用,如下所示 :
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
// 構(gòu)造調(diào)用 handlerMethod 所要使用的數(shù)據(jù)綁定器工廠
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
// 構(gòu)造調(diào)用 handlerMethod 所要使用的數(shù)據(jù)模型工廠
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
// 省略無(wú)關(guān)代碼 ...
}
到此為止,我們基本上可以看到,通過@ControllerAdvice注解的bean組件所定義的@ModelAttribute/@InitBinder方法,或者RequestBodyAdvice/ResponseBodyAdvice,是如何被RequestMappingHandlerAdapter提取和使用的了。雖然我們并未深入到更細(xì)微的組件研究它們最終的使用,不過結(jié)合這些組件命名以及這些更深一層的使用者組件的名稱,即便是猜測(cè),相信你也不難理解猜到它們?nèi)绾伪皇褂昧恕?/p>
不知道你注意到?jīng)]有,關(guān)于@ControllerAdvice和@ExceptionHandler這一組合,在上面提到的RequestMappingHandlerAdapter邏輯中,并未涉及到。那如果使用了這種組合,又會(huì)是怎樣一種工作機(jī)制呢 ?
事實(shí)上,@ControllerAdvice和@ExceptionHandler這一組合所做的定義,會(huì)被ExceptionHandlerExceptionResolver消費(fèi)使用。
不過關(guān)于ExceptionHandlerExceptionResolver我們會(huì)另外行文介紹,通過這篇文章中的例子,理解@ControllerAdvide的工作原理已經(jīng)不是問題了。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot?@DS注解實(shí)現(xiàn)多數(shù)據(jù)源配置以及問題解決辦法
這篇文章主要給大家介紹了關(guān)于SpringBoot?@DS注解實(shí)現(xiàn)多數(shù)據(jù)源配置以及問題解決辦法,所謂多數(shù)據(jù)源就是一個(gè)Java EE項(xiàng)目中采用了不同數(shù)據(jù)庫(kù)實(shí)例中的多個(gè)庫(kù),或者是同一個(gè)數(shù)據(jù)庫(kù)實(shí)例中的多個(gè)不同庫(kù),需要的朋友可以參考下2023-11-11
在SpringBoot中如何使用HttpClient實(shí)現(xiàn)HTTP請(qǐng)求
本文介紹了如何使用Apache?HttpClient來(lái)訪問HTTP協(xié)議的資源,并提供了詳細(xì)的使用示例,包括GET和POST請(qǐng)求的無(wú)參和有參調(diào)用2025-02-02
sql于navicat中能運(yùn)行在mybatis中不能運(yùn)行的解決方案
這篇文章主要介紹了sql于navicat中能運(yùn)行在mybatis中不能運(yùn)行的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
Java實(shí)現(xiàn)斷點(diǎn)下載服務(wù)端與客戶端的示例代碼
這篇文章主要為大家介紹了如何實(shí)現(xiàn)服務(wù)端(Spring Boot)與客戶端(Android)的斷點(diǎn)下載與下載續(xù)傳功能,文中的示例代碼講解詳細(xì),需要的可以參考一下2022-08-08
mybatis Plus 多表聯(lián)合查詢的實(shí)現(xiàn)示例
這篇文章主要介紹了mybatis Plus 多表聯(lián)合查詢的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09

