Spring源码学习(一)从一个简单的servlet开始

前言

磕磕碰碰,终于开始翻看spring源码了。spring使用已经很多,之前一直想学习spring源码,但是一直没有开始。这篇博客开始学习spring源码,手动实现一个简单的基于servlet的mvc框架。

广义的spring mvc调用流程

这个之前我们看过了很多资料,但是一直没有真实理解,毕竟没有一个实际的感受。MVC调用具体流程图如下(个人理解)

要干什么

简单点说,手写一个mvc调用框架(超级简单的版本)。更通俗点说,用代码实现上述流程图。

初始工作准备

新建一个web项目,这一步就不详细说了。

1、模仿编写controller,requestMapping,autowired,requestParam,service注解

自己的SelfController注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SelfController {
    String value() default "";
}

自己的RequestMapping注解

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SelfRequestMapping {
    String value() default "";
}

自己的RequestParam注解

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SelfRequestParam {
    String value() default "";
}

 自己的Autowired注解

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SelfAutowired {
    String value() default "";
}

 自己的service注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SelfService {
    String value() default "";
}

 2、编写自己的servlet

package com.learn.springmvc.servlet.V2;

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.IOException;

/**
 * autor:liman
 * comment: 自己的servlet
 */
public class SelfServlet extends HttpServlet {

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

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

    /**
     * 初始化方法
     *
     * @param config
     * @throws ServletException
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
    }
}

 3、web.xml中配置servlet的映射

    <servlet>
        <servlet-name>selfServlet</servlet-name>
        <!--<servlet-class>com.learn.springmvc.servlet.SelfServlet</servlet-class>-->
        <servlet-class>com.learn.springmvc.servlet.V2.SelfServletV2</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>selfServlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

其中的init-param是给servlet一个初始化的参数,这个后面会详细谈到。 

开始有点复杂的代码编写

Servlet中的init方法中,会完成HandlerMapping,IOC相关内容的初始化,具体如下所示。

    public void init(ServletConfig config) throws ServletException {
        //1.加载配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));
        //2.扫描相关的类
        doScanner(contextConfig.getProperty("scanPackage"));
        //3.初始化扫描的类
        doInstance();
        //4.完成依赖注入
        doAutowired();
        //5.初始化handleMapping
        initHandlerMapping();
        System.out.println("自己写的spring mvc 初始化完成");
    }

1、加载配置文件

加载配置文件,在这里面是最简单的操作,只是将配置文件读取到内存的Properties对象中即可。

    //用于保存application.properties配置文件中的内容,这个实例使用properties文件代替xml文件
    private Properties contextConfig = new Properties();

    /**
     * 加载配置文件
     */
    private void doLoadConfig(String contextConfigLocation) {
        InputStream fis = null;
        fis = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            contextConfig.load(fis);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != fis) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

这里只是简单的一个mvc简单的示例,配置文件中只有一项配置: 

scanPackage=com.learn.springmvc

上述的工作只是将该配置属性读取到属性contextConfig中。该配置中指定了我们需要扫描的基础包名

2、扫描出相关的类

 

    //用于保存所有扫描出来的类名
    private List<String> classNames = new ArrayList<String>();

    /**
     * 扫描出相关的类
     *
     * @param scanPackage
     */
    private void doScanner(String scanPackage) {

        //主要是将包路径转换为文件路径
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
        File classPath = new File(url.getFile());
        for (File file : classPath.listFiles()) {
            if (file.isDirectory()) {
                doScanner(scanPackage + "." + file.getName());
            } else {
                if (!file.getName().endsWith(".class")) {
                    continue;
                }
                String className = (scanPackage + "." + file.getName().replace(".class", ""));
                classNames.add(className);
            }
        }
    }

扫描出所有的类名,将类名放入到List<String> 集合中。 

3、初始化扫描的类

    //传说中的IOC容器,为了简化程序,这里不用ConcurrentHashMap
    private Map<String, Object> ioc = new HashMap<String, Object>();

