【Spring】用300行代码实现spring1.0版本(详细注释)

Alan 244 2019-07-01

通过手写来实现SpringMvc三层模型,spring的Ioc控制反转,Di依赖注入的功能,主要分为配置阶段,初始化阶段和运行阶段这三个阶段。

配置阶段
  1. 配置web.xml,设定init-param和param-name , 设置url-parttern,方法的过滤路径为 /*。

<!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 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:javaee="http://java.sun.com/xml/ns/javaee"
         xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
         version="2.4">

    <display-name>Archetype Created Web Application</display-name>

    <servlet>
        <servlet-name>gpmvc</servlet-name>
        <servlet-class>com.alan.mvcframework.v2.servlet.MyDispatchServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>application.properties</param-value>
        </init-param>

        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>gpmvc</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

  1. 创建 application.properties ,配置包扫描的路径。
scanPackage=com.alan.demo
  1. 创建自己的注解文件。
import java.lang.annotation.*;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPAutowired {
    String value() default "";
}
import java.lang.annotation.*;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPController {
    String value() default "";
}
import java.lang.annotation.*;

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPRequestMapping {
    String value() default "";
}
import java.lang.annotation.*;

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPRequestParam {
    String value() default "";
}
import java.lang.annotation.*;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPService {
    String value() default "";
}
  1. 引入 servlet-api 的依赖jar包。
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>servlet-api</artifactId>
			<version>${servlet.api.version}</version>
			<scope>provided</scope>
		</dependency>
初始化阶段

创建一个类继承 HttpServlet,重写 doGet ,doPost ,init 方法 。

  1. 获取包扫描的配置信息。
    private void doLoadConfig(String contextConfigLocation) {
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            //存入一个property里面
            contextConfig.load(is);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
  1. 扫描包下的所有类,将类名全路径名称存入数组中。
    private void doScanner(String scanPackage) {
        //获取classLoader下面该路径的url,由/拼接
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
        //获取File文件
        File classPath = new File(url.getFile());
        //循环文件路径,获取下面的子文件(文件架)
        for (File file : classPath.listFiles()) {
            //如果是文件夹,递归
            if (file.isDirectory()) {
                doScanner(scanPackage + "." + file.getName());
            } else {
                //如果是文件,判断是否是class文件
                if (!file.getName().endsWith(".class")) {
                    continue;
                }
                //去掉后缀,将文件的全路径名存入数组
                classNames.add(scanPackage + "." + file.getName().replace(".class", ""));
            }
        }
    }
  1. 扫描到的类实例化。
   private void doInstance() {
        if (classNames.isEmpty()) {
            return;
        }
        try {
            //循环扫描的类名称
            for (String className : classNames) {
                //反射获取Class
                Class<?> clazz = Class.forName(className);
                //如果注解是controller
                if (clazz.isAnnotationPresent(MyController.class)) {
                    //获取实例
                    Object instance = clazz.newInstance();
                    //获取类名首字母小写
                    String beanName = toLowerFirstCase(clazz.getSimpleName());
                    //将类名作为key,类实例做为value存入map(ioc容器)
                    ioc.put(beanName, instance);
                    //注解是service
                } else if (clazz.isAnnotationPresent(MyService.class)) {
                    //获取注解内的名称
                    String beanName = clazz.getAnnotation(MyService.class).value();
                    //注解内没有名称,类名为clazz的首字母小写类名
                    if ("".equals(beanName.trim())) {
                        beanName = toLowerFirstCase(clazz.getSimpleName());
                    }
                    //获取内的实例
                    Object instance = clazz.newInstance();
                    //将类名作为key,类实例做为value存入map(ioc容器)
                    ioc.put(beanName, instance);
                    //循环获取class的所有接口
                    for (Class<?> i : clazz.getInterfaces()) {
                        //如果接口有多个实现类,报异常
                        if (ioc.containsKey(i.getName())) {
                            throw new Exception("The " + i.getName() + " is exists!!");
                        }
                        //否则将接口作为key,实例作为value存入map
                        ioc.put(i.getName(), instance);
                    }
                    //没有注解,跳出,下一个
                } else {
                    continue;
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private String toLowerFirstCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }
  1. 将扫描到的类的所有属性进行依赖注入。
    private void doAutowired() {
        if (ioc.isEmpty()) {
            return;
        }
        //循环ioc容器
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            //获取value的所有的 private/protected/default/public 修饰字段
            for (Field field : entry.getValue().getClass().getDeclaredFields()) {
                //没有MyAutowired的注解不管
                if (!field.isAnnotationPresent(MyAutowired.class)) {
                    continue;
                }

                MyAutowired autowired = field.getAnnotation(MyAutowired.class);
                String beanName = autowired.value().trim();
                //如果注解内没有value,beanName就为字段的类型
                if ("".equals(beanName.trim())) {
                    beanName = field.getType().getName();
                }

                //暴力访问
                field.setAccessible(true);

                try {
                    //将这个字段注入到这个类中
                    field.set(entry.getValue(), ioc.get(beanName));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
  1. 获取类的方法和方法的url参数,存入handlerMapping中。
    private void doInitHandlerMapping() {
        if (ioc.isEmpty()) {
            return;
        }
        //循环ioc容器
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            //获取value(Object)的class
            Class<?> clazz = entry.getValue().getClass();
            //如果这个类没有controller注解,不管它
            if (!clazz.isAnnotationPresent(MyController.class)) {
                continue;
            }

            //获取controller上面的url
            String baseUrl = "";
            if (clazz.isAnnotationPresent(MyRequestMapping.class)) {
                MyRequestMapping requestMapping = clazz.getAnnotation(MyRequestMapping.class);
                baseUrl = requestMapping.value();
            }

            //获取class的所有方法
            for (Method method : clazz.getMethods()) {
                //没有MyRequestMapping注解的方法不管
                if (!method.isAnnotationPresent(MyRequestMapping.class)) {
                    continue;
                }
                MyRequestMapping requestMapping = method.getAnnotation(MyRequestMapping.class);
                String url = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/");
                //将url作为key,方法作为value存入handlerMapping中
                handlerMapping.put(url, method);

                System.out.println("Map :" + url + "," + method);
            }
        }
    }
运行阶段
  1. 根据 请求url 从 handlerMapping 获取方法,2. 获取请求参数,3. 调用请求。
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replaceAll(contextPath, "").replaceAll("/+", "/");

        if (!this.handlerMapping.containsKey(url)) {
            resp.getWriter().write("404 not found!!");
            return;
        }

        Map<String, String[]> params = req.getParameterMap();
        //根据url获取方法
        Method method = this.handlerMapping.get(url);

        //获取形参列表
        Class<?>[] parameterTypes = method.getParameterTypes();
        //获取参数类型
        Object[] paramValues = new Object[parameterTypes.length];

        for (int i = 0; i < parameterTypes.length; i++) {
            Class paramterType = parameterTypes[i];
            if (paramterType == HttpServletRequest.class) {
                paramValues[i] = req;
            } else if (paramterType == HttpServletResponse.class) {
                paramValues[i] = resp;
            } else if (paramterType == String.class) {
                //通过运行时的状态去拿到你
                Annotation[][] pa = method.getParameterAnnotations();
                for (int j = 0; j < pa.length; j++) {
                    for (Annotation a : pa[i]) {
                        if (a instanceof MyRequestParam) {
                            String paramName = ((MyRequestParam) a).value();
                            if (!"".equals(paramName.trim())) {
                                String value = Arrays.toString(params.get(paramName))
                                        .replaceAll("\\[|\\]", "")
                                        .replaceAll("\\s+", ",");
                                paramValues[i] = value;
                            }
                        }
                    }
                }

            }
        }

        String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
        //反射调用方法
        method.invoke(ioc.get(beanName), paramValues);
    }

最后来结合整体思路来看看总体代码:

import com.alan.mvcframework.annotation.*;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

public class MyDispatchServlet extends HttpServlet {
    private Properties contextConfig = new Properties();
    //存扫描的类
    private List<String> classNames = new ArrayList<String>();
    //IOC容器
    private Map<String, Object> ioc = new HashMap<String, Object>();
    //handlerMapping存url和method
    private Map<String, Method> handlerMapping = new HashMap<String, Method>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        //委派,根据url去找到一个对应的Method,然后通过response返回
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
            resp.getWriter().write("500 Exception,Detail :" + Arrays.toString(e.getStackTrace()));
        }
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replaceAll(contextPath, "").replaceAll("/+", "/");

        if (!this.handlerMapping.containsKey(url)) {
            resp.getWriter().write("404 not found!!");
            return;
        }

        Map<String, String[]> params = req.getParameterMap();
        //根据url获取方法
        Method method = this.handlerMapping.get(url);

        //获取形参列表
        Class<?>[] parameterTypes = method.getParameterTypes();
        //获取参数类型
        Object[] paramValues = new Object[parameterTypes.length];

        for (int i = 0; i < parameterTypes.length; i++) {
            Class paramterType = parameterTypes[i];
            if (paramterType == HttpServletRequest.class) {
                paramValues[i] = req;
            } else if (paramterType == HttpServletResponse.class) {
                paramValues[i] = resp;
            } else if (paramterType == String.class) {
                //通过运行时的状态去拿到你
                Annotation[][] pa = method.getParameterAnnotations();
                for (int j = 0; j < pa.length; j++) {
                    for (Annotation a : pa[i]) {
                        if (a instanceof MyRequestParam) {
                            String paramName = ((MyRequestParam) a).value();
                            if (!"".equals(paramName.trim())) {
                                String value = Arrays.toString(params.get(paramName))
                                        .replaceAll("\\[|\\]", "")
                                        .replaceAll("\\s+", ",");
                                paramValues[i] = value;
                            }
                        }
                    }
                }

            }
        }

        String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
        //反射调用方法
        method.invoke(ioc.get(beanName), paramValues);
    }


    @Override
    public void init(ServletConfig config) throws ServletException {
        // 加载配置文件,获取web.xml里面init-param的param-name,作为参数传递
        doLoadConfig(config.getInitParameter("contextConfigLocation"));

        // 扫描所有的类,传入配置的扫描包路径
        doScanner(contextConfig.getProperty("scanPackage"));

        // 将类的实例存入IOC容器
        doInstance();

        //DI 依赖注入
        doAutowired();

        //初始化handlerMapping
        doInitHandlerMapping();
    }

    private void doInitHandlerMapping() {
        if (ioc.isEmpty()) {
            return;
        }
        //循环ioc容器
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            //获取value(Object)的class
            Class<?> clazz = entry.getValue().getClass();
            //如果这个类没有controller注解,不管它
            if (!clazz.isAnnotationPresent(MyController.class)) {
                continue;
            }

            //获取controller上面的url
            String baseUrl = "";
            if (clazz.isAnnotationPresent(MyRequestMapping.class)) {
                MyRequestMapping requestMapping = clazz.getAnnotation(MyRequestMapping.class);
                baseUrl = requestMapping.value();
            }

            //获取class的所有方法
            for (Method method : clazz.getMethods()) {
                //没有MyRequestMapping注解的方法不管
                if (!method.isAnnotationPresent(MyRequestMapping.class)) {
                    continue;
                }
                MyRequestMapping requestMapping = method.getAnnotation(MyRequestMapping.class);
                String url = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/");
                //将url作为key,方法作为value存入handlerMapping中
                handlerMapping.put(url, method);

                System.out.println("Map :" + url + "," + method);
            }
        }
    }

    private void doAutowired() {
        if (ioc.isEmpty()) {
            return;
        }
        //循环ioc容器
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            //获取value的所有的 private/protected/default/public 修饰字段
            for (Field field : entry.getValue().getClass().getDeclaredFields()) {
                //没有MyAutowired的注解不管
                if (!field.isAnnotationPresent(MyAutowired.class)) {
                    continue;
                }

                MyAutowired autowired = field.getAnnotation(MyAutowired.class);
                String beanName = autowired.value().trim();
                //如果注解内没有value,beanName就为字段的类型
                if ("".equals(beanName.trim())) {
                    beanName = field.getType().getName();
                }

                //暴力访问
                field.setAccessible(true);

                try {
                    //将这个字段注入到这个类中
                    field.set(entry.getValue(), ioc.get(beanName));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void doInstance() {
        if (classNames.isEmpty()) {
            return;
        }
        try {
            //循环扫描的类名称
            for (String className : classNames) {
                //反射获取Class
                Class<?> clazz = Class.forName(className);
                //如果注解是controller
                if (clazz.isAnnotationPresent(MyController.class)) {
                    //获取实例
                    Object instance = clazz.newInstance();
                    //获取类名首字母小写
                    String beanName = toLowerFirstCase(clazz.getSimpleName());
                    //将类名作为key,类实例做为value存入map(ioc容器)
                    ioc.put(beanName, instance);
                    //注解是service
                } else if (clazz.isAnnotationPresent(MyService.class)) {
                    //获取注解内的名称
                    String beanName = clazz.getAnnotation(MyService.class).value();
                    //注解内没有名称,类名为clazz的首字母小写类名
                    if ("".equals(beanName.trim())) {
                        beanName = toLowerFirstCase(clazz.getSimpleName());
                    }
                    //获取内的实例
                    Object instance = clazz.newInstance();
                    //将类名作为key,类实例做为value存入map(ioc容器)
                    ioc.put(beanName, instance);
                    //循环获取class的所有接口
                    for (Class<?> i : clazz.getInterfaces()) {
                        //如果接口有多个实现类,报异常
                        if (ioc.containsKey(i.getName())) {
                            throw new Exception("The " + i.getName() + " is exists!!");
                        }
                        //否则将接口作为key,实例作为value存入map
                        ioc.put(i.getName(), instance);
                    }
                    //没有注解,跳出,下一个
                } else {
                    continue;
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private String toLowerFirstCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

    private void doScanner(String scanPackage) {
        //获取classLoader下面该路径的url,由/拼接
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
        //获取File文件
        File classPath = new File(url.getFile());
        //循环文件路径,获取下面的子文件(文件架)
        for (File file : classPath.listFiles()) {
            //如果是文件夹,递归
            if (file.isDirectory()) {
                doScanner(scanPackage + "." + file.getName());
            } else {
                //如果是文件,判断是否是class文件
                if (!file.getName().endsWith(".class")) {
                    continue;
                }
                //去掉后缀,将文件的全路径名存入数组
                classNames.add(scanPackage + "." + file.getName().replace(".class", ""));
            }
        }
    }

    private void doLoadConfig(String contextConfigLocation) {
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            //存入一个property里面
            contextConfig.load(is);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


# Spring