深入理解SpringMVC ---- 手写简化版

目的

为了能够更好的理解SpringMVC,自己动手去实现一个SpringMVC的简化版,能够记忆更加深刻.

整体思路

  1. 配置web.xml文件,配置自己自定义的前端控制器FatDispatcherServlet
  2. 配置自定义配置文件.
  3. 配置Annotation文件,自定义自己注解,这里定义了几个常用的注解,
    @FatController,@FatService,@FatAutowired,@FatRequestMapping,@FatRequestParam
  4. 新建FatDispatcherServlet类,继承HttpServlet,并重写其中的init()方法和doGet()方法.
  5. 在初始化方法中完成配置文件的加载,类的扫描,对象的创建并放到ioc容器中,创建完对象之后进行依赖注入,最后做url路径和对应控制器中的方法映射.
  6. 在doGet()方法中根据url请求路径,做请求分发及参数处理.

具体实现代码

  • web.xml
<!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>
  <display-name>Archetype Created Web Application</display-name>
  
  <servlet>
    <servlet-name>FatDispatcherServlet</servlet-name>
    <servlet-class>mvcframework.servlet.FatDispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>application.properties</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>FatDispatcherServlet</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
  
</web-app>

  • 自定义配置文件application.properties (就一行代码,你没看错)
scanPackage=com.fat.demo
  • 自定义注解(五个注解)
package mvcframework.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @ClassName FatController
 * @Auther LangGuofeng
 * @Date: 2019/8/28/028 10:43
 * @Description: TODO
 */
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface FatController {
}

package mvcframework.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @ClassName FatService
 * @Auther LangGuofeng
 * @Date: 2019/8/28/028 10:46
 * @Description: TODO
 */
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface FatService {
    String value() default "";
}

package mvcframework.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @ClassName FatAutowired
 * @Auther LangGuofeng
 * @Date: 2019/8/28/028 11:25
 * @Description: TODO
 */
