4.自己动手写Java Web框架-MVC++

jw的github地址是https://github.com/menyouping/jw

前一篇文章就是一个Java反射,但是却达到了最简单的MVC目的,但是与Sping相比,它的功能确实很简陋。返回值都是json,可不可以是一个jsp文件路径?可不可以返回css,js,html,png等静态文件?还有很多问题,本篇只介绍这两个问题的处理,美其名曰MVC++,只是前一篇文章的++。

MVC++

添加Annotation

前一篇文章我们全部返回json格式,现打算根据能够支持返回jsp路径地址,根据提示返回json格式。

添加@ResponseBody

package com.jw.web.bind.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {

}

Controller方法上有@ResponseBody标记,就返回json格式数据,如果返回值是String类型,就当做返回jsp页面地址(为了更通用,可以自定义文件后缀名)。

根据情况返回内容

修改jw-core/src/main/java/com/jw/web/servlet/DispatcherServlet.java

public void service(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        String appName = request.getSession().getServletContext().getContextPath();
        request.setAttribute("root", appName);//在jsp页面中添加root变量

        String path = request.getRequestURI().substring(appName.length());
        //检查是否是静态文件,例如: css, js, html...
        if(isStaticResource(response, path)) 
            return;
        //...此处代码省略,同前文

        try {
            Class<?> controllerClaze = Class.forName(controllerClazeName);
            Method method = controllerClaze.getMethod(methodName,
                    new Class<?>[] { HttpServletRequest.class, HttpServletResponse.class });

            Object controller = controllerClaze.newInstance();
            Object[] paras = new Object[] { request, response };
            if (JwUtils.isAnnotated(method, ResponseBody.class)) {
                Object result = method.invoke(controller, paras);
                response.setHeader("Content-type", "application/json;charset=UTF-8");
                response.getWriter().write(JSON.toJSONString(result));
            } else if (String.class.equals(method.getReturnType())) {
                String returnUrl = (String) method.invoke(controller, paras);
                if (!StringUtils.isEmpty(returnUrl)) {
                    if (returnUrl.split(":")[0].equals("redirect")) {
                        response.sendRedirect(returnUrl.split(":")[1]);
                    } else {
                        RequestDispatcher dispatcher = request
                                .getRequestDispatcher("/" + getPage(returnUrl + "." + PAGE_DEFAULT_EXTENSION));
                        dispatcher.forward(request, response);
                    }
                }
            } else {
                method.invoke(controller, paras);
            }
        } catch (Exception e) {
            LOGGER.error("Error raised in DispatcherServlet.", e);
            showError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
    }

service方法中加入下面这一句,极大的方便了在jsp页面中获取本app的路径问题。

request.setAttribute("root", appName);//在jsp页面中添加root变量

isStaticResource方法用于判断是否是静态资源,根据配置来确定

处理静态资源

添加配置信息

修改jw-web/src/main/resources/application.properties,添加配置

#The folder that contains static resources, such as css file and js file, etc
web.resources.folder=resources
#The file which is regard as static resource file, others are not
web.resources.extension=js;css;jpg;ico;png;jpeg;gif;bmp;swf;eot;svg;ttf;woff;woff2;less;scss;

逻辑处理

修改jw-core/src/main/java/com/jw/web/servlet/DispatcherServlet.java

private static final String RESOURCE_FOLDER = ConfigUtils.getProperty("web.resources.folder") + "/";

private static final String RESOURCES_EXTENSION = ConfigUtils.getProperty("web.resources.extension");

protected boolean isStaticResource(HttpServletResponse response, String path) throws IOException {
    int index = path.lastIndexOf(".");
    if (index > -1 && index < path.length() - 1) {
        String ext = path.substring(index + 1).toLowerCase();
        if (RESOURCES_EXTENSION.contains(ext)) {
            response.setHeader("Content-type", MimeUtils.getMimeType(ext) + ";charset=UTF-8");
            String page = this.getServletContext().getRealPath(getResource(path));
            FileUtils.copy(page, response.getOutputStream());
            return true;
        }

        if ("html".equals(ext)) {
            response.setHeader("Content-type", "text/html;charset=UTF-8");
            String page = this.getServletContext().getRealPath(getPage(path));
            FileUtils.copy(page, response.getOutputStream());
            return true;
        }
    }
    return false;
}

public static String getResource(String fileName) {
    return RESOURCE_FOLDER + (fileName.startsWith("/") ? fileName.substring(1) : fileName);
}

web.resources.extension中记录了哪些文件后缀名是静态文件。

指定Content-type

根据请求的资源类型,指定返回内容的Content-type,由MimeUtils.getMimeType(ext)确定。
新增类jw-core/src/main/java/com/jw/util/MimeUtils.java

package com.jw.util;

import javax.activation.MimetypesFileTypeMap;

public class MimeUtils {

    private static final MimetypesFileTypeMap MIMETYPE_MAP = new MimetypesFileTypeMap();

    public static String getMimeType(String extension) {
        if ("css".equals(extension))
            return "text/css";
        return MIMETYPE_MAP.getContentType("x." + extension);
    }
}

css进行了特殊处理,因为MimetypesFileTypeMap中的返回值不准确。

写入静态资源内容

如果是静态资源,直接使用FileUtils.copy()方法将文件内容复制到response中返回给前端。

修改jw-core/src/main/java/com/jw/util/FileUtils.java

public static void copy(String file, OutputStream out) throws IOException {
    FileInputStream in = new FileInputStream(file);
    int c;
    byte buffer[] = new byte[1024];
    while ((c = in.read(buffer)) != -1) {
        for (int i = 0; i < c; i++) {
            out.write(buffer[i]);
        }
    }
    in.close();
    out.close();
}

返回jsp内容

修改jw-core/src/main/java/com/jw/web/servlet/DispatcherServlet.java

private static final String PAGE_FOLDER = "WEB-INF/" + ConfigUtils.getProperty("web.page.folder") + "/";
public static String getPage(String fileName) {
    return PAGE_FOLDER + (fileName.startsWith("/") ? fileName.substring(1) : fileName);
}

找到jsp文件的路径,再利用RequestDispatcher.forward(request, response)将内容返回给前台。

处理错误

遇到错误时,我们不能仅仅返回一个500错误,最好能根据错误状态码跳转到一个相应的错误页面。
修改jw-core/src/main/java/com/jw/web/servlet/DispatcherServlet.java

protected void showError(HttpServletRequest request, HttpServletResponse response, int status)
        throws ServletException, IOException {
    response.setStatus(status);
    if ("jsp".equals(PAGE_DEFAULT_EXTENSION)) {
        String page = "/" + getPage(status + "." + PAGE_DEFAULT_EXTENSION);
        RequestDispatcher dispatcher = request.getRequestDispatcher(page);
        dispatcher.forward(request, response);
    } else {
        response.setHeader("Content-type", "text/html;charset=UTF-8");
        String page = this.getServletContext().getRealPath(getPage(status + "." + PAGE_DEFAULT_EXTENSION));
        FileUtils.copy(page, response.getOutputStream());
    }
}

添加配置

修改jw-web/src/main/resources/application.properties

web.page.folder=views
#The default page file extension
web.page.default.extension=jsp
#The folder that contains static resources, such as css file and js file, etc
web.resources.folder=resources
#The file which is regard as static resource file, others are not
web.resources.extension=js;css;jpg;ico;png;jpeg;gif;bmp;swf;eot;svg;ttf;woff;woff2;less;scss;

DispatcherServlet全文

零零碎碎在DispatcherServlet修改了很多,现在把它的全文贴到这里,方便检阅。

package com.jw.web.servlet;

import java.io.IOException;
import java.lang.reflect.Method;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSON;
import com.jw.util.ConfigUtils;
import com.jw.util.FileUtils;
import com.jw.util.JwUtils;
import com.jw.util.MimeUtils;
import com.jw.util.StringUtils;
import com.jw.web.bind.annotation.ResponseBody;

public class DispatcherServlet extends HttpServlet {
    private static final Logger LOGGER = LoggerFactory.getLogger(DispatcherServlet.class);

    private static final long serialVersionUID = -3874308705324703315L;

    private static final String PAGE_FOLDER = "WEB-INF/" + ConfigUtils.getProperty("web.page.folder") + "/";

    private static final String RESOURCE_FOLDER = ConfigUtils.getProperty("web.resources.folder") + "/";

    private static final String RESOURCES_EXTENSION = ConfigUtils.getProperty("web.resources.extension");

    private static final String PAGE_DEFAULT_EXTENSION = ConfigUtils.getProperty("web.page.default.extension");

    public DispatcherServlet() {
    }

    public void init() {
    }

    public void service(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        String appName = request.getSession().getServletContext().getContextPath();
        request.setAttribute("root", appName);//在jsp页面中添加root变量

        String path = request.getRequestURI().substring(appName.length());
        // handle static resource, e.g. css, js, html...
        if (isStaticResource(response, path))
            return;
        LOGGER.info("Request path is {}", path);
        // /index/index/ => /index/index
        if (path.length() > 1 && path.endsWith("/")) {
            path = path.substring(0, path.length() - 1);
        } else if (path.startsWith("/")) {// /index/index => index/index
            path = path.replaceFirst("/", "");
        }
        if (path.isEmpty()) {
            path = "index/index";
        } else if (!path.contains("/")) {
            path += "/index";// index => index/index
        }
        String[] paths = path.split("/", 2);

        String controllerClazeName = ConfigUtils.getProperty("package.scan") + "." + StringUtils.upperFirst(paths[0])
                + "Controller";
        String methodName = paths[1];

        try {
            Class<?> controllerClaze = Class.forName(controllerClazeName);
            Method method = controllerClaze.getMethod(methodName,
                    new Class<?>[] { HttpServletRequest.class, HttpServletResponse.class });

            Object controller = controllerClaze.newInstance();
            Object[] paras = new Object[] { request, response };
            if (JwUtils.isAnnotated(method, ResponseBody.class)) {
                Object result = method.invoke(controller, paras);
                response.setHeader("Content-type", "application/json;charset=UTF-8");
                response.getWriter().write(JSON.toJSONString(result));
            } else if (String.class.equals(method.getReturnType())) {
                String returnUrl = (String) method.invoke(controller, paras);
                if (!StringUtils.isEmpty(returnUrl)) {
                    if (returnUrl.split(":")[0].equals("redirect")) {
                        response.sendRedirect(returnUrl.split(":")[1]);
                    } else {
                        RequestDispatcher dispatcher = request
                                .getRequestDispatcher("/" + getPage(returnUrl + "." + PAGE_DEFAULT_EXTENSION));
                        dispatcher.forward(request, response);
                    }
                }
            } else {
                method.invoke(controller, paras);
            }
        } catch (Exception e) {
            LOGGER.error("Error raised in DispatcherServlet.", e);
            showError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
    }

    protected boolean isStaticResource(HttpServletResponse response, String path) throws IOException {
        int index = path.lastIndexOf(".");
        if (index > -1 && index < path.length() - 1) {
            String ext = path.substring(index + 1).toLowerCase();
            if (RESOURCES_EXTENSION.contains(ext)) {
                response.setHeader("Content-type", MimeUtils.getMimeType(ext) + ";charset=UTF-8");
                String page = this.getServletContext().getRealPath(getResource(path));
                FileUtils.copy(page, response.getOutputStream());
                return true;
            }

            if ("html".equals(ext)) {
                response.setHeader("Content-type", "text/html;charset=UTF-8");
                String page = this.getServletContext().getRealPath(getPage(path));
                FileUtils.copy(page, response.getOutputStream());
                return true;
            }
        }
        return false;
    }

    public static String getPage(String fileName) {
        return PAGE_FOLDER + (fileName.startsWith("/") ? fileName.substring(1) : fileName);
    }

    public static String getResource(String fileName) {
        return RESOURCE_FOLDER + (fileName.startsWith("/") ? fileName.substring(1) : fileName);
    }

    protected void showError(HttpServletRequest request, HttpServletResponse response, int status)
            throws ServletException, IOException {
        response.setStatus(status);
        if ("jsp".equals(PAGE_DEFAULT_EXTENSION)) {
            String page = "/" + getPage(status + "." + PAGE_DEFAULT_EXTENSION);
            RequestDispatcher dispatcher = request.getRequestDispatcher(page);
            dispatcher.forward(request, response);
        } else {
            response.setHeader("Content-type", "text/html;charset=UTF-8");
            String page = this.getServletContext().getRealPath(getPage(status + "." + PAGE_DEFAULT_EXTENSION));
            FileUtils.copy(page, response.getOutputStream());
        }
    }

}

测试

添加依赖

修改jw-parent/pom.xml

<dependency>
    <groupId>jstl</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
</dependency>

修改jw-web/pom.xml

<dependency>
    <groupId>jstl</groupId>
    <artifactId>jstl</artifactId>
</dependency>

新增jsp文件和静态资源

新增文件夹jw-web/src/main/webapp/resources,放入css,js等资源。
新增文件夹jw-web/src/main/webapp/WEB-INF/views,放入jsp文件。

测试错误URL

启动server,输入网址http://localhost:8080/jw-web/abc ,回车。
这里写图片描述
这里显示的是500.jsp。显然这个url找不到对应的controller,显示404.jsp更准确,这里就不做细致划分了。

测试显示jsp

修改jw-web/src/main/java/com/jay/mvc/IndexController.java

public String index(HttpServletRequest request, HttpServletResponse response) {
    return "index";
}

重启Server,输入网址http://localhost:8080/jw-web/index,回车。
这里写图片描述

正确显示index.jsp内容。

修改jw-web/src/main/java/com/jay/mvc/IndexController.java

public String redirect(HttpServletRequest request, HttpServletResponse response) {
    return "redirect:forms";
}

public String forms(HttpServletRequest request, HttpServletResponse response) {
    return "forms";
}

重启Server,输入网址http://localhost:8080/jw-web/index/forms,回车。
这里写图片描述
正确显示forms.jsp内容。

输入网址http://localhost:8080/jw-web/index/redirect,回车。
这里写图片描述
浏览器地址跳转到http://localhost:8080/jw-web/index/forms,正确显示forms.jsp内容。

输入网址http://localhost:8080/jw-web/index/welcome,回车。
显示

{"message":"Welcome to jw's world!","status":200}

@ResponseBody正确显示。

至此MVC++完成。
本节资源地址: https://github.com/menyouping/jw-example/blob/master/4/jw-parent.zip

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值