第12章 手写Spring MVC
第十二章 手写Spring MVC
12.1 基本结构搭建
12.1.1 创建Maven模块
12.1.2 引入Servlet依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springmvc</groupId>
<artifactId>myspringmvc</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<!--servlet api-->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
12.1.3 配置Tomcat服务器
12.1.4 添加web支持
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
</web-app>
12.1.5 创建基本类和接口
根据Spring MVC执行流程,目前先创建出以下的类和接口,后期如果需要其他的再添加:
12.2 部分类和接口的代码完善
12.2.1 @Controller注解
package org.myspringmvc.stereotype;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* ClassName: Controller
* Description: 用来标注处理器,被标注的处理器,纳入IoC容器的管理。该注解只允许出现在类上,另外可以被反射机制读取。
* Datetime: 2024/4/2 9:01
* Author: sqnugy
* Version: 1.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}
12.2.2 RequestMethod枚举(新建)
package org.myspringmvc.web.bind.annotation;
/**
* ClassName: RequestMethod
* Description: 请求方式枚举
* Datetime: 2024/4/2 10:35
* Author: sqnugy
* Version: 1.0
*/
public enum RequestMethod {
GET, POST
}
12.2.3 @RequestMapping注解
package org.myspringmvc.web.bind.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* ClassName: RequestMapping
* Description: 用来标注处理器方法,允许标注方法和类,可以被反射机制读取。
* Datetime: 2024/4/2 8:59
* Author: sqnugy
* Version: 1.0
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
/**
* 用来指定请求路径
* @return
*/
String[] value();
/**
* 用来指定请求方式
* @return
*/
RequestMethod method();
}
12.2.4 HandlerMethod
package org.myspringmvc.web.method;
import java.lang.reflect.Method;
/**
* ClassName: HandlerMethod
* Description: 处理器方法
* Datetime: 2024/4/2 8:53
* Author: sqnugy
* Version: 1.0
*/
public class HandlerMethod {
/**
* 处理器对象
*/
private Object handler;
/**
* 要执行的方法
*/
private Method method;
public HandlerMethod() {
}
public HandlerMethod(Object handler, Method method) {
this.handler = handler;
this.method = method;
}
public Object getHandler() {
return handler;
}
public void setHandler(Object handler) {
this.handler = handler;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
}
12.2.5 HandlerMapping接口
package org.myspringmvc.web.servlet;
import jakarta.servlet.http.HttpServletRequest;
/**
* ClassName: HandlerMapping
* Description: 主要是通过请求获取对应的处理器执行链。
* Datetime: 2024/4/2 8:50
* Author: sqnugy
* Version: 1.0
*/
public interface HandlerMapping {
/**
* 根据请求获取处理器执行链。
* @param request
* @return
* @throws Exception
*/
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
12.2.6 RequestMappingHandlerMapping
package org.myspringmvc.web.servlet.mvc.method.annotation;
import jakarta.servlet.http.HttpServletRequest;
import org.myspringmvc.web.servlet.HandlerExecutionChain;
import org.myspringmvc.web.servlet.HandlerMapping;
/**
* ClassName: RequestMappingHandlerMapping
* Description:
* Datetime: 2024/4/2 9:44
* Author: sqnugy
* Version: 1.0
*/
public class RequestMappingHandlerMapping implements HandlerMapping {
@Override
public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
return null;
}
}
12.2.7 HandlerAdapter接口
package org.myspringmvc.web.servlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* ClassName: HandlerAdapter
* Description: 通过处理器适配器调用处理器方法
* Datetime: 2024/4/2 8:51
* Author: sqnugy
* Version: 1.0
*/
public interface HandlerAdapter {
/**
* 执行处理器方法
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
}
12.2.8 RequestMappingHandlerAdapter
package org.myspringmvc.web.servlet.mvc.method.annotation;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.myspringmvc.web.servlet.HandlerAdapter;
import org.myspringmvc.web.servlet.ModelAndView;
/**
* ClassName: RequestMappingHandlerAdapter
* Description:
* Datetime: 2024/4/2 9:44
* Author: sqnugy
* Version: 1.0
*/
public class RequestMappingHandlerAdapter implements HandlerAdapter {
@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return null;
}
}
12.2.9 View接口
package org.myspringmvc.web.servlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Map;
/**
* ClassName: View
* Description:
* Datetime: 2024/4/2 8:58
* Author: sqnugy
* Version: 1.0
*/
public interface View {
/**
* 获取响应的内容类型
* @return
*/
String getContentType();
/**
* 渲染
* @param model
* @param request
* @param response
* @throws Exception
*/
void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception;
}
)
12.2.10 InternalResourceView
package org.myspringmvc.web.servlet.view;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.myspringmvc.web.servlet.View;
import java.util.Map;
/**
* ClassName: InternalResourceView
* Description:
* Datetime: 2024/4/2 10:17
* Author: sqnugy
* Version: 1.0
*/
public class InternalResourceView implements View {
@Override
public String getContentType() {
return null;
}
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
}
}
12.2.11 ViewResolver接口
package org.myspringmvc.web.servlet;
import java.util.Locale;
/**
* ClassName: ViewResolver
* Description:解析逻辑视图名称,返回视图对象
* Datetime: 2024/4/2 8:58
* Author: sqnugy
* Version: 1.0
*/
public interface ViewResolver {
/**
* 解析逻辑视图名称,返回视图对象
* @param viewName
* @param locale
* @return
* @throws Exception
*/
View resolveViewName(String viewName, Locale locale) throws Exception;
}
12.2.12 InternalResourceViewResolver
package org.myspringmvc.web.servlet.view;
import org.myspringmvc.web.servlet.View;
import org.myspringmvc.web.servlet.ViewResolver;
import java.util.Locale;
/**
* ClassName: InternalResourceViewResolver
* Description:
* Datetime: 2024/4/2 9:45
* Author: sqnugy
* Version: 1.0
*/
public class InternalResourceViewResolver implements ViewResolver {
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
}
12.2.13 DispatcherServlet
package org.myspringmvc.web.servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* ClassName: DispatcherServlet
* Description:
* Datetime: 2024/4/2 8:50
* Author: sqnugy
* Version: 1.0
*/
public class DispatcherServlet extends HttpServlet {
@Override
public void init() throws ServletException {
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doDispatch(req, resp);
}
/**
* 处理请求的核心方法
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
12.2.14 HandlerExecutionChain
package org.myspringmvc.web.servlet;
import java.util.List;
/**
* ClassName: HandlerExecutionChain
* Description:
* Datetime: 2024/4/2 8:55
* Author: sqnugy
* Version: 1.0
*/
public class HandlerExecutionChain {
private Object handler;
private List<HandlerInterceptor> interceptorList;
private int interceptorIndex = -1;
public HandlerExecutionChain() {
}
public HandlerExecutionChain(Object handler, List<HandlerInterceptor> interceptorList) {
this.handler = handler;
this.interceptorList = interceptorList;
}
public Object getHandler() {
return handler;
}
public void setHandler(Object handler) {
this.handler = handler;
}
public List<HandlerInterceptor> getInterceptorList() {
return interceptorList;
}
public void setInterceptorList(List<HandlerInterceptor> interceptorList) {
this.interceptorList = interceptorList;
}
public int getInterceptorIndex() {
return interceptorIndex;
}
public void setInterceptorIndex(int interceptorIndex) {
this.interceptorIndex = interceptorIndex;
}
}
12.2.15 HandlerInterceptor拦截器接口
package org.myspringmvc.web.servlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* ClassName: HandlerInterceptor
* Description: 拦截器接口
* Datetime: 2024/4/2 8:54
* Author: sqnugy
* Version: 1.0
*/
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
12.2.16 ModelMap类(新建)
package org.myspringmvc.ui;
import java.util.LinkedHashMap;
/**
* ClassName: ModelMap
* Description: 将数据存储到域中。
* Datetime: 2024/4/2 11:07
* Author: sqnugy
* Version: 1.0
*/
public class ModelMap extends LinkedHashMap<String, Object> {
public ModelMap() {
}
public ModelMap addAttribute(String name, String value){
this.put(name, value);
return this;
}
}
12.2.17 ModelAndView
package org.myspringmvc.web.servlet;
import org.myspringmvc.ui.ModelMap;
/**
* ClassName: ModelAndView
* Description:
* Datetime: 2024/4/2 8:57
* Author: sqnugy
* Version: 1.0
*/
public class ModelAndView {
private Object view;
private ModelMap model;
public ModelAndView() {
}
public ModelAndView(Object view, ModelMap model) {
this.view = view;
this.model = model;
}
public Object getView() {
return view;
}
public void setView(Object view) {
this.view = view;
}
/**
* 该方法待实现
* @param viewName
*/
public void setViewName(String viewName){
// TODO
}
public ModelMap getModel() {
return model;
}
public void setModel(ModelMap model) {
this.model = model;
}
}
12.3 webapp开发者写应用
12.3.1 web.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<!--配置前端控制器-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.myspringmvc.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
DispatcherServlet
的 <init-param>
的 contextConfigLocation
可以编写代码了:
@Override
public void init() throws ServletException {
ServletConfig servletConfig = this.getServletConfig();
String contextConfigLocation = servletConfig.getInitParameter(Constant.CONTEXT_CONFIG_LOCATION);
String springMvcXmlPath = getSpringMvcXmlPath(contextConfigLocation);
System.out.println("Spring MVC配置文件路径解析完成:" + springMvcXmlPath);
}
private String getSpringMvcXmlPath(String contextConfigLocation) throws UnsupportedEncodingException {
if(contextConfigLocation.startsWith(Constant.CLASSPATH)){
String path = contextConfigLocation.substring(Constant.CLASSPATH.length()).trim();
String springMvcXmlPath = Thread.currentThread().getContextClassLoader().getResource(path).getPath();
// 对路径解码,防止路径中有 % 等字符。
return URLDecoder.decode(springMvcXmlPath, Charset.defaultCharset());
}
return null;
}
定义系统常量类:Constant
package org.myspringmvc.web.constant;
/**
* ClassName: Constant
* Description:SpringMVC系统常量类
* Datetime: 2024/4/2 11:28
* Author: sqnugy
* Version: 1.0
*/
public class Constant {
public static final String CONTEXT_CONFIG_LOCATION = "contextConfigLocation";
public static final String CLASSPATH = "classpath:";
}
12.3.2 编写处理器Controller
package com.powernode.springmvc.controller;
import org.myspringmvc.stereotype.Controller;
import org.myspringmvc.web.bind.annotation.RequestMapping;
import org.myspringmvc.web.bind.annotation.RequestMethod;
/**
* ClassName: UserController
* Description:
* Datetime: 2024/4/2 11:38
* Author: sqnugy
* Version: 1.0
*/
@Controller
public class UserController {
@RequestMapping(value = "/", method = RequestMethod.GET)
public String index(){
return "index";
}
}
12.3.3 编写拦截器
package com.powernode.springmvc.interceptors;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.myspringmvc.web.servlet.HandlerInterceptor;
import org.myspringmvc.web.servlet.ModelAndView;
/**
* ClassName: Interceptor1
* Description:
* Datetime: 2024/4/2 11:40
* Author: sqnugy
* Version: 1.0
*/
public class Interceptor1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("Interceptor1's preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("Interceptor1's postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("Interceptor1's afterCompletion");
}
}
package com.powernode.springmvc.interceptors;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.myspringmvc.web.servlet.HandlerInterceptor;
import org.myspringmvc.web.servlet.ModelAndView;
/**
* ClassName: Interceptor2
* Description:
* Datetime: 2024/4/2 11:41
* Author: sqnugy
* Version: 1.0
*/
public class Interceptor2 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("Interceptor2's preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("Interceptor2's postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("Interceptor2's afterCompletion");
}
}
12.3.4 编写springmvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<!--组件扫描-->
<component-scan base-package="com.powernode.springmvc.controller"/>
<!--视图解析器-->
<bean class="org.myspringmvc.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!--拦截器-->
<interceptors>
<bean class="com.powernode.springmvc.interceptors.Interceptor1"/>
<bean class="com.powernode.springmvc.interceptors.Interceptor2"/>
</interceptors>
</beans>
InternalResourceViewResolver
类中添加属性:suffix和prefix
package org.myspringmvc.web.servlet.view;
import org.myspringmvc.web.servlet.View;
import org.myspringmvc.web.servlet.ViewResolver;
import java.util.Locale;
/**
* ClassName: InternalResourceViewResolver
* Description:
* Datetime: 2024/4/2 9:45
* Author: sqnugy
* Version: 1.0
*/
public class InternalResourceViewResolver implements ViewResolver {
private String suffix;
private String prefix;
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
}
12.3.5 提供视图
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>index jsp</title>
</head>
<body>
<h1>动力节点:手写Spring MVC框架</h1>
</body>
</html>
12.4 服务器启动阶段的处理
12.4.1 分析服务器启动阶段都需要初始化什么
- 初始化Spring容器
- 组件扫描包下的类纳入IoC容器的管理。
- 创建视图解析器对象
- 创建所有的拦截器对象
- 扫描这个包下所有的类:
org.myspringmvc.web.servlet.mvc.method.annotation
,全部实例化,纳入IoC容器管理
- 初始化
HandlerMapping
- 初始化
HandlerAdapter
- 初始化
ViewResolver
12.4.2 初始化Spring容器
Spring容器:ApplicationContext
Spring Web容器:WebApplicationContext
12.4.2.1 组件扫描
添加解析xml文件的依赖
<!--dom4j-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!--jaxen-->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
package org.myspringmvc.context;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.File;
import java.lang.reflect.Constructor;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
/**
* ClassName: ApplicationContext
* Description: Spring容器,启动服务器时,初始化
* Datetime: 2024/4/2 13:52
* Author: sqnugy
* Version: 1.0
*/
public class ApplicationContext {
private Map<String, Object> beanMap = new HashMap<>();
public ApplicationContext(String xmlPath) throws Exception {
// 组件扫描
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(new File(xmlPath));
Element componentScanElement = (Element)document.selectSingleNode("/beans/context:component-scan");
String basePackage = componentScanElement.attributeValue("base-package");
System.out.println("组件扫描:" + basePackage);
componentScan(basePackage);
System.out.println("Spring Web容器当下状态:" + beanMap);
}
private void componentScan(String basePackage) throws Exception{
String dirPath = Thread.currentThread().getContextClassLoader().getResource(basePackage.replace(".", "/")).getPath();
File file = new File(URLDecoder.decode(dirPath));
if(file.isDirectory()){
File[] files = file.listFiles();
for (File classFile : files){
if(classFile.getName().endsWith(".class")){
String className = basePackage + "." + classFile.getName().substring(0, classFile.getName().lastIndexOf("."));
Class<?> clazz = Class.forName(className);
Constructor<?> defaultCon = clazz.getDeclaredConstructor();
Object bean = defaultCon.newInstance();
beanMap.put(firstCharLowerCase(clazz.getSimpleName()), bean);
}
}
}
}
private String firstCharLowerCase(String simpleName) {
return simpleName.substring(0, 1).toLowerCase() + simpleName.substring(1);
}
public Object getBean(String beanName){
return beanMap.get(beanName);
}
}
package org.myspringmvc.context;
import jakarta.servlet.ServletContext;
/**
* ClassName: WebApplicationContext
* Description:
* Datetime: 2024/4/2 14:24
* Author: sqnugy
* Version: 1.0
*/
public class WebApplicationContext extends ApplicationContext{
private ServletContext servletContext;
public WebApplicationContext(String xmlPath, ServletContext servletContext) throws Exception {
super(xmlPath);
this.servletContext = servletContext;
}
public ServletContext getServletContext() {
return servletContext;
}
}
在DispatcherServlet
中添加如下代码:
添加常量值:
启动服务器测试:
12.4.2.2 创建视图解析器对象
InternalResourceViewResolver
类代码改动,添加prefix和suffix属性:
package org.myspringmvc.web.servlet.view;
import org.myspringmvc.web.servlet.View;
import org.myspringmvc.web.servlet.ViewResolver;
import java.util.Locale;
/**
* ClassName: InternalResourceViewResolver
* Description:
* Datetime: 2024/4/2 9:45
* Author: sqnugy
* Version: 1.0
*/
public class InternalResourceViewResolver implements ViewResolver {
private String suffix;
private String prefix;
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
}
// 创建视图解析器对象
Element viewResolverBean = (Element) document.selectSingleNode("/beans/bean");
String viewResolverClassName = viewResolverBean.attributeValue("class");
Class viewResolverClass = Class.forName(viewResolverClassName);
Object viewResolverObj = viewResolverClass.newInstance();
if(viewResolverObj instanceof InternalResourceViewResolver internalResourceViewResolver){
// 前缀
Element prefixProperty = (Element)viewResolverBean.selectSingleNode("property[@name='prefix']");
internalResourceViewResolver.setPrefix(prefixProperty.attributeValue("value"));
// 后缀
Element suffixProperty = (Element)viewResolverBean.selectSingleNode("property[@name='suffix']");
internalResourceViewResolver.setSuffix(suffixProperty.attributeValue("value"));
}
beanMap.put(Constant.VIEW_RESOLVER, viewResolverObj);
System.out.println("Spring Web容器当下状态:" + beanMap);
12.4.2.3 创建所有的拦截器对象
在ApplicationContext
构造方法中继续添加如下代码:
// 创建所有拦截器对象
Element interceptorsElement = (Element) document.selectSingleNode("/beans/interceptors");
List<Element> interceptorBeans = interceptorsElement.elements("bean");
List<HandlerInterceptor> interceptors = new ArrayList<>();
for(Element interceptorBean : interceptorBeans){
String className = interceptorBean.attributeValue("class");
Class<?> clazz = Class.forName(className);
interceptors.add((HandlerInterceptor) clazz.newInstance());
}
beanMap.put(Constant.INTERCEPTORS, interceptors);
System.out.println("Spring Web容器当下状态:" + beanMap);
12.4.2.4 初始化annotation包下所有类的实例
// 将这个包下所有的类实例化:org.myspringmvc.web.servlet.mvc.method.annotation
String dirPath = Thread.currentThread().getContextClassLoader().getResource(Constant.PACKAGE_AUTO_CREATE.replace(".", "/")).getPath();
File file = new File(URLDecoder.decode(dirPath));
if(file.isDirectory()){
File[] files = file.listFiles();
for (File classFile : files){
if(classFile.getName().endsWith(".class")){
String className = Constant.PACKAGE_AUTO_CREATE + "." + classFile.getName().substring(0, classFile.getName().lastIndexOf("."));
Class<?> clazz = Class.forName(className);
Constructor<?> defaultCon = clazz.getDeclaredConstructor();
Object bean = defaultCon.newInstance();
if(bean instanceof HandlerMapping){
beanMap.put(Constant.HANDLER_MAPPING, bean);
}
if(bean instanceof HandlerAdapter){
beanMap.put(Constant.HANDLER_ADAPTER, bean);
}
}
}
}
System.out.println("Spring Web容器当下状态:" + beanMap);
12.4.3 初始化HandlerMapping
12.4.4 初始化HandlerAdapter
12.4.5 初始化ViewResolver
12.5 根据请求流程补充代码
12.5.1 根据请求获取处理器执行链
private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
// 根据请求获取处理器执行链
HandlerExecutionChain mappedHandler = handlerMapping.getHandler(request);
System.out.println(mappedHandler);
} catch (Exception e) {
e.printStackTrace();
}
}
package org.myspringmvc.web.servlet.mvc.method.annotation;
import jakarta.servlet.http.HttpServletRequest;
import org.myspringmvc.context.WebApplicationContext;
import org.myspringmvc.web.constant.Constant;
import org.myspringmvc.web.method.HandlerMethod;
import org.myspringmvc.web.servlet.HandlerExecutionChain;
import org.myspringmvc.web.servlet.HandlerInterceptor;
import org.myspringmvc.web.servlet.HandlerMapping;
import org.myspringmvc.web.servlet.mvc.RequestMappingInfo;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* ClassName: RequestMappingHandlerMapping
* Description:
* Datetime: 2024/4/2 9:44
* Author: sqnugy
* Version: 1.0
*/
public class RequestMappingHandlerMapping implements HandlerMapping {
private Map<RequestMappingInfo, HandlerMethod> map;
public RequestMappingHandlerMapping(Map<RequestMappingInfo, HandlerMethod> map) {
this.map = map;
}
@Override
public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
RequestMappingInfo requestMappingInfo = new RequestMappingInfo(request.getServletPath(), request.getMethod());
HandlerExecutionChain handlerExecutionChain = new HandlerExecutionChain();
handlerExecutionChain.setHandler(map.get(requestMappingInfo));
WebApplicationContext wac = (WebApplicationContext) request.getServletContext().getAttribute(Constant.WEB_APPLICATION_CONTEXT);
handlerExecutionChain.setInterceptorList((List<HandlerInterceptor>)wac.getBean(Constant.INTERCEPTORS));
return handlerExecutionChain;
}
}
private Map<RequestMappingInfo, HandlerMethod> componentScan(String basePackage) throws Exception{
// 初始化HandlerMethod
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = new HashMap<>();
String dirPath = Thread.currentThread().getContextClassLoader().getResource(basePackage.replace(".", "/")).getPath();
File file = new File(URLDecoder.decode(dirPath));
if(file.isDirectory()){
File[] files = file.listFiles();
for (File classFile : files){
if(classFile.getName().endsWith(".class")){
String className = basePackage + "." + classFile.getName().substring(0, classFile.getName().lastIndexOf("."));
Class<?> clazz = Class.forName(className);
Constructor<?> defaultCon = clazz.getDeclaredConstructor();
Object bean = defaultCon.newInstance();
beanMap.put(firstCharLowerCase(clazz.getSimpleName()), bean);
// 如果clazz被@Controller注解标注
if(clazz.isAnnotationPresent(Controller.class)){
// 获取该类中所有的方法
Method[] methods = clazz.getDeclaredMethods();
for(Method method : methods){
if(method.isAnnotationPresent(RequestMapping.class)){
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
// 创建RequestMappingInfo对象
RequestMappingInfo requestMappingInfo = new RequestMappingInfo();
requestMappingInfo.setRequestURI(requestMapping.value()[0]);
requestMappingInfo.setRequestMethod(requestMapping.method().toString());
// 创建HandlerMethod对象
HandlerMethod handlerMethod = new HandlerMethod();
handlerMethod.setMethod(method);
handlerMethod.setHandler(bean);
handlerMethodMap.put(requestMappingInfo, handlerMethod);
}
}
}
}
}
}
return handlerMethodMap;
}
ApplicationContext
代码还有以下改造:
添加一个新的类:RequestMappingInfo
package org.myspringmvc.web.servlet.mvc;
import java.util.Objects;
/**
* ClassName: RequestMappingInfo
* Description:
* Datetime: 2024/4/2 17:58
* Author: sqnugy
* Version: 1.0
*/
public class RequestMappingInfo {
private String requestURI;
private String requestMethod;
public RequestMappingInfo() {
}
public RequestMappingInfo(String requestURI, String requestMethod) {
this.requestURI = requestURI;
this.requestMethod = requestMethod;
}
public String getRequestURI() {
return requestURI;
}
public void setRequestURI(String requestURI) {
this.requestURI = requestURI;
}
public String getRequestMethod() {
return requestMethod;
}
public void setRequestMethod(String requestMethod) {
this.requestMethod = requestMethod;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RequestMappingInfo that = (RequestMappingInfo) o;
return Objects.equals(requestURI, that.requestURI) && Objects.equals(requestMethod, that.requestMethod);
}
@Override
public int hashCode() {
return Objects.hash(requestURI, requestMethod);
}
@Override
public String toString() {
return "RequestMappingInfo{" +
"requestURI='" + requestURI + '\'' +
", requestMethod='" + requestMethod + '\'' +
'}';
}
}
12.5.2 执行拦截器的preHandle
添加以下代码:
HandlerExecutionChain
添加以下代码:
public boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
for (int i = 0; i < interceptorList.size(); i++) {
HandlerInterceptor handlerInterceptor = interceptorList.get(i);
boolean result = handlerInterceptor.preHandle(request, response, handler);
if(!result){
return false;
}
interceptorIndex = i;
}
return true;
}
12.5.3 执行处理器方法
DispatcherServlet
中的doDispatch
方法:
先让handle方法返回一个固定的ModelAndView
,后期在详细编写 handle 方法:
12.5.4 执行拦截器的postHandle
DispatcherServlet
的 doDispatch
方法中:
HandlerExecutionChain
的方法:
12.5.5 处理响应
在 DispatcherServlet
的 doDispatch
方法中:
package org.myspringmvc.web.servlet.view;
import org.myspringmvc.web.servlet.View;
import org.myspringmvc.web.servlet.ViewResolver;
import java.util.Locale;
/**
* ClassName: InternalResourceViewResolver
* Description:
* Datetime: 2024/4/2 9:45
* Author: sqnugy
* Version: 1.0
*/
public class InternalResourceViewResolver implements ViewResolver {
private String suffix;
private String prefix;
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return new InternalResourceView("text/html;charset=UTF-8", prefix + viewName + suffix);
}
}
package org.myspringmvc.web.servlet.view;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.myspringmvc.web.servlet.View;
import java.util.Map;
/**
* ClassName: InternalResourceView
* Description:
* Datetime: 2024/4/2 10:17
* Author: sqnugy
* Version: 1.0
*/
public class InternalResourceView implements View {
private String contentType;
private String path;
public InternalResourceView(String contentType, String path) {
this.contentType = contentType;
this.path = path;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
@Override
public String getContentType() {
return contentType;
}
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 设置响应内容类型
response.setContentType(getContentType());
// 向request域中绑定数据
if(model != null){
model.forEach(request::setAttribute);
}
// 转发
request.getRequestDispatcher(path).forward(request, response);
}
}
12.5.6 执行拦截器的afterCompletion
在DispatcherServlet
类的 doDispatch
方法中:
在HandlerExecutionChain
中:
12.5.7 初步测试
启动服务器,浏览器地址栏:http://localhost:8080/myspringmvc
后台效果:
如果让第二个拦截器返回false尝试一下:
初步测试通过!!!