上一篇文章咱们简单介绍了,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 有问题能够直接联系