300行代码实现Spring核心原理(彻底搞懂IOC、DI)


我们都知道spring的执行原理,甚至为了面试倒背如流(http请求–>dispatcherServlet–>HandlerMapping–>Handler–>…),我之前虽然是知道这些东西,但是感觉对它又很模糊,dispatcherServlet具体是什么?HandlerMapping又是干什么的?IOC、DI是怎么实现的?这些东西在脑海中都是似懂非懂的。百度查资料什么的其实都不如自己手写一遍来的实在。也就几百行代码,手写一遍,相信你一定会变得更自信。

注意:代码中注释都很全。源码地址:https://gitee.com/isczy/mySpringCore.git

1.准备工作

配置application.properties,就和xml中配置的一个性质
在这里插入图片描述
配置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>WebApplication</display-name>
    <servlet>
        <servlet-name>MySpringMvc</servlet-name>
        <servlet-class>com.czy.project.springMVCframework.servlet.MyDispatcherServlet</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>MySpringMvc</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

2.编写自定义注解

自定义一些常用的注解:如
@Autowired

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

@Controller

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

@RequestMapping

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

@RequestParam

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

@Service

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

3.编写简单的Controller、Service

@MyController
@MyRequestMapping("/demo")
public class DemoController {

    @MyAutowired
    private DemoService demoService;

