相親相愛的@Import和@EnableXXX

掃描文末二維碼或者微信搜索公衆號菜鳥飛呀飛,便可關注微信公衆號,閱讀更多Spring源碼分析文章java

1. @Import註解

經過Import註解,咱們有三種方式能夠向Spring容器中註冊Bean。至關於Spring中XML的標籤。git

1.1 直接註冊

  • 例如:@Import(RegularBean.class)。(RegularBean是開發人員自定義的一個類)。代碼以下,在代碼中經過在AppConfig類上加了一行註解:@Import(RegularBean.class),這樣就能從容器中獲取到RegularBean的實例對象的。
/** * 自定義的一個普通類 */
public class RegularBean {
}
複製代碼
/** * 在配置類上經過Import註解向Spring容器中註冊RegularBean */
@Configuration
@Import(RegularBean.class)
public class AppConfig {
}
複製代碼
public class MainApplication {

	public static void main(String[] args) {
		// 啓動容器
		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
		// 獲取bean
		RegularBean bean = applicationContext.getBean(RegularBean.class);
		// 打印bean
		// 打印結果: com.tiantang.study.components.RegularBean@7a675056
		System.out.println(bean);
	}
}
複製代碼

1.2 經過ImportSelector接口

  • Import註解能夠經過ImportSelector接口的實現類來註冊Bean,@Import(DemoImportRegistrar.class)。示例代碼以下:。 DemoImportRegistrar是開發人員自定義的一個類,它實現了ImportSelector接口,重寫了selectImports()方法,在selectImports()的返回的字符串數組中,添加了SelectorBean類的全類名,SelectorBean是自定義一個類。
/** * 自定義的一個普通類 */
public class SelectorBean {
}
複製代碼
/** * 經過@Import導入DemoImportSelector類 * DemoImportSelector類是自定義的一個類,實現了ImportSelector接口 */
@Configuration
@Import(DemoImportSelector.class)
public class AppConfig {
}
複製代碼
/** * 經過實現ImportSelector接口來向Spring容器中添加一個Bean * 該類重寫了ImportSelector接口的selectImports()方法 */
public class DemoImportSelector implements ImportSelector {

	@Override
	public String[] selectImports(AnnotationMetadata importingClassMetadata) {
		// 該方法的返回值是一個String[]數組
		// 向數組中添加類的全類名,這樣就能將該類註冊到Spring容器中了
		return new String[]{"com.tiantang.study.components.SelectorBean"};
	}
}
複製代碼
public class MainApplication {

	public static void main(String[] args) {
		// 啓動容器
		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
		// 獲取bean
		SelectorBean bean = applicationContext.getBean(SelectorBean.class);
		// 打印bean
		// 打印結果:com.tiantang.study.components.SelectorBean@4ef37659
		System.out.println(bean);
	}
}
複製代碼

1.3 經過ImportBeanDefinitionRegistrar接口

  • Import註解能夠經過ImportBeanDefinitionRegistrar接口的實現類來註冊Bean,@Import(DemoImportRegistrar.class)。示例代碼以下:DemoImportRegistrar是開發人員自定義的一個類,它實現了ImportBeanDefinitionRegistrar接口,重寫了registerBeanDefinitions()方法。在registerBeanDefinitions()中經過一段代碼向Spring中註冊了RegistrarBean類(RegistrarBean類是自定義的一個類)。
  • 在重寫的registerBeanDefinitions()方法中,加了很詳細的註釋,方法中的代碼可能對於從未接觸BeanDefinition類的API的朋友來講,可能比較陌生,多看Spring源碼就,多熟悉就行了。
