Handwritten Mini SpringMVC framework

Foreword

Learn how to use Spring, SpringMVC is very fast, but in the process used in the future what will inevitably want to explore what is behind the framework of principles, this article will explain how handwriting by a simple version of the springMVC frame, frame directly from the code point of view the distribution request, and dependency inversion control injection is how to achieve.

Recommendations with sample source code reading, github at the following address:

https://github.com/liuyj24/mini-spring

Project to build

Github project to build the project can refer to, to choose the right jar package management tool, Maven and Gradle will do this project using Gradle.

Under item and then build two modules, it is a framework, used to write a frame; the other is a test, and test an application framework (note depends framework test modules module).

Subsequently created beans, core, context, web modules in accordance with the corresponding packet in the spring module framework, after the completion of write access to the frame.

Request distribution

Speaking before the first request distribution model to comb the entire web:

  1. First, the user client sends a request to the server, parsed by the operating system's TCP / IP stack will be handed in to a web server listening port.
  2. web server listens to the request after the request will be distributed to corresponding processing procedures. Such as Tomcat java program will request (the servlet) corresponding to distributed processing, web server itself is not processing a request.

The project selection Tomcat web server, and in order to make the project run directly up, select the embedded Tomcat in the project, such as spring boot framework will be able to start doing as a key test of time, to facilitate testing.

Servlet

Since the choice to use Java to write server-side program, it would have to mention the Servlet interface. In order to standardize communication between the server and the Java program, the official Java Servlet specification developed, server-side Java application must implement this interface, the language of Java as a server process must also be received and according to the Servlet specification.

Before yet the Spring , so it is developing web applications are: a business logic corresponds to a Servlet, it is a big project there will be multiple Servlet, which will be a large number of Servlet configuration to a configuration file called web.xml, when the server is running, tomcat web.xml file will find the Servlet corresponding to the service request handling according to the request uri.

But you want, each to a request to create a Servlet, and a Servlet implementation class, we usually only rewrite a service method, in addition to the four methods are just empty achieve this too a waste of resources. And braid program to create a lot of Servlet also difficult to manage. Can we improve it?

Spring的DispatcherServlet

The method does have:

We can see from the chart, we turned out to be through a web server to distribute requests to different Servlet; we can change in thinking, so that the web server sends the request to a Servlet, and then by the Servlet in accordance with the request uri distributed in different ways for processing.

As a result, no matter what the request is received, web server will be distributed to the same Servlet (DispatcherServlet), to avoid the problems caused by multiple Servlet, has the following advantages:

  1. This step moves the distribution request from the web server to the inner frame, it is easier to control, but also to facilitate the expansion.
  2. The same approach can be focused to the same service class, named this type controller, the controller has a plurality of processing method configured such that the dispersion of clutter.
  3. Uri configured to map the path when you can not use the configuration file, the configuration can be used directly comment on the approach to solve the centralized configuration, a large and complex problem.

Practical operation

Source recommendations with the beginning of the article will be given a reference

  1. First create three notes in web.mvc package: Controller, RequestMapping, RequestParam, with the comment that we can dynamically obtain configuration information when the frame start.
  2. Since the processing method is annotated, the annotated parse class order, first of all obtain the relevant projects, under the category corresponding to a source code ClassScanner core package
public class ClassScanner {
    public static List<Class<?>> scanClass(String packageName) throws IOException, ClassNotFoundException {
        //用于保存结果的容器
        List<Class<?>> classList = new ArrayList<>();
        //把文件名改为文件路径
        String path = packageName.replace(".", "/");
        //获取默认的类加载器
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        //通过文件路径获取该文件夹下所有资源的URL
        Enumeration<URL> resources = classLoader.getResources(path);

        int index = 0;//测试

        while(resources.hasMoreElements()){
            //拿到下一个资源
            URL resource = resources.nextElement();
            //先判断是否是jar包,因为默认.class文件会被打包为jar包
            if(resource.getProtocol().contains("jar")){
                //把URL强转为jar包链接
                JarURLConnection jarURLConnection = (JarURLConnection)resource.openConnection();
                //根据jar包获取jar包的路径名
                String jarFilePath = jarURLConnection.getJarFile().getName();
                //把jar包下所有的类添加的保存结果的容器中
                classList.addAll(getClassFromJar(jarFilePath, path));
            }else{//也有可能不是jar文件,先放下
                //todo
            }
        }
        return classList;
    }

