手写200行代码实现精简版SpringMVC

1. Spring IOC、DI、MVC 的原理图

在这里插入图片描述

2. 开始构建自己的SpringMVC

项目目录
在这里插入图片描述

2.1 文件配置

web.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
      version="4.0">
 <servlet>
     <servlet-name>myspring</servlet-name>
     <servlet-class>com.gz.myspring.MyDispatchServlet</servlet-class>
     <init-param>
         <param-name>contextConfigLocation</param-name>
         <param-value>classpath:application.properties</param-value>
     </init-param>
     <load-on-startup>1</load-on-startup>
 </servlet>
 <servlet-mapping>
     <servlet-name>myspring</servlet-name>
     <url-pattern>/*</url-pattern>
 </servlet-mapping>
</web-app>		

application.properties 文件

scanPackge=com.gz

2.2 先用后写

HelloController.java 文件

package com.gz.controller;
import com.gz.myspring.annotation.MyAutowried;
import com.gz.myspring.annotation.MyController;
import com.gz.myspring.annotation.MyRequestMapping;
import com.gz.myspring.annotation.MyRequestParam;
import com.gz.service.HelloService;
/**
* @Author guozhi
* @Date 2019/7/14 16:36
* @Description TODO
*/

@MyController
@MyRequestMapping("/myspring")
public class HelloController {

 @MyAutowried
 private HelloService helloService;

 @MyRequestMapping("/")
 public String hello(){
     return helloService.Hello("mySpring");
 }

 @MyRequestMapping("/hello")
 public String hello2(@MyRequestParam("name")String name, @MyRequestParam("age") String age){
     return helloService.Hello(name + " your age is " + age);
 }
}

HelloService.java 文件

package com.gz.service;

/**
* @Author guozhi
* @Date 2019/7/14 16:27
* @Description TODO
*/
public interface HelloService {
 String Hello(String name);
}

HelloServiceImpl.java 文件

package com.gz.service.impl;
import com.gz.myspring.annotation.MyService;
import com.gz.service.HelloService;
/**
* @Author guozhi
* @Date 2019/7/14 16:27
* @Description TODO
*/

@MyService
public class HelloServiceImpl implements HelloService {
   public String Hello(String name) {
       return "hello "+name;
   }
}

2.3 注解

MyAutowried.java 文件

package com.gz.myspring.annotation;
import java.lang.annotation.*;

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

MyController.java 文件

package com.gz.myspring.annotation;
import java.lang.annotation.*;

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

MyRequestMapping.java 文件

package com.gz.myspring.annotation;
import java.lang.annotation.*;

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

MyRequestParam.java 文件

package com.gz.myspring.annotation;
import java.lang.annotation.*;

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

MyService.java 文件

package com.gz.myspring.annotation;
import java.lang.annotation.*;

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

