如何使用不再赘述,解释几个感兴趣的关键实现吧:
1 Endpoint如何实现的?
先看下如何实现一个自定义的Endpoint:
@Component
@Endpoint(id = "myendpoint")
public class MyEndpoint {
public final static class Student {
private String name;
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }
    @ReadOperation
    public Student getData() {
        Student student = new Student();
        student.setName("fujian");
        return student;
    }
}
然后修改配置application.yml:
[Java]
management:
  metrics:
    enable:
      kafka: true
  endpoints:
    web:
      exposure:
        include: myendpoint
[/Java]
然后就可以访问了:http://localhost:8080/actuator/myendpoint
步骤1: 扫描Endpoint,组装成EndpointBean集合
执行的是:org.springframework.boot.actuate.endpoint.annotation.EndpointDiscoverer#discoverEndpoints
	private Collection
		Collection
		addExtensionBeans(endpointBeans);
		return convertToEndpoints(endpointBeans);
	}
	private Collection
		Map
		String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(this.applicationContext,
				Endpoint.class);
                //最终调用的是:org.springframework.beans.factory.ListableBeanFactory#getBeanNamesForAnnotation
		for (String beanName : beanNames) {
			if (!ScopedProxyUtils.isScopedTarget(beanName)) {
				EndpointBean endpointBean = createEndpointBean(beanName);
				EndpointBean previous = byId.putIfAbsent(endpointBean.getId(), endpointBean);
				Assert.state(previous == null, () -> “Found two endpoints with the id ‘” + endpointBean.getId() + “‘: ‘”
						+ endpointBean.getBeanName() + “‘ and ‘” + previous.getBeanName() + “‘”);
			}
		}
		return byId.values();
	}
步骤2: 封装EndpointBean到ServletEndpointRegistrar:这里注意下,它是一种ServletContextInitializer:
执行的是:org.springframework.boot.actuate.autoconfigure.endpoint.web.ServletEndpointManagementContextConfiguration.WebMvcServletEndpointManagementContextConfiguration
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(DispatcherServlet.class)
	public static class WebMvcServletEndpointManagementContextConfiguration {
                //注意这个返回类型的定义:public class ServletEndpointRegistrar implements ServletContextInitializer
		@Bean
		public ServletEndpointRegistrar servletEndpointRegistrar(WebEndpointProperties properties,
				ServletEndpointsSupplier servletEndpointsSupplier, DispatcherServletPath dispatcherServletPath) {
			return new ServletEndpointRegistrar(dispatcherServletPath.getRelativePath(properties.getBasePath()),
					servletEndpointsSupplier.getEndpoints());
		}
}
步骤3:ServletContextInitializer就是启动(Tomcat等容器)成功之后会回调它的onStartup方法。
执行的是:org.springframework.boot.actuate.endpoint.web.ServletEndpointRegistrar#onStartup
        @Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		this.servletEndpoints.forEach((servletEndpoint) -> register(servletContext, servletEndpoint));
	}
	private void register(ServletContext servletContext, ExposableServletEndpoint endpoint) {
		String name = endpoint.getEndpointId().toLowerCaseString() + “-actuator-endpoint”;
		String path = this.basePath + “/” + endpoint.getRootPath();
		String urlMapping = path.endsWith(“/”) ? path + “*” : path + “/*”;
		EndpointServlet endpointServlet = endpoint.getEndpointServlet();
                //关键点
		Dynamic registration = servletContext.addServlet(name, endpointServlet.getServlet());
		registration.addMapping(urlMapping);
		registration.setInitParameters(endpointServlet.getInitParameters());
		registration.setLoadOnStartup(endpointServlet.getLoadOnStartup());
		logger.info(“Registered ‘” + path + “‘ to ” + name);
	}
记录到此,基本应该清楚了,最核心点就是在于扫描功能+ServletContextInitializer的作用。
这里可以自己写一个测试下:
@Component
public class DemoServletContextInitializer implements ServletContextInitializer {
    public void onStartup(ServletContext servletContext) throws ServletException{
        System.out.println(“this is my demo servlet context initializer” );
    }
}
启动程序后,就有输出了。
2 某项服务的Health check是如何开启和关闭的?
先看下如何开启或者关闭?
management:
  health:
    db:
      enabled: true
    redis:
      enabled: true
    cassandra:
      enabled: true
以redis为例:不是enable了,就意味着一定会检查,原因参考:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisConnectionFactory.class)
@ConditionalOnBean(RedisConnectionFactory.class)  //必须有Redis的Jar依赖
@ConditionalOnEnabledHealthIndicator(“redis”)
@AutoConfigureAfter({ RedisAutoConfiguration.class, RedisReactiveHealthContributorAutoConfiguration.class })
public class RedisHealthContributorAutoConfiguration
		extends CompositeHealthContributorConfiguration
	@Bean
	@ConditionalOnMissingBean(name = { “redisHealthIndicator”, “redisHealthContributor” })
	public HealthContributor redisHealthContributor(Map
		return createContributor(redisConnectionFactories);
	}
}
再来看下,management.health.redis怎么开启和关闭的?关键点在于上面的:@ConditionalOnEnabledHealthIndicator(“redis”)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnEnabledHealthIndicatorCondition.class)
public @interface ConditionalOnEnabledHealthIndicator {
	/**
	 * The name of the health indicator.
	 * @return the name of the health indicator
	 */
	String value();
}
其中Conditional表明了条件OnEnabledHealthIndicatorCondition,看下这个条件:
class OnEnabledHealthIndicatorCondition extends OnEndpointElementCondition {
	OnEnabledHealthIndicatorCondition() {
		super(“management.health.”, ConditionalOnEnabledHealthIndicator.class);
	}
}
继续看父类的一段实现:
	@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
                //annotationType就是ConditionalOnEnabledHealthIndicator
		AnnotationAttributes annotationAttributes = AnnotationAttributes
				.fromMap(metadata.getAnnotationAttributes(this.annotationType.getName()));
		String endpointName = annotationAttributes.getString(“value”);
                //value就是redis
		ConditionOutcome outcome = getEndpointOutcome(context, endpointName);
		if (outcome != null) {
			return outcome;
		}
		return getDefaultEndpointsOutcome(context);
	}