    /**
     * 初始化,为DI做准备
     * 加了注解的类才能初始化,这里只列出加了@Controller和@Service注解的类
     */
    private void doInstance() {
        if (classNames.isEmpty()) {
            return;
        }

        try {
            for (String className : classNames) {
                Class<?> clazz = Class.forName(className);
                //如果有Controller注解
                if (clazz.isAnnotationPresent(SelfController.class)) {//如果是Controller
                    Object instance = clazz.newInstance();
                    //首字母小写,并放入ioc容器中
                    String beanName = toLowerFirstCase(clazz.getSimpleName());
                    ioc.put(beanName, instance);
                } else if (clazz.isAnnotationPresent(SelfService.class)) {//如果有Service注解
                    //获取自定义的beanName——@Service("test")获取其中的test
                    SelfService service = clazz.getAnnotation(SelfService.class);
                    String beanName = service.value();//获得注解的值(一般自己制定service的名称的时候)
                    if ("".equals(beanName.trim())) {
                        //如果没有指定名称,service 默认首字母小写
                        beanName = toLowerFirstCase(clazz.getSimpleName());
                    }
                    Object instance = clazz.newInstance();
                    //放入IOC容器
                    ioc.put(beanName, instance);

                    //注入的时候是接口注入的方式,投机取巧,就将接口作为key,实例作为值
                    for (Class<?> i : clazz.getInterfaces()) {
                        if (ioc.containsKey(i.getName())) {//这样就限制了,一个接口只有一个实现类
                            throw new Exception("the " + i.getName() + " is exists");
                        }
                        ioc.put(i.getName(), instance);
                    }
                } else {
                    continue;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 将首字母小写
     *
     * @param simpleName
     * @return
     */
    private String toLowerFirstCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        //之所以加,是因为大小写字母的ASCII码相差32,
        // 而且大写字母的ASCII码要小于小写字母的ASCII码
        //在Java中,对char做算学运算,实际上就是对ASCII码做算学运算
        chars[0] += 32;
        return String.valueOf(chars);
    }

这一步完成了IOC容器的初始化,这一步也算是了解了IOC的基本结构 ——一个类名与类实例的键值对。

4、完成依赖注入(DI)

    /**
     * 完成依赖注入
     */
    private void doAutowired() {
        if (ioc.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            //获取本类中所有的字段
            Field[] fields = entry.getValue().getClass().getDeclaredFields();

            for (Field field : fields) {
                if (!field.isAnnotationPresent(SelfAutowired.class)) {
                    continue;
                }
                //获取有@Autowired注解的属性上的注解对象
                SelfAutowired selfAutowired = field.getAnnotation(SelfAutowired.class);
                //获取注解的值
                String beanName = selfAutowired.value().trim();
                if ("".equals(beanName)) {
                    //如果没有自定义,默认根据类型注入
                    beanName = field.getType().getName();
                }
                //设置属性的访问属性
                field.setAccessible(true);
                try {
                    //设置属性的值
                    field.set(entry.getValue(), ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

依赖注入其实也没有想象中的复杂,只是遍历IOC容器中的实体,并遍历每个实体中的属性,如果属性上有注解,则从IOC中取出指定类型的实例,完成初始化。

5、 初始化HandlerMapping

这是最关键的一步,也是最复杂的一步,同时这一步也引出了Handler与HandlerMapping的关系,HandlerMapping其实就是一个映射,主要完成url与对应的controller中method的映射关系。

    private List<HandlerMapping> handlerMapping = new ArrayList<HandlerMapping>();

    /**
     * 初始化HandlerMapping
     * HandlerMapping——url和method一对一的映射
     */
    private void initHandlerMapping() {
        if (ioc.isEmpty()) {
            return;
        }

        for(Map.Entry entry:ioc.entrySet()){
            Class<?> clazz = entry.getValue().getClass();
            if(!clazz.isAnnotationPresent(SelfController.class)){
                continue;
            }

            String baseUrl = "";
            if(clazz.isAnnotationPresent(SelfRequestMapping.class)){
                SelfRequestMapping selfRequestMapping = clazz.getAnnotation(SelfRequestMapping.class);
                baseUrl = selfRequestMapping.value();
            }

            //默认获取所有的public方法
            for(Method method:clazz.getMethods()){
                if(!method.isAnnotationPresent(SelfRequestMapping.class)){
                    continue;
                }

                SelfRequestMapping requestMapping = method.getAnnotation(SelfRequestMapping.class);
                String url = ("/"+baseUrl+"/"+requestMapping.value()).replaceAll("/+","/");
                this.handlerMapping.add(new HandlerMapping(url,method,entry.getValue()));
                System.out.println("Mapped:"+url+":"+method);
            }
        }
    }

 为了方便后面的操作,如果HandlerMapping中只有url属性和Method属性是远远不够的,在实际编写代码过程中为了方便通过反射获取方法的参数,需要维护方法所在的类类型,因此最终确定的HandlerMapping的属性如下:

    /**
     * 暂时将HandlerMapping写成一个静态内部类
     */
    public static class HandlerMapping {
        private String url;
        private Method method;
        private Object controller;
        private Class<?>[] paramTypes;//参数类型列表
        private Map<String, Integer> paramIndexMapping = new HashMap<>();//参数索引位置

        public String getUrl() {
            return url;
        }

        public void setUrl(String url) {
            this.url = url;
        }

        public Method getMethod() {
            return method;
        }

        public void setMethod(Method method) {
            this.method = method;
        }

        public Object getController() {
            return controller;
        }

        public void setController(Object controller) {
            this.controller = controller;
        }

        public Class<?>[] getParamTypes() {
            return paramTypes;
        }

        /**
         * 构造函数
         *
         * @param url
         * @param method
         * @param controller
         */
        public HandlerMapping(String url, Method method, Object controller) {
            this.url = url;
            this.method = method;
            this.controller = controller;
            this.paramTypes = method.getParameterTypes();
            putParamIndexMapping(method);
        }

        /**
         * 初始化参数的索引位置
         *
         * @param method
         * @return
         */
        private void putParamIndexMapping(Method method) {
            //提取方法中加了注解的参数
            Annotation[][] pa = method.getParameterAnnotations();
            for (int i = 0; i < pa.length; i++) {
                for (Annotation a : pa[i]) {
                    if (a instanceof SelfRequestParam) {
                        String paramName = ((SelfRequestParam) a).value();
                        if (!"".equals(paramName.trim())) {
                            this.paramIndexMapping.put(paramName, i);
                        }
                    }
                }
            }

            //提取方法中的request和response参数
            Class<?>[] paramsTypes = method.getParameterTypes();
            for (int i = 0; i < paramsTypes.length; i++) {
                Class<?> type = paramsTypes[i];
                if (type == HttpServletRequest.class
                        || type == HttpServletResponse.class) {
                    this.paramIndexMapping.put(type.getName(), i);
                }
            }
        }
    }

paramIndexMapping中维护了参数与参数索引的对应关系。 

    /**
     * 找到指定的一致的handlercMapping处理对象
     * @param req
     * @return
     */
    private HandlerMapping getHandler(HttpServletRequest req){
        if(handlerMapping.isEmpty()){
            return null;
        }
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replaceAll(contextPath,"").replaceAll("/+","/");

        for(HandlerMapping handler:this.handlerMapping){
            if(handler.getUrl().equals(url)){
               return handler;
            }
        }
        return null;
    }

 上述其实就是从HandlerMapping集合中获取指定的HandlerMapping。至此,上一步就完成Spring mvc的初始化,IOC容器,DI的注入,以及初始化HandlerMapping,接下来就差最后一步了,完成调用,Method对象的调用需要三个属性:1、方法所在的对象,2、所有的参数值,3、对应的Method。

6、最后的调用逻辑

    /**
     * 开始调用自己写的mvc框架
     *
     * @param req
     * @param resp
     */
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        //获取指定的handler
        HandlerMapping handler = getHandler(req);
        if(handler == null){//没有找到对应的处理器
            resp.getWriter().write("404 Not Found!!!");
            return;
        }
        //获取方法的形参列表
        Class<?> [] paramTypes = handler.getParamTypes();
        Object [] paramValues = new Object[paramTypes.length];
        Map<String,String[]> params= req.getParameterMap();
        for(Map.Entry<String,String[]> param:params.entrySet()){
            String value = Arrays.toString(param.getValue()).
                    replaceAll("\\[|\\]","").replaceAll("\\s",",");

            if(!handler.paramIndexMapping.containsKey(param.getKey())){continue;}

            int index = handler.paramIndexMapping.get(param.getKey());
            paramValues[index] = convert(paramTypes[index],value);
        }
        if(handler.paramIndexMapping.containsKey(HttpServletRequest.class.getName())){
            int reqIndex = handler.paramIndexMapping.get(HttpServletRequest.class.getName());
            paramValues[reqIndex] = req;
        }
        if(handler.paramIndexMapping.containsKey(HttpServletResponse.class.getName())){
            int respIndex = handler.paramIndexMapping.get(HttpServletResponse.class.getName());
            paramValues[respIndex] = resp;
        }
        //调用handler的方法,也就是目标方法
        Object returnValue = handler.method.invoke(handler.controller,paramValues);
        if(returnValue == null || returnValue instanceof Void){
            return;
        }
        resp.getWriter().write(returnValue.toString());
    }

走到最后一步,也就没有什么了,主要是组装参数列表然后直接调用method对象的invoke方法即可。

7、servlet中的调用

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //6.调用
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

测试 

对应的测试controller

package com.learn.springmvc.controller;

import com.learn.springmvc.Annotation.SelfAutowired;
import com.learn.springmvc.Annotation.SelfController;
import com.learn.springmvc.Annotation.SelfRequestMapping;
import com.learn.springmvc.Annotation.SelfRequestParam;
import com.learn.springmvc.service.IDemoService;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

//虽然,用法一样,但是没有功能
@SelfController
@SelfRequestMapping("/demo")
public class DemoController {

  	@SelfAutowired
	private IDemoService demoService;

	@SelfRequestMapping("/query")
	public void query(HttpServletRequest req, HttpServletResponse resp,
					  @SelfRequestParam("name") String name){
		String result = "My name is " + name;
		try {
			resp.getWriter().write(result);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@SelfRequestMapping("/add")
	public void add(HttpServletRequest req, HttpServletResponse resp,
					@SelfRequestParam("a") Integer a, @SelfRequestParam("b") Integer b){
		try {
			resp.getWriter().write(a + "+" + b + "=" + (a + b));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

这个就是自己编写的Controller,启动之后在浏览器中输入:localhost:8080/demo/add?name=test,可以得到以下结果

 总结

在这个实例中反射用的非常多,无非就是利用反射获取注解的值,然后利用反射获取方法的参数列表,处理调用参数,整个过程不管如何总结,其实就是文章开头的流程图。每一步对应的就是指定的函数,但是函数中针对request和response都做了处理,显得些许臃肿。代码书写完之后,博客整理的异常凌乱,一下没理清整理的思路,具体源码可以参见如下地址:一个简单的mvc框架源码地址

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