Springboot Actuator之七:actuator 中原生endpoint源碼解析1

看actuator項目的包結構,以下:html

本文中的介紹Endpoints。java

Endpoints(端點)介紹

Endpoints 是 Actuator 的核心部分,它用來監視應用程序及交互,spring-boot-actuator中已經內置了很是多的Endpoints(health、info、beans、httptrace、shutdown等等),同時也容許咱們擴展本身的端點。web

Endpoints 分紅兩類:原生端點用戶自定義端點:spring

  1. 原生端點是在應用程序裏提供的衆多 restful api 接口,經過它們能夠監控應用程序運行時的內部情況。原生端點又能夠分紅三類:
    • 應用配置類:能夠查看應用在運行期間的靜態信息:例如自動配置信息、加載的spring bean信息、yml文件配置信息、環境信息、請求映射信息;
    • 度量指標類:主要是運行期間的動態信息,例如堆棧、請求連、一些健康指標、metrics信息等;
    • 操做控制類:主要是指shutdown,用戶能夠發送一個請求將應用的監控功能關閉。
  2. 自定義端點主要是指擴展性,用戶能夠根據本身的實際應用,定義一些比較關心的指標,在運行期進行監控。

咱們這裏詳細說明org.springframework.boot.actuate.endpoint中原生端點的實現.經過以下幾個維度來進行分析:數據庫

  • xxxEndpoint的做用
  • xxxEndpoint的字段,構造器
  • xxxEndpoint核心方法invoke 實現分析
  • xxxEndpoint如何進行配置
  • xxxEndpoint如何自動化裝配

在org.springframework.boot.actuate.endpoint 中還有2個子包-jmx(可經過jmx協議訪問),mvc(經過spring mvc 暴露,可經過接口進行訪問,在下篇文章進行分析).這裏咱們不關注這些,這看org.springframework.boot.actuate.endpoint 包下的類,類比較多,先看個類圖吧,以下:json

Endpoint接口:org.springframework.boot.actuate.endpoint.Endpoint.java

Endpoint接口:一個端點能夠用於暴露(系統信息、操做入口等)信息。一般暴露方式是經過spring mvc的,如繼承AbstractEndpoint的方式實現本身的endpoint。api

public interface Endpoint<T> {

    // 端點的邏輯標識(字母、數字和下劃線('_') 組成)
    String getId();

    // 端點是否啓用
    boolean isEnabled();

    // 端點是否輸出敏感數據
    boolean isSensitive();

    // 調用端點,並返回調用結果
    T invoke();
}

 其中泛型參數T爲暴露的數據類型.方法的做用已經註釋。數組

AbstractEndpoint抽象類

Endpoint的一個抽象子類:AbstractEndpoint(Endpoint接口實現的抽象基類),該類實現了EnvironmentAware,所以, AbstractEndpoint也就持有了Environment。restful

一、AbstractEndpoint 有以下屬性:
// 匹配包括下劃線的任何單詞字符。相似但不等價於「[A-Za-z0-9_]」
private static final Pattern ID_PATTERN = Pattern.compile("\\w+");
// 經過EnvironmentAware接口注入
private Environment environment;
// 端點標識符
private String id;
// 是否默認敏感
private final boolean sensitiveDefault;
// 標識該端點是否暴露敏感信息
private Boolean sensitive;
// 是否端點可用
private Boolean enabled;
二、AbstractEndpoint方法

AbstractEndpoint方法實現了Endpoint接口中的getId, isEnabled, isSensitive,其中, getId只需返回AbstractEndpoint中的id屬性便可,咱們分別來看下其餘方法的實現:session

2.一、isEnabled,代碼以下:

@Override
public boolean isEnabled() {
        return EndpointProperties.isEnabled(this.environment, this.enabled);
}
@ConfigurationProperties(prefix = "endpoints")
public class EndpointProperties {

    private static final String ENDPOINTS_ENABLED_PROPERTY = "endpoints.enabled";

    private static final String ENDPOINTS_SENSITIVE_PROPERTY = "endpoints.sensitive";

    public static boolean isEnabled(Environment environment, Boolean enabled) {
        //一、若是AbstractEndpoint#enabled屬性有值,則使用AbstractEndpoint的配置
        if (enabled != null) {
            return enabled;
        }
        //若是Environment 不等於null 而且Environment 配置有endpoints.enabled的屬性,則返回其配置的值
        if (environment != null
                && environment.containsProperty(ENDPOINTS_ENABLED_PROPERTY)) {
            return environment.getProperty(ENDPOINTS_ENABLED_PROPERTY, Boolean.class);
        }
        //三、若是1和2沒有值,則返回默認值true
        return true;
    }

2.二、isSensitive和isEnabled實現差很少,以下:

    @Override
    public boolean isSensitive() {
        return EndpointProperties.isSensitive(this.environment, this.sensitive,
                this.sensitiveDefault);
    }
@ConfigurationProperties(prefix = "endpoints")
public class EndpointProperties {

    private static final String ENDPOINTS_ENABLED_PROPERTY = "endpoints.enabled";

    private static final String ENDPOINTS_SENSITIVE_PROPERTY = "endpoints.sensitive";

    public static boolean isSensitive(Environment environment, Boolean sensitive,
            boolean sensitiveDefault) {
        //一、若是abstractEndpoint的sensitive有值,則使用這個配置
        if (sensitive != null) {
            return sensitive;
        }
        //二、若是environment 不等於null 而且 environment中配置有endpoints.sensitive的屬性,則返回其配置值
        if (environment != null
                && environment.containsProperty(ENDPOINTS_SENSITIVE_PROPERTY)) {
            return environment.getProperty(ENDPOINTS_SENSITIVE_PROPERTY, Boolean.class);
        }
        //三、返回指定的默認值(默認爲false)
        return sensitiveDefault;
    }

EnvironmentEndpoint

AbstractEndpoint的實現類之EnvironmentEndpoint--敏感數據

一、構造函數

@ConfigurationProperties(prefix = "endpoints.env")
public class EnvironmentEndpoint extends AbstractEndpoint<Map<String, Object>> {
    public EnvironmentEndpoint() {
        super("env");
    }
        //調用AbstractEndpoint的構造函數
    public AbstractEndpoint(String id) {
        this(id, true);
    }

最終,設置id爲env,標識爲敏感數據。

二、實現的invoke(),代碼以下:

public Map<String, Object> invoke() {
    // 1. 先定義空的map返回值
    Map<String, Object> result = new LinkedHashMap<String, Object>();
    // 2. 將spring boot 中激活的profile 放入result中,key --> profile
    result.put("profiles", getEnvironment().getActiveProfiles());
    // 3. 得到PlaceholderSanitizingPropertyResolver --> 處理佔位符,處理敏感數據
    PropertyResolver resolver = getResolver();
    // 4. 遍歷environment 配置的PropertySource,依次處理之
    for (Entry<String, PropertySource<?>> entry : getPropertySourcesAsMap()
            .entrySet()) {
        PropertySource<?> source = entry.getValue();
        String sourceName = entry.getKey();
        if (source instanceof EnumerablePropertySource) {
            // 4.1 只針對EnumerablePropertySource 類型的PropertySource 進行處理--> 依次將屬性添加到properties中,
            // 若是屬性值爲string,則在添加前進行佔位符,數據脫敏的處理
            EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) source;
            Map<String, Object> properties = new LinkedHashMap<String, Object>();
            for (String name : enumerable.getPropertyNames()) {
                Object property = source.getProperty(name);
                Object resolved = property instanceof String
                        ? resolver.resolvePlaceholders((String) property) : property;
                //調用Sanitizer類進行脫敏
                properties.put(name, sanitize(name, resolved));
            }
            // 4.2 後置處理,該方法的實現是直接返回原始值,能夠經過覆寫的方式進行擴展
            properties = postProcessSourceProperties(sourceName, properties);
            if (properties != null) {
                // 4.3 若是不爲空,則添加到result中
                result.put(sourceName, properties);
            }
        }
    }
    return result;
}

處理佔位符,處理敏感數據:PlaceholderSanitizingPropertyResolver.java是EnvironmentEndpoint的內部類

public PropertyResolver getResolver() {
// 1. 實例化PlaceholderSanitizingPropertyResolver --> 處理佔位符,處理敏感數據
PlaceholderSanitizingPropertyResolver resolver = new PlaceholderSanitizingPropertyResolver(
        getPropertySources(), this.sanitizer);
// 2. 設置ignoreUnresolvableNestedPlaceholders 爲true
resolver.setIgnoreUnresolvableNestedPlaceholders(true);
return resolver;
}

PlaceholderSanitizingPropertyResolver繼承了PropertySourcesPropertyResolver,這樣就能對佔位符進行處理了,又由於其內部持有Sanitizer(用於敏感數據脫敏),複寫了getPropertyAsRawString,這樣就能處理佔位符,敏感數據了.代碼以下:

