1、DispatcherServlet
javapackage com.csdn.mymvc.core; import jakarta.servlet.RequestDispatcher; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.junit.Test; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Map; @WebServlet("/*") public class DispatcherServlet extends HttpServlet { private final String BEAN_FACTORY = "beanFactory"; private final String CONTROLLER_BEAN_MAP = "controllerBeanMap"; @Test public void uri() { String uri = "/fruit/index"; String[] arr = uri.split("/"); System.out.println(Arrays.toString(arr));//[, fruit, index] } @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String[] staticResourceSuffixes = {".html", ".jsp", ".jpg", ".png", ".gif", ".css", ".js", ".ico"}; String uri = req.getRequestURI(); if (Arrays.stream(staticResourceSuffixes).anyMatch(uri::endsWith)) { RequestDispatcher defaultDispatcher = req.getServletContext().getNamedDispatcher("default"); defaultDispatcher.forward(req, resp); } else { String[] arr = uri.split("/"); if (arr == null || arr.length != 3) { throw new RuntimeException(uri + "非法!"); } //[, fruit, index] String requestMapping = "/" + arr[1]; String methodMapping = "/" + arr[2]; ServletContext application = getServletContext(); ControllerDefinition controllerDefinition = ((Map<String, ControllerDefinition>) application.getAttribute(CONTROLLER_BEAN_MAP)).get(requestMapping); if (controllerDefinition == null) { throw new RuntimeException(requestMapping + "对应的controller组件不存在!"); } //获取请求方式,例如:get或者post String requestMethodStr = req.getMethod().toLowerCase(); //get_/index Method method = controllerDefinition.getMethodMappingMap().get(requestMethodStr + "_" + methodMapping); Object controllerBean = controllerDefinition.getControllerBean(); try { //调用controllerBean对象中的method方法 method.setAccessible(true); method.invoke(controllerBean, req, resp); } catch (IllegalAccessException e) { e.printStackTrace(); throw new RuntimeException(e); } catch (InvocationTargetException e) { e.printStackTrace(); throw new RuntimeException(e); } } } }
2、ControllerDefinition
javapackage com.csdn.mymvc.core; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; //假设有一个uri是:/fruit/index @Data @NoArgsConstructor @AllArgsConstructor public class ControllerDefinition { private String requestMapping; private Object controllerBean; private Map<String, Method> methodMappingMap = new HashMap<>(); }
3、ComponentScan
javapackage com.csdn.mymvc.core; import com.csdn.mymvc.annotation.*; import java.io.File; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; import java.util.*; public class ComponentScan { public static Map<String, Object> beanFactory = new HashMap<>(); public static Map<String, ControllerDefinition> controllerBeanMap = new HashMap<>(); static String path = null; static { //分析文件夹 path = ComponentScan.class.getClassLoader().getResource("").getPath(); // /F:/IdeaProjects/workspace/review/pro13-fruit-DispatcherServlet/target/ // pro13-fruit-DispatcherServlet-1.0-SNAPSHOT/WEB-INF/classes/ //计算机的硬盘根目录是 / ,不论是什么操作系统。只是微软人为的分出盘符的概念 //System.out.println(path); path = path.substring(1); //System.out.println(path); // F:/IdeaProjects/workspace/review/pro13-fruit-DispatcherServlet/target // /pro13-fruit-DispatcherServlet-1.0-SNAPSHOT/WEB-INF/classes/ File rootDir = new File(path); //开始解析文件夹 - 组件扫描工作开始 try { //第 1 步:扫描类路径,解析出所有的bean实例,存放到IOC容器中(beanFactory) parseFile(rootDir); beanFactory.values().forEach(System.out::println); //第 2 步:经过第 1 步,所有的bean实例已经创建就绪,但是bean和bean之间的依赖关系没有注入(Injection) //本步骤实现 注入依赖关系 beanFactory.values().forEach(bean -> { //获取bean内部所有的field Field[] fields = bean.getClass().getDeclaredFields(); //获取每一个field上的注解信息 Arrays.stream(fields) .filter(field -> field.getDeclaredAnnotation(Autowire.class) != null) .forEach(field -> { //获取这个字段的类型的名称 String fieldTypeName = field.getType().getName(); //System.out.println(fieldTypeName); Object filedValue = beanFactory.values().stream().filter(instance -> { return field.getType().isAssignableFrom(instance.getClass()); }).findFirst().orElseThrow(() -> new RuntimeException(fieldTypeName + "装配失败!")); try { field.setAccessible(true); field.set(bean, filedValue); } catch (IllegalAccessException e) { throw new RuntimeException(e); } }); }); //第 3 步:经过前两个步骤:IOC容器中已经准备好了所有的bean实例。并且bean实例之间的依赖关系也注入完成 //这一步需要实现的是:uri是:/fruit/index 我们需要实现的是将uri中的两个标识分别映射到具体的controller实例以及controller方法上去 //简单讲,这一步需要完成将每一个Controller都要存放到controllerBeanMap中 beanFactory.values().stream() .filter(bean -> bean.getClass().getDeclaredAnnotation(RequestMapping.class) != null) .forEach(bean->{ ControllerDefinition controllerDefinition = new ControllerDefinition(); String requestMapping = bean.getClass().getDeclaredAnnotation(RequestMapping.class).value(); Object controllerBean = bean; controllerDefinition.setRequestMapping(requestMapping); controllerDefinition.setControllerBean(controllerBean); //开始分析bean中的每一个方法 Arrays.stream(bean.getClass().getDeclaredMethods()).forEach(method -> { GetMapping getMappingAnnotation = method.getDeclaredAnnotation(GetMapping.class); String methodMapping = null; if (getMappingAnnotation != null) { methodMapping = getMappingAnnotation.value(); methodMapping = "get_" + methodMapping; } PostMapping postMappingAnnotation = method.getDeclaredAnnotation(PostMapping.class); if (postMappingAnnotation != null) { methodMapping = postMappingAnnotation.value(); methodMapping = "post_" + methodMapping; } if (methodMapping != null) { controllerDefinition.getMethodMappingMap().put(methodMapping, method); } }); //将这个controllerDefinition存放到专门的Controller容器中 controllerBeanMap.put(requestMapping, controllerDefinition); }); System.out.println(beanFactory); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } private static void parseFile(File file) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { if (file.exists()) { if (file.isDirectory()) { //获取所有的子目录 File[] childFiles = file.listFiles(); for (File childFile : childFiles) { parseFile(childFile); } } else { String absPath = file.getAbsolutePath(); //System.out.println(absPath); String fullClassPath = absPath.substring(path.length()); //System.out.println(fullClassPath); if (fullClassPath.endsWith(".class")) { String fullClassPathName = fullClassPath.substring(0, fullClassPath.length() - ".class".length()); //System.out.println(fullClassPathName); String fullClassName = fullClassPathName.replaceAll("\\\\", "."); //System.out.println(fullClassName); Class<?> clazz = Class.forName(fullClassName); //System.out.println(clazz.toString()); if (clazz.toString().startsWith("class")) { //排除掉接口、注解....,只关心class if (!Modifier.isAbstract(clazz.getModifiers())) { //排除掉抽象类 Optional<Annotation> optional = Arrays.stream(clazz.getDeclaredAnnotations()).filter(annotation -> { return (annotation instanceof Controller || annotation instanceof Service || annotation instanceof Repository); }).findFirst(); if (!optional.isEmpty()) { Object bean = clazz.getDeclaredConstructor().newInstance(); beanFactory.put(fullClassName, bean); } } } } } } } }
4、ContextLoaderListener
javapackage com.csdn.mymvc.listener; import com.csdn.mymvc.core.ComponentScan; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletContextEvent; import jakarta.servlet.ServletContextListener; import jakarta.servlet.annotation.WebListener; @WebListener public class ContextLoaderListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { try { Class.forName("com.csdn.mymvc.core.ComponentScan"); ServletContext application = sce.getServletContext(); application.setAttribute("beanFactory", ComponentScan.beanFactory); application.setAttribute("controllerBeanMap", ComponentScan.controllerBeanMap); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } }
5、FruitController
javapackage com.csdn.fruit.controller; import com.csdn.fruit.dto.PageInfo; import com.csdn.fruit.dto.PageQueryParam; import com.csdn.fruit.dto.Result; import com.csdn.fruit.pojo.Fruit; import com.csdn.fruit.service.FruitService; import com.csdn.fruit.util.RequestUtil; import com.csdn.fruit.util.ResponseUtil; import com.csdn.mymvc.annotation.*; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; @Controller @RequestMapping("/fruit") public class FruitController { @Autowire private FruitService fruitService; @GetMapping("/index") protected void index(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Integer pageNo = 1; String pageNoStr = req.getParameter("pageNo"); if (pageNoStr != null && !"".equals(pageNoStr)) { pageNo = Integer.parseInt(pageNoStr); } String keyword = ""; String keywordStr = req.getParameter("keyword"); if (keywordStr != null) { keyword = keywordStr; } PageQueryParam pageQueryParam = new PageQueryParam(pageNo, 5, keyword); PageInfo<Fruit> pageInfo = fruitService.getFruitPageInfo(pageQueryParam); Result result = Result.OK(pageInfo); ResponseUtil.print(resp, result); } @PostMapping("/add") protected void add(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Fruit fruit = (Fruit) RequestUtil.readObject(req, Fruit.class); fruitService.addFruit(fruit); ResponseUtil.print(resp, Result.OK()); } @GetMapping("/del") protected void del(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Integer fid = Integer.parseInt(req.getParameter("fid")); fruitService.delFruit(fid); ResponseUtil.print(resp, Result.OK()); } @GetMapping("/edit") protected void edit(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Integer fid = Integer.parseInt(req.getParameter("fid")); Fruit fruit = fruitService.getFruitById(fid); ResponseUtil.print(resp, Result.OK(fruit)); } @GetMapping("/getFname") public void getFname(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String fname = req.getParameter("fname"); Fruit fruit = fruitService.getFruitByFname(fname); ResponseUtil.print(resp, fruit == null ? Result.OK() : Result.Fail()); } @PostMapping("/update") protected void update(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Fruit fruit = (Fruit) RequestUtil.readObject(req, Fruit.class); fruitService.updateFruit(fruit); ResponseUtil.print(resp, Result.OK()); } }
6、有一个小bug,查询的时候需要重置为首页
6.1、index.js
javascriptlet pageNo = 1; let pageCount = 0; let keyword="" //当页面加载完成,执行匿名函数 window.onload=function(){ loadData(); } function search() { keyword=$("#keyword").value pageNo = 1; loadData(pageNo) } function page(str) { if (str) { if (str == "first") { pageNo = 1; }else if (str == "pre") { pageNo = pageNo - 1; }else if (str == "next") { pageNo = pageNo + 1; }else if (str == "last") { pageNo = pageCount; } if (pageNo > pageCount) { pageNo=pageCount } if (pageNo <= 0) { pageNo=1 } } loadData(pageNo) } loadData=function(pageNo=1){//pageNo这个参数有默认值,如果没有传值,则使用默认值 1 axios({ method: 'get', url: '/fruit/index', params: { pageNo: pageNo, keyword:keyword } }).then(response => { debugger let fruitList = response.data.data.list pageNo = response.data.data.pageNo pageCount = response.data.data.pageCount // 此处使用的是axios,那么响应回来的数据自动就是json, // 不需要再进行parse(如果是原始的ajax操作,那么一定需要parse) // let fruitArr = JSON.parse(fruitList) let fruitArr = fruitList; let fruitTbl = $("#fruit_tbl") //向表格中添加行之前,先删除原来的行 let rows=fruitTbl.rows for (let i = rows.length - 1; i >= 1; i--) { fruitTbl.deleteRow(i); } for (let i = 0; i < fruitArr.length; i++) { let tr = fruitTbl.insertRow(); let fnameTD = tr.insertCell(); let priceTD = tr.insertCell(); let fcountTD = tr.insertCell(); let operTD = tr.insertCell(); let fruit = fruitArr[i]; //fnameTD.innerText = fruit.fname fnameTD.innerHTML = '<a href="edit.html?fid=' + fruit.fid + '">' + fruit.fname + '</a>'; priceTD.innerText = fruit.price; fcountTD.innerText = fruit.fcount; operTD.innerHTML = "<img class=\"delImg\" src=\"imgs/del.png\" onclick=\"delFruit(" + fruit.fid + ")\"/>"; } }); } delFruit = function (fid) { if (window.confirm('是否确认删除?')) { axios({ method: 'get', url: '/fruit/del', params:{ fid: fid, } }).then(response=>{ if (response.data.flag) { window.location.reload(); } }); } };