SpringBoot简化配置分析总结

SpringBoot简化配置分析总结

在SpringBoot启动类中,该主类被@SpringBootApplication所修饰,跟踪该注解类,除元注解外,该注解类被如下自定注解修饰。

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan

让我们简单叙述下它们各自的功能:

  • @ComponentScan:扫描需要被IoC容器管理下需要管理的Bean,默认当前根目录下的
  • @EnableAutoConfiguration:装载所有第三方的Bean
  • @SpringBootConfiguration 作用等同于@Configuration

我们来看下@SpringBootConfiguration

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

可以看到该注解类内包含与@Configuration,其作用与@Configuration并无太大区别,只是多了层属性嵌套。

故: @SpringBootConfiguration + @ComponentScan

将根目录下所有被**@Controller、@Service、@Repository、@Component**等所修饰的类交给IoC容器管理。

那么重点来了,@EnableAutoConfiguration是如何装载第三方Bean的呢?让我们跟踪下它的源码。

首先我们可以看到该类被如下注解修饰:

@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})

我们先关注下AutoConfigurationImportSelector这个组件。

// 批量导入第三方的一些Bean
@Import({AutoConfigurationImportSelector.class})

其中该组件的selectImports(AnnotationMetadata annotationMetadata)方法,我们先简述下它的作用:扫描所有需要被管理的第三方Bean并交给IoC容器进行管理。然后我们接着往下追踪。

public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    } else {
        // 让我们跟踪到这个方法
        AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
}
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    } else {
        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
        // 获取所有AutoConfiguration的配置类
        List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
        // 下面就是对AutoConfiguration的去重、排除和过滤等操作
        configurations = this.removeDuplicates(configurations);
        Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
        this.checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = this.getConfigurationClassFilter().filter(configurations);
        // 我们继续追踪这里
        this.fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
    }
}
private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
    List<AutoConfigurationImportListener> listeners = this.getAutoConfigurationImportListeners();
    if (!listeners.isEmpty()) {
        // 加了层包装
        AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
        Iterator var5 = listeners.iterator();

        while(var5.hasNext()) {
            AutoConfigurationImportListener listener = (AutoConfigurationImportListener)var5.next();
            this.invokeAwareMethods(listener);
            // 向ConditionEvaluationReport中导入所有AutoConfiguration
            listener.onAutoConfigurationImportEvent(event);
        }
    }

}

可以猜想IoC容器在启动时会将这里的AutoConfiguration中的每个Bean都注入到容器中。这里的源码我们先跟踪到这里,大致了解了下该方法的作用。

那么SpringBoot又是如何取感知第三方的Bean文件呢?

SpringBoot和第三方Bean之间存在一定的规定。即通过对于相应依赖的Jar包中可能存在一个spring.factories文件,在该文件中就记录了需要被IoC容器管理的Bean文件路径,SpringBoot通过该文件确定需要IoC管理的Bean文件位置。对于spring-boot-autoconfiguration的spring.factories文件中,记录着大量xxxAutoConfiguration的类文件位置,这些类都被@Configuration注解标识,即这些配置类会配置多个Bean从而解决spring.factories可能产生的臃肿问题。

Tomcat的加载时机

对于SpringBoot来说它特点不仅是简化配置,还有内嵌容器等特点。那么就有必要探讨Tomcat容器的加载时机。。在SpringBoot主类中使用了SpringApplication的runrun(Class<?> primarySource, String… args)方法中,该方法会生成该类实例调用run(String… args)方法,我们所关注的首先它会进入createApplicationContext方法反射生成AnnotationConfigServletWebServerApplicationContext实例。之后进入prepareContext方法配置该上下文,注册到BeanFactory,再将其加载到监听器。接着会来到refreshContext(context)方法该方法会调用对应上下文的refresh()方法,经过周转会来到AbstractApplicationContext类的refresh()方法来构建容器【是不是感觉回到了IoC区域】,并在此方法中将tomcat注册到容器并启动。下面我们放上关于该部分的代码并讲解。(关于IoC部分的原理就不是本文所探讨的重点)

// Class AbstractApplicationContext