OnEnabledHealthIndicatorCondition指明了判断条件时,可以依据注解”ConditionalOnEnabledHealthIndicator.class”来获取开关项(value),那么上述的metadata是什么?其实就是基类SpringBootCondition中的AnnotatedTypeMetadata:
public abstract class SpringBootCondition implements Condition {
	@Override
	public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata)
        //……
}
顾名思义,其实就是Conditional标记的类上标记的所有注解信息,所以RedisHealthContributorAutoConfiguration上标记的ConditionalOnEnabledHealthIndicator,自然也是其中一个,所以能拿到它的value:redis,结合前缀”management.health.”,就这样对应上了。
这里也写一个简化版案例:
@Configuration
public class BeanConfig {
private final static class DemoCondition implements Condition {
        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            // style one:
            MergedAnnotations annotations = metadata.getAnnotations();
            MergedAnnotation
            System.out.println(annotation.getString(“value”));
            // style two:
            //MyAnnotation.class.getName(): com.example.actuatorlearning.BeanConfig$MyAnnotation
            Map
            System.out.println(myAnnotation.get(“value”));
            return false;
        }
    }
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface MyAnnotation {
          String value();
     }
    @Conditional(DemoCondition.class)
    @MyAnnotation(“my flag key”)
    @Bean
    public MyEndpoint.Student student(){
        return new MyEndpoint.Student();
    }
}
上面的AnnotatedTypeMetadata包含了三个注解:其中就有MyAnnotation
其他一些知识点:
解析泛型的类型:
		ResolvableType type = ResolvableType.forClass(AbstractCompositeHealthContributorConfiguration.class,
				getClass());
		this.indicatorType = type.resolveGeneric(1);
		this.beanType = type.resolveGeneric(2);