Spring Boot 20天入门(day7)

一群热爱技术并且向往优秀的程序猿同学,不喜欢水文,不喜欢贩卖焦虑,只喜欢谈技术,分享的都是技术干货。Talk is cheap. Show me the code
在这里插入图片描述

Springboot启动配置原理

启动类注解@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 {

前面4个都是java的元注解,没啥好看的,重要的是后面几个:

1)、@SpringBootConfiguration

2)、@EnableAutoConfiguration

3)、@ComponentScan

@SpringBootConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {}

这个注解其实没啥特别的,个人认为就是用来做语义化的注解,里面还是用的@Configuration,标注这是一个配置类

@EnableAutoConfiguration

开启Springboot的自动配置,这个在以前的文章讲过:

https://blog.csdn.net/WXZCYQ/article/details/106167302

@ComponentScan
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {

容器组件扫描的注解。用于扫描所有的容器组件,是最核心的注解之一。

SpringbootApplication.run()

我们都知道,Springboot的启动类(标注了@SpringBootApplication注解和拥有main()),会执行一个叫

SpringApplication.run()的方法,来启动Springboot应用。

让我们来debug,跟一下Springboot到底是如何启动应用的。

run()的截图:

该方法会传递一个启动类的class对象,以及启动类参数,然后执行另外一个run()

在这里插入图片描述

点击step into,将会看到新建一个SpringApplication, 并将配置类传递进去:

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

继续step into,查看SpringbootApplication的构造方法

public SpringApplication(Class<?>... primarySources) {
		this(null, primarySources);
	}

	/**
	 * Create a new {@link SpringApplication} instance. The application context will load
	 * beans from the specified primary sources (see {@link SpringApplication class-level}
	 * documentation for details. The instance can be customized before calling
	 * {@link #run(String...)}.
	 * @param resourceLoader the resource loader to use
	 * @param primarySources the primary bean sources
	 * @see #run(Class, String[])
	 * @see #setSources(Set)
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
        // 将启动类保存起来
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        // 获得当前应用的类型 ,WebApplicationType 是个枚举对象,NONE,SERVLET(servlet类型),REACTIVE(响应式)
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
        // 从类路径下的/META-INF/spring.factories中获取所有的ApplicationContextInitializer并保存起来
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
         // 从类路径下的/META-INF/spring.factories中获取所有的ApplicationListener并保存起来
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
         // 寻找拥有main方法的启动类
		this.mainApplicationClass = deduceMainApplicationClass();
	}

如何判断是启动类的呢?deduceMainApplicationClass()是这样判断的:

// 获取所有的方法名,匹配是不是main方法
private Class<?> deduceMainApplicationClass() {
		try {
			StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
			for (StackTraceElement stackTraceElement : stackTrace) {
				if ("main".equals(stackTraceElement.getMethodName())) {
					return Class.forName(stackTraceElement.getClassName());
				}
			}
		}
		catch (ClassNotFoundException ex) {
			// Swallow and continue
		}
		return null;
	}

创建完SpringApplication后,来到我们的重头戏,SpringApplication.run():

public ConfigurableApplicationContext run(String... args) {
    // 用于监控启动时间的计时器
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
    // 支持报告关于启动的错误对象的集合
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    // java.awt.headless是J2SE的一种模式用于在缺少显示屏、键盘或者鼠标时的系统配置,很多监控工具如jconsole 需要将该值设置为true,系统变量默认为true
		configureHeadlessProperty();
    //获取并启动监听器
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
            // 获取命令行参数
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 配置容器环境(spring.profile.active)
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            // 配置不需要初始化的bean对象,可以在配置文件中设置,前缀是spring.beaninfo.ignore
			configureIgnoreBeanInfo(environment);
            // 打印Banner,也就是我们看到的那个大大的Spring
			Banner printedBanner = printBanner(environment);
            // 创建容器的上下文对象(ApplicationContext)
			context = createApplicationContext();
            // 注册支持报告关于启动错误的对象
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
            // 将上文代码中注册的配置注入进上下文对象中
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            // 刷新容器,清除之前的缓存,从配置文件中导入环境,组件,如果是Web应用,还会启动web容器(Tomcat)
			refreshContext(context);
            // 这是个空方法,个人认为应该是留着扩展,因为传递的是命令行参数
			afterRefresh(context, applicationArguments);
            // 容器启动完毕,关闭计时器,并在控制台打印 : Root WebApplicationContext: initialization completed in 27695 ms
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
            //回调所有 SpringApplicationRunListener实现类 的starting方法
			listeners.started(context);
            // 从容器中获取所有ApplicationRunner,CommandLineRunner,并调用他们的run方法
			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;
	}

prepareContext

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
			SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
    	// 给容器设置环境(spring.profile.active)
		context.setEnvironment(environment);
		postProcessApplicationContext(context);
		applyInitializers(context);
		listeners.contextPrepared(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    	// 给容器注册一些组件
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof DefaultListableBeanFactory) {
			((DefaultListableBeanFactory) beanFactory)
					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		if (this.lazyInitialization) {
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}
		// Load the sources
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		load(context, sources.toArray(new Object[0]));
		listeners.contextLoaded(context);
	}

refreshContext

调用另一个refresh方法,将上下文对象传递进去

private void refreshContext(ConfigurableApplicationContext context) {
		refresh((ApplicationContext) context);
		if (this.registerShutdownHook) {
			try {
				context.registerShutdownHook();
			}
			catch (AccessControlException ex) {
				// Not allowed in some environments.
			}
		}
	}

调用容器的refresh方法

// 该方法是一个接口方法,最终会调用一个叫AbstractApplicationContext的refresh方法
protected void refresh(ConfigurableApplicationContext applicationContext) {
		applicationContext.refresh();
	}

AbstractApplicationContextrefresh方法:

@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
            // 从容器中获取当前应用环境,初始化Web容器环境
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
            // 为容器注册一些依赖
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
                // 为servletContext上下文对象注册web应用的域对象和环境
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
                // 最终会调用AbstractRefreshableWebApplicationContext的onRefresh方法
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}

自定义starter

starter:

​ 1、场景需要使用的依赖是什么?

​ 2、如何编写自动配置

@Configuration  // 指定是一个配置类
@ConditionOnxxx  // 载指定的条件成立的情况下配置生效
@AutoConfigureAfter // 指定自动配置类的顺序
@Bean  //给容器中添加组件
@ConfigurationProperties //结合线管的xxxproperties类来绑定 
@EnableConfigurationProperties // 让xxxProperties生效加入到容器中

自动配置类要能加载
将需要启动就加载的自动配置类,配置在META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

3、模式:

启动器只用来做依赖导入:

例如spring-boot-starter-web:

在这里插入图片描述

启动器依赖自动配置,打包成xxx-starter,使用时引入就行

编写自定义starter

创建两个模块,一个只引入必要的自定义依赖,一个用来编写自定义的核心代码

在这里插入图片描述

weleness-spring-boot-starter-autoconfigurer的pom文件:

因为这个模块是写核心代码的,所以需要引入springboot的自动配置包

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.github.anhTom2000</groupId>
    <artifactId>weleness-spring-boot-starter-autoconfigurer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>weleness-spring-boot-starter-autoconfigurer</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>11</java.version>
    </properties>

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

    </dependencies>



</project>

weleness-starter的pom文件:

这个模块是被别人当成自定义starter引入的,不需要写任何代码,只需要将编写核心功能代码的依赖引入进来

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.githun.starter</groupId>
    <artifactId>weleness</artifactId>
    <version>1.0-SNAPSHOT</version>
<!-- 引入自动配置模块-->
    <dependencies>
        <dependency>
            <groupId>com.github.anhTom2000</groupId>
            <artifactId>weleness-spring-boot-starter-autoconfigurer</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>

编写配置文件类

编写一个测试用的配置文件类,在配置文件中以weleness.hello进行配置

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "weleness.hello")
public class HelloProperties {

    private String prefix;

    private String sufffix;

    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public String getSufffix() {
        return sufffix;
    }

    public void setSufffix(String sufffix) {
        this.sufffix = sufffix;
    }
}

编写测试用服务

public class HelloService {
    HelloProperties helloProperties;

    public HelloProperties getHelloProperties() {
        return helloProperties;
    }

    public void setHelloProperties(HelloProperties helloProperties) {
        this.helloProperties = helloProperties;
    }

    public String sayHello(String name) {
        return helloProperties.getPrefix() + name + helloProperties.getSufffix();
    }
}

编写自动配置类

了解springboot自动配置原理的同学就不必我多说了,懂的人自然懂。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Description : TODO
 * @Author : Weleness
 * @Date : 2020/05/22
 */
@Configuration
@ConditionalOnWebApplication// web应用才生效
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {

    @Autowired
    HelloProperties helloProperties;

    @Bean
    public HelloService helloService(){
        HelloService helloService = new HelloService();
        helloService.setHelloProperties(helloProperties);
        return helloService;
    }
}

在类路径下编写Springboot的自动配置文件

在这里插入图片描述
这样我们自定义的starter就创建好了,现在将他们install到maven仓库中:

在这里插入图片描述

测试自定义starter

新建一个工程,引入刚刚install完毕的依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.github</groupId>
    <artifactId>weleness-test-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>weleness-test-starter</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--引入自定义starter-->
        <dependency>
            <groupId>com.githun.starter</groupId>
            <artifactId>weleness</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

编写controller测试

@RestController
public class HelloController {
    @Autowired
    HelloService helloService;

    @RequestMapping("/hello")
    public String hello(){
        return helloService.sayHello("weleness");
    }
}

编写配置文件

server.port=8889
weleness.hello.prefix=nihao
weleness.hello.sufffix=woshi

以上…

猜你喜欢

转载自blog.csdn.net/WXZCYQ/article/details/106291181
今日推荐