纯手写springmvc,深入了解spring核心思想

3 篇文章 0 订阅
1 篇文章 0 订阅

本文纯手写springmvc核心功能,主要通过反射,注解的形式帮助大家清晰了解spring的核心思想。

1.idea新建web maven工程

2.配置web.xml,注入DispatcherServlet

<!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>gfhMvc</servlet-name>
    <servlet-class>com.gfh.mvc.framework.DispatcherServlet</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>gfhMvc</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>

</web-app>
新增application.properties,写入扫描的包 scanPackage=com.gfh.mvc.framework

3.初始化配置文件

private void initConfig(ServletConfig config) {
    System.out.println("initConfig");
    InputStream is = this.getClass().getClassLoader().getResourceAsStream(config.getInitParameter("contextConfigLocation"));
    try {
        properties.load(is);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if (null != is)
                is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

4.扫描包下的类,并注入ioc容器

/**
 * 扫描包路径下的controller,并完成ioc注入
 *
 * @param contextPath:扫描包的路径
 */
private void scanPackage(String contextPath) {
    URL url = this.getClass().getClassLoader().getResource("/" + contextPath.replaceAll("\\.", "/"));

    File classDir = new File(Objects.requireNonNull(url).getFile());
    for (File file : Objects.requireNonNull(classDir.listFiles())) {
        if (file.isDirectory()) {
            scanPackage(contextPath + "." + file.getName());
            continue;
        }
        //不是文件夹,是class类
        classList.add((contextPath + "." + file.getName()).replaceAll(".class", ""));
    }
    //反射实例化,存放入ioc容器中
    for (String clsName : classList) {
        try {
            Class<?> cls = Class.forName(clsName);
            //查询是否包含指定注解,如果不包含,则不注入
            if (!isAnnotationPresent(cls, classes))
                continue;
            if (cls.isAnnotationPresent(Controller.class)) {
                iocMap.put(lowerFirstCapse(cls.getSimpleName()), cls.newInstance());

            } else if (cls.isAnnotationPresent(Service.class)) {
                iocMap.put(lowerFirstCapse(cls.getSimpleName()), cls.newInstance());

            }
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

5.自动注入service,使用注解@Autowired

/**
 * 自动注入service
 */
private void autowired() {
    for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
        Field[] fields = entry.getValue().getClass().getDeclaredFields();
        for (Field field : fields) {
            if (!field.isAnnotationPresent(Autowired.class)) continue;
            try {
                field.setAccessible(true);
                field.set(entry.getValue(), iocMap.get(lowerFirstCapse(field.getType().getSimpleName())));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
}

扫描ioc实例化过后的controller,找出带有注解@Autowired的field,并且将改field进行反射注入

6.注册路径映射,拦截路径并解析到容器中

private void initHandlerMapping() {
    for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
        Class<?> cls = entry.getValue().getClass();
        if (!cls.isAnnotationPresent(Controller.class))
            continue;
        String baseUrl = cls.getAnnotation(RequestMapping.class).value();
        Method[] methods = cls.getMethods();
        if (null == methods)
            continue;
        if (methods.length == 0) continue;

        for (Method method : methods) {
            if (!method.isAnnotationPresent(RequestMapping.class)) continue;
            urlMap.put(baseUrl + method.getAnnotation(RequestMapping.class).value(), method);
        }
    }

}

7.doDispatch方法处理来访

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

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    try {
        doDispatch(req, resp);
    } catch (Exception e) {
        e.printStackTrace();
        resp.getWriter().write("500");
    }

}

如果该方法出现异常,则返回500的错误给到前端.如果路径容器中不包含来访路径,则返回404,下面是处理来访的核心代码

/**
 * 处理get,post反射
 *
 * @param req                      :HttpServletRequest
 * @param resp:HttpServletResponse
 */
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    String uri = req.getRequestURI();
    if (!urlMap.containsKey(uri)) {
        resp.getWriter().write("404");
        return;
    }
    resp.setContentType("text/html;charset=UTF-8");
    req.setCharacterEncoding("UTF-8");
    Map<String, String[]> parameterMap = req.getParameterMap();
    Method method = urlMap.get(uri);
    String clsName = lowerFirstCapse(method.getDeclaringClass().getSimpleName());
    Object o = iocMap.get(clsName);
    method.setAccessible(true);
    Parameter[] parameters = method.getParameters();

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

    int i = 0;
    for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {

        if (parameters[i].getName().equals(entry.getKey())) {

            Object val = entry.getValue()[0];
            //此处只做几个简单数据类型展示,后续可以在此基础上拓展
            switch (parameters[i].getType().toString()) {
                case "class java.lang.Integer":
                    paramValues[i] = Integer.parseInt(val.toString());
                    break;
                case "class java.lang.String":
                    paramValues[i] = val;
                    break;
                default:
                    paramValues[i] = JSON.parseObject(val.toString(), parameters[i].getType());
                    break;
            }
        }
        i++;
    }
    try {
        Object result = method.invoke(o, paramValues);
        resp.getWriter().write(JSON.toJSONString(result));
    } catch (IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }
}

8.测试controller

package com.gfh.mvc.framework.controller;

import com.gfh.mvc.framework.User;
import com.gfh.mvc.framework.annotation.Autowired;
import com.gfh.mvc.framework.annotation.Controller;
import com.gfh.mvc.framework.annotation.RequestMapping;
import com.gfh.mvc.framework.service.iml.TestServiceIml;


@Controller
@RequestMapping("/test")
public class TestController {
    @Autowired
    private TestServiceIml testServiceIml;

    @RequestMapping("/test")
    public User user(String name, Integer age) {
        System.out.println("请求来自test:=" + name + ";age=" + age + ";");
        testServiceIml.say("你在说什么呢");
        User user = new User();
        user.setName("gfh");
        return user;
    }

}

启动项目后,在浏览器输入http://localhost:8080/test/test?name=df

控制台打印service方法结果

到此便实现了spring的核心部分,需要注意的是,笔者使用的是jdk1.8环境,获取类的Parameter需要配置idea,如下图

代码已上传至github开源仓库

 

 

 

  • 6
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值