基本结构
Tomcat 的基本结构:
Server └───Service ├───Connector (协议, 端口) └───Engine └───Host(虚拟主机 localhost) ├───Context1 (应用1, 可以设置虚拟路径, / 即 url 起始路径; 项目磁盘路径, 即 docBase ) │ │ index.html │ └───WEB-INF │ │ web.xml (servlet, filter, listener) 3.0 │ ├───classes (servlet, controller, service ...) │ ├───jsp │ └───lib (第三方 jar 包) └───Context2 (应用2) │ index.html └───WEB-INF web.xml
关于结构的说明可以看这个。
启动内嵌 Tomcat
用编程的方式启动 Tomcat 服务器:
Tomcat tomcat = new Tomcat();
// 创建临时目录作为 Tomcat 应用的目录
File dir = Files.createTempDirectory("tomcat.").toFile();
dir.deleteOnExit(); // 程序退出后自动删除
// 添加 context,虚拟路径为根路径/,本地文件路径为临时目录
tomcat.addContext("", dir.getAbsolutePath());
// 启动 Tomcat
tomcat.start();
// 设置连接,监听 8080 端口
Connector connector = new Connector(new Http11Nio2Protocol());
connector.setPort(8080);
tomcat.setConnector(connector);
tomcat.getServer().await();
addContext
的第一个参数是虚拟路径,即映射到 http 请求的路径,如果要映射到根路径,这里要使用空字符串。
Http11Nio2Protocol
表示用 nio2 实现的 http1.1 协议。
可以用编程的方式添加 Servlet 到 Tomcat:
Context context = tomcat.addContext("", dir.getAbsolutePath());
// 添加 Servlet 容器的初始化器
context.addServletContainerInitializer(new ServletContainerInitializer() {
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
ctx.addServlet("myServlet", MyServlet.class).addMapping("/hello");
}
}, Collections.emptySet());
Servlet 由 Servlet 容器(Container)管理,需要通过为上下文(Context) 添加 Servlet 容器初始化器(Container Initializer)的方式添加 Servlet,这样在之后的 Tomcat 启动时,就可以利用容器初始化器真正完成添加动作。需要注意的是,添加 Servlet 的时候,需要指定路径映射(addMapping)。
这里使用的 Servlet:
public static class MyServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
resp.getWriter().println("<h1>Hello World</h1>");
}
}
这个 Servlet 必须是 public 的,否则 Tomcat 无法创建。
Spring 容器整合
现在将 Spring 容器和内嵌 Tomcat 进行整合,这样就可以使用 Spring MVC 编写 Controller,而不是直接使用 Servlet。
先创建 Spring 容器:
// 创建 Spring 容器
AnnotationConfigWebApplicationContext springContext = new AnnotationConfigWebApplicationContext();
springContext.register(WebConfig.class);
springContext.refresh();
容器应当选择AnnotationConfigWebApplicationContext
,这是个不带内嵌 Tomcat 的 Web 容器。
这里使用的配置类:
MyController.class)
(static class WebConfig {
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
public RequestMappingHandlerAdapter handlerAdapter() {
RequestMappingHandlerAdapter requestMappingHandlerAdapter = new RequestMappingHandlerAdapter();
requestMappingHandlerAdapter.setMessageConverters(List.of(new MappingJackson2HttpMessageConverter()));
return requestMappingHandlerAdapter;
}
}
包含一个DispatcherServlet
以及重写的RequestMappingHandlerAdapter
。之所以要重写RequestMappingHandlerAdapter
,是因为AnnotationConfigWebApplicationContext
自带的RequestMappingHandlerAdapter
缺少 JSON 消息转换器,会在需要解析或返回 JSON 消息时报错。
包含的 Controller:
static class MyController {
"/hello2")
(
public Map<String, String> hello2() {
return Map.of("msg", "Hello World");
}
}
通过 Tomcat 的 Servlet 容器初始化器添加 DispatcherServlet:
// 添加 Servlet 容器的初始化器
context.addServletContainerInitializer(new ServletContainerInitializer() {
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
ctx.addServlet("myServlet", MyServlet.class).addMapping("/hello");
// 将 Spring 容器的 DispatcherServlet 添加到 Tomcat
DispatcherServlet dispatcherServlet = springContext.getBean(DispatcherServlet.class);
ctx.addServlet("dispatcherServlet", dispatcherServlet).addMapping("/");
}
}, Collections.emptySet());
现在所有匹配不到 Servlet 的请求都将由 Spring 的 DispatcherServlet 进行处理,即转发到 Controller。
启动 Tomcat 后请求 http://localhost:8080/hello2 就能看到效果。
如果 Spring 容器中有多个 DispatcherServlet,难道要编写多行添加 Servlet 的代码?Spring 对此进行了封装,提供一个注册类DispatcherServletRegistrationBean
,用于将 DispatcherServlet 注册到 Tomcat。
MyController.class)
(static class WebConfig {
DispatcherServletRegistrationBean dispatcherServletRegistrationBean(){
return new DispatcherServletRegistrationBean(dispatcherServlet(), "/");
}
// ...
}
注册:
// 添加 Servlet 容器的初始化器
context.addServletContainerInitializer(new ServletContainerInitializer() {
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
ctx.addServlet("myServlet", MyServlet.class).addMapping("/hello");
// 将 Spring 容器的 DispatcherServlet 添加到 Tomcat
for (DispatcherServletRegistrationBean registrationBean : springContext.getBeansOfType(DispatcherServletRegistrationBean.class).values()) {
registrationBean.onStartup(ctx);
}
}
}, Collections.emptySet());
本文的完整示例可以从获取。
The End.
文章评论