随着spring使用的越来越广泛,项目的各种配置文件也随之越来越多,大量的配置文件让开发者很烦恼。springboot的诞生简化了spring应用的创建、运行、部署。
在JDK1.5之后引入的注解也在springboot中大量使用,springboot也提供了一些注解(集成了spring的注解),如SpringBootApplication注解标注了该类是一个启动类。
另外,在过去的springmvc项目中,使用第三方包需要在maven中配置很多行,为了简化我们的配置,很多第三方包也提供了基于springboot的maven包,我们只需引用做简单的配置及可使用。
在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自动化运维得以实现。
springboot提供的actuator插件提供了大量的生产级特性,可以帮助监控和管理springBoot应用,比如健康检查、审计、统计和HTTP追踪。这在微服务中,可以通过actutor提供的端点与外部应用监控系统进行整合,比如Prometheus、DataDog来进行服务监控。
SpringBoot-Actuator提供了很多监控端点
端点 | 描述 | http方法 |
---|---|---|
autoconfig | 显示自动配置信息 | GET |
beans | 显示应用程序上下文所有的spring bean | GET |
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具体实现步骤
$ 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
访问prometheus http://[host]:9090
第一个endpoint是prometheus自己的健康健康端点
第二个是我的springboot应用的actuator/prometheus端点
使用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
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 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所需的参数、环境
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 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 candidates = new LinkedHashSet<>(configCandidates);
Set alreadyParsed = new HashSet<>(configCandidates.size());
do {
parser.parse(candidates);
parser.validate();
Set 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 oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
Set 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 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 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.总结