上一篇文章咱們簡單介紹了,servlet
容器以及Spring Mvc
應用容器的初始化過程。並瞭解如何經過java
代碼,來進行容器的初始化配置。在源碼解析(一)中咱們提到當servlet container
接收到一個請求時,servlet container
會根據servlet的mapping
配置選取一個 servlet
進行請求處理,並返回 response
。瞭解更多servlet container 容器。咱們在實際開發的時候常常會寫不少 controller ,本文將詳細介紹handerMapping
的初始化過程,以及DispatcherServlet
是如何根據Request 獲取對應的handlerMapping
的。html
首先咱們先來明確幾個概念:java
Handler
一般指用於處理request請求的實際對象,能夠類比 XxxController。在Spring Mvc中並無具體的類叫 Handler。HandlerMethod
處理request請求的具體方法對應 Controller 層中的某一個註解了@RequestMappping
的方法。org.springframework.web.method.HandlerMethod
該類中保存了執行某一個 request 請求的方法,以及相應的類信息。RequestMappingInfo
當Spring Mvc處理請求的時候經過 Request的 method、header、參數等來匹配對應的RequestMappingInfo
。RequestMappingInfo
一般保存的是@RequestMaping
註解的信息、用來肯定每個請求具體由哪個Controller方法處理。Spring Mvc 的初始化調用過程以下圖:git
RequestMappingHandlerMapping
實現了InitalizingBean
接口熟悉Spring
容器的同窗應該知道Spring容器在啓動的時候會執行InitalizingBean.afterPropertiesSet()
方法。RequestMappingHandlerMapping
實現了這個方法,這裏也是Spring Mvc
初始化的入口。github
直接看源碼,方法的邏輯很簡單配合註釋很容易理解web
/** * Scan beans in the ApplicationContext, detect and register handler methods. * @see #isHandler(Class) * @see #getMappingForMethod(Method, Class) * @see #handlerMethodsInitialized(Map) */
protected void initHandlerMethods() {
if (logger.isDebugEnabled()) {
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
}
//獲取全部容器託管的 beanName
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class<?> beanType = null;
try {
//獲取 Class 信息
beanType = getApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
}
}
// isHandler()方法判斷這個類是否有 @RequestMapping 或 @Controller
if (beanType != null && isHandler(beanType)) {
//發現並註冊 Controller @RequestMapping方法
detectHandlerMethods(beanName);
}
}
}
//Spring Mvc框架沒有實現、能夠用於功能擴展。
handlerMethodsInitialized(getHandlerMethods());
}
複製代碼
/**
* Look for handler methods in a handler.
* @param handler the bean name of a handler or a handler instance
*/
protected void detectHandlerMethods(final Object handler) {
Class<?> handlerType = (handler instanceof String ?
getApplicationContext().getType((String) handler) : handler.getClass());
final Class<?> userType = ClassUtils.getUserClass(handlerType);
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
new MethodIntrospector.MetadataLookup<T>() {
@Override
public T inspect(Method method) {
try {
//獲取 RequestMappingInfo
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
}
});
if (logger.isDebugEnabled()) {
logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
}
for (Map.Entry<Method, T> entry : methods.entrySet()) {
Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
T mapping = entry.getValue();
//註冊RequestMappingInfo
registerHandlerMethod(handler, invocableMethod, mapping);
}
}
複製代碼
這一步會獲取方法和類上的@RequestMapping
註解並經過 @RequestMaping
註解配置的參數生成相應的RequestMappingInfo
。RequestMappingInfo
中保存了不少Request
須要匹配的參數。spring
一、匹配請求Url PatternsRequestCondition patternsCondition;
json
二、匹配請求方法 GET等 RequestMethodsRequestCondition methodsCondition;
api
三、匹配參數例如@Requestmaping(Params="action=DoXxx")
ParamsRequestCondition paramsCondition;
bash
四、匹配請求頭信息 HeadersRequestCondition headersCondition;
mvc
五、指定處理請求的提交內容類型(Content-Type),例如application/json, text/html; ConsumesRequestCondition consumesCondition
;
六、指定返回的內容類型,僅當request請求頭中的(Accept)類型中包含該指定類型才返回ProducesRequestCondition producesCondition;
七、用於用戶定製請求條件 RequestConditionHolder customConditionHolder;
舉個例子,當咱們有一個需求只要請求中包含某一個參數時均可以掉這個方法處理,就能夠定製這個匹配條件。
/** * Uses method and type-level @{@link RequestMapping} annotations to create * the RequestMappingInfo. * @return the created RequestMappingInfo, or {@code null} if the method * does not have a {@code @RequestMapping} annotation. * @see #getCustomMethodCondition(Method) * @see #getCustomTypeCondition(Class) */
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
info = typeInfo.combine(info);
}
}
return info;
}
/** * Delegates to {@link #createRequestMappingInfo(RequestMapping, RequestCondition)}, * supplying the appropriate custom {@link RequestCondition} depending on whether * the supplied {@code annotatedElement} is a class or method. * @see #getCustomTypeCondition(Class) * @see #getCustomMethodCondition(Method) */
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
RequestCondition<?> condition = (element instanceof Class<?> ?
getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
複製代碼
這一步 Spring Mvc
會保存 Map<RequestmappingInfo, HandlerMethod>
、Map<url, RequestmappingInfo>
的映射關係。這裏咱們能夠和源碼分析(一)裏的DispatcherServlet.doDispatch()
方法聯繫起來,doDispatch
方法經過 url 在Map<url, RequestmappingInfo>
中獲取對應的 RequestMappingInfo
再根據request的信息和RequestMappingInfo
的各個條件比較是否知足處理條件,若是不知足返回 404 若是知足經過RequestMappingInfo
在Map<RequestmappingInfo, HandlerMethod>
獲取正真處理該請求的方法HandlerMathod
. 最後經過反射執行具體的方法(Controller
方法)。
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
assertUniqueMethodMapping(handlerMethod, mapping);
if (logger.isInfoEnabled()) {
logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
}
//保存RequestMappingInfo 和 HandlerMathod的映射關係
this.mappingLookup.put(mapping, handlerMethod);
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
//保存 Request Url 和 RequestMappingInfo 的對應關係
this.urlLookup.add(url, mapping);
}
String name = null;
if (getNamingStrategy() != null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
this.corsLookup.put(handlerMethod, corsConfig);
}
this.registry.put(mapping, new MappingRegistration<T>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
複製代碼
需求:咱們須要對外提供一個 OpenApi 其餘項目簽名經過後既能夠調用,而不用頻繁每次寫簽名校驗的邏輯,咱們如何利用 Spring mvc 的初始化過程來定製咱們的需求?
@Documented
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@ResponseBody
public @interface OpenApi {
//請求 url
String value() default "";
//須要參與簽名的請求參數
String[] params() default {};
//方法
RequestMethod method() default RequestMethod.GET;
····
}
複製代碼
當獲取到方法上有@OpenApi註解時建立 RequestMappingInfo
/** * @author HODO */
public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
//父類的方法調用上文已經分析過
RequestMappingInfo mappingInfo = super.getMappingForMethod(method, handlerType);
if (mappingInfo != null) {
return mappingInfo;
}
OpenApi openApi = AnnotatedElementUtils.findMergedAnnotation(method, OpenApi.class);
return openApi == null ? null : RequestMappingInfo.paths(openApi.value()).methods(openApi.method()).build();
}
}
複製代碼
攔截器攔截請求,當處理的方法包含 OpenApi
註解的時候進行簽名處理,根據業務自身的需求判斷簽名是否有效。
/** * @author HODO */
public class OpenApiSupportInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = ((HandlerMethod) handler);
OpenApi openApi = AnnotatedElementUtils.getMergedAnnotation(handlerMethod.getMethod(), OpenApi.class);
if (openApi != null && !checkSign(request, openApi.params())) {
response.getWriter().write("簽名失敗");
return false;
}
}
return true;
}
private boolean checkSign(HttpServletRequest request, String[] params) {
//get signKey check sign
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
複製代碼
RequestMappingHandlerMapping
使用咱們定製的CustomRequestMappingHandlerMapping
@Configuration
@ComponentScan("demo")
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new OpenApiSupportInterceptor());
}
@Override
protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
return new CustomRequestMappingHandlerMapping();
}
}
複製代碼
/** * @author HODO */
@Controller
@RequestMapping
public class DemoController {
@OpenApi(value = "/api")
public String demo2() {
return "test2";
}
}
複製代碼
Spring Mvc的初始化過程比較清晰,整個過程Spring
提供的方法不少都是 protected
修飾的這樣咱們能夠經過繼承靈活的定製咱們的需求。 回顧一下整個初始化過程:
經過Spring
容器對 InitalizingBean.afterPropertiesSet()方法的支持開始初始化流程。
獲取容器中的全部 bean
經過 isHandler()
方法區分是否須要處理。
經過方法上的@RequestMapping
註解建立 RequestMappingInfo
註冊、保存保存 Map<RequestmappingInfo, HandlerMethod>
、Map<url, RequestmappingInfo>
的映射關係方便後續調用和Request匹配。
項目地址:https://github.com/hoodoly/Spring-Mvc
聯繫方式:gunriky@163.com 有問題能夠直接聯繫