手写简单Spring mvc,原理剖析

mac2025-11-02  17

手写简单版spring mvc

准备工作搭建框架注解文件service和controller层 浇灌创建自己的DispatcherServlet类扫包,获取类名,都有哪些类扫class,通过上面注解MyController,MyService实例化,实例化bean扫bean实例,获得class,根据MyAutowired往类中射入所需(成员)变量将url中的地址映射到方法重写doPost方法,将请求对应相应的方法 自己写 最近要找工作了回忆一下spring,写了一个简易版的spring mvc。代码都上传到了github,请大家下载点星,感谢!! https://github.com/kangwenzhuang/Springmvc 个人理解,观点可能片面,欢迎在评论区喷我,这样我才能成长 适合新手,高手请绕开

准备工作

maven项目 pom.xml

<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency>

既然是手写mvc那就不引入spring包,这里不手写servlet,而是直接添加依赖 需要涉及的知识:web编程,反射,注解

搭建框架

注解文件

1.MyAutowired.java,作用在类的成员变量上面,实现注入的标识

package com.kang.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface MyAutowired { String value() default ""; }

2.MyController.java作用在类上面,作为bean的标识

package com.kang.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MyController { String value() default ""; }

3.MyService.java作用在类上面,作为bean的标识

package com.kang.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MyService { String value() default ""; }

4.MyRequestMapping.java作用在控制层的类和方法上,作为映射的地址

package com.kang.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyRequestMapping { String value() default ""; }

5.MyRequestParam.java作用在控制层方法上的参数,作为请求的参数

package com.kang.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface MyRequestParam { String value() default ""; }

service和controller层

1.创建service接口

package com.kang.service; public interface UserService { String result(String name,String age); }

2.实现service接口

package com.kang.service.serviceImpl; import com.kang.annotation.MyService; import com.kang.service.UserService; @MyService("userService") public class UserServiceImpl implements UserService { public String result(String name, String age) { return "name:" + name + '\n' + "age:" + age; } }

3.控制层

package com.kang.controller; import com.kang.annotation.MyAutowired; import com.kang.annotation.MyController; import com.kang.annotation.MyRequestMapping; import com.kang.annotation.MyRequestParam; import com.kang.service.UserService; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @MyRequestMapping("/user") @MyController("userController") public class UserController { @MyAutowired("userService") private UserService userService; @MyRequestMapping("/hello") public void result(HttpServletRequest request, HttpServletResponse response, @MyRequestParam("name") String name, @MyRequestParam("age") String age) { String result= userService.result(name, age); try { PrintWriter pw=response.getWriter(); pw.write(result); } catch (IOException e) { e.printStackTrace(); } } }

这样就可以了,当然不行啊,这只是搭建了一个框架,空壳子一个,不能用的

浇灌

springmvc也只是一个web项目,肯定要有web.xml,通过url进行访问获取 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> <servlet> <servlet-name>DispatcherServlet</servlet-name> <servlet-class>com.kang.DispatcherServlet</servlet-class> <load-on-startup>0</load-on-startup> </servlet> <servlet-mapping> <servlet-name>DispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>

创建自己的DispatcherServlet类

package com.kang; public class DispatcherServlet extends HttpServlet { public void init(ServletConfig config) { } protected void doGet(HttpServletRequest req, HttpServletResponse res) { this.doPost(req, res); } protected void doPost(HttpServletRequest req, HttpServletResponse res) { } }

扫包,获取类名,都有哪些类

List<String> classNames = new ArrayList<String>(); void doScan(String packageName) { String pn=packageName; URL url = this.getClass().getClassLoader().getResource(packageName.replace(".", "/")); String fileStr = url.getFile(); File file = new File(fileStr); String[] filesStr = file.list(); for (String path : filesStr) { File filePath = new File(fileStr + path); if (filePath.isDirectory()) { doScan((packageName + "." + path)); } else { classNames.add(packageName + "." + filePath.getName()); } } }

我们获取包的方式受到springboot中启发,直接扫描DispatcherServlet 根目录 String packageName = this.getClass().getPackage().getName();

扫class,通过上面注解MyController,MyService实例化,实例化bean

HashMap<String, Object> beans = new HashMap<String, Object>(); void doInstance() { for (String className : classNames) { String cn = className.replace(".class", ""); try { Class<?> clazz = Class.forName(cn); if (clazz.isAnnotationPresent(MyController.class)) { Object instance = clazz.newInstance(); MyController mc = clazz.getAnnotation(MyController.class); String key = mc.value(); beans.put(key, instance); } else if (clazz.isAnnotationPresent(MyService.class)) { Object instance = clazz.newInstance(); MyService ms = clazz.getAnnotation(MyService.class); String key = ms.value(); beans.put(key, instance); } else { continue; } } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { e.printStackTrace(); } } }

