重新学习嵌入式tomcat的心得体会
关于Tomcat
tomcat是目前较为流行的一款轻量级应用服务器,有独立运行版本,还有极简版本,我愿称之为内嵌式版本,因为他可以直接在java项目中导入依赖,通过java代码手动启动,目前最火的Springboot开发框架就是运用了极简版本
Tomcat极简版本启动
第一步 导入依赖
<!-- https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-core -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.80</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-core -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>9.0.80</version>
</dependency>
第二步 通过以下代码启动
public static void main(String[] args) {
// 启动端口 工作目录 访问路径
int port = 8080;
String baseDir = Thread.currentThread().getContextClassLoader().getResource("").getPath();
String contextPath = "/";
// 创建Tomcat实例
Tomcat tomcat = new Tomcat();
tomcat.setPort(port);
// -- 创建一个WebApp
Context tomcatContext = tomcat.addWebapp(contextPath, baseDir);
// -- 在这里可以为创建好的App添加servlet等
// Wrapper app = tomcat.addServlet(tomcatContext, "app", new DispatcherServlet(webApplicationContext));
// app.addMapping("/*");
// 启动Tomcat
tomcat.start();
tomcat.getConnector();
}
从这里可以看到,启动一个tomcat服务器可以添加多个webapp,添加webapp时需要规定该webapp的工作目录和访问路径
在之前非嵌入式tomcat的时候,添加servlet或其他app的配置需要在web.xml中进行相关配置,但在嵌入式tomcat中,可以直接使用Context的实例进行配置
关于ServletContainerInitializer
为了更方便的配置Servlet等webapp信息,tomcat提供了ServletContainerInitializer接口,开发者可以实现此接口用于初始化webapp配置信息
开发者可以将ServletContainerInitializer的实现类的全限定名称写入工程目录下的“/META-INF/services/javax.servlet.ServletContainerInitializer”文件,tomcat启动后会依次扫描工程目录以及其他jar包下的该文件,并调用其onStartup方法
@HandlesTypes(WebApplicationInitializer.class)
public class MyServletContainerInitializer implements ServletContainerInitializer {
// 在该方法中将DispatcherServlet注册进ServletContext中
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext();
webApplicationContext.scan("com.zmj.stu");
webApplicationContext.refresh();
// 创建DispatcherServlet实例,DispatcherServlet有很多组件需要注入,所以需要依赖WebApplicationContext。
// 目前笔者没有看到单独设置这些组件的方法。后续研究明白会继续补充
DispatcherServlet dispatcherServlet = new DispatcherServlet(webApplicationContext);
ServletRegistration.Dynamic app = ctx.addServlet("app", dispatcherServlet);
// DispatcherServlet将会处理所有请求
app.addMapping("/*");
}
}
在调用onStartup方法之前,tomcat会通过反射获取HandlesTypes中指定的类或其子类所有的class对象,并传入参数“c”中,Spring源码中有用到这个注解
Spring初始化接口
展开spring-web的工程目录,可以看到“/META-INF/services/javax.servlet.ServletContainerInitializer”文件下写着org.springframework.web.SpringServletContainerInitializer
,意味着Tomcat启动后会执行该类下的onStartup方法
SpringServletContainerInitializer代码详解
// 可以看出来tomcat调用onStartup之前会扫描WebApplicationInitializer的所有子类,然后将他们放入webAppInitializerClasses对象中作为参数传给onStartup方法
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = Collections.emptyList();
if (webAppInitializerClasses != null) {
initializers = new ArrayList<>(webAppInitializerClasses.size());
// 遍历webAppInitializerClasses,依次实例化并维护在initializers对象中
for (Class<?> waiClass : webAppInitializerClasses) {
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);
// 依次调用WebApplicationInitializer中的onStartup方法
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
总结出来就几步:
- WebApplicationInitializer的所有子类
- 依次初始化
- 排序
- 调用onStartup方法
可以看出来在SpringServletContainerInitializer中依次调用WebApplicationInitializer的onStartup方法时,将ServletContext实例作为参数传递过去,所以在spring-web工程中,可以创建一个WebApplicationInitializer实现类,在该类的onStartup方法中注入servlet等web组件
在做断点调试的时候,发现Spring自己也有一些WebApplicationInitializer实现类,待时间充足我再补充
排序方式我也没看明白,不过我认为这个在这里不是重点
最后
- 今天才下定决心以这样的方式记录自己的学习成果,之前只是打开源码乱看一通,发现那样学习效果不佳,很容易钻进死胡同,毕竟,对于整个java这棵大树,我知道的只是冰山一角,写完这篇博客后发现这样学习的好处甚多,一方面将自己的心得及时记录下来,待之后忘却后回过头来温习,另一方面,通过对博客的构思,能够更好的理清学习的重点,避免钻牛角尖
- 如果这篇博客有幸被你看到,这将是我的荣幸
- 如果文中有错误之处,烦请大家及时指正,我将第一时间更正
- 对于这样的学习方式,我会坚持下去的