2.4 DispatchServlet的init() 初始化

    // 上下文的配置文件
    private  Properties properties = new Properties();
    // 扫描指定包下的类名
    private  List<String> classNames = new ArrayList<String>();
    // ioc 容器
    private  Map<String, Object> ioc = new HashMap<String, Object>();
    // handleMapping url映射
    private Map<String, Method> handleMapping = new HashMap<String, Method>();
 @Override
 public void init(ServletConfig config) throws ServletException {
     // 1. 加载配置文件
     doLoadProperties(config);
     // 2. 扫描相关的类
     doScanClass(properties.getProperty("scanPackge"));
     // 3.初始化相关的类,并放入ioc容器
     doInstance();
     // 4. DI 操作
     doAutoried();
     // 5 handleMapping 的初始化
     initHandleMapping();
     System.out.println("mySpring started !");
 }
 /**
  * 初始化handleMapping 映射
  */
 private void initHandleMapping() {
     if (ioc.isEmpty()) return;
     for (Map.Entry entry : ioc.entrySet()){
         Class clazz = entry.getValue().getClass();
         if (!clazz.isAnnotationPresent(MyController.class)) continue;
         StringBuilder baseUrl = new StringBuilder("/");
         if (clazz.isAnnotationPresent(MyRequestMapping.class)){
             MyRequestMapping myRequestMapping = (MyRequestMapping) clazz.getAnnotation(MyRequestMapping.class);
             baseUrl.append(myRequestMapping.value());
         }
         Method [] methods = entry.getValue().getClass().getMethods();
         for (Method method : methods){
             if (!method.isAnnotationPresent(MyRequestMapping.class))
                 continue;
             MyRequestMapping myRequestMapping = method.getAnnotation(MyRequestMapping.class);
             StringBuilder stringBuilder = new StringBuilder(baseUrl);
             stringBuilder.append("/");
             stringBuilder.append(myRequestMapping.value());
             handleMapping.put(stringBuilder.toString().replaceAll("/+", "/"), method);
         }
     }
 }

 /**
  * 进行Di操作
  * 对 被@Autowried注解了的变量 进行初始化
  */
 private void doAutoried() {
     if (ioc.isEmpty()){
         return;
     }
     for (Map.Entry<String, Object> entry: ioc.entrySet()){
         Field[] fields = entry.getValue().getClass().getDeclaredFields();
         System.out.println(fields.length);
         for (Field field : fields){
             // 过滤没有被 @Autowried 注解的变量
             if (!field.isAnnotationPresent(MyAutowried.class)) continue;
             String beanName = field.getAnnotation(MyAutowried.class).value();
             if ("".equals(beanName)){
                 beanName = toLowerFirst(field.getType().getSimpleName());
             }
             field.setAccessible(true); // 强制授权
             try {
                 field.set(entry.getValue(), ioc.get(beanName));
             } catch (IllegalAccessException e) {
                 e.printStackTrace();
             }
         }
     }

 }

 /**
  * 对扫描到的类分析,如果有 MyController 或者 MyService 注解则进行实例化,并放入ioc容器中
  */
 private void doInstance(){
     if (classNames.isEmpty()) {
         return;
     }
     for (String name: classNames){
         try{
             Class clazz = Class.forName(name);
             Object instance = clazz.newInstance();
             if (clazz.isAnnotationPresent(MyController.class)){
                 // 将类名的首字母小写后,作为key, 其实例作为value,放入ioc容器中
                 ioc.put(toLowerFirst(clazz.getSimpleName()), instance);
             }else if (clazz.isAnnotationPresent(MyService.class)){
                 /*
                  * 首先判断 注解的value 值是否为空
                  * 如果不为空,则将其作为类名,实例化后加入到ioc容器中
                  * 如果为空: 1 获取这个类实现的所有接口,将接口名称首字母小写后,作为类名,加入到ioc容器中
                  *          2 将这个类的首字母小写后,作为类名key 加入到ioc容器中
                  * */
                 MyService myService = (MyService) clazz.getAnnotation(MyService.class);
                 String beanName = myService.value();
                 if ("".equals(beanName.trim())){
                     for (Class<?> i : clazz.getInterfaces()){
                         String iname = toLowerFirst(i.getSimpleName());
                         if (ioc.containsKey(iname)){
                             throw new Exception(String.format("beanName: %s is exists!", iname));
                         }
                         ioc.put(iname, instance);
                     }
                     beanName = toLowerFirst(clazz.getSimpleName());
                 }
                 ioc.put(beanName, instance);
             }
         }catch (Exception e){
             e.printStackTrace();
         }
     }

 }

 /**
  * 首字母小写
  * @param string
  * @return
  */
 private String toLowerFirst(String string){
     char [] chars = string.toCharArray();
     chars[0] += 32;
     return new String(chars);
 }

 /**
  * 扫描包路径下的所有 .class 文件
  * @param packagePath 包路径
  */
 private void doScanClass(String packagePath) {
     URL url = this.getClass().getClassLoader().getResource("/" + packagePath.replaceAll("\\.", "/"));
     for (File file : new File(url.getFile()).listFiles()){
         if (file.isDirectory()){
             this.doScanClass(packagePath + "." + file.getName());
         }else if (file.getName().contains(".class")){
             classNames.add((packagePath + "." + file.getName().replaceAll(".class", "")).trim());
         }
     }
 }

 /**
  * 加载配置文件
  * @param config 配置文件
  */
 private void doLoadProperties(ServletConfig config) {
     String contextConfigLocation = config.getInitParameter("contextConfigLocation").replace("classpath:", "");
     //从项目下取得配置文件的输入流
     InputStream in = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
     try {
         properties.load(in);
     } catch (IOException e) {
         e.printStackTrace();
     }finally {
         if (in != null)
             try {
                 in.close();
             } catch (IOException e) {
                 e.printStackTrace();
             }
     }
 }