    /**
     * 获取jar包中所有路径符合的类文件
     * @param jarFilePath
     * @param path
     * @return
     */
    private static List<Class<?>> getClassFromJar(String jarFilePath, String path) throws IOException, ClassNotFoundException {
        List<Class<?>> classes = new ArrayList<>();//保存结果的集合
        JarFile jarFile = new JarFile(jarFilePath);//创建对应jar包的句柄
        Enumeration<JarEntry> jarEntries = jarFile.entries();//拿到jar包中所有的文件
        while(jarEntries.hasMoreElements()){
            JarEntry jarEntry = jarEntries.nextElement();//拿到一个文件
            String entryName = jarEntry.getName();//拿到文件名,大概是这样:com/shenghao/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;
    }
}
  1. MappingHandler package handler then create classes, during operation of the frame in the future, a MappingHandler corresponds to a service logic, for example, to increase a user. So in MappingHandler have a "uri parameter request, processing method, a method, a method in which the category" four fields, wherein the request for matching request uri uri, this processing is called back by the reflection parameter for the three runs method
public class MappingHandler {

    private String uri;
    private Method method;
    private Class<?> controller;
    private String[] args;

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

    public boolean handle(ServletRequest req, ServletResponse res) throws IllegalAccessException, InstantiationException, InvocationTargetException, IOException {
        //拿到请求的uri
        String requestUri = ((HttpServletRequest)req).getRequestURI();
        if(!uri.equals(requestUri)){//如果和自身uri不同就跳过
            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);
        res.getWriter().println(response.toString());
        return true;
    }
}
  1. Next, create the handler package HandlerManager class that has a static MappingHandler collection, the role of this class is obtained from all classes, find annotated @controller class and controller classes annotated each @ReqeustMapping the method of packaging into a MappingHandler, and then put into a static set MappingHandler
public class HandlerManager {

    public static List<MappingHandler> mappingHandlerList = new ArrayList<>();

    /**
     * 处理类文件集合,挑出MappingHandler
     * @param classList
     */
    public static void resolveMappingHandler(List<Class<?>> classList){
        for(Class<?> cls : classList){
            if(cls.isAnnotationPresent(Controller.class)){//MappingHandler会在controller里面
                parseHandlerFromController(cls);//继续从controller中分离出一个个MappingHandler
            }
        }
    }

    private static void parseHandlerFromController(Class<?> cls) {
        //先获取该controller中所有的方法
        Method[] methods = cls.getDeclaredMethods();
        //从中挑选出被RequestMapping注解的方法进行封装
        for(Method method : methods){
            if(!method.isAnnotationPresent(RequestMapping.class)){
                continue;
            }
            String uri = method.getDeclaredAnnotation(RequestMapping.class).value();//拿到RequestMapping定义的uri
            List<String> paramNameList = new ArrayList<>();//保存方法参数的集合
            for(Parameter parameter : method.getParameters()){
                if(parameter.isAnnotationPresent(RequestParam.class)){//把有被RequestParam注解的参数添加入集合
                    paramNameList.add(parameter.getDeclaredAnnotation(RequestParam.class).value());
                }
            }
            String[] params = paramNameList.toArray(new String[paramNameList.size()]);//把参数集合转为数组,用于反射
            MappingHandler mappingHandler = new MappingHandler(uri, method, cls, params);//反射生成MappingHandler
            mappingHandlerList.add(mappingHandler);//把mappingHandler装入集合中
        }
    }
}
  1. After completion of the above four steps, we start at the time the frame is obtained a MappingHandler set, when the request came, we only find the corresponding uri MappingHandler set according to the request from, you can call the corresponding methods of treatment by reflection, to this will complete the framework for requesting the distribution function.

Inversion of Control and the Dependency Injection

After the completion of the request distribution function, further think such a question:

Suppose now that a request needs to create process A, B, C three objects,
A has field D
B has a field D
C B fields has

If you create ABC in the order, then,
you must first create a D, then create an A;
then create a D, then create a B;
then create a D, then create a B, in order to create a C
created a total of a A , two B, C a, three D.

The above is the one way to create objects we write programs, it can be seen because the object can not be repeated references led to the creation of a large number of duplicate objects.

To solve this problem, spring bean proposed such a concept, you can put a bean understood as an object, but he contrasts the ordinary object has the following characteristics:

