Springboot搭建、外部tomcat、自动配置、run原理

Springboot:
简单来说,springboot有点类似maven的作用,整合了大量的框架,功能,使用起来非常方便快捷,减少了大量的配置文件

Springboot添加maven基本不需要版本号,它的祖父spring-boot-dependencies 基本上把所有常用模块的都添加进去了

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.3.3.RELEASE</version>
  </parent>
<properties>
    <activemq.version>5.15.13</activemq.version>
    <antlr2.version>2.7.7</antlr2.version>
    <appengine-sdk.version>1.9.81</appengine-sdk.version>
    <artemis.version>2.12.0</artemis.version>
    <aspectj.version>1.9.6</aspectj.version>
    <assertj.version>3.16.1</assertj.version>
    <atomikos.version>4.0.6</atomikos.version>
    <awaitility.version>4.0.3</awaitility.version>
    <bitronix.version>2.1.4</bitronix.version>
    <build-helper-maven-plugin.version>3.1.0</build-helper-maven-plugin.version>
    <byte-buddy.version>1.10.14</byte-buddy.version>
    。。。。。。。。

搭建一个简单的Springboot-web项目:
springboot搭建有些基本的要求:
Spring Boot 2.3.0.RELEASE需要Java 8
Maven :3.3+
tomcat : 8.5.x+ Servlet 3.1+

WEB项目和普通项目有点区别,启动类旁边会多个ServletInitializer

1:使用 Spring Initializer快速创建项目;
2:项目下添加web,即main路径下创建webapp及WEB-INF,web.xml
在这里插入图片描述
3:修改pom文件,将jar包形式修改为war包,将嵌入式tomcat改为provided

<groupId>com.springboottest</groupId>
	<artifactId>demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>


<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
</dependency>

4:创建两个简单的页面:
hello.jsp,first.jsp

1.hello.jsp
<%--
  Created by IntelliJ IDEA.
  User: Administrator
  Date: 2020/9/8 0008
  Time: 23:32
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1> hello.........</h1>

<a href="abc">abc</a>
</body>
</html>

2.first.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>

<h2>${msg}</h2>
</body>
</html>

5:创建一个简单的controller

@Controller
public class Controllerhello {
    @GetMapping("/abc")
    public String hello(Model model){
        model.addAttribute("msg","kkkkkkkkkk");
        return "first";
    }
}

6:application.properties配置视图解析

spring.mvc.view.prefix=/WEB-INF/
spring.mvc.view.suffix=.jsp

项目结构
在这里插入图片描述

需要注意的点:必须将内嵌的tomcat 指定为provided

Springboot的核心自动配置:

可以看到启动类上面的注释 @SpringBootApplication 里包含一个注释 @EnableAutoConfiguration, 表示开启自动配置

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

1:点进@AutoConfigurationPackage,看到@Import(AutoConfigurationPackages.Registrar.class)继续

	static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
		}

这个方法主要是将主配置类(@SpringBootApplication标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器;

2:@Import(AutoConfigurationImportSelector.class)
给spring容器导入自动配置类,
Springboot在类路径下的META-INF/spring.factories中获取所有需要的自动配置类
在这里插入图片描述

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}


 /* @return the auto-configurations that should be imported
	 */
	protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		
主要是这个方法获得了所有的自动配置类classPath	
	
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

发现它有两个参数,第二个参数类加载器 不用管,点进第一个参数
	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());
		Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
				+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}

发现它返回的是EnableAutoConfiguration.class,也就是开启自动配置类
	protected Class<?> getSpringFactoriesLoaderFactoryClass() {
		return EnableAutoConfiguration.class;
	}


	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}

发现获取的是String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

点进一个自动配置类看看:

 表示这是一个配置类
@Configuration(proxyBeanMethods = false)
 项目中必须有这两个类 DataSource EmbeddedDatabaseType,否则不导入
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })

容器中不存在指定Bean
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")

启动指定类的 ConfigurationProperties功能;将配置文件中对应的值和DataSourceProperties绑定起来;并把 DataSourceProperties加入到ioc容器中
@EnableConfigurationProperties(DataSourceProperties.class)

导入数据连接池支持类、数据源初始化类
@Import({ DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {}



@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {

	private ClassLoader classLoader;
	}

在这里插入图片描述
1、SpringBoot启动会加载大量的自动配置类
2、根据当前不同的条件判断,决定这个配置类是否生效? 一但这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取 的,这些类里面的每一个属性又是和配置文件绑定的
3、我们看我们需要的功能有没有SpringBoot默认写好的自动配置类;
4、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件有,我们就不需要再来配置了)
5、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们就可以在配置文件中指定这 些属性的值;

@Conditional扩展注解 作用(判断是否满足当前指定条件)
@ConditionalOnJava 系统的java版本是否符合要求
@ConditionalOnBean 容器中存在指定Bean; @ConditionalOnMissingBean 容器中不存在指定Bean;
@ConditionalOnExpression 满足SpEL表达式指定
@ConditionalOnClass 系统中有指定的类
@ConditionalOnMissingClass 系统中没有指定的类
@ConditionalOnSingleCandidate 容器中只有一个指定的Bean,或者这个Bean是首选Bean
@ConditionalOnProperty 系统中指定的属性是否有指定的值
@ConditionalOnResource 类路径下是否存在指定资源文件
@ConditionalOnWebApplication 当前是web环境
@ConditionalOnNotWebApplication 当前不是web环境
@ConditionalOnJndi JNDI存在指定项

Springboot启动原理:

	public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}
	
	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

可以看出springboot启动会先new一个SpringApplication,然后在调run方法

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		
		判断当前是否一个web应用
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
        
        从类路径下找到META‐INF/spring.factories配置的所有ApplicationContextInitializer;然后保存起 来
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		
		从类路径下找到ETA‐INF/spring.factories配置的所有ApplicationListener
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		从多个配置类中找到有main方法的主配置类
		this.mainApplicationClass = deduceMainApplicationClass();
	}

在这里插入图片描述
创建完,调用run方法

	/**
	 * Run the Spring application, creating and refreshing a new
	 * {@link ApplicationContext}.
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return a running {@link ApplicationContext}
	 */
	public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
 
        获取SpringApplicationRunListeners;从类路径下META‐INF/spring.factories
        
		SpringApplicationRunListeners listeners = getRunListeners(args);

        回调所有的获取SpringApplicationRunListener.starting()方法
		listeners.starting();
		try {
            封装命令行参数
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			 
			准备环境 
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
			
			打印springboot的图标
			Banner printedBanner = printBanner(environment);
          
            创建ApplicationContext;决定创建web的ioc还是普通的ioc
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			
			prepareContext运行完成以后回调所有的SpringApplicationRunListener的contextLoaded();
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);

            刷新容器;ioc容器初始化(如果是web应用还会创建嵌入式的Tomcat); 扫描,创建,加载所有组件的地方;(配置类,组件,自动配置)
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
            所有的SpringApplicationRunListener回调started方法
			listeners.started(context);
            
            从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调 
			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;
	}

如何修改SpringBoot的默认配置 模式
1、SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@Bean、@Component)如 果有就用用户配置的,如果没有,才自动配置;如果有些组件可以有多个(ViewResolver)将用户配置的和自己默 认的组合起来;
2、在SpringBoot中会有非常多的xxxConfigurer帮助我们进行扩展配置
3、在SpringBoot中会有很多的xxxCustomizer帮助我们进行定制配置

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值