Spring boot traditional deployment

Using spring boot is very convenient, a jar package can be started, because it has embedded servers such as tomcat.

But spring boot also provides a way to deploy to a standalone server.

If you look at the documentation, converting from jar to war package is very simple, and the configuration modification of pom.xml is omitted.

Just look at the modification of the source, it is very simple, as long as a configuration class inherits from SpringBootServletInitializer and overrides the configure method.

@SpringBootApplication
public class TestApplication extends SpringBootServletInitializer{

	
	@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
		return builder.sources(TestApplication .class);
	}

	public static void main(String[] args) {
		SpringApplication.run(TestApplication.class, args);
	}
}

 Yes, you read that right, it's that simple.

However, I think anyone who is a little curious will not be willing to use it like this, and will always wonder why it works like this?

Then we will find out according to the calling relationship.

SpringBootServletInitializer.configure

<-createRootApplicationContext

<-onStartup

<-SpringServletContainerInitializer.onStartup

 

SpringServletContainerInitializer is a special class that implements interface ServletContainerInitializer. The onStartup method of this class is called by tomcat.

So how did tomcat find it? Is the search for this resource file META-INF/services/javax.servlet.ServletContainerInitializer

In the spring package spring-web-xxxx.jar, there is just this file, and it is precisely this class that it registers.

 

wrote
org.springframework.web.SpringServletContainerInitializer

 

This class has an annotation @HandlesTypes(WebApplicationInitializer.class).

When calling the SpringServletContainerInitializer.onStartup method, all WebApplicationInitializer classes and subclasses will be passed.

Then filter by condition.

if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
					try {
						initializers.add((WebApplicationInitializer) waiClass.newInstance());
					}
					catch (Throwable ex) {
						throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
					}
				}

 That is, as long as it is a non-interface, non-abstract class, and is a subclass of WebApplicationInitializer, it will be instantiated and eventually called.

Then, in the createRootApplicationContext method of SpringBootServletInitializer, SpringApplication will be initialized finally, and its run method will be called, which is the same as the main method of directly running the entry.

 

 Since the logic of the SpringApplication.run method is the same, where is the branch that needs to start the embedded web server?

Go down this line.

SpringApplication.run(String...)
	SpringApplication.createAndRefreshContext(SpringApplicationRunListeners, ApplicationArguments)
		SpringApplication.refresh(ApplicationContext)
			AnnotationConfigEmbeddedWebApplicationContext(EmbeddedWebApplicationContext).refresh()
				AnnotationConfigEmbeddedWebApplicationContext(AbstractApplicationContext).refresh()
					AnnotationConfigEmbeddedWebApplicationContext(EmbeddedWebApplicationContext).onRefresh()
						AnnotationConfigEmbeddedWebApplicationContext(EmbeddedWebApplicationContext).createEmbeddedServletContainer()

 There is the following branch code

		if (localContainer == null && localServletContext == null) {
			EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
			this.embeddedServletContainer = containerFactory
					.getEmbeddedServletContainer(getSelfInitializer());
		}

 localContainer在初始化的时候没有赋值过程,一直会是null,主要是localServletContext,看看什么时候为null,什么时候有值。

它的赋值有三个地方,两个构造函数,一个set方法

	public GenericWebApplicationContext(ServletContext servletContext) {
		this.servletContext = servletContext;
	}
	public GenericWebApplicationContext(DefaultListableBeanFactory beanFactory, ServletContext servletContext) {
		super(beanFactory);
		this.servletContext = servletContext;
	}
	public void setServletContext(ServletContext servletContext) {
		this.servletContext = servletContext;
	}

 查找一下,发现构造函数并没有地方调用,调用的是这个set方法,过程如下

SpringApplication.run(String...)
	SpringApplication.createAndRefreshContext(SpringApplicationRunListeners, ApplicationArguments)
		SpringApplication.applyInitializers(ConfigurableApplicationContext)
			ServletContextApplicationContextInitializer.initialize(ConfigurableApplicationContext)
				ServletContextApplicationContextInitializer.initialize(ConfigurableWebApplicationContext)
					AnnotationConfigEmbeddedWebApplicationContext(GenericWebApplicationContext).setServletContext(ServletContext)

 你会发现,至少到SpringApplication.applyInitializers(ConfigurableApplicationContext)这一步,部署不部署到tomcat,都会执行这个方法的,那么区别在哪儿呢?

先看看这个方法的内容

	protected void applyInitializers(ConfigurableApplicationContext context) {
		for (ApplicationContextInitializer initializer : getInitializers()) {
			Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
					initializer.getClass(), ApplicationContextInitializer.class);
			Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
			initializer.initialize(context);
		}
	}

 也就是说,如果注入的initializers里是否包含了ServletContextApplicationContextInitializer,就能决定是否会调用以后的逻辑。

那么返回到文章的开头,看看抽象类SpringBootServletInitializer,就会发现在方法createRootApplicationContext里,类ServletContextApplicationContextInitializer的注入过程。

builder.initializers(new ServletContextApplicationContextInitializer(servletContext));

 内部实现就是

	public SpringApplicationBuilder initializers(
			ApplicationContextInitializer<?>... initializers) {
		this.application.addInitializers(initializers);
		return this;
	}

 

至于spring的御用servlet——DispatcherServlet,则是通过动态添加方式添加到ServletContext里的。类EmbeddedWebApplicationContext

	private void selfInitialize(ServletContext servletContext) throws ServletException {
	--------省略------------
		for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
			beans.onStartup(servletContext);
		}
	}

 类ServletRegistrationBean

	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		Assert.notNull(this.servlet, "Servlet must not be null");
		String name = getServletName();
		if (!isEnabled()) {
			logger.info("Servlet " + name + " was not registered (disabled)");
			return;
		}
		logger.info("Mapping servlet: '" + name + "' to " + this.urlMappings);
		Dynamic added = servletContext.addServlet(name, this.servlet);
		if (added == null) {
			logger.info("Servlet " + name + " was not registered "
					+ "(possibly already registered?)");
			return;
		}
		configure(added);
	}

 

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326928747&siteId=291194637