  1. Unlike ordinary objects as ephemeral, longer lifecycle
  2. Visible, unlike ordinary object is only visible inside entire virtual machine in a code block
  3. High maintenance costs, present in the form of a single embodiment

In order to make the above-mentioned bean, we have to have a bean factory, a bean factory principle is simple: create the relevant bean at initialization time frame (can also be created when used), when the need to use the bean directly from take the plant. That is, we put the power to create objects to the framework, which is the inversion of control

Once you have created a bean plant ABC sequence as follows:
First, create a D, D into the plant, and then create a A, A into the plant;
then come from a plant D, to create a B, B is also the into the plant;
then come from the factory one, B, creating a C, C is also placed in the factory;
total creates an a, one, B, C a, D a
reached target object reuse

As to create a D, then D is set to a field A of such a process, called dependency injection

So inversion of control and dependency injection concept is actually very good understanding, is an ideological inversion of control, dependency injection and inversion of control is a concrete realization of.

Practical operation

  1. First create @Bean @AutoWired and two annotations in the bean package, also a frame parser class.
  2. Then create a bean bag in the BeanFactory, BeanFactory to be able to provide a class according to obtain an instance of the function, which requires him to have a static getBean () method, and save a set of maps of Bean.
  3. To initialize Bean, have a set of methods according to the parsed bean class file. This method will traverse the set of all classes, the annotated belongs to the category bean extract, create objects of that class and put a static collection.
  4. Here there is an interesting point - in what order to create a bean? In this paper, the source, if not used to skip the current cycle to create a bean, if the bean does not rely on other bean directly create, if there are dependent on others to see other bean bean has not been created, bean, if you have created the current bean.
  5. Possible interdependence between one kind of bean phenomenon in the process loop to create the bean, the source code being thrown against this phenomenon, not for treatment.
public class BeanFactory {

    //保存Bean实例的映射集合
    private static Map<Class<?>, Object> classToBean = new ConcurrentHashMap<>();

    /**
     * 根据class类型获取bean
     * @param cls
     * @return
     */
    public static Object getBean(Class<?> cls){
        return classToBean.get(cls);
    }

    /**
     * 初始化bean工厂
     * @param classList 需要一个.class文件集合
     * @throws Exception
     */
    public static void initBean(List<Class<?>> classList) throws Exception {
        //先创建一个.class文件集合的副本
        List<Class<?>> toCreate = new ArrayList<>(classList);
        //循环创建bean实例
        while(toCreate.size() != 0){
            int remainSize = toCreate.size();//记录开始时集合大小,如果一轮结束后大小没有变证明有相互依赖
            for(int i = 0; i < toCreate.size(); i++){//遍历创建bean,如果失败就先跳过,等下一轮再创建
                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 {
        //创建的bean实例仅包括Bean和Controller注释的类
        if(!cls.isAnnotationPresent(Bean.class) && !cls.isAnnotationPresent(Controller.class)){
            return true;
        }
        //先创建实例对象
        Object bean = cls.newInstance();
        //看看实例对象是否需要执行依赖注入,注入其他bean
        for(Field field : cls.getDeclaredFields()){
            if(field.isAnnotationPresent(AutoWired.class)){
                Class<?> fieldType = field.getType();
                Object reliantBean = BeanFactory.getBean(fieldType);
                if(reliantBean == null){//如果要注入的bean还未被创建就先跳过
                    return false;
                }
                field.setAccessible(true);
                field.set(bean, reliantBean);
            }
        }
        classToBean.put(cls, bean);
        return true;
    }
}
  1. Once you have bean factory, any place used by the bean can directly took bean factory
  2. Finally, we can write a small Demo test whether their framework correctly process the request to complete the response. I believe the entire framework mini line and down, Spring's core functions, as well as inversion of control, control dependent on terms no longer just a concept in your mind, but a clear code line by line.

Guess you like

Origin www.cnblogs.com/tanshaoshenghao/p/11433999.html