手写一个简易的MVC框架——提前建立SpringMVC框架的认知

手写一个MVC框架

目的:

提前建立框架的认识
框架都是别人编写好的代码,我们为了便于项目的编写,也可以实现一个简易的框架。

本框架参考SpringMVC。

当我们要实现一个项目的时候,会需要实现很多功能,如下图。
image-20211014202322451

以我们现在的认知就是一个功能对应一个Servlet,但这样写起来会很麻烦,代码也会非常冗余。

所以当我们注册或者登陆的时候,是否都可以去找一个类?如下图。
image-20211014202440634

MVC是什么?

M:Model,模型层

V:View,视图层

C:Control,控制器层

23种设计模式中并没有MVC设计模式,但是现在MVC模式已经广泛应用,MVC模式是23种模式中几种模式的变形和整合。
image-20211013201043610

框架构成image-20211014193306337

所有代码的详细解释都写了注释,请细细品味。

DispatcherServlet

这个servlet是所有用户请求的入口,一个servlet处理所有请求,根据映射器所指向的某个类的方法,然后调用这个方法,来处理每一个请求。方法处理完之后,都会有相应的结果产生,随后将结果返回给用户。
image-20211014170349143

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 前端控制器,也称为中央控制器或者核心控制器。
 * 它就相当于 mvc 模式中的 c
 */
@WebServlet(name = "DispatcherServlet")
public class DispatcherServlet extends HttpServlet {
    
    /**
     * 初始化方法,加载配置文件
     * @param config
     * @throws ServletException
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        // 通过路径加载输入流进来,获取配置文件里面所存储的每一个键值对
        String path = config.getInitParameter("contentConfigLocation");
        InputStream is = DispatcherServlet.class.getClassLoader().getResourceAsStream(path);
        HandlerMapping.load(is);
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取用户请求的uri
        String uri = req.getRequestURI();
        HandlerMapping.MVCMapping mapping = HandlerMapping.get(uri);
        if (mapping == null){
            resp.sendError(404, "自定义MVC:映射地址不存在" + uri);
            return;
        }
        Object obj = mapping.getObject();
        Method method = mapping.getMethod();
        Object result = null;
        try {
            // 调用invoke()方法实现动态代理
            result = method.invoke(obj, req, resp);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        switch (mapping.getType()){// 判断mapping里面的类型
            case TEXT:
                resp.getWriter().write((String)result);
                break;
            case VIEW:
                resp.sendRedirect((String)result);
                break;
        }
    }
}

HandlerMapping

import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * 映射器(包含了大量的网址与方法的对应关系)
 */
public class HandlerMapping {

    // 将所有使用了@ResponseBody,@ResponseView这两个注解的封装成一个Map对象
    private static Map<String, MVCMapping> data = new HashMap<>();

    /**
     * 获取集合中网址所对应的方法
     * @param uri
     * @return
     */
    public static MVCMapping get(String uri){
        return data.get(uri);
    }

    /**
     * 加载配置文件的方法
     * @param is
     */
    public static void load(InputStream is){
        Properties ppt = new Properties();
        try {
            ppt.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 获取配置文件中描述的一个个的类
        Collection<Object> values = ppt.values();
        for (Object cla : values) {
            String className = (String) cla;
            try {
                // 加载配置文件中描述的每一个类
                Class c = Class.forName(className);
                // 创建这个类的对象
                Object obj = c.getConstructor().newInstance();
                // 获取这个类的所有方法
                Method[] methods = c.getMethods();
                for (Method m:methods) {
                    // 获取这个方法的所有注解
                    Annotation[] as = m.getAnnotations();
                    if(as != null){
                        for(Annotation annotation : as){
                            if(annotation instanceof ResponseBody){
                                // 说明此方法,用于返回字符串给客户端
                                MVCMapping mapping = new MVCMapping(obj, m, ResponseType.TEXT);
                                Object o = data.put(((ResponseBody) annotation).value(), mapping);
                                if(o != null){
                                    // 存在了重复的请求地址
                                    throw new RuntimeException("请求地址重复:"+((ResponseBody) annotation).value());
                                }
                            }else if(annotation instanceof ResponseView){
                                // 说明此方法,用于返回界面给客户端
                                MVCMapping mapping = new MVCMapping(obj, m, ResponseType.VIEW);
                                Object o =  data.put(((ResponseView) annotation).value(), mapping);
                                if(o != null){
                                    // 存在了重复的请求地址
                                    throw new RuntimeException("请求地址重复:"+((ResponseView) annotation).value());
                                }
                            }
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 映射对象,每一个对象封装了一个方法,用于处理请求
     */
    public static class MVCMapping{
        
        private Object object;
        private Method method;
        private ResponseType type;

        public MVCMapping() {
        }

        public MVCMapping(Object object, Method method, ResponseType type) {
            this.object = object;
            this.method = method;
            this.type = type;
        }

        public Object getObject() {
            return object;
        }

        public void setObject(Object object) {
            this.object = object;
        }

        public Method getMethod() {
            return method;
        }

        public void setMethod(Method method) {
            this.method = method;
        }

        public ResponseType getType() {
            return type;
        }

        public void setType(ResponseType type) {
            this.type = type;
        }
    }
}

@ResponseBody

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
/**
 * 注解的作用:
 * 被此注解添加的方法,会被用于处理请求。
 * 方法返回的内容,会以文字形式返回到客户端
 */
public @interface ResponseBody {
    String value();
}

@ResponseView

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
/**
 * 注解的作用:
 * 被此注解添加的方法, 会被用于处理请求。
 * 方法返回的内容,会直接重定向
 */
public @interface ResponseView {
    String value();
}

ResponseType

/**
 * 枚举类
 * 描述相应的类型是文字类型还是视图类型
 */
public enum ResponseType {
    TEXT, VIEW;
}

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>DispatcherServlet</servlet-name>
        <servlet-class>com.lyc.mvc.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contentConfigLocation</param-name>
            <param-value>application.properties</param-value>
        </init-param>
        <load-on-startup>0</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <url-pattern>*.do</url-pattern><!--所有以*.do结尾的请求都经过DispatcherServlet处理-->
    </servlet-mapping>
</web-app>

application.properties

# 在这里配置每一个用于处理请求的类,每一个类中可能包含0-n个用于处理请求的方法
user=com.lyc.test.UserController

Controller测试类

import com.lyc.mvc.ResponseBody;
import com.lyc.mvc.ResponseView;

public class UserController {
    
    /**
     * 登录方法 返回文本类型
     * @return
     */
    @ResponseBody("/login.do")
    public String login(HttpServletRequest request, HttpServletResponse response){
        // 因为返回的是中文,所以在浏览器上会显示乱码
        // 乱码的处理通过过滤器来完成,没必要加入到框架里,框架里如果定好了编码格式会导致它不灵活
        return "登录成功";
    }

    /**
     * 注册方法 返回视图类型
     * @return
     */
    @ResponseView("/reg.do")
    public String reg(HttpServletRequest request, HttpServletResponse response){
        return "success.jsp";
    }
}

success.jsp

<%--
  Created by IntelliJ IDEA.
  User: 成成子
  Date: 2021/10/14
  Time: 19:12
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
注册成功
</body>
</html>

运行截图

login.do请求 返回""文本
image-20211014194551059

reg.do请求 跳转view页面
image-20211014194354517

  • 14
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小成同学_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值