2.5 doPost()进行映射处理

   @Override
 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
     doPost(req, resp);
 }
 @Override
 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
     doDispatch(req, resp);
 }
 
 /**
  * 将请求映射到指定的方法
  * @param req
  * @param resp
  * @throws IOException
  */
 private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException {
     if (handleMapping.isEmpty())
         return;
     String contextPath = req.getContextPath();
     String url = req.getRequestURI().replace(contextPath, "").replaceAll("/+", "/");
     if (!handleMapping.containsKey(url)){
         resp.getWriter().write("404 not found url " + url);
         return;
     }
     Method method = handleMapping.get(url);
     Parameter[] me_parameter = method.getParameters();
     List param_objs = new ArrayList();
     for (Parameter p : me_parameter){
         if (p.isAnnotationPresent((MyRequestParam.class))){
             param_objs.add(req.getParameterMap().get(p.getAnnotation(MyRequestParam.class).value())[0]);
         }
     }
     String beanName = toLowerFirst(method.getDeclaringClass().getSimpleName());
     try {
         resp.getWriter().write((String) method.invoke(ioc.get(beanName), param_objs.toArray()));
     } catch (IllegalAccessException | InvocationTargetException e) {
         e.printStackTrace();
     }
 }

2.6 完整的 MyDispatchServlet 代码

package com.gz.myspring;
import com.gz.myspring.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.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.util.*;
/**
 * @Author guozhi
 * @Date 2019/7/14 16:19
 * @Description TODO
 */
