1、DispatcherServlet
package 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
package 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
package 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
package 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
package 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
let 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();
}
});
}
};
