springboot是如何通过一个@EnableXXX实现自动配置 的,就是因为依赖里面集成了各种spring-boot-starter-xxx,只要满足各种依赖里的XXXAutoConfiguration自动配置类上面的@ConditionalOnClass(xx.class)或者其他xxxProperties条件,就启用自动配置类了。
1.为什么SpringBoot的jar可以直接运行
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
pom.xml中配置打包插件,这个插件会在jar包下的META-INF生成MANIFEST.MF文件(文件里有一个start-class指定启动类),也会打包所依赖的jar到fat jar包中,但是这样它就能做到启动main方法吗?其实不是,而是Main-Class中的自定义类加载器-启动类JarLauncher做到的,通过加载BOOT-INF/classes目录及BOOT-INF/lib目录下jar文件,找到start-class的main方法实现了fat jar的启动
如何验证或者调试?可以创建一个jar application debug,引入这个启动类的依赖,就能搜索到JarLauncher类了,在main方法中打断点并启动debug即可。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
</dependency>
2.SpringBoot是如何启动Spring容器
以前的spring是怎么启动的,用ApplicationContext构造器传入配置类或配置文件实例化一个spring容器,里面会运行一个最重要的refresh方法,他在方法中会调invokeBeanFactoryPostProcessors(beanFactory)读取各种注解Component等等 加载那些配置类读取到beanDefinitionMap,而SpringBoot是在SpringApplication.run中实例化一个spring容器,除了这个还会读取全局配置文件。
public static void main(String[] args) {
//spring
ApplicationContext applicationContext = new ClassPathXMLApplicationContext("xxx.pr");
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
//springboot Application是当前启动类
SpringApplication.run(Application.class, args);
}
// 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));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
1.获取启动类:根据启动类加载ioc容器
2.获取web应用类型(是webflux还是servlet)
3.spring.factories读取了对外扩展的ApplicationContextInitializer ,ApplicationListener 。 对外扩展, 对内解耦(比如全局配置文件、热部署插件)
4.根据main推算出所在的类
// run方法内部
public ConfigurableApplicationContext run(String... args) {
// 省略部分代码
//这里根据前面判断出的web应用类型,创建了servlet spring context
//AnnotationConfigServletWebServerApplicationContext
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 调用spring中的refresh
refreshContext(context);
afterRefresh(context, applicationArguments);
// 省略部分代码
return context;
}
3.SpringBoot如何启动内置Tomcat
在spring的refresh中的onrefresh方法里createWebServer中的getWebServer启动了内嵌tomcat(创建servlet容器注册DispatcherServlet),如果是外置Tomcat走的是else分支因为外部是有ServletContext的。getSelfInitializer里拿到了 DispatcherServlet(自动配置类DispatcherServletAutoConfiguration配置的,内部是ServletRegistrationBean 注册的servlet)和XXFilter.、
@Bean
public ServletRegistrationBean myServlet(){
// 声明一个servlet注册器Bean
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean();
// 设置相应的servlet
servletRegistrationBean.setServlet(new BeanServlet());
// 设置名字
servletRegistrationBean.setName("BeanServlet");
// 添加映射规则
servletRegistrationBean.addUrlMappings("/BeanServlet");
return servletRegistrationBean;
}
这里的factory是TomcatServletWebServerFactory,因为自动配置类ServletWebServerFactoryAutoConfiguration里只有静态内部类里的Tomcat生效了
所以是TomcatServletWebServerFactory.getWebServer,这样tomcat就启动了
4.外置Tomcat如何启动SpringBoot
设置当前maven项目的打包方式,让tomcat相关的依赖不参与打包部署 ,因为外置tomcat服务器已经有这些jar包
<!--打包方式 默认是jar-->
<packaging>war</packaging>
<!--让它不参与打包部署-->
<dependency>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
<scope>provided</scope>
</dependency>
// 当tomcat启动时就会调用configure方法, 从而在springboot启动类的基础启动springboot
// 什么时候调用?
public class TomcatStartSpringBoot extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(Application.class);
}
}
5.什么是SPI机制
SPI ,全称为 Service Provider Interface(服务提供者接口),是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。
大概: 当servlet容器启动时候 就会去META-INF/services 文件夹中找到javax.servlet.ServletContainerInitializer, 这个文件里面肯定绑定一个ServletContainerInitializer实现类. 当servlet容器启动时候就会去该文件中找到ServletContainerInitializer的实现类,从而创建它的实例调用onstartUp,这里面又依次调用WebApplicationInitializer 实现类的onstartUp
在spring-boot-web的META-INF/services的SPI文件中实现类是org.springframework.web.SpringServletContainerInitializer
其实这AbstractDispatcherServletInitializer,AbstractContextLoaderInitializer2个实现类就是帮我们创建了ContextLoaderListener 和DispatcherServlet
@HandlesTypes(WebApplicationInitializer.class),@HandlesTypes传入的类为ServletContainerInitializer感兴趣的,容器会自动在classpath中找到 WebApplicationInitializer 会传入到onStartup方法的webAppInitializerClasses中。Set<Class<?>> webAppInitializerClasses 这里面也包括之前定义的TomcatStartSpringBoot(因为之前继承的SpringBootServletInitializer 实现了WebApplicationInitializer )
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}