@Override
public void refresh() throws BeansException, IllegalStateException {
		// 我们只关注两个方法
		synchronized (this.startupShutdownMonitor) {
			prepareRefresh();
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
			prepareBeanFactory(beanFactory);
			try {
				postProcessBeanFactory(beanFactory);
				invokeBeanFactoryPostProcessors(beanFactory);
				registerBeanPostProcessors(beanFactory);
				initMessageSource();
				initApplicationEventMulticaster();
				// 加载webServer到容器
				onRefresh();
				registerListeners();
				finishBeanFactoryInitialization(beanFactory);
				// 在该方法中WebServer被启动
				finishRefresh();
			}
			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}
				destroyBeans();
				cancelRefresh(ex);
				throw ex;
			}
			finally {
				resetCommonCaches();
			}
		}
	}

// Class ServletWebServerApplicationContext

@Override
protected void onRefresh() {
	// 先进行父类的onRefresh操作
	super.onRefresh();
	try {
		// 注意这个方法我们往下看
		createWebServer();
	}
	catch (Throwable ex) {
		throw new ApplicationContextException("Unable to start web server", ex);
	}
}
	
private void createWebServer() {
	WebServer webServer = this.webServer;
	ServletContext servletContext = getServletContext();
	// 判断是否已经存在webServer
	if (webServer == null && servletContext == null) {
		// 获取webServer工厂
		ServletWebServerFactory factory = getWebServerFactory();
		// 使用工厂创建的webServer并将webServer和serverContext设置的ApllicationContext中
		this.webServer = factory.getWebServer(getSelfInitializer());
		// ※在BeanFactory中注册WebServer
		getBeanFactory().registerSingleton("webServerGracefulShutdown",
				new WebServerGracefulShutdownLifecycle(this.webServer));
		getBeanFactory().registerSingleton("webServerStartStop",
				new WebServerStartStopLifecycle(this, this.webServer));
	}
	else if (servletContext != null) {
		try {
			getSelfInitializer().onStartup(servletContext);
		}
		catch (ServletException ex) {
			throw new ApplicationContextException("Cannot initialize servlet context", ex);
		}
	}
	// 初始化自定义环境变量
	initPropertySources();
}

接着我们来谈谈tomcat的启动。先回到AbstractApplicationContext接着查看refresh()方法,在构建容器的尾声时会来到finishRefresh()方法;

// Class AbstractApplicationContext

protected void finishRefresh() {
	clearResourceCaches();
	initLifecycleProcessor();
	// 我们需要关注这里
	getLifecycleProcessor().onRefresh();
	publishEvent(new ContextRefreshedEvent(this));
	LiveBeansView.registerApplicationContext(this);
	}

// Class DefaultLifecycleProcessor

@Override
public void onRefresh() {
	startBeans(true);
	this.running = true;
}

private void startBeans(boolean autoStartupOnly) {
	// 从容器中获取webServer
	Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();
	Map<Integer, LifecycleGroup> phases = new HashMap<>();
	lifecycleBeans.forEach((beanName, bean) -> {
		if (!autoStartupOnly || (bean instanceof SmartLifecycle && ((SmartLifecycle) bean).isAutoStartup())) {
			// 获取phase值
			int phase = getPhase(bean);
			// 和下面用于判断是否重复
			LifecycleGroup group = phases.get(phase);
			// 没重复则创建LifecycleGroup,并添加进phases
			if (group == null) {
				group = new LifecycleGroup(phase, this.timeoutPerShutdownPhase, lifecycleBeans, autoStartupOnly);
				phases.put(phase, group);
			}
			// 重复则在LifecycleGroup的members里拉链
			group.add(beanName, bean);
		}
	});
	if (!phases.isEmpty()) {
		List<Integer> keys = new ArrayList<>(phases.keySet());
		Collections.sort(keys);
		for (Integer key : keys) {
			// 调用每一个LifecycleGroup的start方法
			phases.get(key).start();
		}
	}
}

public void start() {
	if (this.members.isEmpty()) {
		return;
	}
	if (logger.isDebugEnabled()) {
		logger.debug("Starting beans in phase " + this.phase);
	}
	Collections.sort(this.members);
	for (LifecycleGroupMember member : this.members) {
		doStart(this.lifecycleBeans, member.name, this.autoStartupOnly);
	}
}