/** * 自定義的一個普通類 */
public class RegistrarBean {
}
複製代碼
/** * 經過@Import導入DemoImportRegistrar類 * DemoImportRegistrar類是自定義的一個類 * 實現了ImportBeanDefinitionRegistrar接口 * 重寫了registerBeanDefinitions()方法 */
@Configuration
@Import(DemoImportRegistrar.class)
public class AppConfig {
}
複製代碼
/** * 經過實現ImportBeanDefinitionRegistrar接口, * 重寫registerBeanDefinitions()方法來向Spring容器彙總註冊一個bean */
public class DemoImportRegistrar implements ImportBeanDefinitionRegistrar {

	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		// 經過BeanDefinitionBuilder來建立一個BeanDefinition(建造者設計模式瞭解一下)
		// 這裏也能夠直接經過關鍵字new來建立一個BeanDefinition。因爲BeanDefinition是一個接口,接口是不能new的,所以須要new它的實現類
		// 例如: GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
		// genericBeanDefinition.setBeanClass(RegistrarBean.class);
		// 上面兩行代碼徹底是下面兩行代碼等價的。固然也能夠new一個AnnotatedBeanDefinition。咱們寫的AppConfig類就是被Spring解析成一個AnnotatedBeanDefinition
		// 這裏其實有不少API,例如BeanDefinitionRegistry中註冊bean的方法,BeanDefinition中爲bean設置相關特性的方法
		BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(RegistrarBean.class);
		AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
		
		// 上面兩行代碼是將RegistrarBean的解析成BeanDefinition,下面則是向Spring中註冊RegistrarBean類對應的BeanDefinition
		// 注意,調用registry類的registerBeanDefinition()方法時,咱們爲這個Bean指定了beanName。
		registry.registerBeanDefinition("demoBean",beanDefinition);
	}
}
複製代碼
public class MainApplication {

	public static void main(String[] args) {
		// 啓動容器
		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
		// 獲取bean
		RegistrarBean bean = applicationContext.getBean(RegistrarBean.class);
		// 打印bean
		// 打印結果:com.tiantang.study.components.RegistrarBean@2f465398
		System.out.println(bean);

		// 經過beanName來獲取bean
		RegistrarBean demoBean = (RegistrarBean)applicationContext.getBean("demoBean");
		// 打印結果:com.tiantang.study.components.RegistrarBean@2f465398
		// 和上面的打印結果是同樣的,這也說明二者是同一個對象
		System.out.println(demoBean);
	}
}
複製代碼

2. @Import的原理

經過上面的三個Demo,瞭解了Import註解的三種用法,是否是發現咱們不用經過@ConponentScan和@Component等註解,咱們也能向Spring容器中註冊Bean?那麼問題來了,爲何經過Import註解,就能實現向Spring容器中註冊bean?原理是什麼?github

  • 咱們能夠先猜測一下:
  • 猜測一:咱們都知道Spring能在啓動時,自動幫咱們建立好Bean,那麼這個自動究竟是怎麼實現的呢?在Spring容器中,Spring容器要想爲某個類建立實例對象,就必須先把對應的class類解析爲BeanDefinition,而後才能實例化對象。那麼咱們經過Import註解的方式向容器中註冊Bean,也是必定會先把要註冊的類的class解析爲BeanDefinition。
  • 猜測二:若是有了猜測一,那麼又會出現一個新的問題:何時把這些class變爲BeanDefiniton的呢,如何針對Import註解作到特殊處理?Spring容器在啓動階段,有兩個很重要的過程,一個是經過BeanFactoryPostProcessor後置處理器參與BeanFactory的建造,另一個就是經過BeanPostProcessor後置處理器來參與Bean的建造。後者是建立Bean,而要建立Bean則須要BeanDefinition,因此Import註解的註解不會在這個過程。而前者是BeanFactory的建造過程,根據類名就能猜出,BeanFactory是一個Bean工廠,因此BeanDefinition做爲建立Bean的原料,頗有可能就是在這一步對Import註解作了特殊處理,解析出了要註冊Bean的BeanDefinition。
  • 在上一篇文章中( 點擊此處查看上一篇文章)詳細介紹了Spring中一個很是重要的類:ConfigurationClassPostProcessor,而這個類就是BeanFactoryPostProcessor的實現類,參與了BeanFactory的建造。這個類處理了@Configuration、@ComponentScan等註解,實際上,Import註解也是在這一步被處理的。

