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