手写简易版spring-mvc源码

1.0手写版,后续会继续更新
在这里插入图片描述
web.xml

<?xml version="1.0" encoding="UTF-8"?>
<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>Web Application</display-name>
	<servlet>
		<servlet-name>pablo_mvc</servlet-name>
		<servlet-class>com.bj.summary.spring_mvc.servlet.DispatchServlet</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>pablo_mvc</servlet-name>
		<url-pattern>/*</url-pattern>
	</servlet-mapping>
</web-app>

application.properties

scanPackage=com.bj.summary.spring_mvc.controller,com.bj.summary.spring_mvc.service

注解(扫描时根据注解做不同处理)

/**
 * @Authror PABLO
 * @Date 2022/5/4 13:59
 * @Desc
 */
@Target({
    
    ElementType.FIELD})  //字段(属性)注解
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PABLO_Autowired {
    
    

    String value() default "";
}
/**
 * @Authror PABLO
 * @Date 2022/5/4 13:57
 * @Desc Controller
 */
@Target({
    
    ElementType.TYPE})  //类注解
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PABLO_Controller {
    
    

    String value() default "";
}
/**
 * @Authror PABLO
 * @Date 2022/5/4 14:00
 * @Desc
 */
@Target({
    
    ElementType.TYPE, ElementType.METHOD})//类或方法,即controller和具体method
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PABLO_RequestMapping {
    
    
    String value() default "";
}
/**
 * @Authror PABLO
 * @Date 2022/5/4 14:01
 * @Desc
 */
@Target({
    
    ElementType.PARAMETER})//参数注解
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PABLO_RequestParam {
    
    

    String value() default "";
}
/**
 * @Authror PABLO
 * @Date 2022/5/4 14:01
 * @Desc
 */
@Target({
    
    ElementType.TYPE}) //类注解
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PABLO_Service {
    
    
    String value() default "";
}

Controller

/**
 * @Author PABLO
 * @Date 2022/5/4 15:32
 * @Desc
 */
@PABLO_Controller
@PABLO_RequestMapping("/pablo")
public class TestController {
    
    

    @PABLO_Autowired
    private TestService testService;

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

Service

/**
 * @Authror PABLO
 * @Date 2022/5/4 15:33
 * @Desc
 */
public interface TestService {
    
    


    int add(int a, int b);
}

ServiceImpl

/**
 * @Author PABLO
 * @Date 2022/5/4 15:34
 * @Desc
 */
@PABLO_Service
public class TestServiceImpl implements TestService {
    
    
    @Override
    public int add(int a, int b) {
    
    
        return a + b;
    }
}

servlet实现

package com.bj.summary.spring_mvc.servlet;


import com.bj.summary.spring_mvc.annotation.*;
import lombok.SneakyThrows;

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.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Author PABLO
 * @Date 2022/5/4 13:49
 * @Desc 继承HttpServlet实现servlet规范
 * 项目启动入口
 */
public class DispatchServlet extends HttpServlet {
    
    

    //根据contextConfigLocation获得properties文件路径
    /*
    * <init-param>
			<!--指定配置文件名字-->
			<param-name>contextConfigLocation</param-name>
			<param-value>application.properties</param-value>
		</init-param>
    * */
    private final static String LOCATION = "contextConfigLocation";
    //配置文件key
    private final static String propertiesKey = "scanPackage";
    //根据key获取配置文件内容
    /*
     * scanPackage=com.bj.summary.spring_mvc.controller,com.bj.summary.spring_mvc.service
     * */
    private Properties properties = new Properties();

    //存放扫描后得到的class文件名称
    private List<String> classNames = new ArrayList<String>();

    //IOC容器
    private Map<String, Object> ioc = new HashMap<String, Object>();

    //保存所有的Url和方法的映射关系
    private List<Handler> handlerMapping = new ArrayList<Handler>();

    /**
     * @Description: 初始化操作
     * @Author: PABLO
     * @Date: 2022/5/4 13:50
     * @Params: [config]
     * @Return: void
     **/
    @Override
    public void init(ServletConfig config) throws ServletException {
    
    
        //初始化步骤

        //加载配置文件properties
        doLoadConfig(config.getInitParameter(LOCATION));
        //扫描配置文件properties中定义的所有类
        doScannerConfig((String) properties.get(propertiesKey));
        //初始化所有的类,并实例化,保存IOC容器中
        doInstanceAndSaveIocContainer();
        //依赖注入,初始化对象
        doInitializationInstance();
        //构造handlerMapping
        doBuildHandlerMapping();
    }

