【SpringMVC原理】自己手动开发一个简易版SpringMVC框架

1 【骨架】项目框架搭建

使用Gradle进行依赖管理

  • 首先在Idea中选择File—>New—>Project,在左侧选择Gradle—>Java,填写一些项目信息,这里名为mini-spring,创建项目完成
  • 之后,在项目上右键New—>Module,在左侧选择Gradle—>Java,ArtifactId填为framework,创建模块完成
  • 重复上面的步骤,创建一个名为test的module,作为测试mini-spring功能的模块
  • 由于不需要在主模块下编写代码,删掉主目录下的src目录

至此,项目搭建完成,目录结构如下:在这里插入图片描述
要编写自己的spring框架需要先了解真正的spring的主要结构,如图:
在这里插入图片描述
因此我们的mini-spring结构如下:

  • 实现core模块,包括Beans、Core、Context包;
  • 实现web模块,集成Web和WebMVC
  • 添加starter,实现类springboot的启动方式

在framework模块中创建如下的package:
在这里插入图片描述
在test模块中添加启动类Application并添加main方法输出hello world语句。
在test模块中的build.gradle文件添加如下代码:

jar {
    manifest {
        attributes "Main-Class": "cn.edu.neusoft.Application"
    }
    from {
        configurations.compile.collect() {
            it.isDirectory() ? it : zipTree(it)
        }
    }
}

在terminal中使用gradle buildjava -jar命令查看是否成功输出hello world

为了把framework模块打包到test模块中,需要在dependencies中添加compile(project(':framework')),这样test模块打出的jar包就会包含framework模块的代码了

接着,在framework的starter包中建立一个类MiniApplication,添加如下方法:

    public static void run(Class<?> clz, String[] args) {
        System.out.println("hello mini-spring");
    }

这样做是模拟了springboot的启动方法,在test的Application类的main方法中调用MiniApplication.run(Application.class, args);
重新执行gradle buildjava -jar命令查看是否成功输出hello world和hello mini-spring

2 【载体】集成服务器之Web服务器&Servlet

在这里插入图片描述
由于服务器进程的编写涉及到Java的Socket编程、HTTP请求的封装和服务器IO等知识,较为复杂,这里我们的框架仿照springboot的方式内置一个服务器(Tomcat)。

集成Tomcat
① 首先要为容器引入一个Tomcat包,这里选择目前比较流行的8.5.23版本。在framework模块的build.gradle文件的dependencies中引入compile group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '8.5.23'
②在web包下新建一个server子包并创建TomcatServer类,添加如下代码:

public class TomcatServer {
    //tomcat配置
    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();
        //防止服务器中途退出
        Thread awaitThread = new Thread("tomcat_await_thread") {
            @Override
            public void run() {
                //声明tomcat一直在等待
                TomcatServer.this.tomcat.getServer().await();
            }
        };
        //设置线程为非守护线程
        awaitThread.setDaemon(false);
        awaitThread.start();
    }
}

③在starter包下的MiniApplication类中添加

TomcatServer tomcatServer = new TomcatServer(args);
        try {
            tomcatServer.startServer();
        } catch (LifecycleException e) {
            e.printStackTrace();
        }

gradle build clean后再执行java -jar test-1.0-SNAPSHOT.jar看到控制台输出tomcat信息代表程序编写成功(此时浏览器访问http://localhost:6699会404,是因为我们还没有编写servlet处理请求)
⑤修改TomcatServer类的代码

public class TomcatServer {
    //tomcat配置
    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();

        //建立tomcat与servlet的联系
        Context context = new StandardContext();
        context.setPath("");
        context.addLifecycleListener(new Tomcat.FixContextListener());
        TestServlet servlet = new TestServlet();
        Tomcat.addServlet(context, "TestServlet", servlet).setAsyncSupported(true);
        context.addServletMappingDecoded("/test.json", "TestServlet");
        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://localhost:6699/test.json,看到输出test servlet!,服务器正常响应

3 【入口】控制器Controller的实现

①修改TestServlet类名为DispatcherServlet(这里注意记得修改代码中其它引用了TestServlet的地方),修改context.addServletMappingDecoded("/test.json", "TestServlet");context.addServletMappingDecoded("/", "DispatcherServlet");
②在web包中添加mvc包,并添加@Controller、@RequestMapping、@RequestParam三个常用的注解,代码如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Controller {
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequestMapping {
    String value();
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface RequestParam {
    String value();
}

③在test模块中创建controllers包,并创建一个SalaryController类用来测试我们实现的注解

@Controller
public class SalaryController {
    @RequestMapping("getSalary.do")
    public Integer getSalary(@RequestParam("name") String name, @RequestParam("experience") String experience) {
        return 1000;
    }
}

④在core包中添加一个ClassScanner类添加如下代码:

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();
            //处理资源类型为jar包的类型
            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();
            if (entryName.startsWith(path) && entryName.endsWith(".class")) {
                String classFullName = entryName.replace("/", ".").substring(0, entryName.length() - 6);
                classes.add(Class.forName(classFullName));
            }
        }
        return classes;
    }
}

⑤在框架入口类MiniApplication中修改run()方法为:

public static void run(Class<?> clz, String[] args) {
        System.out.println("hello mini-spring");
        TomcatServer tomcatServer = new TomcatServer(args);
        try {
            tomcatServer.startServer();
            List<Class<?>> classList = ClassScanner.scanClasses(clz.getPackage().getName());
            classList.forEach(it -> System.out.println(it.getName()));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

⑥运行结果
在这里插入图片描述
⑦在web包中添加handler包,添加MappingHandler类,代码如下:

public class MappingHandler {
    private String uri;
    private Method method;
    private Class<?> controller;
    private String[] args;

    MappingHandler(String uri, Method method, Class<?> clz, String[] args) {
        this.uri = uri;
        this.method = method;
        this.controller = clz;
        this.args = args;
    }

    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 = controller.newInstance();
        Object response = method.invoke(ctl, parameters);
        res.getWriter().println(response.toString());
        return true;
    }
}

⑧在handler包中添加HandlerManager类,代码如下:

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) {
            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);
        }
    }
}

