SpringBoot应用默认以Jar包方式并且使用内置Servlet容器(默认Tomcat),该种方式虽然简单但是默认不支持JSP并且优化容器比较复杂。故而我们可以使用习惯的外置Tomcat方式并将项目打War包。
① 同样使用Spring Initializer方式创建项目
② 打包方式选择"war"
③ 选择添加的模块
④ 创建的项目图示
有三个地方需要注意:
ServletInitializer类如下:
public class ServletInitializer extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources(SpringbootwebprojectApplication.class); } }
pom文件如下:
4.0.0 com.web springbootwebproject0.0.1-SNAPSHOT war springbootwebproject Demo project for Spring Boot org.springframework.boot spring-boot-starter-parent2.0.3.RELEASE UTF-8 UTF-8 1.8 org.springframework.boot spring-boot-starter-weborg.springframework.boot spring-boot-starter-tomcatprovided org.springframework.boot spring-boot-starter-testtest org.springframework.boot spring-boot-maven-plugin
⑤ 补全项目结构
第一种方式,手动创建src/main/webapp, WEB/INF以及web.xml。
第二种方式,使用idea创建,步骤如下:
1.如下图所示,点击项目结构图标
2.创建src/main/webapp
3.创建web.xml
此时项目结构图如下:
① 点击"Edit Configurations…"添加Tomcat。
② 设置Tomcat、JDK和端口
③ 部署项目
④ 启动项目
此时如果webapp 下有index.html,index.jsp,则会默认访问index.html。
如果只有index.jsp,则会访问index.jsp;如果webapp下无index.html或index.jsp,则从静态资源文件夹寻找index.html;如果静态资源文件夹下找不到index.html且项目没有对"/"进行额外拦截处理,则将会返回默认错误页面。
index.html显示如下图:
① 首先看Servlet3.0中的规范
总结以下几点:
1)服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面 ServletContainerInitializer实例;
2)jar包的META-INF/services文件夹下,有一个名为 javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名;
如下图所示:
3)还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类;
4)容器启动过程中首先调用 ServletContainerInitializer 实例的onStartup方法。
ServletContainerInitializer 接口如下:
public interface ServletContainerInitializer { void onStartup(Set> c, ServletContext ctx) throws ServletException; }
② 步骤分析如下
第一步,Tomcat启动
第二步,根据Servlet3.0规范,找到 ServletContainerInitializer ,进行实例化
jar包路径:
org\springframework\spring-web.3.14.RELEASE\ spring-web-4.3.14.RELEASE.jar!\METAINF\services\ javax.servlet.ServletContainerInitializer:
Spring的web模块里面有这个文件:
org.springframework.web.SpringServletContainerInitializer
第三步,创建实例
SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型的类都传入到onStartup方法的Set集合,为这些WebApplicationInitializer类型的类创建实例并遍历调用其onStartup方法。
SpringServletContainerInitializer 源码如下(调用其onStartup方法):
//感兴趣的类为WebApplicationInitializer及其子类 @HandlesTypes(WebApplicationInitializer.class) public class SpringServletContainerInitializer implements ServletContainerInitializer { //先调用onStartup方法,会传入一系列webAppInitializerClasses @Override public void onStartup(@Nullable Set> webAppInitializerClasses, ServletContext servletContext) throws ServletException { List 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 { //实例化每个initializer并添加到initializers中 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); //依次调用initializer的onStartup方法。 for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); } }
如上所示,在 SpringServletContainerInitializer方法中又调用每一个initializer的onStartup方法。即先调用SpringServletContainerInitializer实例的onStartup方法,在onStartup()方法内部又遍历每一个WebApplicationInitializer类型的实例,调用其onStartup()方法。
WebApplicationInitializer(Web应用初始化器)是什么?
在Servlet 3.0+环境中提供的一个接口,以便编程式配置ServletContext而非传统的xml配置。该接口的实例被 SpringServletContainerInitializer自动检测(@HandlesTypes(WebApplicationInitializer.class)这种方式)。而SpringServletContainerInitializer是Servlet 3.0+容器自动引导的。通过WebApplicationInitializer,以往在xml中配置的DispatcherServlet、Filter等都可以通过代码注入。你可以不用直接实现WebApplicationInitializer,而选择继承AbstractDispatcherServletInitializer。
WebApplicationInitializer类型的类如下图:
可以看到,将会创建我们的 com.web.ServletInitializer(继承自SpringBootServletInitializer)实例,并调用onStartup方法。
第四步:我们的 SpringBootServletInitializer的实例(com.web.ServletInitializer)会被创建对象,并执行onStartup方法(com.web.ServletInitializer继承自SpringBootServletInitializer,故而会调用SpringBootServletInitializer的onStartup方法)
SpringBootServletInitializer源码如下:
@Override public void onStartup(ServletContext servletContext) throws ServletException { // Logger initialization is deferred in case an ordered // LogServletContextInitializer is being used this.logger = LogFactory.getLog(getClass()); //创建WebApplicationContext WebApplicationContext rootAppContext = createRootApplicationContext( servletContext); if (rootAppContext != null) { //如果根容器不为null,则添加监听--注意这里的ContextLoaderListener, //contextInitialized方法为空,因为默认application context已经被初始化 servletContext.addListener(new ContextLoaderListener(rootAppContext) { @Override public void contextInitialized(ServletContextEvent event) { // no-op because the application context is already initialized } }); } else { this.logger.debug("No ContextLoaderListener registered, as " + "createRootApplicationContext() did not " + "return an application context"); } }
可以看到做了两件事:创建RootAppContext 和为容器添加监听。
创建WebApplicationContext 源码如下:
protected WebApplicationContext createRootApplicationContext( ServletContext servletContext) { //创建SpringApplicationBuilder --这一步很关键 SpringApplicationBuilder builder = createSpringApplicationBuilder(); //设置应用主启动类--本文这里为com.web.ServletInitializer builder.main(getClass()); */从servletContext中获取servletContext.getAttribute( WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)作为parent。第一次获取肯定为null */ ApplicationContext parent = getExistingRootWebApplicationContext(servletContext); if (parent != null) { this.logger.info("Root context already created (using as parent)."); servletContext.setAttribute( //以将ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE重置为null WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null); //注册一个新的ParentContextApplicationContextInitializer--包含parent builder.initializers(new ParentContextApplicationContextInitializer(parent)); } //注册ServletContextApplicationContextInitializer--包含servletContext builder.initializers( new ServletContextApplicationContextInitializer(servletContext)); //设置applicationContextClass为AnnotationConfigServletWebServerApplicationContext builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class); builder = configure(builder); //添加监听器 builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext)); //返回一个准备好的SpringApplication ,准备run-很关键 SpringApplication application = builder.build(); if (application.getAllSources().isEmpty() && AnnotationUtils .findAnnotation(getClass(), Configuration.class) != null) { application.addPrimarySources(Collections.singleton(getClass())); } Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the " + "configure method or add an @Configuration annotation"); // Ensure error pages are registered if (this.registerErrorPageFilter) { application.addPrimarySources( Collections.singleton(ErrorPageFilterConfiguration.class)); } //启动应用 return run(application); }
① createRootApplicationContext().createSpringApplicationBuilder()
跟踪代码到:
public SpringApplicationBuilder(Class>... sources) { this.application = createSpringApplication(sources); }
此时的Sources为空,继续跟踪代码:
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应用类型--Servlet this.webApplicationType = deduceWebApplicationType(); 获取ApplicationContextInitializer,也是在这里开始首次加载spring.factories文件 setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); 这里是第二次加载spring.factories文件 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); }
ApplicationContextInitializer是spring组件spring-context组件中的一个接口,主要是spring ioc容器刷新之前的一个回调接口,用于处于自定义逻辑。
ApplicationContextInitializer(应用上下文初始化器)是什么?
在 ConfigurableApplicationContext-Spring IOC容器称为“已经被刷新”状态前的一个回调接口去初始化ConfigurableApplicationContext。通常用于需要对应用程序上下文进行某些编程初始化的Web应用程序中。例如,与ConfigurableApplicationContext#getEnvironment() 对比,注册property sources或激活配置文件。另外ApplicationContextInitializer(和子类)相关处理器实例被鼓励使用去检测org.springframework.core.Ordered接口是否被实现或是否存在org.springframework.core.annotation.Order注解,如果存在,则在调用之前对实例进行相应排序。
spring.factories文件中的实现类:
# PropertySource Loaders org.springframework.boot.env.PropertySourceLoader=\ org.springframework.boot.env.PropertiesPropertySourceLoader,\ org.springframework.boot.env.YamlPropertySourceLoader # Run Listeners org.springframework.boot.SpringApplicationRunListener=\ org.springframework.boot.context.event.EventPublishingRunListener # Error Reporters org.springframework.boot.SpringBootExceptionReporter=\ org.springframework.boot.diagnostics.FailureAnalyzers # Application Context Initializers org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\ org.springframework.boot.context.ContextIdApplicationContextInitializer,\ org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\ org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer # Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.ClearCachesApplicationListener,\ org.springframework.boot.builder.ParentContextCloserApplicationListener,\ org.springframework.boot.context.FileEncodingApplicationListener,\ org.springframework.boot.context.config.AnsiOutputApplicationListener,\ org.springframework.boot.context.config.ConfigFileApplicationListener,\ org.springframework.boot.context.config.DelegatingApplicationListener,\ org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\ org.springframework.boot.context.logging.LoggingApplicationListener,\ org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener # Environment Post Processors org.springframework.boot.env.EnvironmentPostProcessor=\ org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\ org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\ org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor # Failure Analyzers org.springframework.boot.diagnostics.FailureAnalyzer=\ org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer # FailureAnalysisReporters org.springframework.boot.diagnostics.FailureAnalysisReporter=\ org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter
设置WebApplicationType
private WebApplicationType deduceWebApplicationType() { if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null) && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) { return WebApplicationType.REACTIVE; } for (String className : WEB_ENVIRONMENT_CLASSES) { if (!ClassUtils.isPresent(className, null)) { return WebApplicationType.NONE; } } return WebApplicationType.SERVLET; }
这里主要是通过判断REACTIVE相关的字节码是否存在,如果不存在,则web环境即为SERVLET类型。这里设置好web环境类型,在后面会根据类型初始化对应环境。
设置Initializer– ApplicationContextInitializer类型
privateCollection getSpringFactoriesInstances(Class type, Class>[] parameterTypes, Object... args) { //线程上下文类加载器 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Use names and ensure unique to protect against duplicates Set names = new LinkedHashSet<>( SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
这里ClassLoader 获取的是线程上下文类加载器,这里使用的是Tomcat启动:
Set
获取了6个instance:
设置监听–ApplicationListener类型
此时的type为ApplicationListener,Set
至此SpringApplicationBuilder创建完毕。
② 添加 ServletContextApplicationContextInitializer
builder.initializers( new ServletContextApplicationContextInitializer(servletContext));
此时SpringApplication Initializers和Listener如下:
③ 设置 application.setApplicationContextClass
④ builder = configure(builder);
此时调用我们的ServletInitializer的configure方法:
⑤ SpringApplication application = builder.build()创建应用
把我们的主类添加到application 中:
⑥ 将 ErrorPageFilterConfiguration添加到Set
接下来该run(application)了注意直到此时,我们让没有创建我们想要的容器,容器将会在run(application)中创建。
SpringApplication.run源码如下所示:
/** run Spring application,创建并刷新一个新的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; //异常报告集合 CollectionexceptionReporters = new ArrayList<>(); //java.awt.headless是J2SE的一种模式用于在缺少显示屏、键盘或者鼠标时的系统配置,很 //多监控工具如jconsole 需要将该值设置为true,系统变量默认为true configureHeadlessProperty(); //第一步:获取并启动监听器 SpringApplicationRunListener只有一个实现类EventPublishingRunListener, //EventPublishingRunListener有一个SimpleApplicationEventMulticaster //SimpleApplicationEventMulticaster有一个defaultRetriver //defaultRetriver有个属性为applicationListeners //每一次listeners.XXX()方法调用,都将会广播对应事件给applicationListeners监听器处理 SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting();//run方法第一次被调用时,调用listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); //第二步:构造容器环境 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); //设置需要忽略的bean configureIgnoreBeanInfo(environment); //打印banner Banner printedBanner = printBanner(environment); //第三步:创建容器 context = createApplicationContext(); //第四步:实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误 exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); //第五步:准备容器 prepareContext(context, environment, listeners, applicationArguments, printedBanner); //第六步:刷新容器 refreshContext(context); //第七步:刷新容器后的扩展接口 afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } //容器已经被刷新,但是CommandLineRunners和ApplicationRunners还没有被调用 listeners.started(context); //调用CommandLineRunner和ApplicationRunner的run方法 callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { //在run结束前,且调用CommandLineRunner和ApplicationRunner的run方法后,调用 listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; }
① 获取监听器getRunListeners
SpringApplicationRunListeners listeners = getRunListeners(args);
跟进 SpringApplication.getRunListeners方法(返回SpringApplicationRunListeners):
private SpringApplicationRunListeners getRunListeners(String[] args) { Class>[] types = new Class>[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances( SpringApplicationRunListener.class, types, this, args)); }
上面可以看到,args本身默认为空,但是在获取监听器的方法中, getSpringFactoriesInstances( SpringApplicationRunListener.class, types, this, args)将当前对象作为参数,该方法用来获取spring.factories对应的监听器:
privateCollection getSpringFactoriesInstances(Class type, Class>[] parameterTypes, Object... args) { //获取类加载器 WebappClassLoader ClassLoader classLoader = getClassLoader(); // Use names and ensure unique to protect against duplicates //根据类加载器,获取SpringApplicationRunListener(type)相关的监听器 Set names = new LinkedHashSet<>( SpringFactoriesLoader.loadFactoryNames(type, classLoader)); //创建factories List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
Set
整个 springBoot 框架中获取factories的方式统一如下:
@SuppressWarnings("unchecked") privateList createSpringFactoriesInstances(Class type, Class>[] parameterTypes, ClassLoader classLoader, Object[] args, Set names) { List instances = new ArrayList<>(names.size()); for (String name : names) { try { // //装载class文件到内存 Class> instanceClass = ClassUtils.forName(name, classLoader); Assert.isAssignable(type, instanceClass); Constructor> constructor = instanceClass .getDeclaredConstructor(parameterTypes); //主要通过反射创建实例 T instance = (T) BeanUtils.instantiateClass(constructor, args); instances.add(instance); } catch (Throwable ex) { throw new IllegalArgumentException( "Cannot instantiate " + type + " : " + name, ex); } } return instances; }
上面通过反射获取实例时会触发 EventPublishingRunListener的构造函数。如下图所示将会把application的listener添加到SimpleApplicationEventMulticaster initialMulticaster的ListenerRetriever defaultRetriever的Set> applicationListeners中:
重点来看一下addApplicationListener方法:
public void addApplicationListener(ApplicationListener> listener) { synchronized (this.retrievalMutex) { // Explicitly remove target for a proxy, if registered already, // in order to avoid double invocations of the same listener. Object singletonTarget = AopProxyUtils.getSingletonTarget(listener); if (singletonTarget instanceof ApplicationListener) { this.defaultRetriever.applicationListeners.remove(singletonTarget); } this.defaultRetriever.applicationListeners.add(listener); this.retrieverCache.clear(); } }
上述方法定义在 SimpleApplicationEventMulticaster父类AbstractApplicationEventMulticaster中。关键代码为this.defaultRetriever.applicationListeners.add(listener);,这是一个内部类,用来保存所有的监听器。也就是在这一步,将spring.factories中的监听器传递到SimpleApplicationEventMulticaster中。
继承关系如下:
② listeners.starting()– SpringApplicationRunListener启动–监听器第一次处理事件
listeners.starting();,获取的监听器为 EventPublishingRunListener,从名字可以看出是启动事件发布监听器,主要用来发布启动事件。
SpringApplicationRunListener是run()方法的监听器,其只有一个实现类EventPublishingRunListener。SpringApplicationRunListeners是SpringApplicationRunListener的集合类。
也就是说将会调用 EventPublishingRunListener的starting()方法。
public void starting() { //关键代码,这里是创建application启动事件`ApplicationStartingEvent` this.initialMulticaster.multicastEvent( new ApplicationStartingEvent(this.application, this.args)); }
EventPublishingRunListener这个是springBoot框架中最早执行的监听器,在该监听器执行started()方法时,会继续发布事件,也就是事件传递。这种实现主要还是基于spring的事件机制。
继续跟进 SimpleApplicationEventMulticaster,有个核心方法:
@Override public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) { ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); for (final ApplicationListener> listener : getApplicationListeners(event, type)) { // //获取线程池,如果为空则同步处理。这里线程池为空,还未没初始化。 Executor executor = getTaskExecutor(); if (executor != null) { 异步发送事件 executor.execute(() -> invokeListener(listener, event)); } else { // //同步发送事件 invokeListener(listener, event); } } }
ApplicationListener
其中getApplicationListeners(event, type)主要有四种listener:
这是springBoot启动过程中,第一处根据类型,执行监听器的地方。根据发布的事件类型从上述10种监听器中选择对应的监听器进行事件发布,当然如果继承了 springCloud或者别的框架,就不止10个了。这里选了一个 springBoot 的日志监听器来进行讲解,核心代码如下:
@Override public void onApplicationEvent(ApplicationEvent event) { //在springboot启动的时候 if (event instanceof ApplicationStartedEvent) { onApplicationStartedEvent((ApplicationStartedEvent) event); } //springboot的Environment环境准备完成的时候 else if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent( (ApplicationEnvironmentPreparedEvent) event); } //在springboot容器的环境设置完成以后 else if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent((ApplicationPreparedEvent) event); } //容器关闭的时候 else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event) .getApplicationContext().getParent() == null) { onContextClosedEvent(); } //容器启动失败的时候 else if (event instanceof ApplicationFailedEvent) { onApplicationFailedEvent(); } }
因为我们的事件类型为ApplicationEvent,所以会执行onApplicationStartedEvent((ApplicationStartedEvent) event);。springBoot会在运行过程中的不同阶段,发送各种事件,来执行对应监听器的对应方法。