依赖环境
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- tomcat依赖 -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>8.5.32</version>
</dependency>
<!-- spring 环境 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<!-- springMVC依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
</dependencies>
Servlet3.0规范
实现了Servlet3.0规范的容器在启动时,会通过SPI扩展机制自动扫描所有jar包中包含META-INF/services/javax.servlet.ServletContainerInitializer的文件(该文件指定了ServletContainerInitializer接口的实现),实例化该类,并回调类中的onStartup方法。
实现过程
1.创建AppConfig
@Configuration
@ComponentScan(basePackages = {"com.liang.boot"})
public class AppConfig {
}
这个类有两个注解。
@Configuation:告诉Spring是一个配置类,需要加载到IOC容器
@ComponentScan:配置包扫描路径,spring会扫描其传入的包及其子包的class文件
2.创建MyWebApplicationServletInitializer
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// 初始化Sring 应用上下文环境
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
// 配置Appconfig,也就是设置包扫描路径,用于ApplicationContext的初始化
ac.register(AppConfig.class);
// spring整合tomcat和springmvc
ac.refresh();
DispatcherServlet servlet = new DispatcherServlet(ac);
ServletRegistration.Dynamic app = servletContext.addServlet("app", servlet);
// loadOnStartup > 0:tomcat容器启动时就加载 DispatcherServlet
app.setLoadOnStartup(1);
// 设置访问路径前缀
app.addMapping("/app/*");
}
}
3.创建MySpringServletContainerInitializer
public class MySpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
if (set != null) {
for (Class<?> waiClass : set) {
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()) {
return;
}
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
MySpringServletContainerInitializer 实现了ServletContainerInitializer接口。实现了ServletContainerInitializer接口意味着在tomcat启动时,MySpringServletContainerInitializer会被ServletContext扫描到并调用他的onStartup()方法。从来执行MyWebApplicationInitializer中的onstartUp()方法,完成ApplicationContext的创建以及DispatcherServlet的创建。
4.创建javax.servlet.ServletContainerInitializer文件
这就我们在Servlet3.0规范提到的tomcat的SPI机制
该文件需指定在resources/META-INF/services下。千万不要写错!!!
完整的路径:resources/META-INF/services/javax.servlet.ServletContainerInitializer
5.创建SpringApplication类,编写run方法。
/**
* <h1>SpringApplication就只运行tomcat容器</h1>
*/
public class SpringApplication {
//tomcat端口号为:8080
private int port = 8080;
private Class<?> primary;
private String[] args;
public SpringApplication(Class<?> primary, String... args) {
this.primary = primary;
this.args = args;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public static void run(Class<?> primary, String... args) {
new SpringApplication(primary,args).run();
}
public void run() {
Tomcat tomcat = new Tomcat();
tomcat.setPort(port);
try {
File docBase = createTempDir("tomcat");
//自动配置
tomcat.addWebapp("/", docBase.getAbsolutePath());
tomcat.start();
tomcat.getServer().await();
}catch (Exception e) {
e.printStackTrace();
}
}
// 创建临时目录
protected final File createTempDir(String prefix) {
try {
File tempDir = File.createTempFile(prefix + ".", "." + getPort());
tempDir.delete();
tempDir.mkdir();
tempDir.deleteOnExit();
return tempDir;
}
catch (IOException ex) {
throw new RuntimeException(
"Unable to create tempDir. java.io.tmpdir is set to " + System.getProperty("java.io.tmpdir"), ex);
}
}
}
SpringApplication就只有一个职责,创建tomcat并完成启动。
6.创建主启动类MyRunBoot
public class MyRunBoot {
public static void main(String[] args) {
SpringApplication.run(MyRunBoot.class, args);
}
}
主启动类调用SpringApplication的run方法,从而完成tomcat的启动。
7.编写测试Controller
@RestController
public class HelloController {
@RequestMapping("/test")
public String test() {
return "hello";
}
}
8.测试
页面输入 http://localhost:8080/app/test
返回hello,测试完成。
小结
- 参考SpringBoot项目,SpringApplication的实例化放到SpringApplication类里面。
- tomcat还需要配置端口以及在项目路径类创建tomcat的工作目录,以及映射的自动配置工作。
总结
一、SpringServletContainerInitializer
在Servlet容器启动的时候,会自动探测WebApplicationInitializer的实现类,并把他交给SpringServletContainerInitializer的onStartup()方法的第一个参数,从而执行ServletContainerInitializer的onStartup()方法完成Spring的流程。
二、思考
为什么有了WebApplicationServletContext,springboot在注册DispatcherServlet的时候使用的是ServletContextInitializer?
1.1 WebApplicationInitializer是spring提供的API,它的生命周期受第三方Servlet容器控制。(在Servlet容器启动时回调)。
1.2 和WebApplicationInitializer不同的地方是:这些ServletContextInitializer实例不会被SpringServletContainerInitializer检测,因此不会随着Servlet容器启动,而是在ServletContextInitializer#onStartup接口调用的地方执行初始化
ServletContextInitializer是springboot基于嵌入式容器,提供的API,其生命周期是springboot自身控制的。
上面这个问题参考了下面这个博主的文章。
ServletContextInitializer和WebApplicationInitializer的区别
如有错误,请各位大佬指正。 谢谢!!!!!!!!