    private void doBuildHandlerMapping() {
    
    
        if (Objects.isNull(ioc) || ioc.size() == 0) return;

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
    
    
            Class<?> clazz = entry.getValue().getClass();
            //只针对处理器
            if (!clazz.isAnnotationPresent(PABLO_Controller.class)) {
    
    
                continue;
            }

            String url = "";
            //获取Controller的url配置
            if (clazz.isAnnotationPresent(PABLO_RequestMapping.class)) {
    
    
                PABLO_RequestMapping requestMapping = clazz.getAnnotation(PABLO_RequestMapping.class);
                url = requestMapping.value();
            }

            //获取Method的url配置
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
    
    

                //没有加RequestMapping注解的直接忽略
                if (!method.isAnnotationPresent(PABLO_RequestMapping.class)) {
    
    
                    continue;
                }

                //映射URL controller路径+方法对应路径
                PABLO_RequestMapping requestMapping = method.getAnnotation(PABLO_RequestMapping.class);
                ///pablo/add  类注解路径+某方法路径
                String regex = ("/" + url + requestMapping.value()).replaceAll("/+", "/");
                Pattern pattern = Pattern.compile(regex);
                handlerMapping.add(new Handler(pattern, entry.getValue(), method));
                System.out.println("mapping URL路径" + regex + ",对应方法" + method);
            }
        }
    }

    private void doInitializationInstance() {
    
    
        if (Objects.isNull(ioc) || ioc.size() == 0) return;

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
    
    
            //拿到实例对象中的所有属性(任何修飾符)
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            //遍历该类中的所有字段
            for (Field item : fields
            ) {
    
    
                //过滤不需要自动装配的属性
                if (!item.isAnnotationPresent(PABLO_Autowired.class)) {
    
    
                    continue;
                }
                //获取所有需要装配的字段
                PABLO_Autowired autowired = item.getAnnotation(PABLO_Autowired.class);
                String beanName = autowired.value().trim();
                //未设置对象名
                if ("".equals(beanName)) {
    
    
                    //获取默认类名
                    beanName = item.getType().getName();
                }
                item.setAccessible(true); //设置私有属性的访问权限
                try {
    
    
                    //entry.getValue()是【实例化后的某个类对象】 如testController
                    //item是该类中的属性
                    //ioc.get(beanName),beanName是key,得到该类型对应的【具体实例化对象】
                    //给某个类型的某个属性真实的赋值,建立实例化后对象的 组合关系,这是符号引用-->直接引用
                    item.set(entry.getValue(), ioc.get(beanName));
                } catch (Exception e) {
    
    
                    e.printStackTrace();
                    continue;
                }
            }
        }
    }

    private void doInstanceAndSaveIocContainer() {
    
    
        if (Objects.isNull(classNames) || classNames.size() == 0) return;
        try {
    
    
            //获取每个类上注解
            for (String item : classNames
            ) {
    
    
                //根据完全限定名加载到内存
                Class<?> clazz = Class.forName(item);
                //根据类注解类型进行不同操作
                if (clazz.isAnnotationPresent((PABLO_Controller.class))) {
    
    
                    //默认将类首字母小写作为beanName
                    String beanName = lowerFirst(clazz.getSimpleName());
                    //key  testController小写首字母类名
                    //value  实例化后的对象
                    ioc.put(beanName, clazz.newInstance());
                } else if (clazz.isAnnotationPresent(PABLO_Service.class)) {
    
    
                    PABLO_Service service = clazz.getAnnotation(PABLO_Service.class);
                    String beanName = service.value();
                    //用户自定义对象名优先
                    if (!"".equals(beanName.trim())) {
    
    
                        ioc.put(beanName, clazz.newInstance());
                        continue;
                    }

                    //如果自己没设,就按接口类型创建一个实例
                    //走到这里 一定是某个类上有service注解,并且为自定义名称
                    //key 接口
                    //value 接口实现类实例对象
                    Class<?>[] interfaces = clazz.getInterfaces();
                    for (Class<?> i : interfaces) {
    
    
                        ioc.put(i.getName(), clazz.newInstance());
                    }

                } else {
    
    
                    //可扩展其他类注解如@Respository....
                    //类上没注解的略过
                    continue;
                }
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }

    }

    private void doScannerConfig(String packageNames) {
    
    
        //包可能有多个,数组处理
        String[] packageNameArr = packageNames.split(",");
        for (String item : packageNameArr
        ) {
    
    
            //处理每个包,将包路径转为文件路径  .-->/
            URL url = this.getClass().getClassLoader().getResource("/" + item.replaceAll("\\.", "/"));
            File dir = new File(url.getFile());
            for (File file : dir.listFiles()) {
    
    
                //如果是文件夹,继续递归
                if (file.isDirectory()) {
    
    
                    doScannerConfig(item + "." + file.getName());
                } else {
    
    
                    //E:\IDEALocation\giant-gator\target\classes\com\bj\summary\spring_mvc\controller\TestController.class
                    classNames.add(item + "." + file.getName().replace(".class", "").trim());
                }
            }
        }

    }

    private void doLoadConfig(String location) {
    
    
        InputStream fis = null;
        try {
    
    
            fis = this.getClass().getClassLoader().getResourceAsStream(location);
            //读取配置文件
            properties.load(fis);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (null != fis) {
    
    
                    fis.close();
                }
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
    }

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

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


    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        try {
    
    
            doDispatch(req, resp); //开始始匹配到对应的方方法

        } catch (Exception e) {
    
    
            //如果匹配过程出现异常,将异常信息打印出去
            resp.getWriter().write("500 Exception,Details:\r\n" + Arrays.toString(e.getStackTrace()).replaceAll("\\[|\\]", "").replaceAll(",\\s", "\r\n"));
        }
    }

    @SneakyThrows
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) {
    
    

        try {
    
     //通过请求获取对应handler,在构建handler时就已经指定 某个请求->某个方法(某些参数)
            Handler handler = getHandler(req);

            if (handler == null) {
    
    
                //如果没有匹配上,返回404错误
                resp.getWriter().write("404 Not Found");
                return;
            }


            //获取方法的参数列表 都是类型的完全限定名如java.lang.Integer java.lang.String...
            Class<?>[] paramTypes = handler.method.getParameterTypes();

            //保存所有需要自动赋值的参数值
            Object[] paramValues = new Object[paramTypes.length];

            //得到方法参数名称和对应的值  key=a参数名称  value=[具体入参值]
            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);
            }


            //设置方法中的request和response对象
            int reqIndex = handler.paramIndexMapping.get(HttpServletRequest.class.getName());
            paramValues[reqIndex] = req;
            int respIndex = handler.paramIndexMapping.get(HttpServletResponse.class.getName());
            paramValues[respIndex] = resp;

            handler.method.invoke(handler.controller, paramValues);

        } catch (InvocationTargetException e) {
    
    
            e.getTargetException().printStackTrace();
        }
    }

    //可采用策略,如map
    private Object convert(Class<?> paramType, String value) {
    
    
        if (Integer.class == paramType) {
    
    
            return Integer.valueOf(value);
        }
        return value;
    }

    private Handler getHandler(HttpServletRequest req) throws Exception {
    
    
        if (handlerMapping.isEmpty()) {
    
    
            return null;
        }

        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replace(contextPath, "").replaceAll("/+", "/");
        //根据请求路径匹配mapping
        for (Handler handler : handlerMapping) {
    
    
            try {
    
    
                Matcher matcher = handler.pattern.matcher(url);
                //如果没有匹配上继续下一个匹配
                if (!matcher.matches()) {
    
    
                    continue;
                }

                return handler;
            } catch (Exception e) {
    
    
                throw e;
            }
        }
        return null;
    }

    /**
     * @Author PABLO
     * @Date 2022/5/4 13:49
     * @Desc Handler记录Controller中的RequestMapping和Method的对应关系
     * 这里是在集合中绑定一对一关系,method一定只属于一个controller,一个controller可有N个method
     */
    private class Handler {
    
    

        protected Object controller;    //保存方法对应的实例
        protected Method method;        //保存映射的方法
        protected Pattern pattern;
        // key是注解上的value,value是该参数的下标,记录某个value的参数值,对应的下标
        //如add(@PABLO_RequestParam("a") Integer a, @PABLO_RequestParam("b") Integer b)
        //key=a  value=0
        //key=b  value=1
        protected Map<String, Integer> paramIndexMapping;

        /**
         * 构造一个Handler基本的参数
         *
         * @param controller
         * @param method
         */
        protected Handler(Pattern pattern, Object controller, Method method) {
    
    
            this.controller = controller;
            this.method = method;
            this.pattern = pattern;

            paramIndexMapping = new HashMap<String, Integer>();
            putParamIndexMapping(method);
        }
        //将某个方法和该方法上的参数绑定,具体入参在前,req和resp在后
        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 PABLO_RequestParam) {
    
    
                        String paramName = ((PABLO_RequestParam) a).value();
                        //这里只有该注解中的value值不为null才行
                        if (!"".equals(paramName.trim())) {
    
    
                            //只有标记了该注解的参数才会被识别
                            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) {
    
    
                    paramIndexMapping.put(type.getName(), i);
                }
            }
        }
    }


}

原创不易,谢谢大家支持,如对你有帮助,可以奉献一下你的爱心
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/GiantCrocodile/article/details/124575720