手写 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 可以访问文件资源的特性
具体代码没有细读, 就不说了.
来源: http://www.bubuko.com/infodetail-3098845.html