手写Spring mvc框架
一、依赖管理工具
Maven、Gradle
优点:
- 自动化管理依赖(jar包)
- 解决依赖冲突
- 打包(mvn clean package)
特性:
-
约定大于配置
比如src是约定好的代码目录
-
同一个项目可以有很多模块,每个模块单独构建
-
插件机制
Gradle比起Maven的优点:
- 使用json配置
- 不用安装
二、使用代码集成tomcat
-
引入jar包(Tomcat Embed Core)
-
Code:
public class TomcatServer { private Tomcat tomcat; private String[] args; public TomcatServer(String[] args) { this.args = args; } public void startServer() throws LifecycleException { tomcat = new Tomcat(); tomcat.setPort(6699); tomcat.start(); Context context = new StandardContext(); context.setPath(""); context.addLifecycleListener(new Tomcat.FixContextListener()); DispatcherServlet servlet = new DispatcherServlet(); Tomcat.addServlet(context, "dispatcherServlet", servlet).setAsyncSupported(true); // "/":映射所有的url context.addServletMappingDecoded("/", "dispatcherServlet"); tomcat.getHost().addChild(context); Thread awaitThread = new Thread("tomcat_await_thread"){ @Override public void run() { //内部类调用外部类的实例成员变量 //这行代码会阻塞,作用难道是让tomcat不关闭? TomcatServer.this.tomcat.getServer().await(); } }; awaitThread.setDaemon(false); awaitThread.start(); } }
三、一个请求的流程
-
前端请求经由http协议发送到tomcat监听的端口上,tomcat将请求经由解码、反序列化等操作封装成ServletRequest对象,然后会调用DispatcherServlet的
service()
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { for (MappingHandler mappingHandler : HandlerManager.mappingHandlerList) { try { if (mappingHandler.handle(req, res)) { return; } } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } }
可以看到代码中只有一个操作:遍历mappingHandlerList
-
对请求url进行匹配,寻找对应的函数进行处理
mappingHandlerList保存的是mappingHandler,mappingHandler中存储了请求path和对应执行函数等信息
(ps:由于path很少,代码中没有用map,而是直接遍历了)
我们先看建立mappingHandlerList的建立过程,它先遍历出注解了
@controller
的类,再遍历@RequestMapping
对应的方法,最后建立MappingHandler对象,将其加入list中public class HandlerManager { public static List<MappingHandler> mappingHandlerList = new ArrayList<>(); //项目初始化时,这个静态方法会被调用 public static void resolveMappingHandler(List<Class<?>> classList) { for (Class<?> cls : classList) { if (cls.isAnnotationPresent(Controller.class)) { parseHandlerFromController(cls); } } } private static void parseHandlerFromController(Class<?> cls) { Method[] methods = cls.getDeclaredMethods(); for (Method method : methods) { //寻找加了@RequestMapping的函数 if (!method.isAnnotationPresent(RequestMapping.class)) { continue; } String uri = method.getDeclaredAnnotation(RequestMapping.class).value(); List<String> paramNameList = new ArrayList<>(); for (Parameter parameter : method.getParameters()) { if (parameter.isAnnotationPresent(RequestParam.class)) { paramNameList.add(parameter.getDeclaredAnnotation(RequestParam.class).value()); } } String[] params = paramNameList.toArray(new String[paramNameList.size()]); MappingHandler mappingHandler = new MappingHandler(uri, method, cls, params); HandlerManager.mappingHandlerList.add(mappingHandler); } } }
@Controller,@RequestMapping,@RequestParam
注解定义的代码暂且省略。好了,请求来到了mappingHandler:
public class MappingHandler { private String uri; private Method method; private Class<?> controller; private String[] args; //这个是添加了@RequestParam的参数的参数列表 public boolean handle(ServletRequest req, ServletResponse res) throws IllegalAccessException, InstantiationException, InvocationTargetException, IOException { String requestUri = ((HttpServletRequest) req).getRequestURI(); if (!uri.equals(requestUri)) { return false; } Object[] parameters = new Object[args.length]; for (int i=0;i<args.length;i++) { parameters[i] = req.getParameter(args[i]); } Object ctl = BeanFactory.getBean(controller); Object response = method.invoke(ctl, parameters); //将返回结果写入ServletResponse对象 res.getWriter().println(response.toString()); return true; } MappingHandler(String uri, Method method, Class<?> cls, String[] args) { this.uri = uri; this.method = method; this.controller = cls; this.args = args; } }
写入了数据的ServletResponse对象会转成字节发到前端,这个流程就结束了
四、项目的初始化
为啥我要最后再讲初始化,,似乎更好理解?
回想我们使用spring的时候都会有一个Application类,就是下面这个熟悉的东西:
public class Application {
public static void main(String[] args) {
MiniApplication.run(Application.class, args);
}
}
我们来看看MiniApplication.run(Application.class, args)
public static void run(Class<?> cls, String[] args) {
System.out.println("Hello mini-spring!");
TomcatServer tomcatServer = new TomcatServer(args);
try {
//1、启动服务器
tomcatServer.startServer();
//2、扫描src中的类文件
List<Class<?>> classList = ClassScanner.scanClasses(cls.getPackage().getName());
//3、初始化BeanFactory
BeanFactory.initBean(classList);
//4、建立mappingHandlerList
HandlerManager.resolveMappingHandler(classList);
//5、打印类信息
classList.forEach(it-> System.out.println(it.getName()));
} catch (Exception e) {
e.printStackTrace();
}
}
其中第2步需要细说一下:
-
先说一个注意点,我们传入的参数是:
cls.getPackage().getName()
就是说只会去扫描该包下的类文件,也就是说为什么我们要把Application.java文件放在最外面
public class ClassScanner {
public static List<Class<?>> scanClasses(String packageName) throws IOException, ClassNotFoundException {
List<Class<?>> classList = new ArrayList<>();
String path = packageName.replace(".", "/");
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Enumeration<URL> resources = classLoader.getResources(path);
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
if (resource.getProtocol().contains("jar")) {
JarURLConnection jarURLConnection = (JarURLConnection) resource.openConnection();
String jarFilePath = jarURLConnection.getJarFile().getName();
classList.addAll(getClassesFromJar(jarFilePath, path));
}else {
// todo
}
}
return classList;
}
private static List<Class<?>> getClassesFromJar(String jarFilePath, String path) throws IOException, ClassNotFoundException {
List<Class<?>> classes = new ArrayList<>();
JarFile jarFile = new JarFile(jarFilePath);
Enumeration<JarEntry> jarEntries = jarFile.entries();
while (jarEntries.hasMoreElements()) {
JarEntry jarEntry = jarEntries.nextElement();
String entryName = jarEntry.getName();// com/mooc/zbs/test/Test.class
if (entryName.startsWith(path) && entryName.endsWith(".class")) {
String classFullName = entryName.replace("/", ".").substring(0, entryName.length() - 6);
classes.add(Class.forName(classFullName));
}
}
return classes;
}
}
借助了classLoader可以访问文件资源的特性
具体代码没有细读,就不说了。