手写简易版SpringMVC

为了更好了Spring以及SpringMVC的核心原理,本文参考其他书籍实现了一个简易版的SpringMVC框架,框架的构建过程如下:

  1. 创建个Maven工程,并在pom.xml文件中引入Servlet;

    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
    </dependency>
    
  2. 工程结构如下:
    在这里插入图片描述

  3. 在web.xml里配置前端控制器DispatcherServlet,用于拦截所有的请求,并在初始化参数里配置application.properties文件的路径

  <servlet>
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>com.framework.servlet.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>DispatcherServlet</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>
  1. application.properties文件中只填写包扫描路径即可
scanPackage=com.demo
  1. 创建各种注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {

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

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

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

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

    String value() default "";
}
  1. 创建一组业务处理逻辑,用DemoController接收请求并返回DemoService的处理结果给浏览器
@Controller
@RequestMapping("/demo")
public class DemoController {

    @Autowired
    DemoService demoService;

    @RequestMapping("/query")
    public void query(HttpServletRequest request, HttpServletResponse response, @RequestParam("name") String name) throws IOException {
        String result= demoService.get(name);
        response.getWriter().write(result);
    }

}
public interface DemoService {

    String get(String name);
}

@Service
public class DemoServiceImpl implements DemoService {
    @Override
    public String get(String name)  {
        return "My name is " + name;
    }
}
  1. 接下来是重中之重构造DispatcherServlet类

    我们将在DispatcherServlet的init方法中进行:

    1. 加载配置文件
    2. 扫描所有的类
    3. 实例化所有单例Bean并放到IOC容器中
    4. 完成依赖注入
    5. 初始化HandlerMapping容器

    然后,因为本框架只对Get请求进行处理,所以只重写doGet方法。