⑨在DispatcherServlet中的service方法中使用这些handler:

    @Override
    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();
            }
        }
    }

⑩别忘了在MiniApplication中调用HandlerManager初始化所有的MappingHandler,添加HandlerManager.resolveMappingHandler(classList);

最后打包运行并在浏览器中访问localhost:6699/getSalary.do,输出1000,成功。

4 【基石】Bean管理(IOC&DI)

①仿照sring添加两个bean相关的注解,在beans包下编写如下代码:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface AutoWired {
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Bean {
}

②创建一个bean工厂,编写如下代码:

public class BeanFactory {
    private static Map<Class<?>, Object> classToBean = new ConcurrentHashMap<>();

    public static Object getBean(Class<?> cls) {
        return classToBean.get(cls);
    }

    public static void initBean(List<Class<?>> classList) throws Exception {
        List<Class<?>> toCreate = new ArrayList<>();
        while (toCreate.size() != 0) {
            int remainSize = toCreate.size();
            for (int i = 0; i < toCreate.size(); i++) {
                if (finishCreate(toCreate.get(i))) {
                    toCreate.remove(i);
                }
            }
            if (toCreate.size() == remainSize) {
                throw new Exception("cycle dependency");
            }
        }
    }

    private static boolean finishCreate(Class<?> cls) throws IllegalAccessException, InstantiationException {
        if (!cls.isAnnotationPresent(Bean.class) && !cls.isAnnotationPresent(Controller.class)) {
            return true;
        }

        Object bean = cls.newInstance();
        for (Field field : cls.getDeclaredFields()) {
            if (field.isAnnotationPresent(AutoWired.class)) {
                Class<?> filedType = field.getType();
                Object reliantBean = BeanFactory.getBean(filedType);
                if (reliantBean == null) {
                    return false;
                }
                field.setAccessible(true);
                field.set(bean, reliantBean);
            }
        }
        classToBean.put(cls, bean);
        return true;
    }
}

③在启动类中使用bean工厂初始化类,修改代码如下:

try {
            tomcatServer.startServer();
            List<Class<?>> classList = ClassScanner.scanClasses(clz.getPackage().getName());
            BeanFactory.initBean(classList);
            HandlerManager.resolveMappingHandler(classList);
            classList.forEach(it -> System.out.println(it.getName()));
        }

④修改MappingHandler中的Object ctl = controller.newInstance();Object ctl = BeanFactory.getBean(controller);
⑤在test模块中添加service包,并添加一个测试类:

@Bean
public class SalaryService {
    public Integer calSalary(Integer experience) {
        return experience * 500;
    }
}

⑥修改SalaryController的代码:

@Controller
public class SalaryController {
    @AutoWired
    private SalaryService salaryService;

    @RequestMapping("getSalary.do")
    public Integer getSalary(@RequestParam("name") String name, @RequestParam("experience") String experience) {
        System.out.println("执行了controller方法");
        return salaryService.calSalary(Integer.parseInt(experience));
    }
}

⑦编译运行项目在浏览器访问http://localhost:6699/getSalary.do?experience=3&name=aa返回对应的计算结果,成功

至此,我们的简易版SpringMVC的编写就完成了
我已经将完整代码放在Github上了
https://github.com/Fieeeeee/MySpringMVC.git
谢谢

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章