天天看点

07-项目训练_编写MVC框架

目录

​​一,搭建web项目环境​​

​​1,配置说明​​

​​2,web项目创建过程​​

​​3,将项目上传至GitHub​​

​​二,编写MVC框架(建立对框架的认知)​​

​​1,为什么要写这个框架?​​

​​2,编码​​

​​3,运行结果​​

​​4,保存今天的成果​​

完整项目在这里实时更新<( ̄︶ ̄)↗[GO!]​​https://github.com/GoodbyeFirefly/ExpressManagementSystem​​ 

一,搭建web项目环境

1,配置说明

window10

IDEA2018.3.6(新版的IDEA创建web项目时流程有所不同,建议使用旧版)

Tomcat8.5.34

2,web项目创建过程

这里大致介绍一下,详细的创建过程可以翻一翻之前的教程。

  1. 从IDEA首页选择创建新项目;
  2. 选择Java web application;
  3. 在web/web-INF下创建lib目录,并添加可能用到的jar包;
  4. 通过project structure将lib添加至项目中,此外还要将Tomcat添加进来,否则servlet中会飘红;
  5. 07-项目训练_编写MVC框架
    07-项目训练_编写MVC框架
  6. 通过绿色锤子旁边edit configurations配置Tomcat;
  7. 07-项目训练_编写MVC框架
  8. 配置完成后右边的绿色小三角可以点击,运行项目弹出页面展示$END$标明配置成功;

3,将项目上传至GitHub

详细过程可以参考这里​​@我不想再熬夜了【IDEA上传项目到GitHub】​​

主要有以下几个步骤:

  1. 下载并安装Git;
  2. 在IDEA中设置Git,并登录自己的GitHub账号(已登录过的不需要重新登陆);
  3. 引入版本控制,创建git仓库;
  4. 依次通过add、commit directory将项目改动上传至暂存区、版本库中;
  5. 将项目分享至GitHub;

至此便可以借助GitHub来进行代码的开发,最大的好处就是可以记录每次提交的代码版本,随时回滚之前的版本,大大降低试错的成本(o゜▽゜)o☆

二,编写MVC框架(建立对框架的认知)

这里的框架是指一种映射器,将用户的请求映射到各自不同的方法,比如收集所有*.do请求,然后针对xxx.do、yyy.do调用不同的方法。

07-项目训练_编写MVC框架

1,为什么要写这个框架?

先来看看作为小白的我之前写的web项目是什么样的

07-项目训练_编写MVC框架

仅仅是实现一个登录、注册以及简单的商品操作就用了5个servlet文件来实现,我当时就想,像一些大的web应用那servlet该有多少?一定有某种方法简化了这种实现方式,那就是框架!

类似于这样

07-项目训练_编写MVC框架

2,编码

1,创建DispatcherServlet

07-项目训练_编写MVC框架

2,在web.xml中添加配置

07-项目训练_编写MVC框架
07-项目训练_编写MVC框架

3, 创建application.properties文件

默认在src目录下。用于配置每一个用于处理请求的类,每一个类中可能包含0-n个用于处理请求的方法

07-项目训练_编写MVC框架

4,在DispatcherServlet中重写init方法(先暂时这样写)

