SpringBoot—三大特性

随着spring使用的越来越广泛,项目的各种配置文件也随之越来越多,大量的配置文件让开发者很烦恼。springboot的诞生简化了spring应用的创建、运行、部署。

在JDK1.5之后引入的注解也在springboot中大量使用,springboot也提供了一些注解(集成了spring的注解),如SpringBootApplication注解标注了该类是一个启动类。

另外,在过去的springmvc项目中,使用第三方包需要在maven中配置很多行,为了简化我们的配置,很多第三方包也提供了基于springboot的maven包,我们只需引用做简单的配置及可使用。

1.内置Servlet Container

在pom.xml中,添加spring-boot-starter-web的maven依赖,使其成为一个web项目,其中这个spring-boot-starter-web中包含了spring-boot-starter-tomcat,使得项目能在内置的tomcat容器中运行。
在这里插入图片描述
另外springboot也可以使用其他容器的starter替换tomcat,包括Jetty以及undertow等。这里以undertow为例,具体方法如下:
在这里插入图片描述
springboot内置了servlet容器,项目可以直接打包成jar形式,使用java -jar命令运行,而不必像以前打包成war包形式放到tomcat下运行。正因为这个机制,结合Jenkins、Docker自动化运维得以实现。

2.生产准备特性

springboot提供的actuator插件提供了大量的生产级特性,可以帮助监控和管理springBoot应用,比如健康检查、审计、统计和HTTP追踪。这在微服务中,可以通过actutor提供的端点与外部应用监控系统进行整合,比如Prometheus、DataDog来进行服务监控。
SpringBoot-Actuator提供了很多监控端点

端点描述http方法
autoconfig显示自动配置信息GET
beans显示应用程序上下文所有的spring beanGET
configprops显示所有@ConfigurationProperties的配置属性列表GET
dump显示线程活动的快照GET
env显示应用的环境变量GET
health显示应用程序的健康指标,这些值由HealthIndicator的实现类提供GET
info显示应用的信息,可使用info.*属性自定义info端点公开的数据GET
mappings显示所有的URL路径GET
metrics显示应用的度量标准信息GET
shutdown关闭应用(默认情况下不启用,如需启用,需设置endpoints.shutdown.enabled=true)POST
trace显示跟踪信息(默认情况下为最近的100个HTTP请求)GET

actuator配置,为项目引入以下依赖
在这里插入图片描述
这里需要注意的是,在springboot2.0之后,actuator通过jmx暴露端点,对HTTP屏蔽了对外的访问端点,只提供health和info端点,另外使用 http://{ip}:{port}/actuator/{endpoint}的形式访问,需要做以下的配置才能访问。
在这里插入图片描述
这里表示对http开发所有端口节点。

/health endpoint
在这里插入图片描述
UP表示运行正常,除UP外,还有Down、OUT_OF_SERVICE、UN_KNOWN等状态。health只简单显示了应用的UP状态,如果想要知道详细的信息,需要做如下配置:
在这里插入图片描述
在这里插入图片描述
这里Redis没有连接上,所以状态会变为DOWM

/metrics endpoint
用于追踪应用的度量信息

在浏览器输入http://{ip}:{port}/actuator/metrics,显示所有支持的度量
在这里插入图片描述
可使用http://{ip}:{port}/actuator/metrics/{detailName}查看具体的度量信息,比如http://localhost:18762/actuator/metrics/jvm.threads.states
在这里插入图片描述
/loggers endpoint
展示了应用中logger相关的日志等级和列表,与metrics类似,通过访问
http://{ip}:{port}/actuator/loggers查看所有的logger列表,并使用name访问具体的logger信息
在这里插入图片描述
可通过给以上logger地址发送post请求的方式动态的改变日志等级。
在这里插入图片描述
再次查看日志等级变为DEBUG
在这里插入图片描述
actuator的应用场景:结合普罗米修斯等做服务监控(借用一篇大老杨的文章)

Prometheus
一套开源的系统监控报警框架,可以将Prometheus理解为一个数据库,通过配置http://localhost:8762/actuator/prometheus,爬取actuator接口提供的metric等数据。
官网地址:https://prometheus.io/
Grafana
能够把不同来源的数据(这里数据来源于Prometheus)以图形化的形式进行展示,并通过邮件等形式通知。
官网地址:https://grafana.com/

springboot集成Prometheus具体实现步骤

  1. 添加依赖
    在这里插入图片描述
  2. 配置yml文件在这里插入图片描述
  3. 设置启动类

    启动springboot项目后访问 http://localhost:8762/actuator/prometheus ,可以看到一些度量指标
    在这里插入图片描述
  4. 使用Docker下载和运行Prometheus
    https://prometheus.io/docs/prometheus/latest/getting_started/
$ docker pull prom/prometheus

在这里插入图片描述
配置prometheus.yml文件
在这里插入图片描述
5. 卷和绑定安装
prometheus.yml通过运行以下命令将您从主机绑定:

docker run -p 9090:9090 -v /tmp/prometheus.yml:/etc/prometheus/prometheus.yml \
       prom/prometheus

或者为配置使用额外的卷:

docker run -p 9090:9090 -v /prometheus-data \
       prom/prometheus --config.file=/prometheus-data/prometheus.yml
  1. 访问prometheus http://[host]:9090
    在这里插入图片描述
    第一个endpoint是prometheus自己的健康健康端点
    第二个是我的springboot应用的actuator/prometheus端点

  2. prometheus自带的图形界面
    在这里插入图片描述

  3. 使用Docker下载和运行Grafana
    http://docs.grafana.org/installation/docker/

docker search grafana
docker pull grafana/grafana
docker images

$ docker run -d -p 3000:3000 grafana/grafana

安装完后访问http://[host]:3000 ,并使用默认账号密码登录admin,admin

  1. 配置Grafana导入Prometheus中的metrics数据
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述

3.装配模式

springboot能很方便的使用第三方包,只需做简单的配置即可使用,例如:jdbc、web-mvc等。那么springboot是如何加载这些模块的呢?

首先springboot项目的启动都是从带有@SpringBootApplication的启动类开始的,让我们来看看@SpringBootApplication这个注解。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication 

这里有三个注解@SpringBootConfiguration、@EnableAutoConfiguration、@ComponenetScan,
在这里插入图片描述
从SpringBootConfiguration注解可以看出,这就是个@Configuration,即被@SpringBootApplication标注的类是一个配置类。

而@ComponenetScan是一个包扫描注解,由于没有指定扫描范围,所以它会扫描同级包目录下的所有类,即@SpringBootApplication标注的启动类同级包下的所有类(com.grape.client下的所有类)。
在这里插入图片描述
最后我们来看@EnableAutoConfiguration这个注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration

这里通过@Import注解引入了AutoConfigurationImportSelector这个类,根据字面意思理解(自动装配选择器)


public class AutoConfigurationImportSelector
		implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
		BeanFactoryAware, EnvironmentAware, Ordered {

	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
				autoConfigurationMetadata, annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
}

这里我删掉了其他的一些方法,只留下了selectImports这个方法,AutoConfigurationImportSelector实现了DeferredImportSelector接口,DeferredImportSelector又继承自ImportSelector接口

public interface ImportSelector {

	/**
	 * Select and return the names of which class(es) should be imported based on
	 * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
	 */
	String[] selectImports(AnnotationMetadata importingClassMetadata);

}

那么在AutoConfigurationImportSelector选择器中,selectImports这个方法又做了什么呢?
在这里插入图片描述
首先会判断是否进行自动装配,接着执行

AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);

在这里插入图片描述在这里插入图片描述
这里会从META-INF/spring-autoconfigure-metadata.properties读取元数据和元数据相关的属性,获取所有支持自动装配的信息。在这里插入图片描述
接着执行

AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
autoConfigurationMetadata, annotationMetadata);

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
这里会读取META-INF/spring.factories下的配置,接着进行排除和过滤得到需要装配的类。最后让 META-INF/spring.factories下的装配类执行fireAutoConfigurationImportEvents这个方法,加载应用监听。在这里插入图片描述
以上确定了哪些类需要被装配,但springboot是在何时装配这些类的呢?
在启动类的main方法中,会调用SpringApplication.run()方法,参数是启动类的Class。

public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

该方法会创建一个ApplicationContext并返回

1.准备Context所需的参数、环境

  • 【SpringApplicationRunListeners listeners】,对SpringApplicationRunListeners的解释是 A collection of {@link SpringApplicationRunListener},SpringApplicationRunListener是用于SpringApplication@run方法的监听器
  • 【ApplicationArguments applicationArguments】提供用于运行SpringApplication的参数
  • 【ConfigurableEnvironment environment】提供运行环境
  • 【Banner printedBanner】

2.prepareContext
为context添加相应的运行环境和监听, 创建一个beanFactory用于加载单例的springApplicationArguments、springBootBanner,获取启动配置类,将需要加载的bean加载到context中,为context添加监听
3.refreshContext
调用AbstractApplicationContext.refresh(),执行refresh里的invokeBeanFactoryPostProcessors(beanFactory);处理BeanFactoryPostProcessor

if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
					BeanDefinitionRegistryPostProcessor registryProcessor =
							(BeanDefinitionRegistryPostProcessor) postProcessor;
					registryProcessor.postProcessBeanDefinitionRegistry(registry);
					registryProcessors.add(registryProcessor);
				}

ConfigurationClassPostProcessor实现了BeanDefinitionRegistryPostProcessor,该类主要处理@Configuration注解的