        @Override
        protected String getPropertyAsRawString(String key) {
            String value = super.getPropertyAsRawString(key);
            return (String) this.sanitizer.sanitize(key, value);
        }

三、EnvironmentEndpoint的屬性配置,因爲EnvironmentEndpoint被@ConfigurationProperties(prefix = 「endpoints.env」)註解,所以可經過以下配置進行個性化配置:

endpoints.env.id=env  
endpoints.env.sensitive=true  
endpoints.env.enabled=true

同時,又由於其聲明瞭以下方法:

public void setKeysToSanitize(String... keysToSanitize) {
    this.sanitizer.setKeysToSanitize(keysToSanitize);
}

所以能夠經過endpoints.env.keys-to-sanitize=xx,xx 來配置對指定的數據進行脫敏。脫敏配置

 四、EnvironmentEndpoint的自動化裝配

EnvironmentEndpoint的自動化裝配是在EndpointAutoConfiguration中,代碼以下:

@Configuration
@AutoConfigureAfter({ FlywayAutoConfiguration.class, LiquibaseAutoConfiguration.class })
@EnableConfigurationProperties(EndpointProperties.class)
public class EndpointAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean public EnvironmentEndpoint environmentEndpoint() {
        return new EnvironmentEndpoint();
    }
  • @Bean註解:註冊1個id爲environmentEndpoint,類型爲EnvironmentEndpoint的bean
  • @ConditionalOnMissingBean註解:當beanFactory中不存在EnvironmentEndpoint類型的bean時註冊
InfoEndpoint

AbstractEndpoint的實現類之InfoEndpoint,用於暴露應用信息。

其字段和構造器以下:

private final List<InfoContributor> infoContributors;
public InfoEndpoint(List<InfoContributor> infoContributors) {
    super("info", false);
    Assert.notNull(infoContributors, "Info contributors must not be null");
    this.infoContributors = infoContributors;
}

其內部持有了BeanFactory中全部InfoContributor類型的bean,其經過構造器注入。

二、invoke 實現以下:

public Map<String, Object> invoke() {
        Info.Builder builder = new Info.Builder();
        for (InfoContributor contributor : this.infoContributors) {
            contributor.contribute(builder);
        }
        Info build = builder.build();
        return build.getDetails();
}

經過遍歷其內部的持有infoContributors,所以調用其contribute將info的數據添加到Info.Builder中,最後經過Info.Builder構建出Info,返回Info持有的details(建造者模式). Info中的details爲Map.

InfoContributor接口用於向Info$Builder添加信息,關於這部分的內容,咱們後續文章有分析.這裏就不在贅述了.

三、InfoEndpoint的屬性配置

@ConfigurationProperties(prefix = "endpoints.info")
public class InfoEndpoint extends AbstractEndpoint<Map<String, Object>> {

所以可經過以下進行配置:

endpoints.info.id=info  
endpoints.info.sensitive=true  
endpoints.info.enabled=true

四、InfoEndpoint的自動化裝配–>在EndpointAutoConfiguration中,代碼以下:

    @Bean
    @ConditionalOnMissingBean public InfoEndpoint infoEndpoint() throws Exception {
        return new InfoEndpoint(this.infoContributors == null
                ? Collections.<InfoContributor>emptyList() : this.infoContributors);
    }

 

和EnvironmentEndpoint同樣。

RequestMappingEndpoint:

AbstractEndpoint的實現類之RequestMappingEndpoint,因爲RequestMappingEndpoint同時也實現了ApplicationContextAware接口,所以,在初始化該類時會注入applicationContext。這個類的做用是打印Spring MVC 映射信息。

一、構造函數

    public RequestMappingEndpoint() {
        super("mappings");
    }

所以, RequestMappingEndpoint的id爲 mappings,默認爲敏感

二、invoke 實現以下:

public Map<String, Object> invoke() {
    Map<String, Object> result = new LinkedHashMap<String, Object>();
    // 1. 從handlerMappings中獲取HandlerMapping,默認狀況下handlerMappings是不存在數據的
    extractHandlerMappings(this.handlerMappings, result);
    // 2. 從applicationContext中獲取AbstractUrlHandlerMapping類型的bean,依次將其註冊的handler 添加進去.
    extractHandlerMappings(this.applicationContext, result);
    // 3. 從methodMappings中獲取HandlerMapping,默認狀況下methodMappings是不存在數據的
    extractMethodMappings(this.methodMappings, result);
    // 3. 從applicationContext中獲取AbstractUrlHandlerMapping類型的bean,依次得到其持有的HandlerMethods,進行處理.
    extractMethodMappings(this.applicationContext, result);
    return result;
}

從applicationContext中獲取

    protected void extractHandlerMappings(ApplicationContext applicationContext,
            Map<String, Object> result) {
        if (applicationContext != null) {
            Map<String, AbstractUrlHandlerMapping> mappings = applicationContext
                    .getBeansOfType(AbstractUrlHandlerMapping.class);
            for (Entry<String, AbstractUrlHandlerMapping> mapping : mappings.entrySet()) {
                Map<String, Object> handlers = getHandlerMap(mapping.getValue());
                for (Entry<String, Object> handler : handlers.entrySet()) {
                    result.put(handler.getKey(),
                            Collections.singletonMap("bean", mapping.getKey()));
                }
            }
        }
    }

得到AbstractUrlHandlerMapping類型的bean,此時有4個:
beanNameHandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
resourceHandlerMapping=org.springframework.web.servlet.handler.SimpleUrlHandlerMapping
faviconHandlerMapping=org.springframework.web.servlet.handler.SimpleUrlHandlerMapping
welcomePageHandlerMapping=org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WelcomePageHandlerMapping

依次遍歷mappings:
得到AbstractUrlHandlerMapping中註冊的handler,key–> path,value–>handler
依次遍歷handlerss,存入結果集中,存入的key–>AbstractUrlHandlerMapping的id,value={bean=AbstractUrlHandlerMapping中註冊的handler的路徑}

從methodMappings中獲取HandlerMapping,默認狀況下methodMappings是不存在數據的

從applicationContext中獲取AbstractUrlHandlerMapping類型的bean,依次得到其持有的HandlerMethods,進行處理.代碼以下:

    protected void extractMethodMappings(
            Collection<AbstractHandlerMethodMapping<?>> methodMappings,
            Map<String, Object> result) {
        for (AbstractHandlerMethodMapping<?> mapping : methodMappings) {
            Map<?, HandlerMethod> methods = mapping.getHandlerMethods();
            for (Map.Entry<?, HandlerMethod> entry : methods.entrySet()) {
                result.put(String.valueOf(entry.getKey()), Collections
                        .singletonMap("method", String.valueOf(entry.getValue())));
            }
        }
    }

得到AbstractUrlHandlerMapping類型的bean
依次遍歷AbstractUrlHandlerMapping中註冊的handler,添加至結果集中,key–> Handler 映射路徑 ,value = {bean = AbstractHandlerMethodMapping的id,method=HandlerMethod}

三、RequestMappingEndpoint的配置

@ConfigurationProperties(prefix = "endpoints.mappings")
public class RequestMappingEndpoint extends AbstractEndpoint<Map<String, Object>>
        implements ApplicationContextAware {

從類上的配置註解,可知:

endpoints.mappings.enabled= # Enable the endpoint.
endpoints.mappings.id= # Endpoint identifier.
endpoints.mappings.sensitive= # Mark if the endpoint exposes sensitive information.

四、RequestMappingEndpoint的自動裝配

    @Configuration
    @ConditionalOnClass(AbstractHandlerMethodMapping.class)
    protected static class RequestMappingEndpointConfiguration {

        @Bean
        @ConditionalOnMissingBean
        public RequestMappingEndpoint requestMappingEndpoint() {
            RequestMappingEndpoint endpoint = new RequestMappingEndpoint();
            return endpoint;
        }

    }

當知足以下兩個條件時建立requestMappingEndpoint,即註冊1個id爲requestMappingEndpoint,類型爲RequestMappingEndpoint的bean:

@ConditionalOnClass(AbstractHandlerMethodMapping.class) –> 在beanFactory中存在AbstractHandlerMethodMapping類型的bean時生效
@ConditionalOnMissingBean–>在beanFactory中不存在RequestMappingEndpoint類型的bean時生效

DumpEndpoint

AbstractEndpoint的實現類之DumpEndpoint,這個類的做用是打印線程信息,爲敏感

一、構造函數

    public DumpEndpoint() {
        super("dump");
    }

二、invoke()方法實現

    @Override
    public List<ThreadInfo> invoke() {
        return Arrays
                .asList(ManagementFactory.getThreadMXBean().dumpAllThreads(true, true));
    }

調用了ThreadMXBean的dumpAllThreads來返回全部活動線程的線程信息,並帶有堆棧跟蹤和同步信息。 當此方法返回時,返回數組中包含的一些線程可能已經終止。其中兩個參數指的意義以下:

第1個–>若是爲 true,則轉儲全部鎖定的監視器。
第2個–>若是爲 true,則轉儲全部鎖定的可擁有同步器。

 ThreadMXBean怎麼讀取Thread信息見《JMX學習一

三、DumpEndpoint的配置

@ConfigurationProperties(prefix = "endpoints.dump")
public class DumpEndpoint extends AbstractEndpoint<List<ThreadInfo>> {

 

可知:

endpoints.dump.enabled= # Enable the endpoint.
endpoints.dump.id= # Endpoint identifier.
endpoints.dump.sensitive= # Mark if the endpoint exposes sensitive information.

 

四、DumpEndpoint自動化裝配:

@Bean
@ConditionalOnMissingBean public DumpEndpoint dumpEndpoint() {
    return new DumpEndpoint();
}

FlywayEndpoint

AbstractEndpoint的實現類之FlywayEndpoint,Flyway是一款開源的數據庫版本管理工具。

ShutdownEndpoint

AbstractEndpoint的實現類之ShutdownEndpoint,做用是關閉應用。這個類繼承了ApplicationAware,獲得applicationContext,關閉spring容器調用applicationContext.close()方法。

一、構造函數

    public ShutdownEndpoint() {
        super("shutdown", true, false);
    }

id爲shutdown,爲敏感,關閉應用的endpoin默認是關閉的,不啓用的。

二、invoke()方法實現

    @Override
    public Map<String, Object> invoke() {
                //若是context爲null,直接返回
        if (this.context == null) {
            return NO_CONTEXT_MESSAGE;
        }
                //context不爲null,先返回,再啓動一個線程,在該線程中,先sleep 5秒後,而後調用了ShutdownEndpoint中持有的context的close方法進行關閉.
        try {
            return SHUTDOWN_MESSAGE;
        }
        finally {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(500L);
                    }
                    catch (InterruptedException ex) {
                        Thread.currentThread().interrupt();
                    }
                    ShutdownEndpoint.this.context.close();
                }
            });
            thread.setContextClassLoader(getClass().getClassLoader());
            thread.start();
        }
    }

在返回後,還但願作點啥,用try--finally。

三、ShutdownEndpoint的配置

@ConfigurationProperties(prefix = "endpoints.shutdown")
public class ShutdownEndpoint extends AbstractEndpoint<Map<String, Object>>
        implements ApplicationContextAware {

可知配置有:

endpoints.shutdown.enabled= # Enable the endpoint.
endpoints.shutdown.id= # Endpoint identifier.
endpoints.shutdown.sensitive= # Mark if the endpoint exposes sensitive information.

四、ShutdownEndpoint自動化裝配:

@Bean
@ConditionalOnMissingBean
public ShutdownEndpoint shutdownEndpoint() {
    return new ShutdownEndpoint();
}

AutoConfigurationReportEndpoint

AbstractEndpoint的實現類之AutoConfigurationReportEndpoint,做用是暴露ConditionEvaluationReport.。

一、構造函數

    public AutoConfigurationReportEndpoint() {
        super("autoconfig");
    }

 

id爲autoconfig,爲敏感

二、invoke()方法實現

    @Override
    public Report invoke() {
        return new Report(this.autoConfigurationReport);
    }

 

Report爲其內部類

    @JsonPropertyOrder({ "positiveMatches", "negativeMatches", "exclusions" })
    @JsonInclude(Include.NON_EMPTY)
    public static class Report {

 

  • @JsonPropertyOrder–>做用在類上,被用來指明當序列化時須要對屬性作排序,它有2個屬性:一個是alphabetic:布爾類型,表示是否採用字母拼音順序排序,默認是爲false,即不排序
  • @JsonInclude–> Report中的屬性值爲空集合則不進行展現

Report 有以下字段:

// 匹配的
private final MultiValueMap<String, MessageAndCondition> positiveMatches;

// 不匹配的
private final Map<String, MessageAndConditions> negativeMatches;

// 去除的
private final List<String> exclusions;

// 通常爲null
private final Report parent;

 

其中MessageAndCondition封裝了ConditionAndOutcome中的condition,message以進行更好的展現(json友好).其類上聲明瞭以下註解:

@JsonPropertyOrder({ "condition", "message" })

 

所以在進行輸出的時候,先輸出condition,再輸出message.

構造器以下:

        public MessageAndCondition(ConditionAndOutcome conditionAndOutcome) {
            Condition condition = conditionAndOutcome.getCondition();
            ConditionOutcome outcome = conditionAndOutcome.getOutcome();
            this.condition = ClassUtils.getShortName(condition.getClass());
            if (StringUtils.hasLength(outcome.getMessage())) {
                this.message = outcome.getMessage();
            }
            else {
                this.message = (outcome.isMatch() ? "matched" : "did not match");
            }
        }

 

賦值condition爲ConditionAndOutcome中的Condition的短類名.
賦值message:若是ConditionAndOutcome中的Message有值則直接賦值,不然,若是對應的Condition匹配,則賦值爲matched,不然賦值爲did not match。

回到report,構造器以下:

public Report(ConditionEvaluationReport report) {
this.positiveMatches = new LinkedMultiValueMap<String, MessageAndCondition>();
this.negativeMatches = new LinkedHashMap<String, MessageAndConditions>();
// 1. 經過report#getExclusions 得到不進行加載的bean
this.exclusions = report.getExclusions();
// 2. 
for (Map.Entry<String, ConditionAndOutcomes> entry : report
.getConditionAndOutcomesBySource().entrySet()) {
// 2.1 若是該配置生效條件都匹配,則加入到positiveMatches,不然,加入到negativeMatches
if (entry.getValue().isFullMatch()) {
    add(this.positiveMatches, entry.getKey(), entry.getValue());
}
else {
    add(this.negativeMatches, entry.getKey(), entry.getValue());
}
}
// 3. 若是report存在父report,則進行初始化Report 賦值爲當前類的parent 屬性
boolean hasParent = report.getParent() != null;
this.parent = (hasParent ? new Report(report.getParent()) : null);
}

 

經過ConditionEvaluationReport#getExclusions 得到不進行加載的bean,賦值爲exclusions
調用ConditionEvaluationReport#getConditionAndOutcomesBySource 得到ConditionEvaluationReport中持有匹配信息,返回的map中,key–> 匹配類名,ConditionAndOutcomes–> 匹配結果.

依次遍歷第2步的返回值–>若是該配置生效條件都匹配,則加入到positiveMatches,不然,加入到negativeMatches.其中add 代碼以下:

        private void add(MultiValueMap<String, MessageAndCondition> map, String source,
                ConditionAndOutcomes conditionAndOutcomes) {
            String name = ClassUtils.getShortName(source);
            for (ConditionAndOutcome conditionAndOutcome : conditionAndOutcomes) {
                map.add(name, new MessageAndCondition(conditionAndOutcome));
            }
        }

 

所以positiveMatches,negativeMatches 中的key爲配置類的簡單類名.
若是report存在父report,則進行初始化Report 賦值爲當前類的parent 屬性.通常來講,是不存在父report的

屬性配置(由於有@ConfigurationProperties(prefix = 「endpoints.autoconfig」) 註解):

endpoints.autoconfig.enabled= # Enable the endpoint.
endpoints.autoconfig.id= # Endpoint identifier.
endpoints.autoconfig.sensitive= # Mark if the endpoint exposes sensitive information.

 

自動裝配:

一樣仍是在EndpointAutoConfiguration中,代碼以下:

@Bean
@ConditionalOnBean(ConditionEvaluationReport.class)
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
public AutoConfigurationReportEndpoint autoConfigurationReportEndpoint() {
    return new AutoConfigurationReportEndpoint();
}

 

@Bean –> 註冊1個id爲autoConfigurationReportEndpoint,類型爲AutoConfigurationReportEndpoint的bean
@ConditionalOnBean(ConditionEvaluationReport.class)–>beanFactory中存在ConditionEvaluationReport類型的bean時生效
@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)–> 在當前上下文中不存在AutoConfigurationReportEndpoint類型的bean時生效

ConditionEvaluationReport

AutoConfigurationReportEndpoint 是經過ConditionEvaluationReport 來進行暴露信息.

ConditionEvaluationReport 字段以下:

private static final String BEAN_NAME = "autoConfigurationReport";

// 若是一個配置類中內部配置類不匹配,則在其外部類的所對應的ConditionAndOutcomes中添加1個AncestorsMatchedCondition
private static final AncestorsMatchedCondition ANCESTOR_CONDITION = new AncestorsMatchedCondition();

// key-->配置類類名,ConditionAndOutcomes-->匹配條件結果的封裝
private final SortedMap<String, ConditionAndOutcomes> outcomes = new TreeMap<String, ConditionAndOutcomes>();

// 是否添加AncestorsMatchedCondition,默認爲false
private boolean addedAncestorOutcomes;

// 父ConditionEvaluationReport,通常爲null
private ConditionEvaluationReport parent;

// 去除加載的配置
private List<String> exclusions = Collections.emptyList();

// 在ConditionEvaluationReportAutoConfigurationImportListener#onAutoConfigurationImportEvent 添加,用於保存尚未
// 執行判斷的class
private Set<String> unconditionalClasses = new HashSet<String>();

ConditionEvaluationReport 實例化過程以下:

在SpringApplication#run中會調用AbstractApplicationContext#refresh,在refresh會調用PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors,在該方法中最終會調用到AutoConfigurationImportSelector#selectImports方法.在該方法中會調用fireAutoConfigurationImportEvents,代碼以下:AutoConfigurationImportSelector.fireAutoConfigurationImportEvents()

    private void fireAutoConfigurationImportEvents(List<String> configurations,
            Set<String> exclusions) {
        List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
        if (!listeners.isEmpty()) {
            AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this,
                    configurations, exclusions);
            for (AutoConfigurationImportListener listener : listeners) {
                invokeAwareMethods(listener);
                listener.onAutoConfigurationImportEvent(event);
            }
        }
    }

 

會加載/META-INF/spring.factories 中配置的org.springframework.boot.autoconfigure.AutoConfigurationImportListener,實例化後,依次調用其onAutoConfigurationImportEvent 方法. spring.factories 配置以下:

org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

所以此處會調用ConditionEvaluationReportAutoConfigurationImportListener#onAutoConfigurationImportEvent.代碼以下:

    @Override
    public void onAutoConfigurationImportEvent(AutoConfigurationImportEvent event) {
        if (this.beanFactory != null) {
            ConditionEvaluationReport report = ConditionEvaluationReport
                    .get(this.beanFactory);
            report.recordEvaluationCandidates(event.getCandidateConfigurations());
            report.recordExclusions(event.getExclusions());
        }
    }

 

A、實例化ConditionEvaluationReport,代碼以下:

public static ConditionEvaluationReport get(
ConfigurableListableBeanFactory beanFactory) {
    synchronized (beanFactory) {
        ConditionEvaluationReport report;
        // 1. 若是當前beanFactory包含autoConfigurationReport定義的話,就從beanFactory中獲取,
        if (beanFactory.containsSingleton(BEAN_NAME)) {
            report = beanFactory.getBean(BEAN_NAME,         ConditionEvaluationReport.class);
        }
        else {
            // 不然就實例化一個,而後進行註冊
            report = new ConditionEvaluationReport();
            beanFactory.registerSingleton(BEAN_NAME, report);
        }
        // 2. 若是存在父容器的話,就從父容器中獲取。
        locateParent(beanFactory.getParentBeanFactory(), report);
        return report;
    }
}

 

若是當前beanFactory包含autoConfigurationReport定義的話,就從beanFactory中獲取,不然就實例化一個,而後進行註冊

若是存在父容器的話,就從父容器中獲取,並將其賦值爲當前context中得到的ConditionEvaluationReport的父ConditionEvaluationReport.代碼以下:

    private static void locateParent(BeanFactory beanFactory,
            ConditionEvaluationReport report) {
        if (beanFactory != null && report.parent == null
                && beanFactory.containsBean(BEAN_NAME)) {
            report.parent = beanFactory.getBean(BEAN_NAME,
                    ConditionEvaluationReport.class);
        }
    }

 

通常都是null,不會執行的。

B、設置unconditionalClasses爲event#getCandidateConfigurations的返回值。
C、設置exclusions爲event#getExclusions的返回值。

在AutoConfigurationReportEndpoint中是經過Report來進行暴露信息的,而在其構造器中,調用了ConditionEvaluationReport#getConditionAndOutcomesBySource方法,代碼以下:

public Map<String, ConditionAndOutcomes> getConditionAndOutcomesBySource() {
    if (!this.addedAncestorOutcomes) {
        // 1. 若是addedAncestorOutcomes 設爲false,則依次遍歷outcomes,若是一個配置類中內部配置類不匹配,則在其外部類的所對應的ConditionAndOutcomes中添加1個AncestorsMatchedCondition
        for (Map.Entry<String, ConditionAndOutcomes> entry : this.outcomes.entrySet()) {
            if (!entry.getValue().isFullMatch()) {
                addNoMatchOutcomeToAncestors(entry.getKey());
            }
        }
        this.addedAncestorOutcomes = true;
    }
    return Collections.unmodifiableMap(this.outcomes);
}

 

若是addedAncestorOutcomes 設爲false,則依次遍歷outcomes,若是一個配置類中內部配置類不匹配,則在其外部類的所對應的ConditionAndOutcomes中添加1個AncestorsMatchedCondition

返回outcomes.

問題來了, outcomes 中的數據是如何添加的?

答案: 有2處.

仍是在AutoConfigurationImportSelector#selectImports中,會調用其filter方法.代碼以下

private List<String> filter(List<String> configurations,
AutoConfigurationMetadata autoConfigurationMetadata) {
    long startTime = System.nanoTime();
    String[] candidates = configurations.toArray(new     String[configurations.size()]);
    boolean[] skip = new boolean[candidates.length];
    boolean skipped = false;
    // 1. 獲取META-INFspring.factories/中配置的org.springframework.boot.autoconfigure.AutoConfigurationImportFilter,.OnClassCondition.依次進行遍歷之
    // 此時得到的是org.springframework.boot.autoconfigure.condition
    for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
        // 1.1 進行屬性注入
        invokeAwareMethods(filter);
        // 1.2 調用AutoConfigurationImportFilter#match 進行判斷,依次遍歷其返回值,若是返回的是false,則說明該配置爲跳過,並將skipped設置爲true
        // 得到AutoConfigurationMetadata中配置的            ConditionalOnClass,若是不會空,則依次遍歷之,看是否在當前類路徑下存在
        // 若是不匹配的話,則調用            ConditionEvaluationReport.#ecordConditionEvaluation 進行記錄
        // 因爲此時AutoConfigurationMetadata 什麼都沒有配置,所以此步驟至關於空操做,最終會在第2步返回
        boolean[] match = filter.match(candidates, autoConfigurationMetadata);
        for (int i = 0; i < match.length; i++) {
            if (!match[i]) {
                skip[i] = true;
                skipped = true;
            }
        }
    }
    // 2. 若是skipped 等於false,則直接返回configurations,說明沒有配置是須要跳過的
    if (!skipped) {
        return configurations;
    }
    // 3. 依次遍歷candidates,若是該配置是不進行跳過的,則添加至result中進行返回
    List<String> result = new ArrayList<String>(candidates.length);
    for (int i = 0; i < candidates.length; i++) {
        if (!skip[i]) {
            result.add(candidates[i]);
        }
    }
    return new ArrayList<String>(result);
}

 

獲取META-INFspring.factories/中配置的org.springframework.boot.autoconfigure.AutoConfigurationImportFilter,.OnClassCondition.依次進行遍歷之.此時得到的是org.springframework.boot.autoconfigure.condition
進行屬性注入
調用AutoConfigurationImportFilter#match 進行判斷,依次遍歷其返回值,若是返回的是false,則說明該配置爲跳過,並將skipped設置爲true.因爲此時調用的是OnClassCondition,其判斷邏輯爲得到AutoConfigurationMetadata中配置的ConditionalOnClass,若是不會空,則依次遍歷之,看是否在當前類路徑下存在.若是不匹配的話,則調用ConditionEvaluationReport#ecordConditionEvaluation 進行記錄.因爲此時AutoConfigurationMetadata 什麼都沒有配置,所以此步驟至關於空操做,最終會在第2步返回

若是skipped 等於false,則直接返回configurations,說明沒有配置是須要跳過的
依次遍歷candidates,若是該配置是不進行跳過的,則添加至result中進行返回

在ConfigurationClassParser#processConfigurationClass進行解析加載配置類時,會調用ConditionEvaluator#shouldSkip,在該方法中,會所以遍歷配置類配置的@Conditional所對應的處理類.此時,若是處理類是SpringBootCondition的子類的話,就會調用ConditionEvaluationReport進行記錄匹配結果. 代碼以下:

private void recordEvaluation(ConditionContext context, String classOrMethodName,
ConditionOutcome outcome) {
    if (context.getBeanFactory() != null) {
        ConditionEvaluationReport.get(context.getBeanFactory())
.recordConditionEvaluation(classOrMethodName, this, outcome);
    }
}

 

LiquibaseEndpoint

默認不生效,這裏就不進行分析了
BeansEndpoint

BeansEndpoint的做用 –> 暴露關於beans的json視圖.若是Environment 中設置了spring.liveBeansView.mbeanDomain,則全部spring 上下文的bean都會展現.不然只會展現當前的上下文中的bean.默認是沒有配置的

BeansEndpoint,構造器以下:

    public BeansEndpoint() {
        super("beans");
    }

 

id爲beans,默認爲敏感

BeansEndpoint的字段以下:

// 繼承自LiveBeansView,用於生成json格式的數據
private final HierarchyAwareLiveBeansView liveBeansView = new HierarchyAwareLiveBeansView();

// json 解析器對象
private final JsonParser parser = JsonParserFactory.getJsonParser();

 

因爲BeansEndpoint實現了ApplicationContextAware接口,所以當前初始化時,會調用其setApplicationContext方法,代碼以下:

    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        if (context.getEnvironment()
                .getProperty(LiveBeansView.MBEAN_DOMAIN_PROPERTY_NAME) == null) {
            this.liveBeansView.setLeafContext(context);
        }
    }

 

若是沒有設置spring.liveBeansView.mbeanDomain的屬性,則將HierarchyAwareLiveBeansView中的leafContext設置爲傳入的ApplicationContext(一般是當前應用所對應的上下文)

invoke實現以下:

    @Override
    public List<Object> invoke() {
        return this.parser.parseList(this.liveBeansView.getSnapshotAsJson());
    }

 

調用HierarchyAwareLiveBeansView#getSnapshotAsJson 生成json串.代碼以下:

public String getSnapshotAsJson() {

    if (this.leafContext == null) {
        return super.getSnapshotAsJson();
    }
    // 2. 將leafContext的整個繼承關係都添加到contexts中,即:若是給定的leafContext 存在父context,則一直遞歸的添加至contexts
    // 直至頂級容器,而後調用LiveBeansView#generateJson 來生成json串
    return generateJson(getContextHierarchy());
}

 

若是leafContext 等於null,則調用LiveBeansView#getSnapshotAsJson來生成json串.通常都會執行第2步

