欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

springboot 使用外置servlet 容器

程序员文章站 2022-05-23 17:36:18
...

一、嵌入是和外置容器优缺点对比

嵌入式Servlet容器:应用打成可执行的jar

​优点:简单、便携;

​缺点:默认不支持JSP、优化定制比较复杂;

外置的Servlet容器:外面安装Tomcat - - - 应用war包的方式打包;

二、使用外置的Servlet容器步骤

(1)、使用Spring初始化向导创建warSpringBoot项目
springboot 使用外置servlet 容器
springboot 使用外置servlet 容器
springboot 使用外置servlet 容器
(2)在Project Structure窗口创建目录结构
springboot 使用外置servlet 容器
springboot 使用外置servlet 容器
springboot 使用外置servlet 容器
(3) 在运行窗口配置外置Tomcat
springboot 使用外置servlet 容器

springboot 使用外置servlet 容器
springboot 使用外置servlet 容器

(4) 将嵌入式的Tomcat指定为provided(创建想的时候其实就是了)

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>

(5) 必须编写一个SpringBootServletInitializer的子类,并调用configure方法

public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        //传入SpringBoot应用的主程序
        return application.sources(SpringbootwarApplication.class);
    }

}

(6) 修改 application.properties
application.properties

spring.mvc.view.prefix=/WEB-INF/
spring.mvc.view.suffix=.jsp

(7) 现在就可以愉快的写代码测试了
springboot 使用外置servlet 容器
index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>index</title>
</head>
<body>
    <h1>Hello JSP</h1>
    <a href="abc">abc</a>
</body>
</html>

success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h1>SUCCESS</h1>
    <h3>${msg}</h3>
</body>
</html>

HelloController

@Controller
public class HelloController {
    @GetMapping("/abc")
    public  String hello(Model model){
        model.addAttribute("msg","你好");
        return "success";
    }
}

启动项目测试:
springboot 使用外置servlet 容器
springboot 使用外置servlet 容器

三、外置Servlet容器启动原理

jar包启动步骤:
执行SpringBoot主类的main方法,启动ioc容器,创建嵌入式的Servlet容器;
war包启动步骤:
启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer】,启动ioc容器;

规则

  • ​1.服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面ServletContainerInitializer实例;
  • 2.ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,有一个名为javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名
  • 3.还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类;

流程:启动Servlet容器,再启动SpringBoot应用

1、首先我们启动tomcat
2、应用启动ServletContainerInitializer

Spring的web模块里面有这个文件: javax.servlet.ServletContainerInitializer
springboot 使用外置servlet 容器
内容为:

org.springframework.web.SpringServletContainerInitializer

3、为这些WebApplicationInitializer类型的类创建实例;

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
	//SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)
	//标注的这个类型的所有类都传入到onStartup方法的Set<Class<?>>集合里边
	@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 {
						//为这些WebApplicationInitializer类型的类创建实例;
						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) {
			//调用每个实例自己的onStartup方法
			initializer.onStartup(servletContext);
		}
	}

}

4.每一个WebApplicationInitializer都调用自己的onStartup;
springboot 使用外置servlet 容器
5.相当于我们的SpringBootServletInitializer的类会被创建对象,并执行onStartup方法

6.SpringBootServletInitializer实例执行onStartup的时候会createRootApplicationContext;创建容器

public abstract class SpringBootServletInitializer implements WebApplicationInitializer {
	@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());
		//createRootApplicationContext(servletContext) 这是创建容器的方法 
		WebApplicationContext rootAppContext = createRootApplicationContext(servletContext);
		if (rootAppContext != null) {
			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");
		}
	}

}

7.Spring的应用就启动并且创建IOC容器

	protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
		//1、创建SpringApplicationBuilder
		SpringApplicationBuilder builder = createSpringApplicationBuilder();
		builder.main(getClass());
		ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
		if (parent != null) {
			this.logger.info("Root context already created (using as parent).");
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
			builder.initializers(new ParentContextApplicationContextInitializer(parent));
		}
		
		builder.initializers(new ServletContextApplicationContextInitializer(servletContext));
		builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
		//调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入了进来
		builder = configure(builder);
		builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
		//使用builder创建了一个spring应用
		SpringApplication application = builder.build();
		if (application.getAllSources().isEmpty()
				&& MergedAnnotations.from(getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) {
			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));
		}
		//启动spring应用
		return run(application);
	}

7.spring的应用启动并且创建ioc容器

	public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			//刷新iooc容器
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			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;
	}