Springmvc其实没有想象中的那么难,咱自己也来写一个!

背景

     编码一年多,接触的项目每个都用到springmvc,只会用,而不知其原理。今日咱就把它弄清楚了!原本以为会有很多代码好多类,但其实源码很少。

开动

通过写一个自己的springmvc,来深化自己对其的了解。首先展示下代码结构:

然后我们一个个来看!

首先都是一些springmvc的注解类:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyController {
    String value() default "";
}
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAutowired {
    String value() default "";
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestMapping {
    String value() default "";
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyService {
    String value() default "";
}

从上面可以看到,这些注解除了一些细微的差别,基本上都是一样的。不一样的地方我们也能想到,无非是区分什么时候起作用@Retention。注解是加在哪里的,类?方法?还是成员变量上?@Target  这里注解不做过多解释。

接下来就是我们的重点来了!DispatchServlet类,springmvc的核心!

咱们一点一点来,先看看它是否继承了什么?有哪些成员变量?

public class MyDispatchServlet extends HttpServlet {

    //配置文件属性
    private Properties contextConfig = new Properties();

    //所有的类路径集合
    private List<String> classNameLists = new ArrayList<String>();

    //spring的ioc容器
    private HashMap<String, Object> iocMap = new HashMap<String, Object>();

    //url对应的具体方法
    private HashMap<String, Method> handlerMapping = new HashMap<String, Method>();

可以看到继承了HttpServlet,继承了它,然后我们重写一下它的init方法,doget,dopost方法。

@Override
public void init(ServletConfig config) throws ServletException {
    //1、加载配置文件
    doLoadConfig(config.getInitParameter("contextConfigLocation"));
    //2、扫描所有类
    doScanPackage(contextConfig.getProperty("scan-package"));
    //3、初始化ioc容器,通过反射创建所有相关的类实例,并添加到ioc容器中
    doCreateInstanceToIOC();
    //4、依赖注入-简称DI
    doAutowiredFields();
    //5、初始化 HandlerMapping
    initHandlerMapping();

    //6、打印数据
    doPrintData();
}

众所周知,springmvc启动后,会加载该类,然后第一步就是初始化,也就是init方法,让我们来看看里面都具体做了什么?

(1)doLoadConfig(config.getInitParameter("contextConfigLocation"));加载配置文件application.properties

路径在web.xml有配置

代码如下:它会把application.properties里的所有配置加载到成员变量contextConfig里,以作后面使用。

/**
 * 1 初始化加载配置文件
 * @param contextConfigLocation
 */
private void doLoadConfig(String contextConfigLocation) {
    InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);

    try {
        contextConfig.load(inputStream);
        System.out.println("[1].配置文件已经保存在contextConfig.");
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        if (null != inputStream) {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

(2)doScanPackage(contextConfig.getProperty("scan-package"));扫描指定包下的所有类

代码如下:将所有类的路径保存到成员变量classNameLists里,以作后面使用

/**
 * 2 扫描所有包下的类文件,将包下的所有类的路径加入到list里
 * @param scanPackage
 */
private void doScanPackage(String scanPackage) {
    URL rootPath = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));

    if (rootPath == null) {
        return;
    }

    File files = new File(rootPath.getFile());
    for (File file : files.listFiles()) {
        if (file.isDirectory()) {
            //递归调用,遍历子目录
            doScanPackage(scanPackage + "." + file.getName());
        } else {
            if (!file.getName().endsWith(".class")) {
                //如果不是java类文件,则继续下次循环
                continue;
            }
            String classPath = scanPackage + "." + file.getName().replace(".class", "");
            //将类的路径存入集合中
            classNameLists.add(classPath);

            System.out.println("[2]. {" + classPath + "} 已经存入集合classNameLists中.");
        }
    }
}

(3)doCreateInstanceToIOC();见其名,知其意,将所有创建的实例,也就是上一步扫描到的所有类,以反射形式创建对象,加载到成员变量iocMap中!这也就是我们经常说的IOC的一种运用,控制反转。

代码如下:

/**
 * 3 反射创建所有相关的类实例,并添加到ioc容器中
 */
private void doCreateInstanceToIOC() {
    if (classNameLists.isEmpty()) {
        return;
    }
    try {
        for (String className : classNameLists) {
            Class<?> clazz = null;
            try {
                clazz = Class.forName(className);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            //如果是MyController注解
            if (clazz.isAnnotationPresent(MyController.class)) {
                String beanName = toLowerFirstCase(clazz.getSimpleName());
                //创建对象,存入ioc
                Object instance = clazz.newInstance();
                iocMap.put(beanName, instance);
                System.out.println("[3] {" + beanName + "} 已经存入 iocMap.");
            } else if (clazz.isAnnotationPresent(MyService.class)) {//如果是MyService注解

                String beanName = toLowerFirstCase(clazz.getSimpleName());
                //如果该类有自定义名称
                MyService myService = clazz.getAnnotation(MyService.class);
                if (!"".equals(myService.value())) {
                    beanName = myService.value();
                }
                //创建对象,存入ioc
                Object instance = clazz.newInstance();
                iocMap.put(beanName, instance);
                System.out.println("[3] {" + beanName + "} 已经存入 iocMap.");

                //如果该类有接口类,同样将该实例存入ioc中,key为接口的全路径
                for (Class<?> inter : clazz.getInterfaces()) {
                    if (iocMap.containsKey(inter.getName())) {
                        throw new Exception("The Bean Name Is Exist.");
                    }
                    iocMap.put(inter.getName(), instance);
                    System.out.println("[3] {" + inter.getName() + "} 已经存入 iocMap.");
                }
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

(4)doAutowiredFields();依赖注入,简称DI,将每个ioc容器中的对象所拥有的的成员变量创建然后注入进该对象中,也就是我们经常用到的@Autowired的原理。

代码如下:

/**
 * 4 依赖注入,为该类中注入的成员变量赋值
 */
private void doAutowiredFields() {
    if (iocMap.isEmpty()) {
        return;
    }
    //遍历ioc,依次注入
    for (Map.Entry<String,Object> entry : iocMap.entrySet()) {
        //获取所有成员属性
        Field[] fields = entry.getValue().getClass().getDeclaredFields();
        for (Field field : fields) {
            if (!field.isAnnotationPresent(MyAutowired.class)) {
                continue;
            }
            // 获取注解对应的类
            MyAutowired myAutowired = field.getAnnotation(MyAutowired.class);
            String beanName = myAutowired.value().trim();

            // 获取 XAutowired 注解的值
            if ("".equals(beanName)) {
                System.out.println("[INFO] myAutowired.value() is null");
                beanName = field.getType().getName();
            }
            // 只要加了注解,都要加载,不管是 private 还是 protect
            field.setAccessible(true);

            try {
                field.set(entry.getValue(), iocMap.get(beanName));

                System.out.println("[4] field set {" + entry.getValue() + "} - {" + iocMap.get(beanName) + "}.");
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

        }

    }
}

(5)initHandlerMapping();初始化 HandlerMapping,扫描iocMap,解析MyRequestMapping注解,将其对应的方法与其url放入成员变量handlerMapping中。

代码如下:

/**
 * 5 将url与方法一一对应
 */
private void initHandlerMapping() {
    if (iocMap.isEmpty()) {
        return;
    }

    for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
        Class<?> clazz = entry.getValue().getClass();
        if (!clazz.isAnnotationPresent(MyController.class)) {
            continue;
        }

        //检查类上面是否有MyRequestMapping注解
        String baseUrl = "";
        if (clazz.isAnnotationPresent(MyRequestMapping.class)) {
            baseUrl = clazz.getAnnotation(MyRequestMapping.class).value();
        }

        Method[] methods = clazz.getMethods();
        //遍历类下面的所有方法
        for (Method method : methods) {
            if (!method.isAnnotationPresent(MyRequestMapping.class)) {
                continue;
            }
            MyRequestMapping myRequestMapping = method.getAnnotation(MyRequestMapping.class);

            String url = ("/" + baseUrl + "/" + myRequestMapping.value()).replaceAll("/+", "/");

            handlerMapping.put(url, method);
            System.out.println("[5] handlerMapping put {" + url + "} - {" + method + "} success.");
        }
    }
}

(6)doPrintData(); 最后一步,打印日志。代码如下:

/**
 * 6 打印相关日志
 */
private void doPrintData() {
    System.out.println("[6]----data--------------------");

    System.out.println("contextConfig.propertyNames()-->" + contextConfig.propertyNames());

    System.out.println("[classNameList]-->");
    for (String str : classNameLists) {
        System.out.println(str);
    }

    System.out.println("[iocMap]-->");
    for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
        System.out.println(entry);
    }

    System.out.println("[handlerMapping]-->");
    for (Map.Entry<String, Method> entry : handlerMapping.entrySet()) {
        System.out.println(entry);
    }

    System.out.println("[6]----done-----------------------");

    System.out.println("====启动成功====");


}

可以看到,springmvc加载的时候,就做了很多的工作!

接下来,看看调用是如何开始的,我们知道,在调用与之对应的url时,会调用doget或者dopost方法,然后进入doDispatch(req, resp);

通过url在成员变量hardmapping中获取对应的方法路径,然后反射调用该方法。即完成一次调用!

/**
 * 7 拦截,匹配,运行
 * @param req
 * @param resp
 */
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) {

    String url = req.getRequestURI();

    String contextPath = req.getContextPath();

    url = url.replace(contextPath, "").replaceAll("/+", "");

    System.out.println("[7]request url -->" + url);

    //如果路径不存在,则报404
    if (!this.handlerMapping.containsKey(url)) {
        try {
            resp.getWriter().write("404 NOT FOUND!!");
            return;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //反射调用具体的方法
    Method method = this.handlerMapping.get(url);
    String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
    try {
        method.invoke(iocMap.get(beanName), req, resp);
        System.out.println("[7] method.invoke success,class is {" + iocMap.get(beanName) + "}.");
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }

}

@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 {
    try {
        doDispatch(req, resp);
    } catch (Exception e) {
        e.printStackTrace();
        resp.getWriter().write("500 Exception Detail:\n" + Arrays.toString(e.getStackTrace()));
    }

}
 /**
     * 将首字母变为小写
     * @param className
     * @return
     */
    private String toLowerFirstCase(String className) {
        char[] charArray = className.toCharArray();
        charArray[0] += 32;
        return String.valueOf(charArray);
    }

收尾

       以上代码并非源代码所有,一些地方省略了很多。不过足以让我们知道其原理了。码农朋友们,看完之后,是否对springmvc有了新的认知,是不是觉得其实它也就那么回事,如果认同博主的话,点个赞呗!

 

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值