摘要:并且,并且如果或者不為空不為且不為,將中斷處理直接返回不再渲染頁面對返回值的處理對返回值的處理是使用完成的對異步處理結果的處理使用示例文首說了,作為一個非公開,如果你要直接使用起來,還是稍微要費點勁的。
每篇一句
想當火影的人沒有近道可尋,當上火影的人同樣無路可退前言
HandlerMethod它作為Spring MVC的非公開API,可能絕大多數小伙伴都對它比較陌生,但我相信你對它又不是那么的生疏,因為你可能沒用過但肯定見過。
比如Spring MVC的攔截器HandlerInterceptor的攔截方法的第三個入參Object handler,雖然它是Object類型,但其實絕大部分情況下我們都會當作HandlerMethod來使用;又比如我之前的這篇講RequestMappingHandlerMapping的文章也大量的提到過HandlerMethod這個類。
經由我這么“忽悠”,你是否覺得它還是相對比較重要的一個類了呢?不管你信不信,反正我是這么認為的:HandlerMethod它是理解Spring MVC不可或缺的一個類,甚至可以說是你希望參與到Spring MVC的定制化里面來不可忽略的一個關鍵API。
HandlerMethodHandlerMethod它不是一個接口,也不是個抽象類,且還是public的。HandlerMethod封裝了很多屬性,在訪問請求方法的時候可以方便的訪問到方法、方法參數、方法上的注解、所屬類等并且對方法參數封裝處理,也可以方便的訪問到方法參數的注解等信息。
// @since 3.1 public class HandlerMethod { // Object類型,既可以是個Bean,也可以是個BeanName private final Object bean; // 如果是BeanName,拿就靠它拿出Bean實例了~ @Nullable private final BeanFactory beanFactory; private final Class> beanType; // 該方法所屬的類 private final Method method; // 該方法本身 private final Method bridgedMethod; // 被橋接的方法,如果method是原生的,它的值同method // 封裝方法參數的類實例,**一個MethodParameter就是一個入參** // MethodParameter也是Spring抽象出來的一個非常重要的概念 private final MethodParameter[] parameters; @Nullable private HttpStatus responseStatus; // http狀態碼(畢竟它要負責處理和返回) @Nullable private String responseStatusReason; // 如果狀態碼里還要復數原因,就是這個字段 可以為null // 通過createWithResolvedBean()解析此handlerMethod實例的handlerMethod。 @Nullable private HandlerMethod resolvedFromHandlerMethod; // 標注在**接口入參**上的注解們(此處數據結構復雜,List+二維數組) @Nullable private volatile ListinterfaceParameterAnnotations; // 它的構造方法眾多 此處我只寫出關鍵的步驟 public HandlerMethod(Object bean, Method method) { ... this.beanType = ClassUtils.getUserClass(bean); this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); this.parameters = initMethodParameters(); ... evaluateResponseStatus(); } // 這個構造方法拋出了一個異常NoSuchMethodException public HandlerMethod(Object bean, String methodName, Class>... parameterTypes) throws NoSuchMethodException { ... this.method = bean.getClass().getMethod(methodName, parameterTypes); this.parameters = initMethodParameters(); ... evaluateResponseStatus(); } // 此處傳的是BeanName public HandlerMethod(String beanName, BeanFactory beanFactory, Method method) { ... // 這部判斷:這個BeanName是必須存在的 Class> beanType = beanFactory.getType(beanName); if (beanType == null) { throw new IllegalStateException("Cannot resolve bean type for bean with name "" + beanName + """); } this.parameters = initMethodParameters(); ... evaluateResponseStatus(); } // 供給子類copy使用的 protected HandlerMethod(HandlerMethod handlerMethod) { ... } // 所有構造都執行了兩個方法:initMethodParameters和evaluateResponseStatus // 初始化該方法所有的入參,此處使用的是內部類HandlerMethodParameter // 注意:處理了泛型的~~~ private MethodParameter[] initMethodParameters() { int count = this.bridgedMethod.getParameterCount(); MethodParameter[] result = new MethodParameter[count]; for (int i = 0; i < count; i++) { HandlerMethodParameter parameter = new HandlerMethodParameter(i); GenericTypeResolver.resolveParameterType(parameter, this.beanType); result[i] = parameter; } return result; } // 看看方法上是否有標注了@ResponseStatus注解(接口上或者父類 組合注解上都行) // 若方法上沒有,還會去所在的類上去看看有沒有標注此注解 // 主要只解析這個注解,把它的兩個屬性code和reason拿過來,最后就是返回它倆了~~~ // code狀態碼默認是HttpStatus.INTERNAL_SERVER_ERROR-->(500, "Internal Server Error") private void evaluateResponseStatus() { ResponseStatus annotation = getMethodAnnotation(ResponseStatus.class); if (annotation == null) { annotation = AnnotatedElementUtils.findMergedAnnotation(getBeanType(), ResponseStatus.class); } if (annotation != null) { this.responseStatus = annotation.code(); this.responseStatusReason = annotation.reason(); } } ... // 省略所有屬性的get方法(無set方法) // 返回方法返回值的類型 此處也使用的MethodParameter public MethodParameter getReturnType() { return new HandlerMethodParameter(-1); } // 注意和上面的區別。舉個列子:比如方法返回的是Object,但實際return “fsx”字符串 // 那么上面返回永遠是Object.class,下面你實際的值是什么類型就是什么類型 public MethodParameter getReturnValueType(@Nullable Object returnValue) { return new ReturnValueMethodParameter(returnValue); } // 該方法的返回值是否是void public boolean isVoid() { return Void.TYPE.equals(getReturnType().getParameterType()); } // 返回標注在方法上的指定類型的注解 父方法也成 // 子類ServletInvocableHandlerMethod對下面兩個方法都有復寫~~~ @Nullable public A getMethodAnnotation(Class annotationType) { return AnnotatedElementUtils.findMergedAnnotation(this.method, annotationType); } public boolean hasMethodAnnotation(Class annotationType) { return AnnotatedElementUtils.hasAnnotation(this.method, annotationType); } // resolvedFromHandlerMethod雖然它只能被構造進來,但是它實際是銅鼓調用下面方法賦值 @Nullable public HandlerMethod getResolvedFromHandlerMethod() { return this.resolvedFromHandlerMethod; } // 根據string類型的BeanName把Bean拿出來,再new一個HandlerMethod出來~~~這才靠譜嘛 public HandlerMethod createWithResolvedBean() { Object handler = this.bean; if (this.bean instanceof String) { Assert.state(this.beanFactory != null, "Cannot resolve bean name without BeanFactory"); String beanName = (String) this.bean; handler = this.beanFactory.getBean(beanName); } return new HandlerMethod(this, handler); } public String getShortLogMessage() { return getBeanType().getName() + "#" + this.method.getName() + "[" + this.method.getParameterCount() + " args]"; } // 這個方法是提供給內部類HandlerMethodParameter來使用的~~ 它使用的數據結構還是蠻復雜的 private List getInterfaceParameterAnnotations() { List parameterAnnotations = this.interfaceParameterAnnotations; if (parameterAnnotations == null) { parameterAnnotations = new ArrayList<>(); // 遍歷該方法所在的類所有的實現的接口們(可以實現N個接口嘛) for (Class> ifc : this.method.getDeclaringClass().getInterfaces()) { // getMethods:拿到所有的public的方法,包括父接口的 接口里的私有方法可不會獲取來 for (Method candidate : ifc.getMethods()) { // 判斷這個接口方法是否正好是當前method復寫的這個~~~ // 剛好是復寫的方法,那就添加進來,標記為接口上的注解們~~~ if (isOverrideFor(candidate)) { // getParameterAnnotations返回的是個二維數組~~~~ // 因為參數有多個,且每個參數前可以有多個注解 parameterAnnotations.add(candidate.getParameterAnnotations()); } } } this.interfaceParameterAnnotations = parameterAnnotations; } return parameterAnnotations; } // 看看內部類的關鍵步驟 protected class HandlerMethodParameter extends SynthesizingMethodParameter { @Nullable private volatile Annotation[] combinedAnnotations; ... // 父類只會在本方法拿,這里支持到了接口級別~~~ @Override public Annotation[] getParameterAnnotations() { Annotation[] anns = this.combinedAnnotations; if (anns == null) { // 都只需要解析一次 anns = super.getParameterAnnotations(); int index = getParameterIndex(); if (index >= 0) { // 有入參才需要去分析嘛 for (Annotation[][] ifcAnns : getInterfaceParameterAnnotations()) { if (index < ifcAnns.length) { Annotation[] paramAnns = ifcAnns[index]; if (paramAnns.length > 0) { List merged = new ArrayList<>(anns.length + paramAnns.length); merged.addAll(Arrays.asList(anns)); for (Annotation paramAnn : paramAnns) { boolean existingType = false; for (Annotation ann : anns) { if (ann.annotationType() == paramAnn.annotationType()) { existingType = true; break; } } if (!existingType) { merged.add(adaptAnnotation(paramAnn)); } } anns = merged.toArray(new Annotation[0]); } } } } this.combinedAnnotations = anns; } return anns; } } // 返回值的真正類型~~~ private class ReturnValueMethodParameter extends HandlerMethodParameter { @Nullable private final Object returnValue; public ReturnValueMethodParameter(@Nullable Object returnValue) { super(-1); // 此處傳的-1哦~~~~ 比0小是很有意義的 this.returnValue = returnValue; } ... // 返回值類型使用returnValue就行了~~~ @Override public Class> getParameterType() { return (this.returnValue != null ? this.returnValue.getClass() : super.getParameterType()); } } }
可以看到HandlerMethod它持有的屬性是非常多的,提供的能力也是很強的。
但是不知道小伙伴有沒有發現,雖然它持有了目標的Method,但是它并沒有提供invoke執行它的能力,如果你要執行它還得自己把Method拿去自己執行。
所以總的來說它的職責還是很單一的:HandlerMethod它只負責準備數據,封裝數據,而而不提供具體使用的方式方法~
看看它的繼承樹:
它主要有兩個子類:InvocableHandlerMethod和ServletInvocableHandlerMethod,從命名就知道他倆都是有invoke調用能力的~
它是對HandlerMethod的擴展,增加了調用能力。這個能力在Spring MVC可是非常非常重要的,它能夠在調用的時候,把方法入參的參數都封裝進來(從HTTP request里,當然借助的必然是HandlerMethodArgumentResolver)
// @since 3.1 public class InvocableHandlerMethod extends HandlerMethod { private static final Object[] EMPTY_ARGS = new Object[0]; // 它額外提供的幾個屬性,可以看到和數據綁定、數據校驗就扯上關系了~~~ // 用于產生數據綁定器、校驗器 @Nullable private WebDataBinderFactory dataBinderFactory; // HandlerMethodArgumentResolver用于入參的解析 private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite(); // 用于獲取形參名 private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); ... // 省略構造函數 全部使用super的 // 它自己的三大屬性都使用set方法設置進來~~~并且沒有提供get方法 // 也就是說:它自己內部使用就行了~~~ // 在給定請求的上下文中解析方法的參數值后調用該方法。 也就是說:方法入參里就能夠自動使用請求域(包括path里的,requestParam里的、以及常規對象如HttpSession這種) // 解釋下providedArgs作用:調用者可以傳進來,然后直接doInvoke()的時候原封不動的使用它 //(彌補了請求域沒有所有對象的不足,畢竟有些對象是用戶自定義的嘛~) @Nullable public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 雖然它是最重要的方法,但是此處不講,因為核心原來還是`HandlerMethodArgumentResolver` // 它只是把解析好的放到對應位置里去~~~ // 說明:這里傳入了ParameterNameDiscoverer,它是能夠獲取到形參名的。 // 這就是為何注解里我們不寫value值,通過形參名字來匹配也是ok的核心原因~ Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { // trace信息,否則日志也特多了~ logger.trace("Arguments: " + Arrays.toString(args)); } return doInvoke(args); } // doInvoke()方法就不說了,就是個普通的方法調用 // ReflectionUtils.makeAccessible(getBridgedMethod()); // return getBridgedMethod().invoke(getBean(), args); }
對于最后的invoke(),說明一點:這里可是執行的目標方法getBean()哦~~~
這個子類主要提供的能力就是提供了invoke調用目標Bean的目標方法的能力,在這個調用過程中可大有文章可為,當然最為核心的邏輯可是各種各樣的HandlerMethodArgumentResolver來完成的,詳見下文有分曉。
InvocableHandlerMethod這個子類雖然它提供了調用了能力,但是它卻依舊還沒有和Servlet的API綁定起來,畢竟使用的是Spring自己通用的的NativeWebRequest,so很容易想到它還有一個子類就是干這事的~
它是對InvocableHandlerMethod的擴展,它增加了返回值和響應狀態碼的處理,另外在ServletInvocableHandlerMethod有個內部類ConcurrentResultHandlerMethod繼承于它,支持異常調用結果處理,Servlet容器下Controller在查找適配器時發起調用的最終就是ServletInvocableHandlerMethod。
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod { private static final Method CALLABLE_METHOD = ClassUtils.getMethod(Callable.class, "call"); // 處理方法返回值 @Nullable private HandlerMethodReturnValueHandlerComposite returnValueHandlers; // 構造函數略 // 設置處理返回值的HandlerMethodReturnValueHandler public void setHandlerMethodReturnValueHandlers(HandlerMethodReturnValueHandlerComposite returnValueHandlers) { this.returnValueHandlers = returnValueHandlers; } // 它不是復寫,但是是對invokeForRequest方法的進一步增強 因為調用目標方法還是靠invokeForRequest // 本處是把方法的返回值拿來進一步處理~~~比如狀態碼之類的 public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); // 設置HttpServletResponse返回狀態碼 這里面還是有點意思的 因為@ResponseStatus#code()在父類已經解析了 但是子類才用 setResponseStatus(webRequest); // 重點是這一句話:mavContainer.setRequestHandled(true); 表示該請求已經被處理過了 if (returnValue == null) { // Request的NotModified為true 有@ResponseStatus注解標注 RequestHandled=true 三個條件有一個成立,則設置請求處理完成并返回 if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) { mavContainer.setRequestHandled(true); return; } // 返回值不為null,@ResponseStatus存在reason 同樣設置請求處理完成并返回 } else if (StringUtils.hasText(getResponseStatusReason())) { mavContainer.setRequestHandled(true); return; } // 前邊都不成立,則設置RequestHandled=false即請求未完成 // 繼續交給HandlerMethodReturnValueHandlerComposite處理 // 可見@ResponseStatus的優先級還是蠻高的~~~~~ mavContainer.setRequestHandled(false); Assert.state(this.returnValueHandlers != null, "No return value handlers"); try { // 關于對方法返回值的處理,參見:https://blog.csdn.net/f641385712/article/details/90370542 this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(formatErrorForReturnValue(returnValue), ex); } throw ex; } } // 設置返回的狀態碼到HttpServletResponse 里面去 private void setResponseStatus(ServletWebRequest webRequest) throws IOException { HttpStatus status = getResponseStatus(); if (status == null) { // 如果調用者沒有標注ResponseStatus.code()此注解 此處就忽略它 return; } HttpServletResponse response = webRequest.getResponse(); if (response != null) { String reason = getResponseStatusReason(); // 此處務必注意:若有reason,那就是sendError 哪怕你是200哦~ if (StringUtils.hasText(reason)) { response.sendError(status.value(), reason); } else { response.setStatus(status.value()); } } // 設置到request的屬性,把響應碼給過去。為了在redirect中使用 // To be picked up by RedirectView webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, status); } private boolean isRequestNotModified(ServletWebRequest webRequest) { return webRequest.isNotModified(); } // 這個方法RequestMappingHandlerAdapter里有調用 ServletInvocableHandlerMethod wrapConcurrentResult(Object result) { return new ConcurrentResultHandlerMethod(result, new ConcurrentResultMethodParameter(result)); } // 內部類們 private class ConcurrentResultMethodParameter extends HandlerMethodParameter { @Nullable private final Object returnValue; private final ResolvableType returnType; public ConcurrentResultMethodParameter(Object returnValue) { super(-1); this.returnValue = returnValue; // 主要是這個解析 兼容到了泛型類型 比如你的返回值是List它也能把你的類型拿出來 this.returnType = (returnValue instanceof ReactiveTypeHandler.CollectedValuesList ? ((ReactiveTypeHandler.CollectedValuesList) returnValue).getReturnType() : ResolvableType.forType(super.getGenericParameterType()).getGeneric()); } // 若返回的是List 這里就是List的類型哦 下面才是返回泛型類型 @Override public Class> getParameterType() { if (this.returnValue != null) { return this.returnValue.getClass(); } if (!ResolvableType.NONE.equals(this.returnType)) { return this.returnType.toClass(); } return super.getParameterType(); } // 返回泛型類型 @Override public Type getGenericParameterType() { return this.returnType.getType(); } // 即使實際返回類型為ResponseEntity >,也要確保對@ResponseBody-style處理從reactive 類型中收集值 // 是對reactive 的一種兼容 @Override public boolean hasMethodAnnotation(Class annotationType) { // Ensure @ResponseBody-style handling for values collected from a reactive type // even if actual return type is ResponseEntity > return (super.hasMethodAnnotation(annotationType) || (annotationType == ResponseBody.class && this.returnValue instanceof ReactiveTypeHandler.CollectedValuesList)); } } // 這個非常有意思 內部類繼承了自己(外部類) 進行增強 private class ConcurrentResultHandlerMethod extends ServletInvocableHandlerMethod { // 返回值 private final MethodParameter returnType; // 此構造最終傳入的handler是個Callable // result方法返回值 它支持支持異常調用結果處理 public ConcurrentResultHandlerMethod(final Object result, ConcurrentResultMethodParameter returnType) { super((Callable
HandlerMethod用于封裝Handler和處理請求的Method;InvocableHandlerMethod增加了方法參數解析和調用方法的能力;ServletInvocableHandlerMethod在此基礎上在增加了如下三個能力:
對@ResponseStatus注解的支持
1.當一個方法注釋了`@ResponseStatus`后,**響應碼就是注解上的響應碼**。 **并且,并且如果returnValue=null或者reason不為空**(不為null且不為“”),將中斷處理直接返回(不再渲染頁面)
對返回值returnValue的處理
1. 對返回值的處理是使用`HandlerMethodReturnValueHandlerComposite`完成的
對異步處理結果的處理
使用示例文首說了,HandlerMethod作為一個非公開API,如果你要直接使用起來,還是稍微要費點勁的。
但本文還是給出一個Demo,給出小伙伴們最為關心也是對你們最有用的一個需求:ModelFactory.getNameForParameter(parameter)這個靜態方法是給入參生成默認名稱的,當然默認處理方案最底層依賴的是它Conventions.getVariableNameForParameter(parameter),為了驗證這塊對象、Object、List等等常用數據結構的默認處理,此處我借助HandlerMethod一次性全部打印出這個結論:
@Getter @Setter @ToString public class Person { @NotNull private String name; @NotNull @Positive private Integer age; public Object demoMethod(Person person, Object object, ListintList, List personList, Set intSet, Set personSet, Map myMap, String name, Integer age, int number, double money) { return "hello parameter"; } }
借助HandlerMethod完成此測試用例
public static void main(String[] args) { // 準備一個HandlerMethod HandlerMethod handlerMethod = new HandlerMethod(new Person(), getPersonSpecfyMethod()); // 拿到該方法所有的參數 MethodParameter[] methodParameters = handlerMethod.getMethodParameters(); for (MethodParameter parameter : methodParameters) { Class> parameterType = parameter.getParameterType(); String nameForParameter = ModelFactory.getNameForParameter(parameter); System.out.println("類型" + parameterType.getName() + "--->缺省的modelKey是:" + nameForParameter); } } private static Method getPersonSpecfyMethod() { for (Method method : Person.class.getMethods()) if (method.getName().equals("demoMethod")) return method; return null; }
運行,打印結果如下:
類型com.fsx.bean.Person--->缺省的modelKey是:person 類型java.lang.Object--->缺省的modelKey是:object 類型java.util.List--->缺省的modelKey是:integerList 類型java.util.List--->缺省的modelKey是:personList 類型java.util.Set--->缺省的modelKey是:integerList // 可以看到即使是set 名稱也是同List的 類型java.util.Set--->缺省的modelKey是:personList 類型java.util.Map--->缺省的modelKey是:map 類型java.lang.String--->缺省的modelKey是:string 類型java.lang.Integer--->缺省的modelKey是:integer 類型int--->缺省的modelKey是:int 類型double--->缺省的modelKey是:double
這個結果是不同類型對應的缺省的ModelKey,希望小伙伴們能夠記下來,這對理解和正確使用`
@SessionAttribute、@ModelAttribute`都是很重要的~
HandlerMethod雖然接觸少,但并不影響它的重要性。在理解Spring MVC的處理流程上它很重要,在與使用者關系較大的攔截器HandlerInterceptor定制化處理的時候,學會使用它一樣是非常有必要的。
在最后還提示大家一個你可能沒有關心到的小細節:
HandlerMethod位于org.springframework.web.method包下,且是3.1后才有的
MethodParameter位于org.springframework.core核心包中。2.0就存在了
相關閱讀【小家Spring】Spring MVC容器的web九大組件之---HandlerAdapter源碼詳解---一篇文章帶你讀懂返回值處理器HandlerMethodReturnValueHandler
知識交流==The last:如果覺得本文對你有幫助,不妨點個贊唄。當然分享到你的朋友圈讓更多小伙伴看到也是被作者本人許可的~==
**若對技術內容感興趣可以加入wx群交流:Java高工、架構師3群。
若群二維碼失效,請加wx號:fsx641385712(或者掃描下方wx二維碼)。并且備注:"java入群" 字樣,會手動邀請入群**
若文章格式混亂或者圖片裂開,請點擊`:原文鏈接-原文鏈接-原文鏈接
==若對Spring、SpringBoot、MyBatis等源碼分析感興趣,可加我wx:fsx641385712,手動邀請你入群一起飛==
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/75786.html
摘要:見名之意,它是處理器,也就是解析這個注解的核心。管理通過標注了的特定會話屬性,存儲最終是委托了來實現。只會清楚注解放進去的,并不清除放進去的它的唯一實現類實現也簡單。在更新時,模型屬性與會話同步,如果缺少,還將添加屬性。 每篇一句 不是你當上了火影大家就認可你,而是大家都認可你才能當上火影 前言 該注解顧名思義,作用是將Model中的屬性同步到session會話當中,方便在下一次請求中...
摘要:雖然它不是必須,但是它是個很好的輔助官方解釋首先看看官方的對它怎么說它將方法參數方法返回值綁定到的里面。解析注解標注的方法參數,并處理標注的方法返回值。 每篇一句 我們應該做一個:胸中有藍圖,腳底有計劃的人 前言 Spring MVC提供的基于注釋的編程模型,極大的簡化了web應用的開發,我們都是受益者。比如我們在@RestController標注的Controller控制器組件上用@...
摘要:并且,并且如果或者不為空不為且不為,將中斷處理直接返回不再渲染頁面對返回值的處理對返回值的處理是使用完成的對異步處理結果的處理使用示例文首說了,作為一個非公開,如果你要直接使用起來,還是稍微要費點勁的。 每篇一句 想當火影的人沒有近道可尋,當上火影的人同樣無路可退 前言 HandlerMethod它作為Spring MVC的非公開API,可能絕大多數小伙伴都對它比較陌生,但我相信你對它...
摘要:并且,并且如果或者不為空不為且不為,將中斷處理直接返回不再渲染頁面對返回值的處理對返回值的處理是使用完成的對異步處理結果的處理使用示例文首說了,作為一個非公開,如果你要直接使用起來,還是稍微要費點勁的。 每篇一句 想當火影的人沒有近道可尋,當上火影的人同樣無路可退 前言 HandlerMethod它作為Spring MVC的非公開API,可能絕大多數小伙伴都對它比較陌生,但我相信你對它...
摘要:和一起使用參照博文從原理層面掌握的使用一起學。至于具體原因,可以移步這里輔助理解從原理層面掌握的使用核心原理篇一起學再看下面的變種例子重要訪問。 每篇一句 每個人都應該想清楚這個問題:你是祖師爺賞飯吃的,還是靠老天爺賞飯吃的 前言 上篇文章 描繪了@ModelAttribute的核心原理,這篇聚焦在場景使用上,演示@ModelAttribute在不同場景下的使用,以及注意事項(當然有些...
閱讀 1951·2021-09-07 10:24
閱讀 2086·2019-08-30 15:55
閱讀 2037·2019-08-30 15:43
閱讀 668·2019-08-29 15:25
閱讀 1043·2019-08-29 12:19
閱讀 1927·2019-08-23 18:32
閱讀 1515·2019-08-23 17:59
閱讀 946·2019-08-23 12:22