重新学习嵌入式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方法
spring-web工程目录

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);
		}
	}
}

总结出来就几步:

  1. WebApplicationInitializer的所有子类
  2. 依次初始化
  3. 排序
  4. 调用onStartup方法

可以看出来在SpringServletContainerInitializer中依次调用WebApplicationInitializer的onStartup方法时,将ServletContext实例作为参数传递过去,所以在spring-web工程中,可以创建一个WebApplicationInitializer实现类,在该类的onStartup方法中注入servlet等web组件

在做断点调试的时候,发现Spring自己也有一些WebApplicationInitializer实现类,待时间充足我再补充
排序方式我也没看明白,不过我认为这个在这里不是重点

最后

  1. 今天才下定决心以这样的方式记录自己的学习成果,之前只是打开源码乱看一通,发现那样学习效果不佳,很容易钻进死胡同,毕竟,对于整个java这棵大树,我知道的只是冰山一角,写完这篇博客后发现这样学习的好处甚多,一方面将自己的心得及时记录下来,待之后忘却后回过头来温习,另一方面,通过对博客的构思,能够更好的理清学习的重点,避免钻牛角尖
  2. 如果这篇博客有幸被你看到,这将是我的荣幸
  3. 如果文中有错误之处,烦请大家及时指正,我将第一时间更正
  4. 对于这样的学习方式,我会坚持下去的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值