文章目录
一、前言
本文是笔者阅读Spring源码的记录文章,由于本人技术水平有限,在文章中难免出现错误,如有发现,感谢各位指正。在阅读过程中也创建了一些衍生文章,衍生文章的意义是因为自己在看源码的过程中,部分知识点并不了解或者对某些知识点产生了兴趣,所以为了更好的阅读源码,所以开设了衍生篇的文章来更好的对这些知识点进行进一步的学习。
全集目录:
Spring源码分析:全集整理
在上文
Spring源码分析二十五 :Actuator 浅析①
中我们介绍了 Springboot 中对于 Endpoint 的处理,我们知道了每个端点发现器中都保存了满足自身条件的 EndpointBean,但上面的逻辑并没有提供 http 访问方式,下面我们来看看 为 Endpoint 创建 Http 请求的过程。
这里我们简单描述Spring mvc 的流程 :当我们发起Http 请求后, Spring 会通过 DispatcherServlet 来接收请求,之后根据请求路径找到对应的 HandlerMapping 获取 HandlerExecutionChain(处理器执行链路)。并根据 HandlerExecutionChain 中的 Handler 来找到对应 HandlerAdapter 来进行处理调用,最终将结果返回。
基于上面的流程,我们来看 Actuator 是如何完成的 Http 访问,
二、HandlerMapping
我们在
Spring 源码分析衍生篇十一 :HandlerMapping
对 HandlerMapping 中的流程进行过介绍 :
HandlerMapping 有一个抽象实现类 AbstractHandlerMethodMapping ,而AbstractHandlerMethodMapping 在初始化后会调用 AbstractWebMvcEndpointHandlerMapping#initHandlerMethods 方法来初始化处理器方法,如被 @Controller 或者 @RestController 修饰的类中有 @RequestMapping 注解修饰的方法,则该方法会被封装成 HandlerMethod 并注册到 AbstractHandlerMethodMapping.MappingRegistry#registry中。当发生调用时会从 MappingRegistry#registry 中根据路径找到对应的 HandlerMethod 进行调用。
而 Springboot 中引入了 Actuator 依赖后会自动装配 WebMvcEndpointManagementContextConfiguration类,该类会注入 WebMvcEndpointHandlerMapping 和 ControllerEndpointHandlerMapping 两个HandlerMapping 类: ,如下:
/**
*
* @param webEndpointsSupplier :实际类型 WebEndpointDiscoverer
* @param servletEndpointsSupplier : 实际类型 ServletEndpointDiscoverer
* @param controllerEndpointsSupplier : 实际类型 ControllerEndpointDiscoverer
* @param endpointMediaTypes : 默认情况下,由端点生成和使用的媒体类型
* @param corsProperties : 映射 management.endpoints.web.cors 配置
* @param webEndpointProperties : 映射 management.endpoints.web 配置
* @param environment : 上线文环境信息
* @return
*/
@Bean
@ConditionalOnMissingBean
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier,
ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier,
EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties,
WebEndpointProperties webEndpointProperties, Environment environment) {
List<ExposableEndpoint<?>> allEndpoints = new ArrayList<>();
// 获取 WebEndpointDiscoverer 的 Endpoint 并保存,这里直接调用了 WebEndpointDiscoverer#getEndpoints 方法来获取
Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
allEndpoints.addAll(webEndpoints);
// 获取 ServletEndpointDiscoverer 的 Endpoint 并保存
allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
// 获取 ControllerEndpointDiscoverer 的 Endpoint 并保存
allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
// 获取 配置的基础访问路径。即 management.endpoints.web.base-path 配置
String basePath = webEndpointProperties.getBasePath();
EndpointMapping endpointMapping = new EndpointMapping(basePath);
boolean shouldRegisterLinksMapping = StringUtils.hasText(basePath)
|| ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT);
// 创建 WebMvcEndpointHandlerMapping 并返回
return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes,
corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath),
shouldRegisterLinksMapping);
}
/**
*
* @param controllerEndpointsSupplier 实际类型 ControllerEndpointDiscoverer
* @param corsProperties : 映射 management.endpoints.web.cors 配置
* @param webEndpointProperties 映射 management.endpoints.web 配置
* @return
*/
@Bean
@ConditionalOnMissingBean
public ControllerEndpointHandlerMapping controllerEndpointHandlerMapping(
ControllerEndpointsSupplier controllerEndpointsSupplier, CorsEndpointProperties corsProperties,
WebEndpointProperties webEndpointProperties) {
EndpointMapping endpointMapping = new EndpointMapping(webEndpointProperties.getBasePath());
return new ControllerEndpointHandlerMapping(endpointMapping, controllerEndpointsSupplier.getEndpoints(),
corsProperties.toCorsConfiguration());
}
这里我们可以看到,在 WebMvcEndpointHandlerMapping 和 ControllerEndpointHandlerMapping 初始化时就已经通过 EndpointDiscoverer#getEndpoints 获取到了 Endpoint 集合。
1. WebMvcEndpointHandlerMapping
下面我们以 WebMvcEndpointHandlerMapping 为例,其结构如下,
可以看到 WebMvcEndpointHandlerMapping 间接继承了 AbstractHandlerMethodMapping,而我们在上面提到过 AbstractHandlerMethodMapping 初始化结束后会通过 AbstractHandlerMethodMapping #afterPropertiesSet 调用 initHandlerMethods 方法用于初始化 Handler Method。下面我们来看WebMvcEndpointHandlerMapping#initHandlerMethods 的实现 (该方法在 ControllerEndpointHandlerMapping#initHandlerMethods 中进行了重写,我们下面会介绍)
// WebMvcEndpointHandlerMapping 本身并没有实现该方法,而是在其父类 AbstractWebMvcEndpointHandlerMapping#initHandlerMethods 中实现
@Override
protected void initHandlerMethods() {
// 遍历所有的 endpoints,endpoints是在该类创建时获取的
for (ExposableWebEndpoint endpoint : this.endpoints) {
// 获取 WebOperation
for (WebOperation operation : endpoint.getOperations()) {
// 1. 注册 Mapping
registerMappingForOperation(endpoint, operation);
}
}
// 是否需要注册基础映射地址
if (this.shouldRegisterLinksMapping) {
// 2. 注册基础映射地址,即 base-path 指定的访问的地址
registerLinksMapping();
}
}
我们可以看到两个关键方法:
- registerMappingForOperation : 将 Operation 作为 Mapping 进行注册
-
registerLinksMapping : 注册基础映射地址,注册基础映射地址,即 base-path 指定的访问的地址,默认情况下为
http://{ip}:{port}/actuator
,通过此接口,我们可以查看到所有的 endpoint 接口。如下图
下面我们来看上面两步的具体实现 :
1.1 registerMappingForOperation
AbstractWebMvcEndpointHandlerMapping#registerMappingForOperation 实现如下:
private void registerMappingForOperation(ExposableWebEndpoint endpoint, WebOperation operation) {
WebOperationRequestPredicate predicate = operation.getRequestPredicate();
// 获取请求根路径, 对路径进行进一步处理
String path = predicate.getPath();
// 返回用于捕获所有剩余路径段的变量的名称
String matchAllRemainingPathSegmentsVariable = predicate.getMatchAllRemainingPathSegmentsVariable();
if (matchAllRemainingPathSegmentsVariable != null) {
path = path.replace("{*" + matchAllRemainingPathSegmentsVariable + "}", "**");
}
// 1. 将参数包装成 ServletWebOperation,默认即是将 ServletWebOperationAdapter 返回
ServletWebOperation servletWebOperation = wrapServletWebOperation(endpoint, operation,
new ServletWebOperationAdapter(operation));
// 2. 注册映射关系
registerMapping(createRequestMappingInfo(predicate, path), new OperationHandler(servletWebOperation),
this.handleMethod);
}
我们这里看两点 :
-
AbstractWebMvcEndpointHandlerMapping#wrapServletWebOperation : 该方法默认情况下是直接返回了入参中的 ServletWebOperationAdapter实例, ServletWebOperationAdapter 是 AbstractWebMvcEndpointHandlerMapping 的内部类,当请求来到时会调用 ServletWebOperationAdapter#handle 来处理请求,如下:
@Override public Object handle(HttpServletRequest request, @RequestBody(required = false) Map<String, String> body) { HttpHeaders headers = new ServletServerHttpRequest(request).getHeaders(); Map<String, Object> arguments = getArguments(request, body); try { ApiVersion apiVersion = ApiVersion.fromHttpHeaders(headers); ServletSecurityContext securityContext = new ServletSecurityContext(request); InvocationContext invocationContext = new InvocationContext(apiVersion, securityContext, arguments); // 调用 Operation#invoke 来执行具体的处理,并封装请求结果返回。 return handleResult(this.operation.invoke(invocationContext), HttpMethod.resolve(request.getMethod())); } catch (InvalidEndpointRequestException ex) { throw new BadOperationRequestException(ex.getReason()); } }
-
AbstractHandlerMethodMapping#registerMapping :对映射关系进行注册。该方法的实现很简单,如下:
// 直接调用 AbstractHandlerMethodMapping.MappingRegistry#register 进行注册 public void registerMapping(T mapping, Object handler, Method method) { this.mappingRegistry.register(mapping, handler, method); }
需要注意的是 .mappingRegistry.register 方法会调用 AbstractHandlerMethodMapping#createHandlerMethod 方法来创建 HandlerMethod ,而 AbstractWebMvcEndpointHandlerMapping 重写了该方法,其实现如下:
@Override protected HandlerMethod createHandlerMethod(Object handler, Method method) { HandlerMethod handlerMethod = super.createHandlerMethod(handler, method); // 使用 WebMvcEndpointHandlerMethod进行包装 return new WebMvcEndpointHandlerMethod(handlerMethod.getBean(), handlerMethod.getMethod()); }
映射关系注册后,如果有请求匹配 mapping 规则,则会调用 handler 的 method 方法来进行处理
,所以我们这里来看看其入参具体的由来 , 如下:-
createRequestMappingInfo(predicate, path)
:该方法创建了RequestMappingInfo,具体实现在AbstractWebMvcEndpointHandlerMapping#createRequestMappingInfo中,如下:private RequestMappingInfo createRequestMappingInfo(WebOperationRequestPredicate predicate, String path) { // 根据请求地址、请求类型等来创建 RequestMappingInfo return RequestMappingInfo.paths(this.endpointMapping.createSubPath(path)) .methods(RequestMethod.valueOf(predicate.getHttpMethod().name())) .consumes(predicate.getConsumes().toArray(new String[0])) .produces(predicate.getProduces().toArray(new String[0])).build(); }
-
new OperationHandler(servletWebOperation)
: 这里创建了一个 OperationHandler 实例 , OperationHandler 是 AbstractWebMvcEndpointHandlerMapping 内部类,其实现如下:private static final class OperationHandler { private final ServletWebOperation operation; OperationHandler(ServletWebOperation operation) { this.operation = operation; } @ResponseBody Object handle(HttpServletRequest request, @RequestBody(required = false) Map<String, String> body) { // 这里会调用 AbstractWebMvcEndpointHandlerMapping.ServletWebOperation#handle 方法来处理请求 return this.operation.handle(request, body); } @Override public String toString() { return this.operation.toString(); } }
这里注意,当对一个请求处理时,其处理过程如下:
OperationHandler#handle -> ServletWebOperationAdapter#handle -> Operation#invoke
-
this.handleMethod
: handleMethod 是 AbstractWebMvcEndpointHandlerMapping的一个属性,定义如下:// 这里可以看到 handleMethod 即是通过反射获取到的 OperationHandler#handle 方法 private final Method handleMethod = ReflectionUtils.findMethod(OperationHandler.class, "handle", HttpServletRequest.class, Map.class);
-
1.2 registerLinksMapping
registerLinksMapping 方法完成了基础接口的注册功能,通过此接口我们可以看到可以访问的端点接口地址,具体实现如下:
private void registerLinksMapping() {
RequestMappingInfo mapping = RequestMappingInfo.paths(this.endpointMapping.createSubPath(""))
.methods(RequestMethod.GET).produces(this.endpointMediaTypes.getProduced().toArray(new String[0]))
.options(builderConfig).build();
LinksHandler linksHandler = getLinksHandler();
// 当访问路径匹配 mapping 时,会调用 linksHandler 的 links 方法,
registerMapping(mapping, linksHandler, ReflectionUtils.findMethod(linksHandler.getClass(), "links",
HttpServletRequest.class, HttpServletResponse.class));
}
而
linksHandler
在 Web 环境下的实现是 WebMvcEndpointHandlerMapping 的内部类WebMvcLinksHandler,其实现如下:
class WebMvcLinksHandler implements LinksHandler {
@Override
@ResponseBody
public Map<String, Map<String, Link>> links(HttpServletRequest request, HttpServletResponse response) {
// 返回 links map,其中 WebMvcEndpointHandlerMapping.this.linksResolver 是 WebMvcEndpointHandlerMapping 初始化时的入参,保存了所有的 endpoint 信息。
return Collections.singletonMap("_links",
WebMvcEndpointHandlerMapping.this.linksResolver.resolveLinks(request.getRequestURL().toString()));
}
@Override
public String toString() {
return "Actuator root web endpoint";
}
}
2. ControllerEndpointHandlerMapping
ControllerEndpointHandlerMapping 结构如下:
ControllerEndpointHandlerMapping 的逻辑和 WebMvcEndpointHandlerMapping 基本相同,有些许不同,我们来简单看一下。
2.1 ControllerEndpointHandlerMapping 构造函数
ControllerEndpointHandlerMapping 构造函数如下,这里我们注意 ControllerEndpointHandlerMapping 在构造函数中直接将endpoint 转换为 handlers。
private final EndpointMapping endpointMapping;
private final CorsConfiguration corsConfiguration;
private final Map<Object, ExposableControllerEndpoint> handlers;
public ControllerEndpointHandlerMapping(EndpointMapping endpointMapping,
Collection<ExposableControllerEndpoint> endpoints, CorsConfiguration corsConfiguration) {
Assert.notNull(endpointMapping, "EndpointMapping must not be null");
Assert.notNull(endpoints, "Endpoints must not be null");
this.endpointMapping = endpointMapping;
// 将 endpoints 转换为 handlers
this.handlers = getHandlers(endpoints);
this.corsConfiguration = corsConfiguration;
setOrder(-100);
setUseSuffixPatternMatch(false);
}
private Map<Object, ExposableControllerEndpoint> getHandlers(Collection<ExposableControllerEndpoint> endpoints) {
Map<Object, ExposableControllerEndpoint> handlers = new LinkedHashMap<>();
// 以请求路径为 key,value 为 endpoint
endpoints.forEach((endpoint) -> handlers.put(endpoint.getController(), endpoint));
return Collections.unmodifiableMap(handlers);
}
2.2 ControllerEndpointHandlerMapping#initHandlerMethods
ControllerEndpointHandlerMapping 重写了 initHandlerMethods 方法,其实现如下:
@Override
protected void initHandlerMethods() {
// 这里的 this::detectHandlerMethods 调用是 AbstractHandlerMethodMapping#detectHandlerMethods 方法
this.handlers.keySet().forEach(this::detectHandlerMethods);
}
AbstractHandlerMethodMapping#detectHandlerMethods 的实现如下(如有需要,详参
Spring 源码分析衍生篇十一 :HandlerMapping
):
// 检测 handler 方法并注册
protected void detectHandlerMethods(Object handler) {
// 获取 handlerType。这里传入的 handler如果是 String 就是 beanName,从上下文中获取type,否则就直接认为是 Handler
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
// 返回给定类的用户定义类:通常只是给定的类,但对于CGLIB生成的子类,则返回原始类
Class<?> userType = ClassUtils.getUserClass(handlerType);
// 遍历当前bean的所有方法,筛选出合适的 handler 方法 以及 注解信息
// 这里需要注意的是 MethodIntrospector.selectMethods 底层将 getMappingForMethod 返回为null 的值给过滤掉了。
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
// 供子类实现
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
});
if (logger.isTraceEnabled()) {
logger.trace(formatMappings(userType, methods));
}
// 遍历所有的methods(这里的methods经过上面的筛选后,都是被 @RequestMapping 注解修饰的方法)
methods.forEach((method, mapping) -> {
// 这里是针对 cglib 代理特殊处理。
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
//注册 HandlerMethod 。
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
在上述代码中通过
MethodIntrospector#selectMethods(...)
方法遍历了当前bean的所有方法,并调用
getMappingForMethod
方法进行处理。
getMappingForMethod
方法在
AbstractHandlerMethodMapping
中并未有具体实现,这里我们看看
RequestMappingHandlerMapping
中的实现。
// RequestMappingHandlerMapping 中的实现
@Override
@Nullable
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
// 转换成 RequestMappingInfo ,如果方法没有被 @RequestMapping 注解修饰,则会返回null
// 解析出来方法上 @RequestMapping 注解的各种信息
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
// 解析出来 bean 上 @RequestMapping 注解的各种信息
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
// 合并类和方法的 @RequestMapping 注解信息
info = typeInfo.combine(info);
}
// 获取前缀,拼接前缀
String prefix = getPathPrefix(handlerType);
if (prefix != null) {
info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
}
}
// 返回信息
return info;
}
getMappingForMethod
方法的作用是解析 方法上的
@RequestMapping
注解的信息并和类上
@RequestMapping
注解 的信息相结合。
@Nullable
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
// 获取当前方法上的 @RequestMapping 注解
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
// 获取自定义的方法条件
RequestCondition<?> condition = (element instanceof Class ?
getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
// 这里可以看到 如果 requestMapping = null,则会直接返回null,否则会封装成一个 RequestMappingInfo (包含 @RequestMapping 注解的各种参数) 返回。
return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
三、调用过程
上面我们介绍了 WebMvcEndpointHandlerMapping 对 Endpoint 的处理过程,下面我们来看看当我们通过Http 发起端点请求时容器的处理过程。具体的 Spring mvc 流程本文不再详情讲述,如有需要详参 :
-
Spring源码分析二十:Spring MVC① 搭建
-
Spring源码分析二十一:Spring MVC② DispatcherServlet的初始化
-
Spring源码分析二十二:Spring MVC③ DispatcherServlet的逻辑
Spring会在DispatcherServlet#doDispatch 中进行处理 http 请求。
-
首先 会调用 DispatcherServlet#getHandler 方法来找到适合处理当前请求的 Handler。DispatcherServlet#getHandler 实现如下:
@Nullable protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { // 遍历 handlerMapping 根据request 找到合适的 handler,优先级最高的是 WebMvcEndpointHandlerMapping for (HandlerMapping mapping : this.handlerMappings) { // 默认的逻辑是根据请求路径做匹配规则。 HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null) { return handler; } } } return null; }
这里的匹配首先会调用 WebMvcEndpointHandlerMapping#getHandler 方法来寻找处理该请求的处理器,如果找到,则直接返回,否则再遍历其他的 HandlerMapping 来寻找。
-
之后会通过 DispatcherServlet#getHandlerAdapter 方法找到合适的 HandlerAdapter。DispatcherServlet#getHandlerAdapter 实现如下:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { if (this.handlerAdapters != null) { for (HandlerAdapter adapter : this.handlerAdapters) { if (adapter.supports(handler)) { return adapter; } } } throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); }
我们上面提到过 端点注册的HandlerMethod 实际类型为 AbstractWebMvcEndpointHandlerMapping#WebMvcEndpointHandlerMethod,其实现如下 :
private static class WebMvcEndpointHandlerMethod extends HandlerMethod { WebMvcEndpointHandlerMethod(Object bean, Method method) { super(bean, method); } @Override public String toString() { return getBean().toString(); } @Override public HandlerMethod createWithResolvedBean() { return this; } }
AbstractWebMvcEndpointHandlerMapping#WebMvcEndpointHandlerMethod作为HandlerMethod 的子类,是可以被 RequestMappingHandlerAdapter 匹配上的,因此这里返回的 HandlerAdapter 类型为 RequestMappingHandlerAdapter。
-
最后通过 HandlerAdapter#handle 方法来处理 Handler,即 RequestMappingHandlerAdapter#handle 来处理。这里会调用 HandlerMethod#method 的方法完成调用。而 HandlerMethod#method 实际方法为 AbstractWebMvcEndpointHandlerMapping$OperationHandler.handle。经过
OperationHandler#handle -> ServletWebOperationAdapter#handle -> Operation#invoke
调用链路后调用到 Operation#invoke 完成调用。
以上:内容部分参考
https://www.cnblogs.com/caoweixiong/p/15325382.html
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正