实现WebMVC
对于一个Web应用程序来说,启动时,应用程序本身只是一个war包,并没有main()方法,因此,启动时执行的是Server的main()方法。以Tomcat服务器为例:
- 启动服务器,即执行Tomcat的
main()方法; - Tomcat根据配置或自动检测到一个
xyz.war包后,为这个xyz.war应用程序创建Servlet容器; - Tomcat继续查找
xyz.war定义的Servlet、Filter和Listener组件,按顺序实例化每个组件(Listener最先被实例化,然后是Filter,最后是Servlet); - 用户发送HTTP请求,Tomcat收到请求后,转发给Servlet容器,容器根据应用程序定义的映射,把请求发送个若干Filter和一个Servlet处理;
- 处理期间产生的事件则由Servlet容器自动调用Listener。
详细流程:Tomcat启动,创建ServletContext,解析web.xml文件,找到ServletContextListener,调用里面的方法
csharp
public interface ServletContextListener {
// Tomcat会在Web应用启动时自动调用
void contextInitialized(ServletContextEvent sce);
// Tomcat会在Web应用关闭时自动调用
void contextDestroyed(ServletContextEvent sce);
}
在contextInitialized(ServletContextEvent sce);中向WebMvcConfiguration中注入servletContext,创建ApplicationContext(创建PropertyResolver),创建IOC容器,并注册拦截器和前端控制器(通过WebUtils实现具体逻辑)
ini
public class ContextLoaderListener implements ServletContextListener {
final Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void contextInitialized(ServletContextEvent sce) {
logger.info("init {}.", getClass().getName());
var servletContext = sce.getServletContext();
WebMvcConfiguration.setServletContext(servletContext);
var propertyResolver = WebUtils.createPropertyResolver();
String encoding = propertyResolver.getProperty("${summer.web.character-encoding:UTF-8}");
servletContext.setRequestCharacterEncoding(encoding);
servletContext.setResponseCharacterEncoding(encoding);
//扫描配置类,创建ApplicationContext,创建IOC容器
var applicationContext = createApplicationContext(servletContext.getInitParameter("configuration"), propertyResolver);
// register filters:
WebUtils.registerFilters(servletContext);
// register DispatcherServlet:
WebUtils.registerDispatcherServlet(servletContext, propertyResolver);
servletContext.setAttribute("applicationContext", applicationContext);
}
//创建 ApplicationContext ApplicationContext createApplicationContext(String configClassName, PropertyResolver propertyResolver) {
logger.info("init ApplicationContext by configuration: {}", configClassName);
if (configClassName == null || configClassName.isEmpty()) {
throw new NestedRuntimeException("Cannot init ApplicationContext for missing init param name: configuration");
}
Class<?> configClass;
try {
configClass = Class.forName(configClassName);
} catch (ClassNotFoundException e) {
throw new NestedRuntimeException("Could not load class from init param 'configuration': " + configClassName);
}
return new AnnotationConfigApplicationContext(configClass, propertyResolver);
}
}
前端控制器详细方法调用链
阶段1: 框架初始化 (init() 方法调用链)
text
- DispatcherServlet.init() ↓
- applicationContext.findBeanDefinitions(Object.class) ↓
- 遍历每个Bean定义: ↓
- beanClass.getAnnotation(Controller.class) / RestController.class ↓
- addController(false/true, beanName, beanInstance) ↓
- addMethods(isRest, name, instance, instance.getClass()) ↓
- 遍历类的所有方法: ↓
- method.getAnnotation(GetMapping.class) / PostMapping.class ↓
- checkMethod(method) ↓
- new Dispatcher("GET/POST", isRest, instance, method, urlPattern) ↓
- Dispatcher构造函数:
- 设置isRest, isResponseBody, isVoid
- PathUtils.compile(urlPattern) - 编译URL正则
- 解析方法参数: new Param[...] ↓
- Param构造函数:
- 解析参数注解: @PathVariable, @RequestParam, @RequestBody
- 确定ParamType和参数名称
scala
@Override
public void init() throws ServletException {
logger.info("init {}.", getClass().getName());
// scan @Controller and @RestController:
for (var def : ((ConfigurableApplicationContext) this.applicationContext).findBeanDefinitions(Object.class)) {
Class<?> beanClass = def.getBeanClass();
Object bean = def.getRequiredInstance();
Controller controller = beanClass.getAnnotation(Controller.class);
RestController restController = beanClass.getAnnotation(RestController.class);
if (controller != null && restController != null) {
throw new ServletException("Found @Controller and @RestController on class: " + beanClass.getName());
}
if (controller != null) {
addController(false, def.getName(), bean);
}
if (restController != null) {
addController(true, def.getName(), bean);
}
}
}
阶段2: 请求处理 (doGet() 方法调用链)
text
- DispatcherServlet.doGet(HttpServletRequest, HttpServletResponse) ↓
- 获取请求URL: req.getRequestURI() ↓
- 判断请求类型:
- 如果是静态资源: doResource(url, req, resp)
- 如果是动态请求: doService(req, resp, getDispatchers) ↓
- doService(req, resp, dispatchers):
- 异常处理包装
- 调用doService(url, req, resp, dispatchers) ↓
- doService(url, req, resp, dispatchers): ↓
- 遍历dispatchers列表: for (Dispatcher dispatcher : dispatchers) { ↓
- dispatcher.process(url, req, resp) ↓
- URL正则匹配: urlPattern.matcher(url).matches() ↓
- 如果匹配成功: ↓
- 构建参数数组 arguments[methodParameters.length] ↓
- 遍历每个参数: for (int i = 0; i < arguments.length; i++) { Param param = methodParameters[i]; ↓
```arduino
switch (param.paramType):
↓
```
```csharp
case PATH_VARIABLE:
- matcher.group(param.name) 从URL提取值
- convertToType() 类型转换
↓
```
```css
case REQUEST_PARAM:
- request.getParameter(param.name) 获取参数
- getOrDefault() 处理默认值
- convertToType() 类型转换
↓
```
```css
case REQUEST_BODY:
- request.getReader() 获取输入流
- JsonUtils.readJson() JSON反序列化
↓
```
```vbscript
case SERVLET_VARIABLE:
- 根据类型返回HttpServletRequest/Response等
}
↓
```
- 反射调用: handlerMethod.invoke(controller, arguments) ↓
- 控制器方法执行业务逻辑 ↓
- 返回Result(true, result) } ↓
- 结果处理: if (dispatcher.isRest) { // REST处理逻辑 } else { // MVC处理逻辑 }
具体场景分析
场景1: REST API 请求
css
请求: GET /api/users/123
执行流程:
1. DispatcherServlet.doGet()
2. doService() -> 遍历getDispatchers
3. 找到匹配的Dispatcher (URL: /api/users/{id})
4. Dispatcher.process():
- URL正则匹配: /api/users/123 匹配 /api/users/{id}
- 构建参数:
* @PathVariable("id") Long id → convertToType(Long.class, "123") → 123L
5. 调用: userController.getUser(123L)
6. 返回: User对象
7. 结果处理 (isRest = true):
- resp.setContentType("application/json")
- JsonUtils.writeJson(pw, userObject)
8. 响应: {"id":123, "name":"John"}
场景2: MVC 页面请求
css
请求: GET /user/profile
执行流程:
1. DispatcherServlet.doGet()
2. doService() -> 遍历getDispatchers
3. 找到匹配的Dispatcher (URL: /user/profile)
4. Dispatcher.process():
- 无参数,直接调用userController.profile()
5. 返回: ModelAndView("profile", model)
6. 结果处理 (isRest = false):
- resp.setContentType("text/html")
- viewResolver.render("profile", model, req, resp)
7. 响应: 渲染profile.ftl模板生成的HTML
场景3: 表单提交请求
sql
请求: POST /user/update
执行流程:
1. DispatcherServlet.doPost()
2. doService() -> 遍历postDispatchers
3. 找到匹配的Dispatcher (URL: /user/update)
4. Dispatcher.process():
- 构建参数: @RequestBody User user
- JsonUtils.readJson(reader, User.class) → User对象
5. 调用: userController.update(user)
6. 返回: "redirect:/user/list"
7. 结果处理:
- resp.sendRedirect("/user/list")
使用框架时:可以自定义过滤器,编写逻辑
相关知识
Record类型
Record 是 Java 14 引入、Java 16 正式化的特性,用于创建不可变的数据载体类。
Record会自动生成:
-
私有final字段(与组件名称相同)
-
规范构造函数(canonical constructor)
-
访问器方法 (
name()、age(),不是getXxx) -
equals()、hashCode()、toString()
Function函数式编程
Function<T, R> 是Java函数式编程的核心接口,表示接收一个参数T,返回结果R的函数。
创建
typescript
import java.util.function.Function;
// 1. Lambda表达式(最常用)
Function<String, Integer> stringToInt1 = s -> Integer.parseInt(s);
// 2. 方法引用
Function<String, Integer> stringToInt2 = Integer::parseInt;
Function<String, String> toUpperCase = String::toUpperCase;
Function<String, Integer> getLength = String::length;
// 3. 匿名内部类(传统方式)
Function<String, Integer> stringToInt3 = new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return Integer.parseInt(s);
}
};
// 4. 已有方法赋值
Function<String, Integer> myMethod = this::parseStringToInt;
private Integer parseStringToInt(String s) {
return Integer.parseInt(s);
}
使用apply()方法
ini
// 创建Function
Function<String, Integer> stringToInt = s -> Integer.parseInt(s);
// 使用apply()调用
Integer number = stringToInt.apply("123");
System.out.println(number); // 123
// 更多例子
Function<Integer, String> intToString = i -> "Number: " + i;
String result = intToString.apply(456); // "Number: 456"
Function<String, String> process = s -> s.trim().toUpperCase();
String processed = process.apply(" hello "); // "HELLO"