private void doStart(Map<String, ? extends Lifecycle> lifecycleBeans, String beanName, boolean autoStartupOnly) {
	Lifecycle bean = lifecycleBeans.remove(beanName);
	if (bean != null && bean != this) {
		// 判断是否存在依赖
		String[] dependenciesForBean = getBeanFactory().getDependenciesForBean(beanName);
		// 如果有依赖先启动相应依赖
		for (String dependency : dependenciesForBean) {
			doStart(lifecycleBeans, dependency, autoStartupOnly);
		}
		if (!bean.isRunning() &&
				(!autoStartupOnly || !(bean instanceof SmartLifecycle) || ((SmartLifecycle) bean).isAutoStartup())) {
			if (logger.isTraceEnabled()) {
				logger.trace("Starting bean '" + beanName + "' of type [" + bean.getClass().getName() + "]");
			}
			try {
				// 调用每个Lifecycle的start方法
				bean.start();
			}
			catch (Throwable ex) {
				throw new ApplicationContextException("Failed to start bean '" + beanName + "'", ex);
			}
			if (logger.isDebugEnabled()) {
				logger.debug("Successfully started bean '" + beanName + "'");
			}
    	}
	}
}

最后他会来到WebServerStartStopLifecycle中,该类可以看做为对WebServer的封装。
// Class WebServerStartStopLifecycle

@Override
public void start() {
	// ※调用对应webServer的start方法
	this.webServer.start();
	// 设置启动状态,并发布事件
	this.running = true;
	this.applicationContext
			.publishEvent(new ServletWebServerInitializedEvent(this.webServer, this.applicationContext));
}

SpringBoot是如何集中配置呢?

谈论这个问题前我们不妨先按照之前yml或properties的文件配置下

server:
  port: 8080

通过IDE,跟踪到port所配置的成员变量所在类,发现该类被@ConfigurationProperties所修饰,该注解就是将yml或properties中配置按照对应前缀注入到指定类的成员变量。该注解具体实现感兴趣的小伙伴们可以去如下链接学习。@ConfigurationProperties实现原理与实战

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
   private Integer port;
*******
}

下面两个代码和前述作用大致相同

environment.getProperty("xxx");

@Value("${xxx}")

我们在使用SpringBoot时只需要做哪些事情?

通常我们再使用SpringBoot时只需要在Maven中引入类似如下的starter依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

最多再需要配置一些类似mybatis这类框架的一些属性参数。而这些starter按照我们之前的逻辑其内部应该存有spring.factories文件,我们先去对应jar包查找下。

如果有些starter的jar包没有找到我们想要的spring.factories文件。我们可以去spring-boot-test-autoconfiguretion中的spring.factories查看下,SpringBoot内部其实已经定义好相当一定数量的AutoConfiguration。
在这里插入图片描述

果然该jar包内确实存在spring.factories文件,代码如下。

org.springframework.data.repository.core.support.RepositoryFactorySupport=org.springframework.data.redis.repository.support.RedisRepositoryFactory

这意味着我们已经简单地了解了SpringBoot如何简化配置,那么我们也应该可以自己来实现一个starter依赖交给SpringBoot来使用,只要在对应Jar包中添加spring.factories文件,在其中添加如下代码。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=xxxAutoConfiguration

大家若有时间还请实现下自己的starter依赖,对加深这部分理解还是很有帮助的。Spring Cloud就是运用了这样的一种思想。感兴趣的小伙伴可以看下我做的一个简单的实现。[自定义starter实现]

最后我们在说下最后@SpringBootApplication中@AutoConfigurationPackage这个注解类,发现其中导入了Registrar组件。

@Import({Registrar.class})

让我们重点关注registerBeanDefinitions这个方法,该方法最终会来到DefaultListableBeanFactory中registerBeanDefinition(String beanName, BeanDefinition beanDefinition)方法,将AutoConfigurationPackages.class注册到IoC容器中,然后将主配置类所在包下所有组件导入到SpringIoC容器中

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    // 里面就这一个方法我们跟踪下
    AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
    // 判断beanDefinitionMap是否存在AutoConfigurationPackages
    if (registry.containsBeanDefinition(BEAN)) {
        BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
        ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
        constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
    } else {
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(AutoConfigurationPackages.BasePackages.class);
        beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
        beanDefinition.setRole(2);
        // 将设置好的AutoConfigurationPackages注册到beanDefinitionMap(是不是很熟悉这一步)
        registry.registerBeanDefinition(BEAN, beanDefinition);
    }

}

怎么样,在为我们简化了配置的同时,SpringBoot居然帮我们做了如此多的事情,而我们只需要简单地集中配置其中一部分的属性。关于SpirngBoot我们就探讨到这里,这些内容是阅读一些文章,观看部分讲解和源码的总结,若有错误还请接纳与指教。这是本人的第一篇文章,最后感谢各位的阅读。

  • 9
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值