public class DispatcherServlet extends javax.servlet.http.HttpServlet {
    @Override
    public void init(ServletConfig config) throws ServletException {
        String path = config.getInitParameter("contentConfigLocation");
        InputStream is = DispatcherServlet.class.getClassLoader().getResourceAsStream(path);
        Properties ppt = new Properties();
        try {
            ppt.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}      

5,通过反射技术读取配置文件中类的注解的情况,将请求与对应的处理方法存放在map中用于后面的调用

自定义ResponseBody注解

07-项目训练_编写MVC框架
07-项目训练_编写MVC框架
package com.xxy.mvc;

import java.lang.annotation.*;

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

自定义ResponseView注解

package com.xxy.mvc;

import java.lang.annotation.*;

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

定义枚举类型,标记response类型

07-项目训练_编写MVC框架
package com.xxy.mvc;

public enum ResponseType {
    TEXT, VIEW;
}      

编写映射器HandlerMapping,通过读取配置文件application.properties中的类的方法以及注解,获得请求(字符串形式)与方法的对应关系

package com.xxy.mvc;

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 {
    // 静态集合,存储请求与方法的对应关系
    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 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 method : methods) {
                    Annotation[] as = method.getAnnotations();
                    if (as != null) {
                        for (Annotation a : as) {
                            if (a instanceof ResponseBody) {
                                // 该方法返回字符串给客户端
                                MVCMapping mapping = new MVCMapping(obj, method, ResponseType.TEXT);
                                Object o = data.put((((ResponseBody) a).value()), mapping);
                                if (o != null) {
                                    // 表明存在重复的请求地址
                                    throw new RuntimeException("请求地址重复:" + ((ResponseBody) a).value());
                                }
                            } else if (a instanceof  ResponseView) {
                                // 该方法返回页面给客户端
                                MVCMapping mapping = new MVCMapping(obj, method, ResponseType.VIEW);
                                Object o = data.put((((ResponseView) a).value()), mapping);
                                if (o != null) {
                                    // 表明存在重复的请求地址
                                    throw new RuntimeException("请求地址重复:" + ((ResponseView) a).value());
                                }
                            }
                        }
                    }
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 映射对象,每一个对象封装了一个方法,用于处理请求
     */
    public static class MVCMapping {
        private Object obj;
        private Method method;
        private ResponseType 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 Method getMethod() {
            return method;
        }

        public ResponseType getType() {
            return type;
        }

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

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

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

}      

6,在DispatcherServlet中重写service方法,通过DispatcherServlet处理*.do请求

servlet接收请求实际上是通过service方法来调用doGet、doPost方法,所以这里重写service方法时,可以将doGet、doPost方法删掉。
07-项目训练_编写MVC框架
package com.xxy.mvc;


import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
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;
import java.util.Properties;

public class DispatcherServlet extends javax.servlet.http.HttpServlet {
    @Override
    public void init(ServletConfig config) throws ServletException {
        String path = config.getInitParameter("contentConfigLocation");
        InputStream is = DispatcherServlet.class.getClassLoader().getResourceAsStream(path);

        // 加载application.properties中包含的类中对应的方法,将请求与对应方法存放在map集合中用于后续调用
        HandlerMapping.load(is);

    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String uri = req.getRequestURI();
        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) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        switch (mapping.getType()) {
            case TEXT:
                resp.getWriter().write((String) result);
                break;
            case VIEW:
                resp.sendRedirect((String) result);
                break;
        }
    }
}      

7,实现UserController类,用于处理用户相关的请求

07-项目训练_编写MVC框架

在application.properties中补充该类的描述

07-项目训练_编写MVC框架
package com.xxy.test;

import com.xxy.mvc.ResponseBody;
import com.xxy.mvc.ResponseView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class UserController {
    @ResponseBody("/login.do")
    public String login(HttpServletRequest req, HttpServletResponse resp) {// HttpServletxxx 别打成Httpxxx
        // 返回中文的话可能出现乱码,后面可以通过过滤器处理,这里没有必要加入到框架中(不灵活)
        return "login successfully";
    }

    @ResponseView("/reg.do")
    public String reg(HttpServletRequest req, HttpServletResponse resp) {
        return "register.jsp";
    }
}      

3,运行结果

07-项目训练_编写MVC框架
07-项目训练_编写MVC框架

4,保存今天的成果

07-项目训练_编写MVC框架

同样,选择Commit Directory,将暂存区中的代码作为一次commit提交到版本库中,如果后面项目出现问题可以通过git命令回滚到此版本。是不是很方便呢q(≧▽≦q)

07-项目训练_编写MVC框架

通过push,可以将代码上传至GitHub中

07-项目训练_编写MVC框架