接下來就看下Import註解的實現原理。在Spring容器自動過程當中,會執行refresh()方法,refresh()方法中會調用postProcessBeanFactory()。在postProcessBeanFactory()方法中又會執行全部BeanFactoryPostProcessor後置處理器。那麼就會執行到ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry()方法,而在該方法中調用了processConfigBeanDefinitions()方法。下面是processConfigBeanDefinitions()的部分代碼(只保留了幾行和今天內容有關的代碼,能夠閱筆者的上一篇文章點擊此處查看上一篇文章,裏面詳細介紹了該方法的所有代碼)。spring

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
	
	// 建立一個parser
	ConfigurationClassParser parser = new ConfigurationClassParser(
			this.metadataReaderFactory, this.problemReporter, this.environment,
			this.resourceLoader, this.componentScanBeanNameGenerator, registry);

	do {
		// 解析配置類,在此處會解析配置類上的註解(ComponentScan掃描出的類,@Import註冊的類,以及@Bean方法定義的類)
		// 注意:這一步只會將加了@Configuration註解以及經過@ComponentScan註解掃描的類纔會加入到BeanDefinitionMap中
		// 經過其餘註解(例如@Import、@Bean)的方式,在parse()方法這一步並不會將其解析爲BeanDefinition放入到BeanDefinitionMap中,而是先解析成ConfigurationClass類
		// 真正放入到map中是在下面的this.reader.loadBeanDefinitions()方法中實現的
		parser.parse(candidates);
		
		// 將上一步parser解析出的ConfigurationClass類加載成BeanDefinition
		// 實際上通過上一步的parse()後,解析出來的bean已經放入到BeanDefinition中了,可是因爲這些bean可能會引入新的bean,例如實現了ImportBeanDefinitionRegistrar或者ImportSelector接口的bean,或者bean中存在被@Bean註解的方法
		// 所以須要執行一次loadBeanDefinition(),這樣就會執行ImportBeanDefinitionRegistrar或者ImportSelector接口的方法或者@Bean註釋的方法
		this.reader.loadBeanDefinitions(configClasses);
		
	}
	while (!candidates.isEmpty());

}
複製代碼
  • 在parser.parse()方法中,最終會調用到doProcessConfigurationClass()方法。
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass) throws IOException {
	// 省略無關代碼...

	// Process any @Import annotations
	// 處理Import註解註冊的bean,這一步只會將import註冊的bean變爲ConfigurationClass,不會變成BeanDefinition
	// 而是在loadBeanDefinitions()方法中變成BeanDefinition,再放入到BeanDefinitionMap中
	processImports(configClass, sourceClass, getImports(sourceClass), true);
	
	// 省略無關代碼...
	return null;
}
複製代碼
  • 能夠看到,processImports()方法處理了Import註解。在processImports()方法中,分別對Import註解的三種狀況作了處理。方法做用的解釋和源碼以下:
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates, boolean checkForCircularImports) {

	for (SourceClass candidate : importCandidates) {
		if (candidate.isAssignable(ImportSelector.class)) {
			// 處理DeferredImportSelector的實現類,回調開發人員重寫的selectImports()方法
			Class<?> candidateClass = candidate.loadClass();
			ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
			ParserStrategyUtils.invokeAwareMethods(
					selector, this.environment, this.resourceLoader, this.registry);
			if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
				this.deferredImportSelectors.add(
						new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
			}
			else {
				// 處理DeferredImportSelector的實現類,回調開發人員重寫的selectImports()方法
				// 返回值是一個字符串數組,數組元素爲類的全類名,而後把全類名變爲SourceClass
				// 爲何要變爲SourceClass呢?由於在此處解析時,Spring是經過SourceClass來解析類的
				String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
				Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
				// 遞歸調用processImports,爲何要遞歸調用該方法?
				// 由於上面返回的全類名所表示的類多是ImportSelector或者ImportBeanDefinitionRegistrar
				processImports(configClass, currentSourceClass, importSourceClasses, false);
			}
		}
		else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
			// 處理ImportBeanDefinitionRegistrar類
			Class<?> candidateClass = candidate.loadClass();
			ImportBeanDefinitionRegistrar registrar =
					BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
			// 在此處回調開發人員重寫的ImportBeanDefinitionRegistrar的registerBeanDefinitions()方法 
			ParserStrategyUtils.invokeAwareMethods(
					registrar, this.environment, this.resourceLoader, this.registry);
			configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
		}
		else {
			// 處理經過Import註解導入的普通類,例如本次Demo中的RegularBean
			// 這裏只須要直接調用processConfigurationClass()方法便可,把RegularBean當作一個配置類去解析
			// 由於RegularBean這個類型可能加了@ConponentScan,@Bean等註解
			this.importStack.registerImport(
					currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
			processConfigurationClass(candidate.asConfigClass(configClass));
		}
	}
}
複製代碼
  • 在parse()方法執行完,經過Import註解註冊的類,此時尚未將對應的BeanDefinition加入工廠的BeanDefinitionMap中,而只是將class類解析爲成ConfigurationClass對象了。爲何呢?筆者也沒想沒明白,猜想多是由於parse()方法只是用來作解析Class用,並且解析出來的類可能又是一些特殊的配置類,例如類中含有@Bean註解,@Import註解,或者是Spring中拓展點接口的實現類。因此暫時沒有將其加到BeanDefinitionMap中
  • 執行完parse()方法後,接着經過loadBeanDefinitions()方法,將解析出來的ConfigurationClass類變爲BeanDefinition,而後放入到BeanDefinitionMap中。在loadBeanDefinition()方法中最終會調用到registerBeanDefinitionForImportedConfigurationClass()。
  • 源碼以下。看到下面的代碼是否是有一種很熟悉的感受?沒錯,上面demo中DemoImportRegistrar類中的代碼就是參考這兒寫的。在實際工做中碰到相似的場景,須要咱們向容器中添加一個BeanDefiniton,就能夠參考這兒的示例代碼去寫。(^-^這也算是看源碼的一個好處吧)