    @MyRequestMapping("/query")
    public void query(HttpServletRequest req, HttpServletResponse resp,
                      @MyRequestParam("name") String name,@MyRequestParam("age") int age){

        String result = demoService.query(name)+" and age is "+age;
        try {
            resp.setHeader("Content-type", "text/html;charset=UTF-8");
            resp.setCharacterEncoding("utf-8");
            resp.getWriter().write(result);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @MyRequestMapping("/add")
    public void add(HttpServletRequest req, HttpServletResponse resp,
                    @MyRequestParam("name") String name){
        String result = demoService.add(name);
        try {
            resp.setHeader("Content-type", "text/html;charset=UTF-8");
            resp.setCharacterEncoding("utf-8");
            resp.getWriter().write(result);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @MyRequestMapping("/remove")
    public void remove(HttpServletRequest req,HttpServletResponse resp,
                       @MyRequestParam("name") String name){
        try {
            String result = demoService.remove(name);
            resp.setHeader("Content-type", "text/html;charset=UTF-8");
            resp.setCharacterEncoding("utf-8");
            resp.getWriter().write(result);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
public interface DemoService {

    String query (String name);
    String add(String name);
    String remove(String name);
}
@MyService
public class DemoServiceImpl implements DemoService {

    @Override
    public String query(String name) {
        return "query success :My name is " + name ;
    }

    @Override
    public String add(String name) {
        return "添加成功:新增用户 " + name ;
    }

    @Override
    public String remove(String name) {
        return "删除成功:删除用户 " + name ;
    }
}

4.编写核心重点:DispatcherServlet

/**
 * 入口类
 * 即spring中的DispatcherServlet
 * @author czy
 */
public class MyDispatcherServlet extends HttpServlet {

    private static final String LOCATION = "contextConfigLocation";//即web.xml中配置的init-param-name

    private Properties properties = new Properties();//用于保存配置文件application.properties的内容

    private List<String> classNames = new ArrayList<String>();//保存所有扫描出的全限定类名的集合

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

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

    /**
     * 初始化一系列操作
     * @throws ServletException
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        //1.加载配置文件
        //根据contextConfigLocation名称从web.xml中获取出配置文件全称:application.properties

        String initParameter = config.getInitParameter(LOCATION);
        doLoadConfig(initParameter);

        /**
         * 2.扫描指定包下的类:spring中在application.xml中配置scanPackage指定扫描的包路径
         * 这里直接用application.properties代替
         */
        String scanPackage = properties.getProperty("scanPackage");//通过scanPackage键获取值,即获取要扫描的包路径
        doScanner(scanPackage);
        //3.初始化所有相关类的实例(这里就是初始化加@MyController,@MyService的类),并保存到IOC容器中
        doInstance();
        //4.依赖注入
        doAutowired();
        //5.构造HandlerMapping
        initHandlerMapping();
        //******************到此为止,spring相关ioc、di等初始化完成**********************
        //******************等待请求,匹配URL,定位方法, 反射调用执行*******************
        //******************调用doGet或者doPost方法*************************************
        System.out.println("MyDispatcherServlet[初始化完成]。。。等待调用。。。");
    }

    /**
     * doGet和doPost方法大家应该相当了解了
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req,resp);//doGet的操作交个doPost来执行
    }

    @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"));//将‘[’或者‘]’替换成"",将空白字符替换成换行符
        }

    }

    /**
     *匹配URL
     * @param req
     * @param resp
     */
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        Handler handler = getHandler(req);
        if (null == handler){
        //如果没有匹配上,返回404错误
            resp.getWriter().write("404 Not Found");//常见操作。。。
            return;
        }
        System.out.println("已匹配到处理器["+handler+"]");
        //获取方法的参数列表
        Class<?>[] paramTypes = handler.method.getParameterTypes();
        //保存所有需要自动赋值的参数值
        Object [] paramValues = new Object[paramTypes.length];
        Map<String,String[]> parameterMap = req.getParameterMap();//获取用户传入的参数map
        for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
            //获取所有参数即数组,并将数组转化成string,并替换[]为""、空白字符为","
            String value = new String(Arrays.toString(param.getValue()).getBytes("iso8859-1"),"utf-8").
                    replaceAll("\\[|\\]", "").replaceAll("\\s", ",");

            //如果handler的参数列表没有用户传入的key则跳出本次循环
            if (!handler.paramIndexMapping.containsKey(param.getKey()))continue;
            //找到匹配的参数,则开始填充参数值
            int index = handler.paramIndexMapping.get(param.getKey());//根据参数名获取参数对应的序号
            //url传过来的参数都是String类型的,HTTP是基于字符串协议
            //只需要把String转换为所需类型就好
            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;
        //执行url对应的方法
        System.out.println("开始执行url对应的方法:"+handler.method.getName());
        handler.method.invoke(handler.controller, paramValues);
    }

    /**
     *获取处理器Handler
     * @param request
     * @return
     */
    private Handler getHandler(HttpServletRequest request) {
        if (handlerMapping.isEmpty())return null;
        String url = request.getRequestURI();
        String contextPath = request.getContextPath();
        url = url.replace(contextPath,"").replaceAll("/+","/");

        for (Handler handler : handlerMapping) {
            Matcher matcher = handler.pattern.matcher(url);//根据用户的url获取一个匹配器matcher
            //matches方法用于全字符串匹配也就是100%匹配
            if (!matcher.matches())continue;//如果没有匹配到则跳过本次循环
            return handler;
        }
        return null;
    }

    /**
     * 初始化HandlerMapping(处理器映射器):
     * HandlerMapping其实就是一个包含Handler的集合
     */
    private void initHandlerMapping() {
        if (ioc.isEmpty())return;
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();
            //如果该类上没有MyController注解则跳过本次循环
            if (!clazz.isAnnotationPresent(MyController.class))continue;
            String url = "";
            if (clazz.isAnnotationPresent(MyRequestMapping.class)){
                //如果类上有MyRequestMapping注解,获取Controller的url配置
                url =clazz.getAnnotation(MyRequestMapping.class).value();
            }

            //获取Method的url配置
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                //没有加RequestMapping注解的直接忽略
                if(!method.isAnnotationPresent(MyRequestMapping.class))continue;
                //映射URL
                MyRequestMapping requestMapping = method.getAnnotation(MyRequestMapping.class);
                String regex =("/"+url+"/"+requestMapping.value()).replaceAll("/+","/");
                Pattern pattern = Pattern.compile(regex);
                handlerMapping.add(new Handler(entry.getValue(),method,pattern));
                System.out.println("HandlerMapping初始化完成【url:" + regex +"】" +"【method:" + method+"】");
            }
        }
    }

    /**
     * 依赖注入:就是拿到ioc容器中的类,然后访问类中的字段属性,是否包含Autowired注解
     * 然后从ioc容器中拿到实例并初始化该字段
     */
    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) {
                //如果该属性上没有MyAutowired注解则跳过本次循环
                if (!field.isAnnotationPresent(MyAutowired.class))continue;

                MyAutowired autowired = field.getAnnotation(MyAutowired.class);
                String beanName = autowired.value();
                if ("".equals(beanName)){//用户没有指定注入的beanName
                    beanName = field.getType().getName();//那么beanName就是该属性的名称
                }
                field.setAccessible(true);//暴力访问私有属性
                try {
                    field.set(entry.getValue(),ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                    continue ;
                }
            }
        }
    }

    /**
     * 初始化所有相关类的实例,并保存到IOC容器中
     */
    private void doInstance() {
        if(classNames.size() == 0)return;
        //遍历全限定类名集合,拿到各类,并初始化
        try {
            for (String className : classNames) {
                Class<?> clazz = Class.forName(className);
                if (clazz.isAnnotationPresent(MyController.class)){ //如果类上声明了MyController注解
                   //将类名作为beanName,即ioc容器的key
                    String beanName = clazz.getName();
                    ioc.put(clazz.getName(),clazz.newInstance());
                    System.out.println("["+beanName+"]已添加到IOC容器");
                }else if (clazz.isAnnotationPresent(MyService.class)){//如果类上声明了MyService注解
                    MyService myService = clazz.getAnnotation(MyService.class);
                    String beanName = myService.value();//获取自定义的beanName
                    if (!"".equals(beanName.trim())){//如果用户设置了自定义的beanName,就用用户自己设置
                        ioc.put(beanName,clazz.newInstance());
                        System.out.println("["+beanName+"]已添加到IOC容器");
                        continue;
                    }
                    //如果自己没设,就按接口类型创建一个实例
                    Class<?>[] interfaces = clazz.getInterfaces();//获取该类实现的接口类
                    for (Class<?> c : interfaces) {
                        ioc.put(c.getName(),clazz.newInstance());
                        System.out.println("["+c.getName()+"]已添加到IOC容器");
                    }
                }else{
                    continue;
                }
            }

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


    /**
     * 扫描指定包下的类
     * @param scanPackage:包扫描路径
     */
    private void doScanner(String scanPackage) {
        //将包路径转换为文件路径:即将com.czy.project.demo转化为 com/czy/project/demo
        String s = scanPackage.replace(".", "/");
        URL url = this.getClass().getClassLoader().getResource(s);
        File dir = new File(url.getFile());//获取该路径的文件
        //遍历该文件夹下的所有文件
        for (File file : dir.listFiles()) {
            if (file.isDirectory()){
                doScanner(scanPackage+"."+file.getName());//如果该文件是个文件夹,继续递归
            }else {
                //将全限定类名添加到集合
                classNames.add(scanPackage+"."+file.getName().replace(".class","").trim());
            }
        }
        for (String className : classNames) {
            System.out.println("已扫描到:["+className+"]");
        }
    }

    /**
     * 加载配置文件:application.properties
     * @param initParameter
     */
    private void doLoadConfig(String initParameter){
        //将application.properties加载到输入流
        try(InputStream is = this.getClass().getClassLoader().getResourceAsStream(initParameter)) {
            properties.load(is);//读取配置文件
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 内部类
     * Handler记录Controller中的RequestMapping和Method的对应关系
     * 即spring中一个url请求对应一个方法
     */
    private class Handler{
        protected Object controller;	//保存方法对应的实例
        protected Method method;		//保存映射的方法
        protected Pattern pattern;      //url对应的正则表达式,用于映射匹配
        protected Map<String,Integer> paramIndexMapping;	//参数名以及序号

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

            paramIndexMapping = new HashMap<String, Integer>();
            putParamIndexMapping(method);
        }

        /**
         * 处理方法中的参数
         * @param method
         */
        private void putParamIndexMapping(Method method) {
            //获取方法参数上的注解,注意Annotation是一个二维数组
            Annotation[][] p = method.getParameterAnnotations();
            for (int i = 0; i < p.length; i++) {
                for (Annotation a : p[i]) {
                    if (a instanceof MyRequestParam){//如果这个注解是MyRequestParam
                        String param = ((MyRequestParam) a).value();
                        if (!"".equals(param)){
                            paramIndexMapping.put(param,i);
                        }

                    }
                }
            }

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

        @Override
        public String toString() {
            return "Handler{" +
                    "controller=" + controller +
                    ", method=" + method +
                    ", pattern=" + pattern +
                    ", paramIndexMapping=" + paramIndexMapping +
                    '}';
        }
    }

    /**
     *  HTTP是基于字符串协议,所以url传过来的参数都是String类型的,
     *  只需要把String转换为对应方法中的类型就好
     * @param type
     * @param value
     * @return
     */
    private Object convert(Class<?> type,String value){
        if(Integer.class == type||type == int.class){
            return Integer.valueOf(value);
        }
        //如果还有double或者其他类型,继续加if
        //这时候,我们应该想到策略模式了
        return value;
    }

}

5.测试

启动项目后可以看到,初始化MyDispatcherServlet都做了什么
在这里插入图片描述
浏览器地址输入路径:http://localhost:8088/demo/query?name=zhangsan&age=18
访问结果:query success :My name is zhangsan and age is 18

在这里插入图片描述

  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值