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帮助我们进行定制配置