private void registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass) {
	// metadata中包含的就是咱們要註冊的類的信息,例如本次demo中的RegularBean、SelectorBean、RegistrarBean
	AnnotationMetadata metadata = configClass.getMetadata();
	// new一個BeanDefinition的實現類
	AnnotatedGenericBeanDefinition configBeanDef = new AnnotatedGenericBeanDefinition(metadata);

	ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(configBeanDef);
	configBeanDef.setScope(scopeMetadata.getScopeName());
	String configBeanName = this.importBeanNameGenerator.generateBeanName(configBeanDef, this.registry);
	AnnotationConfigUtils.processCommonDefinitionAnnotations(configBeanDef, metadata);

	BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(configBeanDef, configBeanName);
	definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
	// 經過registry對象,將BeanDefinition註冊到BeanDefinitionMap中
	this.registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition());
	configClass.setBeanName(configBeanName);

}
複製代碼

3. @Enable系列註解

在實際工做當中,咱們常常會碰到帶有@Enable前綴的註解,一般咱們稱之爲開啓某某功能,例如@EnableAsync(開啓@Async註解實現異步執行的功能)、@EnableScheduling(開啓@Scheduling註解實現定時任務的功能)、@EnableTransactionManagement(開啓事物管理的功能)等註解,尤爲是如今絕大部分項目中都要SpringBoot框架搭建,接入了SpringCloud等微服務,碰見@Enable系列的註解更是屢見不鮮,例如:@EnableDiscoveryClient、@EnableFeignClients。既然這麼常見,那就頗有必要知道@Enable系列註解的原理了。docker

  • 下面是EnableAsync註解的源代碼
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {

	Class<? extends Annotation> annotation() default Annotation.class;

	boolean proxyTargetClass() default false;

	AdviceMode mode() default AdviceMode.PROXY;

	int order() default Ordered.LOWEST_PRECEDENCE;

}
複製代碼
  • 在開發工具中,咱們選擇一個@Enable的註解,例如:@EnableAsync註解,咱們查看一下這個註解的源碼。咱們能夠看到這個註解上面有加了四個註解,@Target、@Retention、@Documented這三個註解比較常見,另一個註解就是@Import,今天這篇文章的主角。