/**
	 * Prepare the Configuration classes for servicing bean requests at runtime
	 * by replacing them with CGLIB-enhanced subclasses.
	 */
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		int factoryId = System.identityHashCode(beanFactory);
		if (this.factoriesPostProcessed.contains(factoryId)) {
			throw new IllegalStateException(
					"postProcessBeanFactory already called on this post-processor against " + beanFactory);
		}
		this.factoriesPostProcessed.add(factoryId);
		if (!this.registriesPostProcessed.contains(factoryId)) {
			// BeanDefinitionRegistryPostProcessor hook apparently not supported...
			// Simply call processConfigurationClasses lazily at this point then.
			processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
		}

		enhanceConfigurationClasses(beanFactory);
		beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
	}
/**
	 * Build and validate a configuration model based on the registry of
	 * {@link Configuration} classes.
	 */
	public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		String[] candidateNames = registry.getBeanDefinitionNames();

		for (String beanName : candidateNames) {
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
			if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
					ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
				if (logger.isDebugEnabled()) {
					logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
				}
			}
			else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}

		// Return immediately if no @Configuration classes were found
		if (configCandidates.isEmpty()) {
			return;
		}

		// Sort by previously determined @Order value, if applicable
		configCandidates.sort((bd1, bd2) -> {
			int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
			int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
			return Integer.compare(i1, i2);
		});

		// Detect any custom bean name generation strategy supplied through the enclosing application context
		SingletonBeanRegistry sbr = null;
		if (registry instanceof SingletonBeanRegistry) {
			sbr = (SingletonBeanRegistry) registry;
			if (!this.localBeanNameGeneratorSet) {
				BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
				if (generator != null) {
					this.componentScanBeanNameGenerator = generator;
					this.importBeanNameGenerator = generator;
				}
			}
		}

		if (this.environment == null) {
			this.environment = new StandardEnvironment();
		}

		// Parse each @Configuration class
		ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);

		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
		do {
			parser.parse(candidates);
			parser.validate();

			Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
			configClasses.removeAll(alreadyParsed);

			// Read the model and create bean definitions based on its content
			if (this.reader == null) {
				this.reader = new ConfigurationClassBeanDefinitionReader(
						registry, this.sourceExtractor, this.resourceLoader, this.environment,
						this.importBeanNameGenerator, parser.getImportRegistry());
			}
			this.reader.loadBeanDefinitions(configClasses);
			alreadyParsed.addAll(configClasses);

			candidates.clear();
			if (registry.getBeanDefinitionCount() > candidateNames.length) {
				String[] newCandidateNames = registry.getBeanDefinitionNames();
				Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
				Set<String> alreadyParsedClasses = new HashSet<>();
				for (ConfigurationClass configurationClass : alreadyParsed) {
					alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
				}
				for (String candidateName : newCandidateNames) {
					if (!oldCandidateNames.contains(candidateName)) {
						BeanDefinition bd = registry.getBeanDefinition(candidateName);
						if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
								!alreadyParsedClasses.contains(bd.getBeanClassName())) {
							candidates.add(new BeanDefinitionHolder(bd, candidateName));
						}
					}
				}
				candidateNames = newCandidateNames;
			}
		}
		while (!candidates.isEmpty());

		// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
		if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
			sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
		}

		if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
			// Clear cache in externally provided MetadataReaderFactory; this is a no-op
			// for a shared cache since it'll be cleared by the ApplicationContext.
			((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
		}
	}
public void parse(Set<BeanDefinitionHolder> configCandidates) {
		for (BeanDefinitionHolder holder : configCandidates) {
			BeanDefinition bd = holder.getBeanDefinition();
			try {
				if (bd instanceof AnnotatedBeanDefinition) {
					parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
				}
				else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
					parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
				}
				else {
					parse(bd.getBeanClassName(), holder.getBeanName());
				}
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException(
						"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
			}
		}

		this.deferredImportSelectorHandler.process();
	}
public void process() {
			List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
			this.deferredImportSelectors = null;
			try {
				if (deferredImports != null) {
					DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
					deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
					deferredImports.forEach(handler::register);
					handler.processGroupImports();
				}
			}
			finally {
				this.deferredImportSelectors = new ArrayList<>();
			}
		}

在这里将会对DeferredImportSelector进行处理,这样我们就和AutoConfigurationSelectImporter结合到一起了

public void processGroupImports() {
			for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
				grouping.getImports().forEach(entry -> {
					ConfigurationClass configurationClass = this.configurationClasses.get(
							entry.getMetadata());
					try {
						processImports(configurationClass, asSourceClass(configurationClass),
								asSourceClasses(entry.getImportClassName()), false);
					}
					catch (BeanDefinitionStoreException ex) {
						throw ex;
					}
					catch (Throwable ex) {
						throw new BeanDefinitionStoreException(
								"Failed to process import candidates for configuration class [" +
										configurationClass.getMetadata().getClassName() + "]", ex);
					}
				});
			}
		}

到此执行自动装配的所有操作

4.afterRefresh
5.总结

  • 自动装配利用SpringFactoriesLoader来加载META-INF/spring.factoires文件里所有配置的EnableAutoConfgruation,它会经过exclude和filter等操作,最终确定要装配的类
  • 实现自动装配主要是 ((AbstractApplicationContext) applicationContext).refresh();这个方法
  • 5
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值