Java-Web手写MVC框架

文件结构

1、编写Web.xml

将所有 .do 后缀的请求全都映射到DispatchServlet 中,在DispatchServlet 跳转的时候将配置文件名传递过去,配置文件的编写在后面, 因为测试类还没写。

<?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> <!-- DispatchServlet 对应com.ae.web.DispatchServlet 路径下的文件 -->
        <servlet-name>DispatchServlet</servlet-name>
        <servlet-class>com.ae.DispatchServlet</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>
    <servlet-mapping><!-- 将所有 *.do 的请求映射到DispatchServlet上 -->
        <servlet-name>DispatchServlet</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>

</web-app> 

2、自定义两个注解

ResponseBody 以及 ResponseView 内容如下,用于区分不同类型的请求。

3、编写ResponseType枚举类

主要作用就是给你自定义的类定义两个不同的类型,之后有其他类型可以直接在这里面进行添加。

4、编写DispatchServlet

该Servlet在初始化的时候会将配置文件的Input流获取,然后传递给HandlerMapping进行加载,结果就行,将所有请求与对应的类和方法建立映射关系。

Service方法进行的时候,应为已经在初始化的时候将所有的url与方法建立了映射关系,所以可以直接可以获取对应的方法,然后执行对应的方法,并且获取返回的结果。

public class DispatchServlet extends HttpServlet {
    @Override
    public void init(ServletConfig config) throws ServletException {
        // 首先获取对应的配置文件的名字
        String path = config.getInitParameter("contentConfigLocation");
        // 获取资源配置文件的读取流InputStream
        InputStream is = DispatchServlet.class.getClassLoader().getResourceAsStream(path);
        // 将读取流InputStream传递给HandlerMapping,将所有键值进行加载
        HandlerMapping.load(is);
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
        //1.获取用户请求的uri
        String uri = req.getRequestURI();
        // 根据请求的uri获取对应的映射内容
        HandlerMapping.MVCMapping mapping = HandlerMapping.get(uri);
        // 为空则不存在该请求
        if(mapping == null) {
            resp.sendError(404, "MVC映射地址不存在" + uri);
            return ;
        }
        // 获取请求对应的类对象
        Object obj = mapping.getObj();
        // 获取请求对应的方法
        Method method = mapping.getMethod();
        // 用于存取请求结果
        Object result = null;
        try {
            // 执行对应的方法返回结果
            result = method.invoke(obj, req, resp);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
        switch(mapping.getType()){ // 根据对应的内容进行不同的操作
            case TEXT: // 返回的文本则直接写入
                resp.getWriter().write((String)result);
                break;
            case VIEW: // 返回的页面文件名则进行跳转
                resp.sendRedirect((String) result);
                break;
        }
    }
}

5、HandlerMapping的编写

作用是将配置文件进行加载读取,并且将请求与对应的类的方法建立映射关系,并且存储。

public class HandlerMapping {
    // 记录每一个用过的注解值,防止请求地址使用重复
    private static Map<String, MVCMapping> data = new HashMap();
    public static MVCMapping get(String uri) {
        return data.get(uri);
    }
    public static void load(InputStream is) {
        Properties properties = new Properties();
        try {
            // 加载配置文件中的内容
            properties.load(is);
            // 获取配置文件中描述的一个个的类
            Collection<Object> values = properties.values();
            for(Object cla : values) {
                // 获取配置文件中第一个类文件名
                String className = (String)cla;
                try {
                    // 获取对应的类文件
                    Class c = Class.forName(className);
                    // 创建这个类的对象
                    Object o = c.getConstructor().newInstance();
                    // 获取这个类的所有方法
                    Method[] ms = c.getMethods();
                    // 枚举类中每个方法
                    for(Method m : ms) {
                        // 获取当前方法中所有的注解
                        Annotation[] an = m.getAnnotations();
                        if(an != null) {
                            // 枚举当前方法中所有的注解
                            for(Annotation annotation : an) {
                                if(annotation instanceof ResponseBody) {
                                    // 说明此方法用于返回字符串给客户端
                                    MVCMapping mapping = new MVCMapping(o, m, ResponseType.TEXT);
                                    Object object = data.put(((ResponseBody) annotation).value(), mapping);
                                    if(object != null) {
                                         // 存在了重复的请求地址
                                        throw new RuntimeException("请求地址重复:" + ((ResponseBody) annotation).value());
                                    }
                                } else if(annotation instanceof ResponseView) {
                                    // 说明此方法用于返回界面给客户端
                                    MVCMapping mapping = new MVCMapping(o, m, ResponseType.VIEW);
                                    Object object = data.put(((ResponseView) annotation).value(), mapping);
                                    if(object != null) {
                                        // 存在了重复的请求地址
                                        throw new RuntimeException("请求地址重复:" + ((ResponseView) annotation).value());
                                    }
                                }
                            }
                        }

                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }


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

        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;
        }

        public MVCMapping() {
        }

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

        public Object getObj() {
            return obj;
        }
        public void setObj(Object obj) {
            this.obj = obj;
        }
    }
}

6、编写测试类以及添加配置文件

以上已经写好了,测试一下就行了。建立两个测试类。Test1Controller, Test2Controller

然后将两个类的路径添加到配置文件中。这里只需要将所有请求类的路径加进行就行,等号左边的名字随便填就行。​​

​​​注意资源配置文件跟数据库druid.properties文件问题一样,建在resources文件下,否则无法加载,老版本的项目需要建立在源文件目录下即可。

Test1Controller

success.jsp随便写几个内容就行,进行区分。注意建立在Webapp目录下

public class Test1Controller {
    @ResponseBody("/login.do") // 测试返回文本内容的请求
    public String login(HttpServletRequest request, HttpServletResponse response) {
        return "login success";
    }
    @ResponseView("/register.do") // 测试返回文件名以及跳转页面的请求
    public String register(HttpServletRequest request, HttpServletResponse response) {
        return "success.jsp";
    }
}

Test2Controller

xxx.html也随便谢谢进行区分即可,注意建立在Webapp目录下

public class Test2Controller {
    @ResponseBody("/test1.do") // 测试返回文本的请求
    public String test1(HttpServletRequest request, HttpServletResponse response) {
        return "ha ha ha";
    }
    @ResponseView("/test2") // 测试不加后缀是否能使用
    public String test2(HttpServletRequest request, HttpServletResponse response) {
        return "hei hei hei";
    }

    @ResponseView("/test3.do") // 测试返回页面文件名以及跳转的请求
    public String test3(HttpServletRequest request, HttpServletResponse response) {
        return "xxx.html";
    }
}

  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值