@Target({ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface FatAutowired {
    String value() default "";
}

package mvcframework.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @ClassName FatRequestMapping
 * @Auther LangGuofeng
 * @Date: 2019/8/28/028 12:31
 * @Description: TODO
 */
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface FatRequestMapping {
    String value();
}

package mvcframework.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface FatRequestParam {
    String value();
}

  • FatDispatcherServlet(这个类就比较核心了)
package mvcframework.servlet;

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

/**
 * @ClassName FatDispatcherServlet
 * @Auther LangGuofeng
 * @Date: 2019/8/28/028 8:52
 * @Description: TODO
 */
public class FatDispatcherServlet extends HttpServlet {

    //保存application配置文件中的内容
    private Properties contexConfig = new Properties();
    //保存扫描到的类名
    private List<String> classNames = new ArrayList<>();
    //IOC容器
    private Map<String, Object> ioc = new HashMap<>();
    //保存url和Method的对应关系
    private Map<String, Method> handlerMapping = new HashMap<>();


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //7.调用, 运行阶段
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
            resp.getWriter().write("500 Excetion,Detail : " + Arrays.toString(e.getStackTrace()));
        }
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
//      1. 加载配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));
//      2.扫描相关的类
        doScanner(contexConfig.getProperty("scanPackage"));
//      3.初始化扫描到的类,并将它放入IOC容器
        doInstance();
//      4.完成依赖注入
        doAutowired();
//      5.初始化HandlerMapping
        initHandlerMapping();

        System.out.println("Fat Spring framework is init.");
    }

    /**
     * @Author LangGuofeng
     * @Description 请求分发及参数处理
     * @Date  2019/8/30/030 15:22
     * @Param [req, resp]
     * @return void
     **/
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException, InvocationTargetException, IllegalAccessException {
        //绝对路径
        String url = req.getRequestURI();
        //处理成相对路径
        String contexPath = req.getContextPath();
        url = url.replaceAll(contexPath, "").replaceAll("/+", "/");
        if (!this.handlerMapping.containsKey(url)) {
            resp.getWriter().write("404 Not Found!!!");
            return;
        }

        Method method = this.handlerMapping.get(url);

        //通过反射通过method获取class method.getDeclaringClass()
        String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());

        //从request中获取参数列表
        Map<String, String[]> parameterMap = req.getParameterMap();
        //从method中获取形参列表
        Parameter[] params = method.getParameters();


        Object[] paramValues = new Object[params.length];

        for (int i = 0; i < params.length; i++) {
            Parameter param = params[i];
            Class parameterType = param.getType();
            if (parameterType == HttpServletRequest.class) {
                paramValues[i] = req;
                continue;
            } else if (parameterType == HttpServletResponse.class) {
                paramValues[i] = resp;
                continue;
            } else {
                //获取param上的注解
                Annotation[] annos = param.getAnnotations();

                for (Annotation annotation : annos) {
                    if (annotation instanceof FatRequestParam) {
                        String paramName = ((FatRequestParam) annotation).value();
                        //判断req参数列表中是否包含paramName
                        if (parameterMap.containsKey(paramName.trim())) {
                            String value = Arrays.toString(parameterMap.get(paramName)).replaceAll("\\[|\\]|,", "");
                            System.out.println(value);
                            Object val = convert(parameterType, value);

                            paramValues[i] = val;
                        }
                    }
                }
            }
        }

        method.invoke(ioc.get(beanName), paramValues);
    }

    /**
     * @return java.lang.Object
     * @Author LangGuofeng
     * @Description String 转换成对应类型
     * @Date 2019/8/30/030 10:31
     * @Param [parameterType, value]
     **/
    private Object convert(Class parameterType, String value) {

        Object val = null;


        if (parameterType == Integer.class || parameterType == int.class) {
            val = new Integer(value);
        }

        if (parameterType == String.class) {
            val = value;
        }

        return val;
    }


    /**
     * @return void
     * @Author LangGuofeng
     * @Description 初始化url和method的一对一对应关系
     * @Date 2019/8/28/028 12:01
     * @Param []
     **/
    private void initHandlerMapping() {
        if (ioc.isEmpty()) {
            return;
        }

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();

            if (!clazz.isAnnotationPresent(FatController.class)) {
                continue;
            }

            //保存写在类上面的@FatRequestMapping("/demo")
            String baseUrl = "";
            if (clazz.isAnnotationPresent(FatRequestMapping.class)) {
                FatRequestMapping fatRequestMapping = clazz.getAnnotation(FatRequestMapping.class);
                baseUrl = fatRequestMapping.value();
            }

            //默认获取所有的public方法
            for (Method method : clazz.getMethods()) {

                if (!method.isAnnotationPresent(FatRequestMapping.class)) {
                    continue;
                }

                FatRequestMapping requestMapping = method.getAnnotation(FatRequestMapping.class);
                String url = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/");
                handlerMapping.put(url, method);
                System.out.println("Mapped :" + url + "," + method);
            }
        }
    }

    /**
     * @return void
     * @Author LangGuofeng
     * @Description 依赖注入
     * @Date 2019/8/28/028 11:17
     * @Param []
     **/
    private void doAutowired() {
        if (ioc.isEmpty()) {
            return;
        }

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            //Declared 所有的, 特定的 字段, 包括private
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields) {
                if (!field.isAnnotationPresent(FatAutowired.class)) {
                    continue;
                }
                FatAutowired autowired = field.getAnnotation(FatAutowired.class);

                //如果用户没有自定义beanName,默认就根据类型注入
                String beanName = autowired.value().trim();
                if ("".equals(beanName)) {
                    //获得接口的类型名,做为key,用来在ioc中取值
                    beanName = field.getType().getName();
                }

                //如果是public以外的修饰符,只要加了@Autowired注解,都要强制赋值
                //反射中叫做暴力赋值
                field.setAccessible(true);

                try {
                    field.set(entry.getValue(), ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * @return void
     * @Author LangGuofeng
     * @Description 初始化为DI做准备
     * @Date 2019/8/28/028 10:24
     * @Param []
     **/
    private void doInstance() {
        if (classNames.isEmpty()) {
            return;
        }

        for (String className : classNames) {
            try {
                Class<?> clazz = Class.forName(className);
                if (clazz.isAnnotationPresent(FatController.class)) {
                    Object instance = clazz.newInstance();
                    String beanName = toLowerFirstCase(clazz.getSimpleName());
                    ioc.put(beanName, instance);
                } else if (clazz.isAnnotationPresent(FatService.class)) {
                    //1.自定义的beanName
                    FatService service = clazz.getAnnotation(FatService.class);
                    String beanName = service.value();
                    //2.默认类名首字母小写
                    if ("".equals(beanName)) {
                        beanName = toLowerFirstCase(clazz.getSimpleName());
                    }

                    Object instance = clazz.newInstance();
                    ioc.put(beanName, instance);

                    //3.根据类型自动赋值
                    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 (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * @return java.lang.String
     * @Author LangGuofeng
     * @Description 首字母大写转小写
     * @Date 2019/8/28/028 10:49
     * @Param [simpleName]
     **/
    private String toLowerFirstCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

    /**
     * @return void
     * @Author LangGuofeng
     * @Description 扫描出相关的类
     * @Date 2019/8/28/028 9:31
     * @Param [scanPackage]
     **/
    private void doScanner(String scanPackage) {
        //scanPackage=com.fat.demo , 存储的是包路径
        //转换为文件路径,实际上就是把 . 替换为 /
        //classpath
        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);
            }
        }
    }

    /**
     * @return void
     * @Author LangGuofeng
     * @Description 加载配置文件
     * @Date 2019/8/28/028 9:25
     * @Param [contextConfigLocation]
     **/
    private void doLoadConfig(String contextConfigLocation) {

        //直接从类路径中找到Spring主配置文件所在的路径
        //并且将其读取出来放到properties对象中
        //相当于将配置文件保存到内存中
        InputStream fis = null;
        fis = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);

        try {
            contexConfig.load(fis);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {

            }
        }

    }
}

  • Controller类(简易版,传参的时候一定要使用@FatRequestParam注解)
package com.fat.demo;

import mvcframework.annotation.FatAutowired;
import mvcframework.annotation.FatController;
import mvcframework.annotation.FatRequestMapping;
import mvcframework.annotation.FatRequestParam;

import javax.management.Query;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @ClassName DemoController
 * @Auther LangGuofeng
 * @Date: 2019/8/28/028 12:45
 * @Description: TODO
 */
@FatController()
@FatRequestMapping("demo")
public class DemoController {

    @FatAutowired()
    private IDemoService demoService;

    @FatRequestMapping("query")
    public void query(HttpServletResponse resp, HttpServletRequest req, @FatRequestParam("name")String name) {

        String res = demoService.query(name);

        try {
            resp.getWriter().write(res);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @FatRequestMapping("add")
    public void add(@FatRequestParam("a")int a, @FatRequestParam("b")int b, HttpServletResponse resp){

        int c = a + b;
        String str = a+" + "+b + " = " + c;

        try {
            resp.getWriter().write(str);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

  • IDemoService
package com.fat.demo;

public interface IDemoService {

    String query(String name);
}

  • DemoServiceImpl
package com.fat.demo;

import mvcframework.annotation.FatService;

/**
 * @ClassName DemoServiceImpl
 * @Auther LangGuofeng
 * @Date: 2019/8/28/028 12:50
 * @Description: TODO
 */
@FatService()
public class DemoServiceImpl implements IDemoService {

    @Override
    public String query(String name) {
        return "The Student name is " + name + ".";
    }

}

注意: 需要注意的一点就是你的FatDispatcherServlet需要继承HttpServlet的话需要引用servlet的api,我这里用的maven构建的项目,如果不是maven构建的自行下个jar包.

  • maven引入servlet-api
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.5</version>
    </dependency>

总结

每天进步一点点,只要你去做了,就一定会有收获.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值