將leafContext的整個繼承關係都添加到contexts中,即:若是給定的leafContext 存在父context,則一直遞歸的添加至contexts.代碼以下:

        private Set<ConfigurableApplicationContext> getContextHierarchy() {
            Set<ConfigurableApplicationContext> contexts = new LinkedHashSet<ConfigurableApplicationContext>();
            ApplicationContext context = this.leafContext;
            while (context != null) {
                contexts.add(asConfigurableContext(context));
                context = context.getParent();
            }
            return contexts;
        }

 

將leafContext的整個繼承關係都添加到contexts中,即:若是給定的leafContext 存在父context,則一直遞歸的添加至contexts直至頂級容器。

調用LiveBeansView#generateJson 來生成json串.在該方法中沒有使用第3方的json解析庫的目的是爲了不對其進行依賴.返回的格式爲數組格式.代碼以下:

因爲BeansEndpoint聲明瞭@ConfigurationProperties(prefix = 「endpoints.beans」),所以能夠經過以下屬性來配置:

endpoints.beans.id
endpoints.beans.sensitive
endpoints.beans.enabled

 

自動化配置:

beansEndpoint 是在EndpointAutoConfiguration 中進行配置的,代碼以下:

@Bean
@ConditionalOnMissingBean
public BeansEndpoint beansEndpoint() {
    return new BeansEndpoint();
}

 

@Bean –> 註冊1個id爲 beansEndpoint,類型爲BeansEndpoint的bean
@ConditionalOnMissingBean–> 當beanFactory中不存在BeansEndpoint類型的bean時生效

ConfigurationPropertiesReportEndpoint

做用:–>暴露被@ConfigurationProperties 註解的bean的屬性.爲了保護數據,將敏感數據進行了脫敏

字段:

private static final String CONFIGURATION_PROPERTIES_FILTER_ID = "configurationPropertiesFilter";

// 數據脫敏
private final Sanitizer sanitizer = new Sanitizer();

// 經過實現ApplicationContextAware 進行自動注入
private ApplicationContext context;

public ConfigurationPropertiesReportEndpoint() {
    super("configprops");
}

invoke 實現:

public Map<String, Object> invoke() {
return extract(this.context);
}

 

調用:

protected Map<String, Object> extract(ApplicationContext context) {
    // Serialize beans into map structure and sanitize values
    ObjectMapper mapper = new ObjectMapper();
    // 1. 配置ObjectMapper的屬性
    configureObjectMapper(mapper);
    // 2. 抽取數據
    return extract(context, mapper);
}

 

對ObjectMapper 進行配置:

代碼以下:

    protected void configureObjectMapper(ObjectMapper mapper) {
        mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        mapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
        applyConfigurationPropertiesFilter(mapper);
        applySerializationModifier(mapper);
    }

 

SerializationFeature.FAIL_ON_EMPTY_BEANS –> 設置當對於給定的類型沒法訪問時如何處理,true–> 拋出異常,false–> 返回null
SerializationFeature.WRITE_NULL_MAP_VALUES –> 設置當map對於的value爲null時如何處理,treu–>序列化,false–> 跳過

applyConfigurationPropertiesFilter 代碼以下:

    private void applyConfigurationPropertiesFilter(ObjectMapper mapper) {
        mapper.setAnnotationIntrospector(
                new ConfigurationPropertiesAnnotationIntrospector());
        mapper.setFilterProvider(new SimpleFilterProvider()
                .setDefaultFilter(new ConfigurationPropertiesPropertyFilter()));
    }

 

設置AnnotationIntrospector爲ConfigurationPropertiesAnnotationIntrospector.該類的做用是:得到@JsonFilter註解的值,若是獲取不到,則返回configurationPropertiesFilter.代碼以下:

private static class ConfigurationPropertiesAnnotationIntrospector
extends JacksonAnnotationIntrospector {
    @Override
    public Object findFilterId(Annotated a) {
        // 1. 得到@JsonFilter註解的值,若是獲取不到,則返回    configurationPropertiesFilter
        Object id = super.findFilterId(a);
        if (id == null) {
            id = CONFIGURATION_PROPERTIES_FILTER_ID;
        }
        return id;
    }
}

 

設置默認的過濾器爲ConfigurationPropertiesPropertyFilter,該類的做用是進行以下規則的過濾:
類名以$$開頭的被過濾掉
自我引用的字段被過濾掉
當在序列化時拋出異常時過濾掉

代碼以下:

public void serializeAsField(Object pojo, JsonGenerator jgen,
SerializerProvider provider, PropertyWriter writer) throws Exception {
if (writer instanceof BeanPropertyWriter) {
try {
// 1. 自我引用的字段被過濾掉
if (pojo == ((BeanPropertyWriter) writer).get(pojo)) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping '" + writer.getFullName() + "' on '"
+ pojo.getClass().getName()
+ "' as it is self-referential");
}
return;
}
}
catch (Exception ex) {
// 2. 當在序列化時拋出異常時過濾掉
if (logger.isDebugEnabled()) {
logger.debug("Skipping '" + writer.getFullName() + "' on '"
+ pojo.getClass().getName() + "' as an exception "
+ "was thrown when retrieving its value", ex);
}
return;
}
}
// 3. 序列化字段
super.serializeAsField(pojo, jgen, provider, writer);
}

 

設置序列化工廠中的方法序列化器爲GenericSerializerModifier.代碼以下:

public List<BeanPropertyWriter> changeProperties(SerializationConfig config,
BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
List<BeanPropertyWriter> result = new ArrayList<BeanPropertyWriter>();
for (BeanPropertyWriter writer : beanProperties) {
boolean readable = isReadable(beanDesc, writer);
if (readable) {
result.add(writer);
}
}
return result;
}

 

依次遍歷beanProperties,若是可讀的話,則添加至result中.可讀的判斷邏輯以下:
根據bean 的類型和屬性的類型 找出對應的set方法
若是set方法 存在或者是同一包下的類或者是map或者集合的子類,則返回true

調用extract方法抽取數據.代碼以下:

private Map<String, Object> extract(ApplicationContext context, ObjectMapper mapper) {
Map<String, Object> result = new HashMap<String, Object>();
// 1. 得到beanFactory中ConfigurationBeanFactoryMetaData類型的bean
ConfigurationBeanFactoryMetaData beanFactoryMetaData = getBeanFactoryMetaData(
context);
// 2. 得到被@ConfigurationProperties註解的bean,key--> bean的id,value --> bean
Map<String, Object> beans = getConfigurationPropertiesBeans(context,
beanFactoryMetaData);
// 3. 依次遍歷beans
for (Map.Entry<String, Object> entry : beans.entrySet()) {
String beanName = entry.getKey();
Object bean = entry.getValue();
Map<String, Object> root = new HashMap<String, Object>();
String prefix = extractPrefix(context, beanFactoryMetaData, beanName, bean);
// 3.1 得到@ConfigurationProperties註解的前綴,添加至root中,key-->prefix,value-->@ConfigurationProperties註解的前綴
root.put("prefix", prefix);
// 3.2 
root.put("properties", sanitize(prefix, safeSerialize(mapper, bean, prefix)));
// 3.3 添加至result中,key--> bean id,value-->map{prefix=xx,properties=xx}
result.put(beanName, root);
}
// 4. 若是ApplicationContext存在父容器的,則遞歸調用extract提取數據,key爲parent.
if (context.getParent() != null) {
result.put("parent", extract(context.getParent(), mapper));
}
return result;
}

 

得到beanFactory中ConfigurationBeanFactoryMetaData類型的bean
得到被@ConfigurationProperties註解的bean,key–> bean的id,value –> bean

依次遍歷beans
得到@ConfigurationProperties註解的前綴,添加至root中,key–>prefix,value–>@ConfigurationProperties註解的前綴

