@
前言
前面幾篇文章,學習了Spring IOC、Bean例項化過程、AOP、事務的原始碼和設計思想,瞭解了Spring的整體執行流程,但如果是web開發,那麼必不可少的還有Spring MVC,本篇主要分析在請求呼叫過程中SpringMVC的實現原理,通過本篇要搞懂它是怎麼解決請求、引數、返回值對映等問題的。
正文
請求入口
我們都知道前端呼叫後端介面時,都會通過Servlet進行轉發,而Servlet的宣告週期包含下面四個階段:
- 例項化(new)
- 初始化(init)
- 執行(service呼叫doGet/doPost)
- 銷燬(destroy)
前兩個階段在Spring啟動階段就做好了(init根據配置可能是第一次請求時才會呼叫),銷燬是服務關閉的時候進行,本文主要分析的就是請求執行階段。我們知道SpringMVC的核心就是DispatcherServlet,該類是對Servlet的擴充套件,所以直接從該類的service方法開始,但在此類中沒有service方法,那肯定是在其父類中,我們先來看看其繼承體系:
逐個往上找,在FrameworkServlet方法中就有一個service方法:
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
processRequest(request, response);
}
else {
super.service(request, response);
}
}
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < lastModified) {
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
但其主要還是呼叫父類HttpServlet中的方法,而該類又會根據不同的請求方式會調到子類中,最後的核心方法就是DispatcherServlet中的doDispatch方法:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
//非同步管理
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
//檔案上傳
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
//這個方法很重要,重點看
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
//獲取跟HandlerMethod匹配的HandlerAdapter物件
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
//前置過濾器,如果為false則直接返回
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//呼叫到Controller具體方法,核心方法呼叫,重點看看
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
//中置過濾器
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
//檢視渲染及後置過濾器執行
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
MVC的所有處理邏輯都在這個方法中,先總結一下這個方法的實現邏輯,首先根據請求的url拿到快取中的HandlerMethod物件和執行鏈物件,HandlerMethod中封裝了controller物件、方法物件和方法引數等資訊,執行鏈則是包含了一個個HandlerInterceptor攔截器;然後再通過HandlerMethod拿到對應的HandlerAdapter,這個物件的作用就是去適配我們的controller;準備工作做完後,首先會執行前置過濾,如果被攔截則直接返回,否則就去呼叫controller中的方法執行我們的業務邏輯並返回一個ModelView物件;接著執行中置過濾器,以及處理全域性異常捕獲器捕獲到異常;最後進行檢視渲染返回並執行後置過濾器進行資源釋放等工作。
以上就是MVC的整體執行流程,下面就逐個來分析,首先進入getHandler方法:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//handlerMappering例項
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
//獲取HandlerMethod和過濾器鏈的包裝類
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
是委託給HandlerMapping物件的,這是一個介面,主要的實現類是RequestMappingHandlerMapping,同樣先來看看其繼承體系:
這個類是管理請求和處理類之間的對映關係的,你是否疑惑它是在哪裡例項化的呢?下面先來看看MVC元件的初始化。
元件初始化
這裡我以自動化配置的註解方式說明,Spring提供了一個@EnableWebMvc,通過前面的學習我們知道在這個註解中必定匯入了一個配置類,點進去可以看到是DelegatingWebMvcConfiguration,這個類就是負責MVC的元件和擴充套件實現的初始化,其本身我們先不看,先看其父類WebMvcConfigurationSupport,這個類我們應該不陌生,要做一些自定義擴充套件時就需要繼承該類(如攔截器Interceptor),同樣作用的類還有WebMvcConfigurerAdapter,這個類是對前者相對安全的擴充套件,為什麼是相對安全呢?因為繼承前者會導致自動配置失效,而使用後者則不必擔心此問題,只需要在類上加上@EnableWebMvc註解。
在WebMvcConfigurationSupport中我們可以看到很多@Bean標註的方法,也就是mvc元件的例項化,這裡主要看看requestMappingHandlerMapping,其餘的可自行閱讀理解,也就是一些Bean的註冊:
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
mapping.setOrder(0);
mapping.setInterceptors(getInterceptors());
mapping.setContentNegotiationManager(mvcContentNegotiationManager());
mapping.setCorsConfigurations(getCorsConfigurations());
......省略
return mapping;
}
這裡主要看getInterceptors方法如何獲取攔截器的:
protected final Object[] getInterceptors() {
if (this.interceptors == null) {
InterceptorRegistry registry = new InterceptorRegistry();
//鉤子方法,需要自己定義
addInterceptors(registry);
registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService()));
registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider()));
this.interceptors = registry.getInterceptors();
}
return this.interceptors.toArray();
}
第一次進來會呼叫addInterceptors新增攔截器,這是一個模板方法,在子類DelegatingWebMvcConfiguration中實現:
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
protected void addInterceptors(InterceptorRegistry registry) {
this.configurers.addInterceptors(registry);
}
public void addInterceptors(InterceptorRegistry registry) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addInterceptors(registry);
}
}
可以看到最終是呼叫WebMvcConfigurer的addInterceptors方法,也就是我們對WebMvcConfigurerAdapter的自定義擴充套件。看到這裡我們應該明白了MVC的元件是如何新增到IOC容器中的,但是DispatcherServlet又是怎麼獲取到它們的呢?回到之前的程式碼中,在DispatcherServlet這個類中有一個onRefresh方法,這個方法又呼叫了initStrategies方法完成了MVC九大元件的註冊:
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
}
}
以initHandlerMappings為例,其它元件實現邏輯基本一樣。首先從IOC容器中拿到handlerMappings的所有實現類(WebMvcConfigurationSupport中注入的物件就在這裡被獲取到),若沒有,則從DispatcherServlet.properties配置檔案中(這個配置在spring-webmvc工程下org/springframework/web/servlet/DispatcherServlet.properties)獲取預設的配置:
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
但是onRefresh又是在什麼時候呼叫的呢?有兩個地方,一個是Servlet初始化時會呼叫到initWebApplicationContext進行容器的初始化,這個方法中就會觸發onRefresh;另外還有一個,在FrameworkServlet中有一個onApplicationEvent方法,而這個方法又會被內部類ContextRefreshListener呼叫,這個類實現了ApplicationListener介面,表示會接收容器重新整理事件。
以上就就是MVC HandlerMapping元件的初始化邏輯,其它元件實現邏輯相同,下面不再分析。
呼叫Controller
回到getHandler方法,其呼叫的是AbstractHandlerMapping類的方法:
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//根據請求的uri拿到對應的HandlerMethod物件
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
//獲取HandlerMethod和過濾器鏈的包裝類
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (logger.isTraceEnabled()) {
logger.trace("Mapped to " + handler);
}
else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
logger.debug("Mapped to " + executionChain.getHandler());
}
//是否是跨域請求,就是檢視request請求頭中是否有Origin屬性
if (CorsUtils.isCorsRequest(request)) {
//自定義的鉤子方法獲取跨域配置
CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);
//註解獲取跨域配置
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
//這裡設定了跨域的過濾器CorsInterceptor
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
先看AbstractHandlerMethodMapping.getHandlerInternal:
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
//從request物件中獲取uri,/common/query2
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
this.mappingRegistry.acquireReadLock();
try {
//根據uri從對映關係中找到對應的HandlerMethod物件
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
//把Controller類例項化
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
// 根據url拿到對應的RequestMappingInfo
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
if (!matches.isEmpty()) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
if (logger.isTraceEnabled()) {
logger.trace(matches.size() + " matching mappings: " + matches);
}
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
Match secondBestMatch = matches.get(1);
//如果兩個RequestMappinginfo什麼都相同,報錯
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
String uri = request.getRequestURI();
throw new IllegalStateException(
"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
}
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
for (T mapping : mappings) {
// 拿到匹配的RequestMappingInfo物件,有可能url相同,@RequestMapping的屬性(請求方式、引數等)匹配不上
T match = getMatchingMapping(mapping, request);
if (match != null) {
//RequestMappingInfo物件和HandlerMethod物件封裝到Match物件中,其實就是註解屬性和Method物件的對映
matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
}
}
}
這裡邏輯很簡單,就是通過請求url從urlLookup中拿到對應的RequestMappingInfo(每一個 @RequestMapping對應一個RequestMappingInfo物件)物件,再根據RequestMappingInfo物件從mappingLookup拿到對應的HandlerMethod並返回。
但這裡你可能會比較好奇urlLookup和mappingLookup從哪裡來的,仔細觀察你會發現當前這個類實現了一個介面InitializingBean,實現了這個介面的類會在該類的Bean例項化完成後呼叫afterPropertiesSet方法,上面的對映關係就是在這個方法中做的。實際上這個方法不止完成了上面兩個對映關係,還有下面兩個:
- corsLookup:handlerMethod -> corsConfig
- registry:RequestMappingInfo -> MappingRegistration(包含url、handlerMethod、RequestMappingInfo、name等資訊)
這裡就不展開分析了,奉上一張時序圖,讀者可根據下面的時序圖自行分析:
拿到HandlerMethod物件後,又會通過getHandlerExecutionChain方法去獲取到所有的HandlerInterceptor攔截器物件,並連同HandlerMethod物件一起封裝為HandlerExecutionChain。之後是獲取跨域配置,這裡不詳細分析。
拿到HandlerExecutionChain物件後返回到doDispatch方法,又呼叫了getHandlerAdapter
方法拿到HandlerAdapter:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
//根據handlerMethod物件,找到合適的HandlerAdapter物件,這裡用到了策略模式
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
}
這裡的handlerAdapters變數值從哪裡來?相信不用我再分析,主要看這裡的設計思想,典型的策略模式。
之後呼叫完前置過濾器後,才是真正呼叫我們controller方法的邏輯,通過HandlerAdapter.handle去呼叫,最終會呼叫到ServletInvocableHandlerMethod.invokeAndHandle:
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//具體呼叫邏輯,重點看
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
//返回值處理
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
這個方法裡面主要看invokeForRequest和handleReturnValue的呼叫,前者是完成引數繫結並呼叫controller,後者則是對返回值進行處理並封裝到ModelAndViewContainer中。先來看invokeForRequest:
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//獲取引數陣列
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
return doInvoke(args);
}
doInvoke就是完成反射呼叫,主要還是看引數繫結的實現邏輯,在getMethodArgumentValues方法中:
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
if (ObjectUtils.isEmpty(getMethodParameters())) {
return EMPTY_ARGS;
}
//入參的包裝類,裡面包裝了引數型別,引數名稱,引數註解等等資訊
MethodParameter[] parameters = getMethodParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
//設定引數名稱解析器
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
//典型的策略模式,根據parameter能否找到對應引數的處理類,能找到就返回true
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
//具體引數值解析過程,重點看看
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled..
if (logger.isDebugEnabled()) {
String error = ex.getMessage();
if (error != null && !error.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, error));
}
}
throw ex;
}
}
return args;
}
引數、返回值解析
因為引數型別非常多,同時還會伴隨各種註解,如:@RequestBody、@RequestParam、@PathVariable等,所以引數解析的工作是非常繁雜的,同時還要考慮到擴充套件性,所以SpringMVC依然採用了策略模式來完成對各種引數型別的解析繫結,其頂層介面就是HandlerMethodArgumentResolver,而預設SpringMVC提供的解析方式就高達20多種:
上面是類圖,讀者可根據自己熟悉的引數型別找到對應的類進行分析,最核心的還是要掌握這裡的設計思想。
接著方法呼叫完成後就是對返回值的處理,同樣的,返回值型別也是非常多,也可以使用各種註解標註,所以也是使用策略模式實現,其頂層介面是HandlerMethodReturnValueHandler,實現類如下:
呼叫完成之後就是執行後續操作了:執行中置過濾器、處理全域性異常、檢視渲染以及執行後置過濾器,這些與主流程沒有太大關係,本篇不展開分析了,最後是MVC的執行時序圖:
總結
本篇是Spring核心原理系列的最後一篇,前前後後花了一個月時間,終於從巨集觀上大致上理解了Spring的實現原理和執行機制,明白了之前專案中一些坑是如何產生的,最主要的是學到設計模式的運用以及如何利用Spring的一些常用的擴充套件點進行自定義擴充套件。但對於Spring這個龐大的體系來說,還有很多是要去理解學習的,尤其是設計思想,只有長期琢磨才能深刻的理解掌握。在我之前的文章中包括本篇還有很多沒分析到的細節,在後面我會不定期分享出來。