文章目录
导入依赖
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 容器