簡單介紹下@Target、@Retention、@Documented這三個註解的做用。@Target註解用來指明咱們定義的註解能夠做用在什麼地方,例如ElementType.TYPE表示能夠做用在類上、ElementType.METHOD表示能夠做用在方法上,其餘枚舉值能夠參考ElementType的枚舉類。@Retention註解是指明咱們定義的註解的生命週期,RetentionPolicy.RUNTIME表示在JVM虛擬機加載咱們的class文件時,仍保留咱們所寫的註解;RetentionPolicy.SOURCE表示註解在Java源文件中存在,當編譯成class文件時就去除了咱們定義的註解信息;RetentionPolicy.CLASS表示當類編譯成class文件時,咱們自定義的註解信息仍存在,但當虛擬機加載class文件時,不會加載咱們自定義的註解。@Documented註解表示註釋會成爲API文檔中展現。數據庫

  • 咱們發如今@EnableAsync這個註解中,出現了@Import註解。在@Import註解引入了AsyncConfigurationSelector類。看類名就知道,這個類實現了ImportSelector接口。
/** * 該類繼承了AdviceModeImportSelector,而AdviceModeImportSelector實現了ImportSelector接口 * 在父類中重寫了selectImports(AnnotationMetadata importingClassMetadata)。 * 同時在父類中又重載了selectImports(AdviceMode adviceMode)。 */
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {

	private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME =
			"org.springframework.scheduling.aspectj.AspectJAsyncConfiguration";

	@Override
	public String[] selectImports(AdviceMode adviceMode) {
		switch (adviceMode) {
			case PROXY:
				return new String[] { ProxyAsyncConfiguration.class.getName() };
			case ASPECTJ:
				return new String[] { ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME };
			default:
				return null;
		}
	}

}
複製代碼
  • AsyncConfigurationSelector的父類的源代碼
public abstract class AdviceModeImportSelector<A extends Annotation> implements ImportSelector {

	public static final String DEFAULT_ADVICE_MODE_ATTRIBUTE_NAME = "mode";

	protected String getAdviceModeAttributeName() {
		return DEFAULT_ADVICE_MODE_ATTRIBUTE_NAME;
	}

	// 重寫ImportSelector接口中的方法
	@Override
	public final String[] selectImports(AnnotationMetadata importingClassMetadata) {
		Class<?> annoType = GenericTypeResolver.resolveTypeArgument(getClass(), AdviceModeImportSelector.class);
		AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(importingClassMetadata, annoType);
		if (attributes == null) {
			throw new IllegalArgumentException(String.format(
				"@%s is not present on importing class '%s' as expected",
				annoType.getSimpleName(), importingClassMetadata.getClassName()));
		}
		// @EnableAsync註解中有個mode屬性,能夠指定一個值,此處是獲取指定的值。
		// 開發人員在使用@EnableAsync若是沒有指定具體值,則使用@EnableAsync註解中的默認值AdviceMode.PROXY
		AdviceMode adviceMode = attributes.getEnum(this.getAdviceModeAttributeName());
		// 調用子類的selectImports()方法
		String[] imports = selectImports(adviceMode);
		if (imports == null) {
			throw new IllegalArgumentException(String.format("Unknown AdviceMode: '%s'", adviceMode));
		}
		return imports;
	}

