运行环境
JDK :17
IntelliJ IDEA : 2022.3
Tomcat:8.5.86
前期工作
- 先创建一个新的Maven项目,按照图示操作:
- 在这里我们选择
Maven Archetype
选项,写好项目名称:Handwriting-SpringMVC,以及路径并选好Archetype为webapp。点击creat创建。
- 编辑环境:按照图示选择对应的选项,最后点击OK即可!
注意:在开始本项目前你需要了解一些MVC常用注解及其流程。
项目结构
项目类继承关系
main结构
java
com.bruce.controller
:用户自定义的SpringMVC控制层
com.bruce.pojo
:用户自定义的对象(User)
com.bruce.service
:用户自定义的接口和实现类(UserService、UserServiceImpl)
com.springmvc.annotation
:SpringMVC的相关注解
com.springmvc.context
:Spring容器
com.springmvc.exception
:用户自定义异常
com.springmvc.handler
:负责处理HTTP请求的组件
com.springmvc.servlet
:SpringMVC的核心控制器
com.springmvc.xml
:解析springmvc.xml文件
resources
目前只有一个springmvc.xml
文件
webapp
WEB-INF
:Java的Web应用的安全目录
test结构
java
com.bruce.test
:测试XML解析工具
resources
NULL
两个重要的XML文件和一个JSP文件:
springmvc.xml
:SpringMVC的配置文件web.xml
:web应用的配置文件。user.jsp
:后面用来跳转的JSP文件index.jsp
:自动生成的,可以不用管 (非必须)
项目流程
- 在启动
Tomcat
后就会自动解析webapp中的WEB-INF中的web.xml
,所以我们先配置web.xml
- 创建
DispatcherServlet
(前端控制器)和WebApplicationContext
(spring容器) - 接下来会执行
DispatcherServlet
中的init()
方法,创建Spring容器,并从springmvc.xml
文件中读取base-package中的包路径,并生成对象,存放在iocMap
中 - 当我们的对象生成完毕之后,就会执行
initHandlerMappring()
方法,遍历spring容器中的iocMap
,遍历其中所有的Controller类型的对象的Class对象,然后判断每一个Class对象中的所有方法,将有@RequestMapping
注解的方法封装成一个MyHandler
对象,其中包含@RequestMapping
注解的名字(url),Controller对象(controller),该方法(method)。然后将这个MyHandler
对象放到handlerList
集合中。 - 当有get请求的时候,就会执行
DispatcherServlet
中的doGet()
方法。遍历handlerList
,寻找浏览器url请求路径和我们MyHandler
对象中储存的@RequestMapping
中相等的对象,然后定义一个参数的Object数组,经过一系列的关于@RequestParam
参数的判断,将浏览器请求路径中的参数放到对应位置的Object数组中,然后通过handler.getMethod().invoke()
执行这个方法就可以调用控制中的方法并获得返回值,然后做一个返回值类型的判断:String还是Json。最后做跳转JSP或返回JSON数据的代码逻辑。 - 最后运行的结果如下,成功跳转到我们的
user.jsp
文件中:
注意:整体的流程最好自己debug一遍,印象会更深刻!
pom.xml文件:
<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/maven-v4_0_0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>Handwriting-SpringMVC</artifactId><packaging>war</packaging><version>1.0-SNAPSHOT</version><name>Handwriting-SpringMVC Maven Webapp</name><url>http://maven.apache.org</url><dependencies><!--单元测试--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>provided</scope></dependency><!--解析XML文件--><dependency><groupId>dom4j</groupId><artifactId>dom4j</artifactId><version>1.6.1</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.5</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.8</version><scope>provided</scope></dependency><!--JSON转换工具--><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.16.1</version></dependency></dependencies><build><finalName>Handwriting-SpringMVC</finalName><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>17</source><target>17</target><!--JDK8新特性,能够获取到方法中的参数--><!--[request, response, name]--><compilerArgs><arg>-parameters</arg></compilerArgs><encoding>UTF-8</encoding></configuration></plugin></plugins></build>
</project>
main
com.bruce.controller包:
UserController类:
package com.bruce.controller;import com.bruce.pojo.User;
import com.bruce.service.UserService;
import com.springmvc.annotation.Autowired;
import com.springmvc.annotation.Controller;
import com.springmvc.annotation.RequestMapping;
import com.springmvc.annotation.ResponseBody;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;/*** @Author: Juechen* @Date: 2024/4/24* @Description: 控制器* @Version: 1.0**/
@Controller
public class UserController {// 持有业务逻辑层的对象@AutowiredUserService userService;@RequestMapping("/user/query")public String findUsers(HttpServletRequest request, HttpServletResponse response, String name) {// 处理响应的中文乱码问题response.setContentType("text/html;charset=utf-8");String message = userService.getMessage(name);request.setAttribute("message",message);//转发到user.jspreturn "forward:/user.jsp";}@RequestMapping("/user/queryjson")@ResponseBodypublic List<User> queryUsers(HttpServletRequest request, HttpServletResponse response, String name){return userService.findUsers(name);}}
com.bruce.pojo包:
User类:
package com.bruce.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;/*** @Author: Juechen* @Date: 2024/4/24* @Description: TODO* @Version: 1.0**/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {private Integer id;private String name;private String pass;}
com.bruce.service包:
UserService接口:
package com.bruce.service;import com.bruce.pojo.User;import java.util.List;public interface UserService {List<User> findUsers(String name);String getMessage(String name);}
com.bruce.service.impl包:
UserServiceImpl类:
package com.bruce.service.impl;import com.bruce.pojo.User;
import com.bruce.service.UserService;
import com.springmvc.annotation.Service;import java.util.ArrayList;
import java.util.List;/*** @Author: Juechen* @Date: 2024/4/24* @Description: UserService的实现类* @Version: 1.0**/
@Service // 默认就是类名首字母小写public class UserServiceImpl implements UserService {@Overridepublic List<User> findUsers(String name) {// 模拟数据List<User> users = new ArrayList<>();users.add(new User(1,"老王","admin"));users.add(new User(2,"小王","12345"));return users;}@Overridepublic String getMessage(String name) {return "我是getMessage方法," + name;}
}
com.springmvc.annotation包:
Autowired注解:
package com.springmvc.annotation;import java.lang.annotation.*;@Target(ElementType.FIELD) // 元注解
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {String value() default "";
}
Controller注解:
package com.springmvc.annotation;import java.lang.annotation.*;@Target(ElementType.TYPE) // 元注解
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {String value() default "";
}
RequestMapping注解:
package com.springmvc.annotation;import java.lang.annotation.*;@Target(ElementType.METHOD) // 元注解
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {String value() default "";
}
RequestParm注解:
package com.springmvc.annotation;import java.lang.annotation.*;@Target(ElementType.PARAMETER) // 元注解
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParm {String value() default "";
}
ResponseBody注解:
package com.springmvc.annotation;import java.lang.annotation.*;@Target(ElementType.METHOD) // 元注解
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {}
Service注解:
package com.springmvc.annotation;import java.lang.annotation.*;@Target(ElementType.TYPE) // 元注解
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {String value() default "";
}
com.springmvc.context包:
WebApplicationContext
类:Spring容器
package com.springmvc.context;import com.springmvc.annotation.Autowired;
import com.springmvc.annotation.Controller;
import com.springmvc.annotation.Service;
import com.springmvc.exception.ContextException;
import com.springmvc.xml.XmlParser;import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** @Author: Juechen* @Date: 2024/4/24* @Description: Spring容器* @Version: 1.0**/
public class WebApplicationContext {// classpath:springmvc.xmlString contextConfigLocation;List<String> classNameList = new ArrayList<>();// Spring的Ioc容器public Map<String, Object> iocMap = new ConcurrentHashMap<>();public WebApplicationContext(String contextConfigLocation) {this.contextConfigLocation = contextConfigLocation;}/*** 初始化Spring容器*/public void refresh() {// 1.解析springmvc.xml文件// split方法用于将字符串根据指定的正则表达式进行拆分,这里得到的是springmvc.xmlString basePackage = XmlParser.getbasePackage(contextConfigLocation.split(":")[1]);// 这里通过逗号分隔成了两个字符串:com.bruce.service 和 com.bruce.controllerString[] basePackages = basePackage.split(",");if (basePackages.length > 0) {for (String pack : basePackages) {// com.bruce.service// com.bruce.controllerexcuteScanPackage(pack);}}System.out.println("扫描后的结果是:" + classNameList);// 2.实例化Spring容器中的beanexcuteInstance();// Ioc容器中的对象是:System.out.println("spring的Ioc容器对象是:" + iocMap);// 3.需要实现spring容器中对象的注入excuteAutowired();}/*** 扫描包*/public void excuteScanPackage(String pack) {// /com/bruce/serviceURL url = this.getClass().getClassLoader().getResource("/" + pack.replaceAll("\\.", "/"));String path = url.getFile();File dir = new File(path);for (File f : dir.listFiles()) {if (f.isDirectory()) {// 当前是一个文件目录excuteScanPackage(pack + "." + f.getName());} else {// 文件目录下文件 获取全路径String className = pack + "." + f.getName().replaceAll(".class", "");classNameList.add(className);}}}/*** 实例化Spring容器中的bean对象*/public void excuteInstance() {if (classNameList.size() == 0) {// 没有扫描到需要实例化的类throw new ContextException("没有要实例化的class!");}try {for (String className : classNameList) {Class<?> clazz = Class.forName(className);if (clazz.isAnnotationPresent(Controller.class)) {// 控制层的类 com.bruce.controller// UserController -> userController 控制层对象的名字String beanName = clazz.getSimpleName().substring(0, 1).toLowerCase() + clazz.getSimpleName().substring(1);iocMap.put(beanName, clazz.newInstance());} else if (clazz.isAnnotationPresent(Service.class)) {// 业务逻辑层的类 com.bruce.service.implService serviceAnnotation = clazz.getAnnotation(Service.class);String beanName = serviceAnnotation.value();if ("".equals(beanName)) {Class<?>[] interfaces = clazz.getInterfaces();for (Class<?> c1 : interfaces) {String beanName1 = c1.getSimpleName().substring(0, 1).toLowerCase() + c1.getSimpleName().substring(1);// 接口名字作为beanNameiocMap.put(beanName1, clazz.newInstance());}} else {iocMap.put(beanName, clazz.newInstance());}}}} catch (Exception e) {throw new RuntimeException(e);}}/*** 实现spring容器中的对象的依赖注入*/private void excuteAutowired() {try {if (iocMap.isEmpty()) {throw new ContextException("没有找到初始化的bean对象");}// Map的一种迭代方法for (Map.Entry<String, Object> entry : iocMap.entrySet()) {// 获取到了一个一个对象名称String key = entry.getKey();// 获取到了一个一个对象实例Object bean = entry.getValue();Field[] declaredFields = bean.getClass().getDeclaredFields();for (Field declaredField : declaredFields) {if (declaredField.isAnnotationPresent(Autowired.class)) {Autowired autowiredAnnotation = declaredField.getAnnotation(Autowired.class);String beanName = autowiredAnnotation.value();// 进一步判断是否设置了 value 属性,如果未设置,则根据字段的类型推断 bean 的名称。if (beanName.equals("")) {// 这个拿到的其实是属性类的全名称Class<?> type = declaredField.getType();// 做剪枝操作beanName = type.getSimpleName().substring(0, 1).toLowerCase() + type.getSimpleName().substring(1);}// 暴力反射,因为Controller层的属性类有可能是私有的declaredField.setAccessible(true);// 属性注入 调用反射给属性赋值declaredField.set(bean, iocMap.get(beanName));}}}} catch (IllegalAccessException e) {e.printStackTrace();}}
}
com.springmvc.exception包:
ContextException类:自定义异常
package com.springmvc.exception;/*** @Author: Juechen* @Date: 2024/4/24* @Description: 自定义异常* @Version: 1.0**/
public class ContextException extends RuntimeException {public ContextException(String message) {super(message);}public ContextException(Throwable cause) {super(cause);}@Overridepublic String getMessage() {return super.getMessage();}
}
com.springmvc.handler包:
MyHandler类:自定义的处理器对象
package com.springmvc.handler;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.lang.reflect.Method;/*** @Author: Juechen* @Date: 2024/4/25* @Description: 自定义的处理器对象* @Version: 1.0**/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MyHandler {private String url;private Object controller;private Method method;}
com.springmvc.servlet包:
DispatcherServlet
类:SpringMVC的核心控制器
package com.springmvc.servlet;import com.fasterxml.jackson.databind.ObjectMapper;
import com.springmvc.annotation.Controller;
import com.springmvc.annotation.RequestMapping;
import com.springmvc.annotation.RequestParm;
import com.springmvc.annotation.ResponseBody;
import com.springmvc.context.WebApplicationContext;
import com.springmvc.exception.ContextException;
import com.springmvc.handler.MyHandler;import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;/*** @Author: Juechen* @Date: 2024/4/24* @Description: SpringMVC的核心控制器* @Version: 1.0**/
public class DispatcherServlet extends HttpServlet {private WebApplicationContext webApplicationContext;// 存储url和对象方法的映射List<MyHandler> handlerList = new ArrayList<>();@Overridepublic void init() throws ServletException {// 1.Servlet初始化的时候,读取初始化的参数 classpath:springmvc.xmlString contextConfigLocation = this.getServletConfig().getInitParameter("contextConfigLocation");// 2.创建Spring容器webApplicationContext = new WebApplicationContext(contextConfigLocation);// 3.初始化Spring容器webApplicationContext.refresh();// 4.初始化请求映射 /user/query ----> Controller ----> method ----->parameterinitHandlerMappring();System.out.println(handlerList);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 进行请求分发处理excuteDispatch(req, resp);}@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doPost(req, resp);}/*** 初始化请求映射*/private void initHandlerMappring() {//判断Iocmap中是否有bean对象if (webApplicationContext.iocMap.isEmpty()) {throw new ContextException("Spring容器为空");}for (Map.Entry<String, Object> entry : webApplicationContext.iocMap.entrySet()) {Class<?> clazz = entry.getValue().getClass();if (clazz.isAnnotationPresent(Controller.class)) {Method[] declaredMethods = clazz.getMethods();for (Method declaredMethod : declaredMethods) {if (declaredMethod.isAnnotationPresent(RequestMapping.class)) {RequestMapping requestMappingAnnotation = declaredMethod.getAnnotation(RequestMapping.class);// ("/user/query")String url = requestMappingAnnotation.value();// 构造一个自定义的处理器对象MyHandler handler = new MyHandler(url, entry.getValue(), declaredMethod);handlerList.add(handler);}}}}}/*** 请求的分发处理*/public void excuteDispatch(HttpServletRequest req, HttpServletResponse resp) {MyHandler handler = getHandler(req);try {if (handler == null) {resp.getWriter().print("<h1>404 NOT FOUND!</h1>");} else {Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();//定义一个参数的数组Object[] params = new Object[parameterTypes.length];for (int i = 0; i < parameterTypes.length; i++) {Class<?> parameterType = parameterTypes[i];if ("HttpServletRequest".equals(parameterType.getSimpleName())) {params[i] = req;} else if ("HttpServletResponse".equals(parameterType.getSimpleName())) {params[i] = resp;}}// 获取请求中的参数集合Map<String, String[]> parameterMap = req.getParameterMap();for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {String name = entry.getKey(); // "name"String value = entry.getValue()[0]; // "bruce"int index = hasRequestParam(handler.getMethod(), name);if (index != -1) {params[index] = value;} else {// 不加@RequestParam的情况List<String> names = getParameterNames(handler.getMethod());System.out.println(names);for (int i = 0; i < names.size(); i++) {if (name.equals(names.get(i))) {params[i] = value;break;}}}}//调用控制中的方法Object result = handler.getMethod().invoke(handler.getController(), params);if (result instanceof String) {// 跳转jspString viewName = (String) result;if (viewName.contains(":")) {// forward:/user.jspString viewType = viewName.split(":")[0];String viewPage = viewName.split(":")[1];if (viewType.equals("forward")) {req.getRequestDispatcher(viewPage).forward(req, resp);} else {// redirect:/user.jspresp.sendRedirect(viewPage);}} else {// 默认就是转发req.getRequestDispatcher(viewName).forward(req, resp);}} else {// 返回JSON数据Method method = handler.getMethod();if (method.isAnnotationPresent(ResponseBody.class)) {ObjectMapper objectMapper = new ObjectMapper();String json = objectMapper.writeValueAsString(result);resp.setContentType("text/html;charset=utf-8");PrintWriter out = resp.getWriter();out.print(json);out.flush();out.close();}}}} catch (Exception e) {e.printStackTrace();}}/*** 获取请求对应的handler** @return*/public MyHandler getHandler(HttpServletRequest req) {String requestURI = req.getRequestURI();for (MyHandler myHandler : handlerList) {if (myHandler.getUrl().equals(requestURI)) {return myHandler;}}return null;}/*** 判断控制器方法的参数是否有RequestParm注解,且找到对应的value值** @param method* @param name* @return*/public int hasRequestParam(Method method, String name) {Parameter[] parameters = method.getParameters(); //method:public void com.bruce.controller.UserController.findUsersfor (int i = 0; i < parameters.length; i++) {Parameter p = parameters[i];boolean b = p.isAnnotationPresent(RequestParm.class);if (b) {RequestParm requestParm = p.getAnnotation(RequestParm.class);String requestParmValue = requestParm.value();if (name.equals(requestParmValue)) {return i;}}}return -1;}/*** 获取控制器方法的参数的名字** @param method* @return*/public List<String> getParameterNames(Method method) {List<String> list = new ArrayList<>();for (Parameter parameter : method.getParameters()) {String parameterName = parameter.getName();list.add(parameterName);}return list;}
}
com.springmvc.xml包:
XmlParser
类:解析springmvc.xml文件
package com.springmvc.xml;import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;import java.io.InputStream;/*** @Author: Juechen* @Date: 2024/4/24* @Description: 解析springmvc.xml文件* @Version: 1.0**/
public class XmlParser {public static String getbasePackage(String xml) {try {SAXReader saxReader = new SAXReader();// 获取XML文件的输入流InputStream inputStream = XmlParser.class.getClassLoader().getResourceAsStream(xml);// 使用SAXReader解析XML,并将结果存储在Document对象中Document document = saxReader.read(inputStream);// 获取XML文档的根元素Element rootElement = document.getRootElement();// 在根元素中查找名为"component-scan"的子元素Element element = rootElement.element("component-scan");// 获取"component-scan"元素的名为"base-package"的属性Attribute attribute = element.attribute("base-package");// 返回"base-package"属性的文本值,即要扫描的基础包名return attribute.getText();} catch (DocumentException e) {e.printStackTrace();}return "";}}
springmvc.xml
文件:
<?xml version="1.0" encoding="UTF-8" ?>
<beans><!--设置使用注解的类所在的jar包--><component-scan base-package="com.bruce.service,com.bruce.controller"></component-scan>
</beans>
web.xml
文件:
<!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app><display-name>Archetype Created Web Application</display-name><!--SpringMVC的核心控制器--><servlet><servlet-name>DispatcherServlet</servlet-name><servlet-class>com.springmvc.servlet.DispatcherServlet</servlet-class><!--SpringMVC的配置文件--><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:springmvc.xml</param-value></init-param><!--Web服务器一旦启动,Servlet就会实例化创建对象,然后初始化--><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>DispatcherServlet</servlet-name><!--/表示所有请求都能被DispatcherServlet访问--><url-pattern>/</url-pattern></servlet-mapping></web-app>
user.jsp文件:
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head><title>User.jsp</title>
</head>
<body><h1>${requestScope.message}</h1>
</body>
</html>
index.jsp文件(自动生成的):
<html>
<body>
<h2>Hello World!</h2>
</body>
</html>
test
com.bruce.test包:
TestSpringMvc类:测试解析XML
package com.bruce.test;import com.springmvc.xml.XmlParser;
import org.junit.Test;/*** @Author: Juechen* @Date: 2024/4/24* @Description: TODO* @Version: 1.0**/
public class TestSpringMvc {@Testpublic void testreadXml() {String basePackage = XmlParser.getbasePackage("springmvc.xml");System.out.println(basePackage);}
}