對數據進行脫敏.代碼以下:

private Map<String, Object> sanitize(String prefix, Map<String, Object> map) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
String qualifiedKey = (prefix.isEmpty() ? prefix : prefix + ".") + key;
Object value = entry.getValue();
if (value instanceof Map) {
// 1. 若是對應的屬性值爲Map,List 則遞歸調用sanitize 進行數據脫敏
map.put(key, sanitize(qualifiedKey, (Map<String, Object>) value));
}
else if (value instanceof List) {
map.put(key, sanitize(qualifiedKey, (List<Object>) value));
}
else {
// 2. 若是屬性名包含password", "secret", "key", "token", ".*credentials.*", "vcap_services,則將其替換爲******
value = this.sanitizer.sanitize(key, value);
value = this.sanitizer.sanitize(qualifiedKey, value);
map.put(key, value);
}
}
return map;
}

 

若是對應的屬性值爲Map,List 則遞歸調用sanitize 進行數據脫敏
不然若是屬性名包含password, secret, key, token, .*credentials.*, vcap_services,則將其替換爲**
添加至result中,key–> bean id,value–>map{prefix=xx,properties=xx}

若是ApplicationContext存在父容器的,則遞歸調用extract提取數據,key爲parent.

因爲ConfigurationPropertiesReportEndpoint 被@ConfigurationProperties(prefix = 「endpoints.configprops」)註解,所以可經過以下屬性配置:

endpoints.configprops.id=configprops 
endpoints.configprops.sensitive=true 
endpoints.configprops.enabled=true 
endpoints.configprops.keys-to-sanitize=password,secret

之因此能夠經過 endpoints.configprops.keys-to-sanitize 進行配置,是由於ConfigurationPropertiesReportEndpoint聲明瞭以下方法:

public void setKeysToSanitize(String... keysToSanitize) {
this.sanitizer.setKeysToSanitize(keysToSanitize);
}

自動化配置(在EndpointAutoConfiguration中配置):

代碼以下:

@Bean
@ConditionalOnMissingBean
public ConfigurationPropertiesReportEndpoint configurationPropertiesReportEndpoint() {
return new ConfigurationPropertiesReportEndpoint();
}

 

@Bean –> 註冊1個id爲configurationPropertiesReportEndpoint,類型爲ConfigurationPropertiesReportEndpoint的Bean
@ConditionalOnMissingBean –> beanFactory中不存在ConfigurationPropertiesReportEndpoint類型的bean時生效

TraceEndpoint

做用:–> 該端點用來返回基本的HTTP跟蹤信息。默認狀況下,跟蹤信息的存儲採用org.springframework.boot.actuate.trace.InMemoryTraceRepository實現的內存方式,始終保留最近的100條請求記錄.其中,返回的Trace定義以下:

public final class Trace {

// 時間戳
private final Date timestamp;

// 保存用於分析上下文信息,例如HTTP頭
private final Map<String, Object> info;

public Trace(Date timestamp, Map<String, Object> info) {
super();
Assert.notNull(timestamp, "Timestamp must not be null");
Assert.notNull(info, "Info must not be null");
this.timestamp = timestamp;
this.info = info;
}

public Date getTimestamp() {
return this.timestamp;
}

public Map<String, Object> getInfo() {
return this.info;
}
}

 

字段:

private final TraceRepository repository;

這裏咱們有必要說明一下TraceRepository:

TraceRepository 是用來保存Trace的. 接口定義以下:

public interface TraceRepository {

// 返回保存的Trace
List<Trace> findAll();

// 進行添加
void add(Map<String, Object> traceInfo);
}

TraceRepository 只要1個實現–>InMemoryTraceRepository.其字段以下:

// 容量爲100
private int capacity = 100;
// 是否倒序展現,默認爲false
private boolean reverse = true;
// 容器,用於保存Trace
private final List<Trace> traces = new LinkedList<Trace>();

findAll只需簡單的返回保存的traces便可.實現以下:

public List<Trace> findAll() {
synchronized (this.traces) {
return Collections.unmodifiableList(new ArrayList<Trace>(this.traces));
}
}

add,代碼以下:

public void add(Map<String, Object> map) {
// 1. 實例化Trace,時間戳爲當前時間
Trace trace = new Trace(new Date(), map);
synchronized (this.traces) {
// 2. 若是traces中的容量大於等於了閾值,則進行刪除.若是reverse等於true 則刪除最後1個,不然,刪除第1個
while (this.traces.size() >= this.capacity) {
this.traces.remove(this.reverse ? this.capacity - 1 : 0);
}
// 3. 進行添加,若是reverse等於true 則添加至第1個,不然,添加至最後
if (this.reverse) {
this.traces.add(0, trace);
}
else {
this.traces.add(trace);
}
}
}

實例化Trace,時間戳爲當前時間
若是traces中的容量大於等於了閾值,則進行刪除.若是reverse等於true 則刪除最後1個,不然,刪除第1個
進行添加,若是reverse等於true 則添加至第1個,不然,添加至最後

TraceRepository 是在何處配置的呢?

在TraceRepositoryAutoConfiguration中,代碼以下:

@ConditionalOnMissingBean(TraceRepository.class)
@Bean
public InMemoryTraceRepository traceRepository() {
return new InMemoryTraceRepository();
}

@Bean –> 註冊1個id爲traceRepository,類型爲InMemoryTraceRepository的bean
@ConditionalOnMissingBean(TraceRepository.class)–> 當beanFactory中不存在TraceRepository類型的bean時生效

invoke 實現:

@Override
public List<Trace> invoke() {
return this.repository.findAll();
}

只需調用TraceRepository# findAll,返回保存的Trace便可.

屬性配置,所以其被@ConfigurationProperties(prefix = 「endpoints.trace」)註解,所以能夠經過以下屬性進行配置:

endpoints.trace.id=trace
endpoints.trace.sensitive=true
endpoints.trace.enabled=true

自動裝配:

在EndpointAutoConfiguration中進行了裝配,代碼以下:

@Bean
@ConditionalOnMissingBean
public TraceEndpoint traceEndpoint() {
return new TraceEndpoint(this.traceRepository == null
? new InMemoryTraceRepository() : this.traceRepository);
}

@Bean–> 註冊1個id爲traceEndpoint,類型爲TraceEndpoint的bean
@ConditionalOnMissingBean–> 當beanFactory中不存在TraceEndpoint類型的bean時生效

這裏提1個問題,TraceEndpoint 是經過TraceRepository獲取Trace,那麼TraceRepository中的Trace是如何保存的呢?

答案:

經過WebRequestTraceFilter(Filter)來實現. WebRequestTraceFilter實現了Ordered接口,指定了其在過濾器鏈中的順序,代碼以下:

private int order = Ordered.LOWEST_PRECEDENCE - 10;

public int getOrder() {
return this.order;
}

WebRequestTraceFilter中的字段以下:

private static final Log logger = LogFactory.getLog(WebRequestTraceFilter.class);

// debug時使用.若是啓用的話,而且log的trace級別可用的話,則打印請求頭信息,默認爲false
private boolean dumpRequests = false;

// Not LOWEST_PRECEDENCE, but near the end, so it has a good chance of catching all
// enriched headers, but users can add stuff after this if they want to
private int order = Ordered.LOWEST_PRECEDENCE - 10;

private final TraceRepository repository;

private ErrorAttributes errorAttributes;

private final TraceProperties properties;

其中TraceProperties的定義以下:

@ConfigurationProperties(prefix = "management.trace")
public class TraceProperties {

private static final Set<Include> DEFAULT_INCLUDES;

static {
Set<Include> defaultIncludes = new LinkedHashSet<Include>();
defaultIncludes.add(Include.REQUEST_HEADERS);
defaultIncludes.add(Include.RESPONSE_HEADERS);
defaultIncludes.add(Include.COOKIES);
defaultIncludes.add(Include.ERRORS);
defaultIncludes.add(Include.TIME_TAKEN);
DEFAULT_INCLUDES = Collections.unmodifiableSet(defaultIncludes);
}


private Set<Include> include = new HashSet<Include>(DEFAULT_INCLUDES);

public Set<Include> getInclude() {
return this.include;
}

public void setInclude(Set<Include> include) {
this.include = include;
}
}

默認配置的是Include.REQUEST_HEADERS, Include.RESPONSE_HEADERS, Include.COOKIES ,Include.ERRORS, Include.TIME_TAKEN. 能夠經過以下進行配置

management.trace.include=REQUEST_HEADERS,RESPONSE_HEADERS
1

可選值爲org.springframework.boot.actuate.trace.TraceProperties.Include.這裏就不在貼出了

WebRequestTraceFilter 繼承了OncePerRequestFilter,所以只需實現doFilterInternal便可,代碼以下:

protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
long startTime = System.nanoTime();
// 1. 得到trace
Map<String, Object> trace = getTrace(request);
// 2. 打印日誌-->若是log的trace級別可用的話而且dumpRequests等於true,則打印請求頭信息,默認爲false
logTrace(request, trace);
int status = HttpStatus.INTERNAL_SERVER_ERROR.value();
try {
// 3. 繼續過濾器鏈的過濾,最後得到響應狀態
filterChain.doFilter(request, response);
status = response.getStatus();
}
finally {
// 4. 添加Http請求耗時統計-->若是TraceProperties中配置了Include#TIME_TAKEN(默認配置了),則添加到trace中,key爲timeTaken,value-->當前時間-開始時間的毫秒值
addTimeTaken(trace, startTime);
// 5. 添加響應頭信息
enhanceTrace(trace, status == response.getStatus() ? response
: new CustomStatusResponseWrapper(response, status));
// 6. 添加至TraceRepository 中
this.repository.add(trace);
}
}

記錄開始時間

得到trace.代碼以下:

protected Map<String, Object> getTrace(HttpServletRequest request) {
// 1. 得到HttpSession
HttpSession session = request.getSession(false);
// 2. 得到javax.servlet.error.exception,所對應的異常--> 當spring mvc 出現異常時,會加入到javax.servlet.error.exception中
Throwable exception = (Throwable) request
.getAttribute("javax.servlet.error.exception");
// 3. 得到Principal,若是返回null,說明沒有該請求沒有進行驗證
Principal userPrincipal = request.getUserPrincipal();
Map<String, Object> trace = new LinkedHashMap<String, Object>();
Map<String, Object> headers = new LinkedHashMap<String, Object>();
// 4. 添加請求方法,請求路徑,請求頭到trace中
trace.put("method", request.getMethod());
trace.put("path", request.getRequestURI());
trace.put("headers", headers);
if (isIncluded(Include.REQUEST_HEADERS)) {
headers.put("request", getRequestHeaders(request));
}
// 省略掉默認不執行的代碼....
// 5. 若是有異常而且errorAttributes不等於null,則記錄error
if (isIncluded(Include.ERRORS) && exception != null
&& this.errorAttributes != null) {
trace.put("error", this.errorAttributes
.getErrorAttributes(new ServletRequestAttributes(request), true));
}
return trace;
}

繼續過濾器鏈的過濾,最後得到響應狀態

添加Http請求耗時統計–>若是TraceProperties中配置了Include#TIME_TAKEN(默認配置了),則添加到trace中,key爲timeTaken,value–>當前時間-開始時間的毫秒值.代碼以下:

private void addTimeTaken(Map<String, Object> trace, long startTime) {
long timeTaken = System.nanoTime() - startTime;
add(trace, Include.TIME_TAKEN, "timeTaken",
"" + TimeUnit.NANOSECONDS.toMillis(timeTaken));
}

添加響應頭信息.代碼以下:

protected void enhanceTrace(Map<String, Object> trace, HttpServletResponse response) {
if (isIncluded(Include.RESPONSE_HEADERS)) {
Map<String, Object> headers = (Map<String, Object>) trace.get("headers");
headers.put("response", getResponseHeaders(response));
}
}

getResponseHeaders 代碼以下:

private Map<String, String> getResponseHeaders(HttpServletResponse response) {
Map<String, String> headers = new LinkedHashMap<String, String>();
// 1. 依次遍歷響應頭,添加至headers 中,key--> 響應頭名,value-->響應頭對應的值
for (String header : response.getHeaderNames()) {
String value = response.getHeader(header);
headers.put(header, value);
}
// 2. 若是TraceProperties中沒有配置Include#COOKIES,則在headers中刪除key爲Set-Cookie的值.默認是配置了的,所以不會刪除
if (!isIncluded(Include.COOKIES)) {
headers.remove("Set-Cookie");
}
// 3. 向headers 中添加 key--> status,value-->響應狀態碼
headers.put("status", "" + response.getStatus());
return headers;
}

遍歷響應頭,添加至headers 中,key–> 響應頭名,value–>響應頭對應的值
若是TraceProperties中沒有配置Include#COOKIES,則在headers中刪除key爲Set-Cookie的值.默認是配置了的,所以不會刪除
向headers 中添加 key–> status,value–>響應狀態碼
添加至TraceRepository中

WebRequestTraceFilter 是如何配置的呢?

答案: 在TraceWebFilterAutoConfiguration中.代碼以下:

@Bean
@ConditionalOnMissingBean
public WebRequestTraceFilter webRequestLoggingFilter(BeanFactory beanFactory) {
WebRequestTraceFilter filter = new WebRequestTraceFilter(this.traceRepository,
this.traceProperties);
if (this.errorAttributes != null) {
filter.setErrorAttributes(this.errorAttributes);
}
return filter;
}

@Bean–> 註冊1個id爲webRequestLoggingFilter,類型爲WebRequestTraceFilter的bean
@ConditionalOnMissingBean–> 當beanFactory中不存在WebRequestTraceFilter類型的bean時生效

同時因爲TraceWebFilterAutoConfiguration聲明瞭以下註解:

@Configuration
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, ServletRegistration.class })
@AutoConfigureAfter(TraceRepositoryAutoConfiguration.class)
@ConditionalOnProperty(prefix = "endpoints.trace.filter", name = "enabled", matchIfMissing = true)
@EnableConfigurationProperties(TraceProperties.class)

@Configuration–> 配置類 @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, ServletRegistration.class })–> 在當前的類路徑下存在Servlet.class, DispatcherServlet.class, ServletRegistration.class時生效 @AutoConfigureAfter(TraceRepositoryAutoConfiguration.class)–> 在TraceRepositoryAutoConfiguration以後進行自動裝配,這樣就能夠自動注入TraceRepository @ConditionalOnProperty(prefix = 「endpoints.trace.filter」, name = 「enabled」, matchIfMissing = true)–> 當配置有endpoints.trace.filter.enabled 等於true時生效,若是沒有配置,默認生效 @EnableConfigurationProperties(TraceProperties.class)–> 能夠經過management.trace.include 進行配置.轉:https://blog.csdn.net/qq_26000415/article/details/79060258

相關文章
相關標籤/搜索