public class MyDispatchServlet extends HttpServlet {
    // 上下文的配置文件
    private  Properties properties = new Properties();
    // 扫描指定包下的类名
    private  List<String> classNames = new ArrayList<String>();
    // ioc 容器
    private  Map<String, Object> ioc = new HashMap<String, Object>();
    // handleMapping url映射
    private Map<String, Method> handleMapping = new HashMap<String, Method>();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doDispatch(req, resp);
    }

    /**
     * 将请求映射到指定的方法
     * @param req
     * @param resp
     * @throws IOException
     */
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        if (handleMapping.isEmpty())
            return;
        String contextPath = req.getContextPath();
        String url = req.getRequestURI().replace(contextPath, "").replaceAll("/+", "/");
        if (!handleMapping.containsKey(url)){
            resp.getWriter().write("404 not found url " + url);
            return;
        }
        Method method = handleMapping.get(url);
        Parameter[] me_parameter = method.getParameters();
        List param_objs = new ArrayList();
        for (Parameter p : me_parameter){
            if (p.isAnnotationPresent((MyRequestParam.class))){
                param_objs.add(req.getParameterMap().get(p.getAnnotation(MyRequestParam.class).value())[0]);
            }
        }
        String beanName = toLowerFirst(method.getDeclaringClass().getSimpleName());
        try {
            resp.getWriter().write((String) method.invoke(ioc.get(beanName), param_objs.toArray()));
        } catch (IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void init(ServletConfig config) throws ServletException {
        // 1. 加载配置文件
        doLoadProperties(config);
        // 2. 扫描相关的类
        doScanClass(properties.getProperty("scanPackge"));
        // 3.初始化相关的类,并放入ioc容器
        doInstance();
        // 4. DI 操作
        doAutoried();
        // 5 handleMapping 的初始化
        initHandleMapping();
        System.out.println("mySpring started !");
    }
    /**
     * 初始化handleMapping 映射
     */
    private void initHandleMapping() {
        if (ioc.isEmpty()) return;
        for (Map.Entry entry : ioc.entrySet()){
            Class clazz = entry.getValue().getClass();
            if (!clazz.isAnnotationPresent(MyController.class)) continue;
            StringBuilder baseUrl = new StringBuilder("/");
            if (clazz.isAnnotationPresent(MyRequestMapping.class)){
                MyRequestMapping myRequestMapping = (MyRequestMapping) clazz.getAnnotation(MyRequestMapping.class);
                baseUrl.append(myRequestMapping.value());
            }
            Method [] methods = entry.getValue().getClass().getMethods();
            for (Method method : methods){
                if (!method.isAnnotationPresent(MyRequestMapping.class))
                    continue;
                MyRequestMapping myRequestMapping = method.getAnnotation(MyRequestMapping.class);
                StringBuilder stringBuilder = new StringBuilder(baseUrl);
                stringBuilder.append("/");
                stringBuilder.append(myRequestMapping.value());
                handleMapping.put(stringBuilder.toString().replaceAll("/+", "/"), method);
            }
        }
    }

    /**
     * 进行Di操作
     * 对 被@Autowried注解了的变量 进行初始化
     */
    private void doAutoried() {
        if (ioc.isEmpty()){
            return;
        }
        for (Map.Entry<String, Object> entry: ioc.entrySet()){
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            System.out.println(fields.length);
            for (Field field : fields){
                // 过滤没有被 @Autowried 注解的变量
                if (!field.isAnnotationPresent(MyAutowried.class)) continue;
                String beanName = field.getAnnotation(MyAutowried.class).value();
                if ("".equals(beanName)){
                    beanName = toLowerFirst(field.getType().getSimpleName());
                }
                field.setAccessible(true); // 强制授权
                try {
                    field.set(entry.getValue(), ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    /**
     * 对扫描到的类分析,如果有 MyController 或者 MyService 注解则进行实例化,并放入ioc容器中
     */
    private void doInstance(){
        if (classNames.isEmpty()) {
            return;
        }
        for (String name: classNames){
            try{
                Class clazz = Class.forName(name);
                Object instance = clazz.newInstance();
                if (clazz.isAnnotationPresent(MyController.class)){
                    // 将类名的首字母小写后,作为key, 其实例作为value,放入ioc容器中
                    ioc.put(toLowerFirst(clazz.getSimpleName()), instance);
                }else if (clazz.isAnnotationPresent(MyService.class)){
                    /*
                     * 首先判断 注解的value 值是否为空
                     * 如果不为空,则将其作为类名,实例化后加入到ioc容器中
                     * 如果为空: 1 获取这个类实现的所有接口,将接口名称首字母小写后,作为类名,加入到ioc容器中
                     *          2 将这个类的首字母小写后,作为类名key 加入到ioc容器中
                     * */
                    MyService myService = (MyService) clazz.getAnnotation(MyService.class);
                    String beanName = myService.value();
                    if ("".equals(beanName.trim())){
                        for (Class<?> i : clazz.getInterfaces()){
                            String iname = toLowerFirst(i.getSimpleName());
                            if (ioc.containsKey(iname)){
                                throw new Exception(String.format("beanName: %s is exists!", iname));
                            }
                            ioc.put(iname, instance);
                        }
                        beanName = toLowerFirst(clazz.getSimpleName());
                    }
                    ioc.put(beanName, instance);
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }

    }

    /**
     * 首字母小写
     * @param string
     * @return
     */
    private String toLowerFirst(String string){
        char [] chars = string.toCharArray();
        chars[0] += 32;
        return new String(chars);
    }

    /**
     * 扫描包路径下的所有 .class 文件
     * @param packagePath 包路径
     */
    private void doScanClass(String packagePath) {
        URL url = this.getClass().getClassLoader().getResource("/" + packagePath.replaceAll("\\.", "/"));
        for (File file : new File(url.getFile()).listFiles()){
            if (file.isDirectory()){
                this.doScanClass(packagePath + "." + file.getName());
            }else if (file.getName().contains(".class")){
                classNames.add((packagePath + "." + file.getName().replaceAll(".class", "")).trim());
            }
        }
    }

    /**
     * 加载配置文件
     * @param config 配置文件
     */
    private void doLoadProperties(ServletConfig config) {
        String contextConfigLocation = config.getInitParameter("contextConfigLocation").replace("classpath:", "");
        //从项目下取得配置文件的输入流
        InputStream in = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            properties.load(in);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (in != null)
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }
    }
}

3. 运行效果

在这里插入图片描述
在这里插入图片描述

4. 遗留问题

  1. 在对请求的URL进行method映射时,只能将参数作为String类型处理,即 @MyRequestParam 注解的形参只能是String类型,这个问题是出在 method.invoke(ioc.get(beanName), objects) 中,其中objects是需要给方法传入的参数,是 Object[ ] 类型,预想的解决在从request中获取请求参数时,判断参数类型并进行强制转型,然后再放入 Objects[ ] 中
  2. 目前这个项目只对 doGet() 与doPost() 方法进行了处理,其他 put、delete等, 需要额外加代码,预想的解决办法就是像 doGet()中写的那样, 直接调用doPost()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值