【声明】文章为本人学习时记录的笔记。 原课程地址:www.liaoxuefeng.com/wiki/154595…
一、前言
我们的WebApp通过Maven打包成war包以后,会将应用的.class文件和资源统一打包起来。 格式为:
这里有个问题,tomcat在启动时,tomcat服务器的类均可以被JVM的AppClassLoader加载。而tomcat中WebApp中的类如何加载到jvm中?AppClassLoader肯定是不行了,因为tomcat服务器的classpath和WebApp的并不是同一个,所以此时需要我们自己来编写ClassLoader,从而将WebApp中的.class文件加载到jvm中来。
WebApp中的.class文件存在于WEB-INF下的classes目录(WebApp的业务代码编译产物)和lib目录下(WebApp项目依赖的jar文件)。
通过我们自己的classLoader将WebApp中的class(主要是Servlet的三大组件)加载到jvm中后,注册到ServletContext中,并读取Server的配置,启动Server。就能完成一个Web Server的功能了。
二、目标
初步完成一个mini的Web Server服务器。
1、编写ClassLoader,来加载WebApp的class
2、server信息配置化
3、Connector完善(启用线程池)
4、ServletContext完善(完成WebApp中的Servlet三大组件的注册)
5、Connector的handler完善(设置线程的上下文类加载器为我们自己的ClassLoader)
三、设计
Mini WebServer的系统架构图:
WebServer启动流程:
服务处理请求流程:
四、实现
1、实现我们自己的ClassLoader,用来加载WebApp的class
// 扫描WebApp的classPath
void scanClassPath0(Consumer<Resource> handler, Path basePath, Path path) {
try {
Files.list(path).sorted().forEach(p -> {
if (Files.isDirectory(p)) {
scanClassPath0(handler, basePath, p);
} else if (Files.isRegularFile(p)) {
Path subPath = basePath.relativize(p);
handler.accept(new Resource(p, subPath.toString().replace('\\', '/')));
}
});
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
// 扫描WebApp依赖的jar包
void scanJar0(Consumer<Resource> handler, Path jarPath) throws IOException {
JarFile jarFile = new JarFile(jarPath.toFile());
jarFile.stream().filter(entry -> !entry.isDirectory()).forEach(entry -> {
String name = entry.getName();
handler.accept(new Resource(jarPath, name));
});
}
2、创建Connector。初始化ServletContxt,设置Executor,启动web服务器。
public HttpConnector(Config config, String webRoot, Executor executor, ClassLoader classLoader, List<Class<?>> autoScannedClasses) throws IOException {
logger.info("starting http server at {}:{}...", config.server.host, config.server.port);
this.config = config;
this.classLoader = classLoader;
// init servlet context:
Thread.currentThread().setContextClassLoader(this.classLoader);
ServletContextImpl ctx = new ServletContextImpl(classLoader, config, webRoot);
ctx.initialize(autoScannedClasses);
this.servletContext = ctx;
Thread.currentThread().setContextClassLoader(null);
// start http server:
this.httpServer = HttpServer.create(new InetSocketAddress(config.server.host, config.server.port), config.server.backlog, "/", this);
this.httpServer.setExecutor(executor);
this.httpServer.start();
logger.info("http server started at {}:{}...", config.server.host, config.server.port);
}
3、ServletContext处理请求
public void process(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
String path = request.getRequestURI();
// 根据请求路径过滤目标Servlet
Servlet servlet = null;
for (ServletMapping mapping : this.servletMappings) {
if (mapping.matches(path)) {
servlet = mapping.servlet;
break;
}
}
if (servlet == null) {
// 没有匹配到 则404
PrintWriter pw = response.getWriter();
pw.write("<h1>404 Not Found</h1><p>No mapping for URL: " + path + "</p>");
pw.close();
return;
}
// 过滤filter,组建filterchain
List<Filter> enabledFilters = new ArrayList<>();
for (FilterMapping mapping : this.filterMappings) {
if (mapping.matches(path)) {
enabledFilters.add(mapping.filter);
}
}
Filter[] filters = enabledFilters.toArray(Filter[]::new);
logger.atDebug().log("process {} by filter {}, servlet {}", path, Arrays.toString(filters), servlet);
// filterChain不仅包含一组filter,还包含一个servlet,这样保证最后一个请求是servlet
FilterChain chain = new FilterChainImpl(filters, servlet);
try {
chain.doFilter(request, response);
} catch (ServletException e) {
logger.error(e.getMessage(), e);
throw new IOException(e);
} catch (IOException e) {
logger.error(e.getMessage(), e);
throw e;
}
}
4、filter执行完后,执行目标servlet
@Override
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (index < total) {
int current = index;
index++;
filters[current].doFilter(request, response, this);
} else {
servlet.service(request, response);
}
}