Spring源码分析二十六 :Actuator 浅析②

  • Post author:
  • Post category:其他




一、前言

本文是笔者阅读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();
		}
	}

我们可以看到两个关键方法:

  1. registerMappingForOperation : 将 Operation 作为 Mapping 进行注册
  2. 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);
	}


我们这里看两点 :

  1. 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());
    			}
    		}
    
  2. 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();
      	}
      

    1. 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
      

    2. 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 流程本文不再详情讲述,如有需要详参 :


  1. Spring源码分析二十:Spring MVC① 搭建

  2. Spring源码分析二十一:Spring MVC② DispatcherServlet的初始化

  3. Spring源码分析二十二:Spring MVC③ DispatcherServlet的逻辑

Spring会在DispatcherServlet#doDispatch 中进行处理 http 请求。

  1. 首先 会调用 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 来寻找。

  2. 之后会通过 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。

  3. 最后通过 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


如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正



版权声明:本文为qq_36882793原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。