	// 重載selectImports()方法
	protected abstract String[] selectImports(AdviceMode adviceMode);

}
複製代碼
  • 從源碼中能夠知道,最終會調用到AsyncConfigurationSelector類的selectImports()方法。該方法中根據EnableAsync註解中指定的mode屬性的值,來返回不一樣的全類名,從而向Spring容器中註冊不一樣類型的Bean。在此處就是根據指定的代理類型,來向Spring容器中註冊ProxyAsyncConfiguration類或者AspectJAsyncConfiguration。前者是經過JDK的動態代理方式來加強加了@Async註解的方法或者類,後者是經過AspectJ的方式來加強目標方法或者類。

@Async的做用就是讓方法異步執行,因此須要對目標方法進行加強,那麼能夠採用代理的方式或者AspectJ技術操做字節碼,對字節碼進行靜態織入,從而達到目標。設計模式

  • 同理,再去看看@EnableScheduling、@EnableTransactionManagement、@EnableDiscoveryClient、@EnableFeignClients等註解,是否是在它們的源碼裏面,都有一個@Import,在Import註解中添加的類要麼是框架或者開發自定義的類,要麼是ImportSelector接口的實現類,或者是ImportBeanDefinitionRegistrar接口的實現類。而在它們各自的實現類中,重寫了接口的方法,向Spring容器中添加了一個帶有特殊功能的類,從而達到開啓某某功能的目的。
  • 在SpringBoot中,一般會須要整合第三方jar包,一般咱們的作法是先引入一個starter,而後在配置類或者啓動類上加一個@EnableXXX,這樣就和第三方jar包整合完畢了。閱讀完本文,如今應該都知道這個原理了吧。

4. 爲何要用?

經過上面分析咱們知道,@Enable系列註解,就是向容器中註冊一個Bean,既然註冊一個Bean,咱們爲何不經過@Component等註解註冊呢?而要用@Import註解這種方式?數組

  • 答案很明顯,靈活。例如第三方jar包,可能在某些項目中並不須要使用該jar包中的某些功能,若是咱們直接在類的代碼上加上@Component註解,這樣在和Spring整合時,首先要確保Spring的ComponentScan註解能掃描到第三方jar包中類所在的包,其次,這樣Spring容器啓動後,無論用不用這個功能,都會在容器中添加這個Bean,這樣就不太合適,因此靈活度不夠。而用@EnableXXX註解,就能達到想用就用,不想用就關閉的目的,並且還不須要確保Spring掃描到這個第三方jar包的包名。

5. 總結

  • 本文先經過3個demo介紹了Import註解的3種使用場景,而後結合ConfigurationClassPostProcessor類的源碼分析了Import註解的使用原理。
  • 接着經過@Import註解,揭開了@Enable系列註解的神祕面紗。並結合@EnableAsync註解的源碼,舉例說明了@Enable註解的原理。
  • 最後解釋了使用@Import和@Enable系列註解的好處。
  • 看到這兒,是否是能夠立馬本身去寫一個@Enable註解的組件了呢?或者本身寫一個第三方的插件包了呢?

6. 推薦

最後推薦一款本人所在公司開源的性能監控工具——Pepper-Metrics微信

  • 地址: github.com/zrbcool/pep…
  • 或者 點擊此處跳轉
  • Pepper-Metrics是坐我對面的兩位同事一塊兒開發的開源組件,主要功能是經過比較輕量的方式與經常使用開源組件(jedis/mybatis/motan/dubbo/servlet)集成,收集並計算metrics,並支持輸出到日誌及轉換成多種時序數據庫兼容數據格式,配套的grafana dashboard友好的進行展現。項目當中原理文檔齊全,且所有基於SPI設計的可擴展式架構,方便的開發新插件。另有一個基於docker-compose的獨立demo項目能夠快速啓動一套demo示例查看效果https://github.com/zrbcool/pepper-metrics-demo。若是你們以爲有用的話,麻煩給個star,也歡迎你們參與開發,謝謝:)

掃描下方二維碼便可關注微信公衆號菜鳥飛呀飛,一塊兒閱讀更多Spring源碼。mybatis

微信公衆號
相關文章
相關標籤/搜索