扫bean实例,获得class,根据MyAutowired往类中射入所需(成员)变量

void doAutowired() { for (Map.Entry<String, Object> m : beans.entrySet()) { Class<?> clazz = m.getValue().getClass(); if (clazz.isAnnotationPresent(MyController.class)) { Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(MyAutowired.class)) { MyAutowired ma = field.getAnnotation(MyAutowired.class); String key = ma.value(); Object object = beans.get(key); field.setAccessible(true); try { field.set(m.getValue(), object); } catch (IllegalAccessException e) { e.printStackTrace(); } } else { continue; } } } else { continue; } } }

将url中的地址映射到方法

HashMap<String, Object> handlerMaps = new HashMap<String, Object>(); void urlHanding() { for (Map.Entry<String, Object> m : beans.entrySet()) { Object object = m.getValue(); Class<?> clazz = object.getClass(); if (clazz.isAnnotationPresent(MyController.class)) { MyRequestMapping mr = clazz.getAnnotation(MyRequestMapping.class); String path1 = mr.value();//得到/user路径 Method[] methods = clazz.getMethods(); for (Method mt : methods) { if (mt.isAnnotationPresent(MyRequestMapping.class)) { MyRequestMapping q = mt.getAnnotation(MyRequestMapping.class); String path2 = q.value();//得到/hello handlerMaps.put(path1 + path2+"/", mt);//把路径和对应的方法保存起来,然后在doGet中实现 } else { continue; } } } } }

重写doPost方法,将请求对应相应的方法

protected void doPost(HttpServletRequest req, HttpServletResponse res) { String uri = req.getRequestURI(); Method method = (Method) handlerMaps.get(uri); Object object = null; Here: //获取拥有该方法的对象,该对象拥有注解有@MyRequestMapping,且为url //先获得类上的@MyRequestMapping(/user),然后看有没有方法注解@MyRequestMapping(/hello),只有同时拥有就找到了bean,不知道是不是还有更简便的方法,欢迎指导 for (Map.Entry<String, Object> map : beans.entrySet()) { Class<?> clazz = map.getValue().getClass(); if (clazz.isAnnotationPresent(MyController.class)) { if (clazz.isAnnotationPresent(MyRequestMapping.class)) { if (clazz.getAnnotation(MyRequestMapping.class).value().equals("/" + uri.split("/")[1])) { Method[] methods = clazz.getMethods(); for (Method med : methods) { if (med.isAnnotationPresent(MyRequestMapping.class)) { MyRequestMapping ma = med.getAnnotation(MyRequestMapping.class); if (ma.value().equals("/" + uri.split("/")[2])) { object = map.getValue(); break Here; } } else { continue; } } } } } } //首先要获取参数列表getArgs,参数获取再次用到反射 try { method.invoke(object, getArgs(req,res,method)); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } Object[] getArgs(HttpServletRequest req, HttpServletResponse res,Method method){ Class<?>[] paramClazzs=method.getParameterTypes(); Object[] args=new Object[paramClazzs.length]; int i=0; int index=0; for(Class<?> paramclazz:paramClazzs){ if(ServletRequest.class.isAssignableFrom(paramclazz)){ args[i++]=req; } if(ServletResponse.class.isAssignableFrom(paramclazz)){ args[i++]=res; } Annotation[] paramAns=method.getParameterAnnotations()[index];//第0和1个的时候长度为0,因为参数前面没有注解,第3和4个参数前面有注解,因为每个参数的注解可以有多个,所以二维数组 if(paramAns.length>0){ for(Annotation paramAn:paramAns){ if(MyRequestParam.class.isAssignableFrom(paramAn.getClass())){ MyRequestParam rp=(MyRequestParam) paramAn; args[i++]=req.getParameter(rp.value()); } } } index++; } return args; }

Done!!!

自己写

如果我想写一个Responsebody注解,返回json格式,那怎么办呢?这就交给你自己了 思路: 1.获取方法返回值 Object object=method.invoke(object, getArgs(req,res,method)); 2.使用阿里爸爸的fastjson包解析object转json,具体的自己完成 json=f(object); 3.设置res的返回格式 res.setCharacterEncoding(“utf-8”); res.setContentType(“application/json; charset=utf-8”); PrintWriter pw = resp.getWriter(); pw.write(json);

最新回复(0)