具体过程如下:

  1. 创建DispatcherServlet成员变量:
   //加载application.properties配置文件中的内容
   private Properties contextConfig = new Properties();

   //保存扫描到的所有类名称
   private List<String> classNames = new ArrayList<>();

   //IOC容器,保存所有Bean实例
   private Map<String, Object> ioc = new HashMap<>();

   //handlerMapping容器,保存URL和Method的映射关系
   private Map<String, Method> handlerMapping = new HashMap<>();
  1. 在DispatcherServlet中重写init方法并实现以下方法:
   @Override
   public void init(ServletConfig config) {

       //加载配置文件
       loadConfig(config.getInitParameter("contextConfigLocation"));

       //包扫描
       initScanner(contextConfig.getProperty("scanPackage"));

       //实例化扫描到的类,并放入到IOC容器中
       initBeans();

       //完成依赖注入
       initAutowired();

       //建立所有URL和Controller中方法的对应关系,并保存到HandlerMapping容器中
       initHandlerMapping();
   }
  1. 实现loadConfig方法
    private void loadConfig(String contextConfigLocation) {
        //找到application.properties文件路径
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        //加载application.properties
        try {
            contextConfig.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
  1. 实现initScanner方法,扫描com.demo包以及子包的类,并将类的全限定名称保存起来,为initBean实例化这些类做准备;
    private void initScanner(String scanPackage) {
        //扫描com.demo包以及子包的类
        URL url = getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
        File classPath = new File(url.getFile());
        for (File file: classPath.listFiles()) {
            if (file.isDirectory()) {
                initScanner(scanPackage + "." +file.getName());
            } else {
                if (!file.getName().endsWith(".class")) continue;
                String className = scanPackage + "." + file.getName().replace(".class", "");
                classNames.add(className);
            }
        }
    }
  1. 通过遍历classNames中的路径名称,并以反射的方法实例化带有@Controller和@Service注解的类,最后把实例加入到IOC容器中;
    private void initBeans() throws Exception {
        if (classNames.isEmpty()) return;
        for (String className: classNames) {
            Class<?> clazz = Class.forName(className);
            //加了注解的类才能初始化
            if (clazz.isAnnotationPresent(Controller.class)) {
                Object instance = clazz.newInstance();
                ioc.put(clazz.getSimpleName(), instance);
            } else if (clazz.isAnnotationPresent(Service.class)) {
                Object instance = clazz.newInstance();
                for (Class<?> i : clazz.getInterfaces()) {
                    if (ioc.containsKey(i.getName())) {
                        throw new RuntimeException(i.getName() + "已存在");
                    }
                    ioc.put(i.getName(), instance);
                }
            } else {
                continue;
            }
        }
    }
  1. 实现initAutowired方法,遍历IOC容器中所有的实例,找到实例中带有@Autowire注解的成员变量,这些成员变量都是对象的引用,还没用进行赋值,所以用反射的方法将这些变量进行引用赋值。
    private void initAutowired() throws IllegalAccessException {
        if (ioc.isEmpty()) return;
        for (Map.Entry<String, Object> entry: ioc.entrySet()) {
            //获取被@Autowire注解的字段
            Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
            for (Field field: declaredFields) {
                if (!field.isAnnotationPresent(Autowired.class)) continue;

                //根据变量类型获取类路径
                String beanName = field.getType().getName();
                //设置暴力访问
                field.setAccessible(true);
                //将变量所注入的类和根据类路径查找到的实例设置给这个变量
                field.set(entry.getValue(), ioc.get(beanName));
            }
        }
    }
  1. 初始化HandlerMapping,它是SpringMVC中完成URL到Controller映射的组件,实质上是一个Map,容器初始化会建立所有URL和Controller中方法的对应关系,保存到HandlerMapping<URL, Method>中,用户请求时根据请求的URL快读定位到Controller中的某个方法。确定了方法后,就把请求参数绑定到方法的形参上。
private void initHandlerMapping() {
        if (ioc.isEmpty()) return;
        for (Map.Entry<String, Object> entry: ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();
            if (!clazz.isAnnotationPresent(Controller.class)) continue;
            //保存类上面的@RequestMapping("/demo")
            String baseUrl = "";
            if (clazz.isAnnotationPresent(RequestMapping.class)) {
                RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
                baseUrl = requestMapping.value();
            }
            //获取方法上的Url
            for (Method method: clazz.getMethods()) {
                if (!method.isAnnotationPresent(RequestMapping.class)) continue;
                RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
                String fullUrl = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/");
                //把URL和对应的方法放到HandlerMapping容器中
                handlerMapping.put(fullUrl, method);
            }
        }
    }
  1. 重写doGet方法,将请求转发给doDispatch方法进行处理
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replaceAll(contextPath, "").replaceAll("/+", "/");
        //handlerMapping中查到请求的URL,没有就返回404
        if (!handlerMapping.containsKey(url)) {
            resp.getWriter().write("404 Not Found");
            return;
        }
        //通过url找到对应的方法,用反射的方式知道方法对应的实例对象,并执行这个方法
        Method method = handlerMapping.get(url);
        Map<String, String[]> parameterMap = req.getParameterMap();
        String beanName = method.getDeclaringClass().getSimpleName();
        method.invoke(ioc.get(beanName), req, resp, parameterMap.get("name")[0]);

    }

通过以上步骤,我们完成了简易版SpringMVC框架的构建,下面看看运行结果:

  1. 访问路径为http://localhost:8080/mvc_write_war/时,返回404
    在这里插入图片描述
  2. 访问路径为http://localhost:8080/mvc_write_war/demo/query?name=peter
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
手写Spring MVC是指自己实现一个简单Spring MVC框架,而不是使用官方提供的Spring MVC框架。在手写Spring MVC时,你需要实现以下几个关键部分: 1. 创建一个前端控制器(Front Controller):前端控制器是整个请求处理过程的入口点,负责接收请求并进行路由。你可以使用Servlet作为前端控制器,接收所有的HTTP请求,并将它们分发给相应的控制器。 2. 定义控制器类:控制器类负责处理特定URL的请求,并根据请求参数进行相应的处理。你可以使用注解(如@RequestMapping)来定义控制器类和方法的映射关系。 3. 实现视图解析器(View Resolver):视图解析器负责解析控制器返回的逻辑视图名,并将其转换为具体的视图对象或视图模板。你可以使用模板引擎(如Thymeleaf、Freemarker等)来渲染动态内容。 4. 注册控制器和视图解析器:在前端控制器中,你需要注册所有的控制器类和视图解析器,以便能够正确地处理请求和渲染视图。 5. 处理请求和响应:在控制器中,你需要编写相应的方法来处理请求,并根据业务逻辑生成响应。你可以使用HttpServletRequest和HttpServletResponse对象来访问请求参数和生成响应。 6. 配置URL映射:你需要在配置文件中配置URL与控制器方法的映射关系,以便能够正确地将请求分发给对应的控制器。 手写Spring MVC的过程可以帮助你更好地理解Spring MVC框架的工作原理和核心组件。但请注意,手写一个完整的Spring MVC框架可能会比较繁琐和复杂,特别是对于初学者来说。因此,如果你只是想学习Spring MVC的基本原理和用法,我建议你先阅读官方文档或参考一些教程来快速入门。如果你确实有兴趣手写Spring MVC,你可以参考引用中的博客文章,里面提供了一个手写Spring MVC框架的实现示例。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [手写 springmvc](https://download.csdn.net/download/knight_black_bob/10207699)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [SpringMvc手写简单实现篇 - MVC完结篇](https://blog.csdn.net/qq_35551875/article/details/121811048)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值