Spring 整合嵌入式 Tomcat 容器

本文介绍了如何在SpringBoot中集成Tomcat并自定义Servlet,通过Tomcat的ServletContainerInitializer接口实现Spring配置加载。详细讲解了从创建Tomcat实例到启动服务,再到注册Servlet和配置父子容器的过程,最后展示了Controller的实现及前端后端效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

导入依赖

description = "Spring Web MVC"

apply plugin: "kotlin"
dependencies {
    compile(project(":spring-webmvc"))
    compile(group: "org.apache.tomcat.embed",name: "tomcat-embed-core",version: "8.5.64")
    compile(group: "org.apache.tomcat",name: "tomcat-jasper",version: "9.0.50")
    compile group: 'javax.servlet', name: 'javax.servlet-api', version: '3.1.0'
}

编写 Tomcat 启动服务

/**
 * 启动这个 Tomcat 的时候注意版本,有的版本可能半天都跑不起来哦
 */
public class SpringApplication {

	public static void run() {
		try {
			Tomcat tomcat = new Tomcat();

			tomcat.setPort(8888);

			tomcat.setBaseDir("IdeaProjects/spring-framework/spring-test-boot/src/main");

			tomcat.addWebapp("/boot", "IdeaProjects/spring-framework/spring-test-boot/src/main");

			// 第一种方法:给 Tomcat 中注册一个 Servlet 和 ServletMapping
			// tomcat.addServlet("/boot", "myHttpServlet", new MyHttpServlet()).addMapping("/hello");

			// 第二种方法: 直接给利用 Tomcat SPI 机制,实现父子容器加载,这种方式直接可以写 Controller 了
			// 就不用上面这么麻烦了还有自己注册 Servlet 参考 QuickStartWebApplicationInitializer 这个类

			tomcat.start();

			tomcat.getServer().await();
		} catch (LifecycleException e) {
			e.printStackTrace();
		}
	}
}

实现 Servlet

可以将 MyHttpServlet 注册到 Tomcat 中,然后访问测试下看效果。

public class MyHttpServlet extends HttpServlet implements Serializable {
	/** use serialVersionUID from Spring 1.2 for interoperability. */
	private static final long serialVersionUID = 7099057708183571937L;

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		doPost(req,resp);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		System.out.println("doGet........");
		resp.getWriter().write("<h1>Hello Tomcat.....</h1>");
	}
}

通过 Tomcat 中的第一种方式就可以把 Servlet 注册到 Tomcat 中,然后进行访问,效果如下:

在这里插入图片描述

Config 配置

但是第一种注册 Servlet 太麻烦了,第二种方式通过 Tomcat SPI 机制加载配置类来启动 Spring IOC 容器。

// mvc_tag: 表示我不扫描标有 web 注解的组件
// 这个就是父容器配置类,父子容器就是这么来的
@ComponentScan(value = "com.gwm.springboot",excludeFilters = {
		@ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)
})
@Configuration
public class SpringConfig {

}


// mvc_tag: 表示只扫描 SpringMVC 组件,注意要禁用默认过滤行为
// 这个就是子容器配置类,父子容器就是这么来的
@ComponentScan(value = "com.gwm.springboot",includeFilters = {
		@ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)
}, useDefaultFilters = false)
@Configuration
public class SpringMVCConfig {

}

实现 AbstractAnnotationConfigDispatcherServletInitializer 接口

Tomcat SPI 机制中暴露了一个接口 ServletContainerInitializer,在 Tomcat 启动生命周期过程中会去加载加载该接口的所有实现类,并且还会解析 @HandlersTypes 注解,解析到的结果存放到一个 Set<Class<?>> c 集合中传给 onStartup() 方法,先看下 ServletContainerInitializer 代码如下:

public interface ServletContainerInitializer {
    void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}

其中 spring-web 模块就去实现了这个 ServletContainerInitializer 接口,如图所示:

在这里插入图片描述

然后进入到这个实现类如下:

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
	/**
	 * 此处的 onStartup() 方法会被 tomcat 服务启动调用
	 * 这个方法主要是为 Web 容器
	 * 1、添加 WebServlet 请求
	 * 2、添加 WebFilter 过滤器
	 * 3、添加 WebListener 监听器
	 * @Param  webAppInitializerClasses 表示加载所有被注解:@HandlesTypes(WebApplicationInitializer.class) 修饰的类装载进容器
	 * @Param servletContext Web 容器上下文
	 * @throws ServletException
	 */
	@Override
	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {
		// ...省略
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}
}

Tomcat 启动过程中会调用 onStartup() 方法,然后被注解 @HandlesType 修饰的子类都会被添加到 onStartup() 方法中的第一个参数 Set 集合中,进行挨个执行 onStartup() 方法。

AbstractAnnotationConfigDispatcherServletInitializer 类是 WebApplicationInitializer 的子类,所以最终 Tomcat 也会调用 onStartup() 方法执行里面的逻辑。

而且实现这个类对于父子容器理解非常清晰,代码如下:

public class QuickStartWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
	/**
	 * mvc_tag: 获取 Spring 配置文件 在 mvc_fun_call1 被回调
	 */
	@Override
	protected Class<?>[] getRootConfigClasses() {
		return new Class<?>[]{SpringConfig.class};
	}
	/**
	 * mvc_tag: 获取 SpringMVC 配置文件 在 mvc_fun_call2 被回调
	 */
	@Override
	protected Class<?>[] getServletConfigClasses() {
		return new Class<?>[]{SpringMVCConfig.class};
	}
	/**
	 * mvc_tag: 获取 SpringMVC 根路径 在 mvc_fun_call3 被回调
	 */
	@Override
	protected String[] getServletMappings() {
		return new String[]{"/"};
	}
}

写个 Controller 类测试即可,代码如下:


@RestController
public class HelloController {

	@Autowired
	private HelloService helloService;

	@RequestMapping("/hello")
	public String hello() {
		System.out.println("hellow -----------------");
		helloService.sayHello();
		return "hellow";
	}
}

@Service
public class HelloServiceImpl implements HelloService {
	@Override
	public void sayHello() {
		System.out.println("HelloServiceImpl execute....");
	}
}

前端效果如下:

在这里插入图片描述

后端效果如下:

在这里插入图片描述

推荐阅读文章

1、使用 Spring 框架构建 MVC 应用程序:初学者教程
2、有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误
3、如何理解应用 Java 多线程与并发编程?
4、Java Spring 中常用的 @PostConstruct 注解使用总结
5、线程 vs 虚拟线程:深入理解及区别
6、深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别
7、10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!
8、“打破重复代码的魔咒:使用 Function 接口在 Java 8 中实现优雅重构!”
9、Java 中消除 If-else 技巧总结
10、线程池的核心参数配置(仅供参考)
11、【人工智能】聊聊Transformer,深度学习的一股清流(13)
12、Java 枚举的几个常用技巧,你可以试着用用
13、如何理解线程安全这个概念?
14、理解 Java 桥接方法
15、Spring 整合嵌入式 Tomcat 容器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

魔道不误砍柴功

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值