前端控制器(Font Controller)
Java Web中的前端控制器是应用的门面,所有请求都需要经过这个前端控制器,由前端控制器根据请求的内容来决定如何处理并将处理的结果返回给浏览器。
Model1
Model1的中心是JSP页面,JSP页面中结合业务逻辑、服务端处理过程和HTML等。
Model2
Model2表示的是基于MVC模式的框架。
前端控制器的理念就是设计模式中门面模式(外观模式)在Web项目中的实际应用。SUN公司为Java Web开发定义了两种模式,Model1和Model2。Model2是基于MVC(Model-View-Controller,模型-视图-控制)架构模式,通常将服务(Servlet)或过滤器(Filter)作为控制器,作用就是接受用户请求并获得模型数据然后跳转到视图;将JSP页面作为视图,用来显示用户操作的结果;模型是POJO(Plain Old Java Object),它是区别于EJB(Enterprise JavaBean)的普通Java对象,不实现任何其他框架的接口也不扮演其他的角色,而是负责承载数据,可以作为VO(Value Object)或DTO(Data Transfer Object)来使用。
@WebServlet
在Servlet 3.0中,可以使用标注(Annotation)来告知容器哪些Servlet会提供服务以及额外信息。
编写作为处理用户各种请求门面的前端控制器:
package com.test.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("*.do")
public class FrontController extends HttpServlet{
private static final long serialVersionUID = 1L;
private static final String DEFAULT_PACKAGE_NAME = "com.test.action.*";//默认的Action类的包名前缀
private static final String DEFAULT_ACTION_NAME = "Action";//默认的Action类的类名后缀
@Override
protected void service(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException{
//获得请求的服务路径
String servletPath = req.getServletPath();
//从servletPath中去掉开头的斜杠和末尾的.do就是要执行的动作(Action)的名字
int start = 1;//去掉第一个字符斜杠从第二个字符开始
int end = servletPath.lastIndexOf(".do");//找到请求路径的后缀.do的位置
String actionName = end > start ? servletPath.substring(start,end)+DEFAULT_ACTION_NAME:"";
String actionClassName = DEFAULT_PACKAGE_NAME + actionName.substring(0,1).toUpperCase() + actionName.substring(1);
//接下来可以通过反射来创建Action对象并调用
System.out.println(actionClassName);
}
}
上面的FrontServlet类中用@WebServlet注解对该服务做了映射,只要是后缀为.do的请求,都会经过这个前置控制器(当然,也可以在web.xml中使用和标签对服务进行映射,使用注解通常是为了提升开发效率),
Action
有了前置控制器,我们还需要使用不同的Action类来处理用户不同的请求,这里需要在前置控制器中通过已经得到Action类的完全限定名(带包名的类名)利用反射机制来创建对象。
而每个Action要执行的的处理是不一样的,怎样才能写一个通用的前置控制器呢?使用多态(继承,运行时多态)。
我们可以先定义一个Action接口并定义一个抽象方法,不同的Action子类会对该方法进行重写,这样用Action的引用去引用不同的Action子类对象,再调用子类重写过的方法,那么就可以执行不同的行为。
首先,需要定义一个Action类的接口:
package com.test.action;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 处理用户请求的控制器接口
*/
public interface Action{
public ActionResult execute() throws ServletException,IOException;
}
接口中的execute方法是处理用户请求的方法, 所以它的两个参数分别是HttpServletRequest和HttpServletResponse对象,到时候我们会在前端控制中通过反射创建Action,并调用execute方法,exeucte方法的返回值是一个ActionResult对象:
package com.test.action;
/**
*Action执行结果
*/
public class ActionResult{
private ResultContent resultContent;
private ResultType resultType;
public ActionResult(ResultContent resultContent){
this(resultContent,ResultType.Forward);
}
public ActionResult(ResultContent resultContent, ResultType type) {
this.resultContent = resultContent;
this.resultType = type;
}
/**
* 获得执行结果的内容
*/
public ResultContent getResultContent() {
return resultContent;
}
/**
* 获得执行结果的类型
*/
public ResultType getResultType() {
return resultType;
}
}
ActionResult类中的ResultContent代表了Action对用户请求进行处理后得到的内容,它可以存储一个字符串表示要跳转或重定向到的资源的URL,它也可以存储一个对象来保存对用户请求进行处理后得到的数据(模型),为了支持Ajax操作,我们可以将此对象处理成JSON格式的字符串。
package com.test.action;
import com.google.gson.Gson;
/**
* Action执行结束产生的内容
*/
public class ResultContent {
private String url;
private Object obj;
public ResultContent(String url) {
this.url = url;
}
public ResultContent(Object obj) {
this.obj = obj;
}
public String getUrl() {
return url;
}
public String getJson() {
return new Gson().toJson(obj);// 这里使用了Google的JSON工具类gson
}
}
ActionResult类中的ResultType代表了对用户请求处理后如何向浏览器产生响应,它是个枚举类型:
package com.test.action;
/**
* Action执行结果类型
*/
public enum ResultType {
/**
* 重定向
*/
Redirect,
/**
* 转发
*/
Forward,
/**
* 异步请求
*/
Ajax,
/**
* 数据流
*/
Stream,
/**
* 跳转到向下一个控制器
*/
Chain,
/**
* 重定向到下一个控制器
*/
RedirectChain
}
我们还需要一个工具类来封装常用的工具方法:
package com.test.util;
import java.awt.Color;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 通用工具类
*/
public final class CommonUtil {
private static final List<String> patterns = new ArrayList<>();
private static final List<TypeConverter> converters = new ArrayList<>();
static {
patterns.add("yyyy-MM-dd");
patterns.add("yyyy-MM-dd HH:mm:ss");
}
private CommonUtil() {
throw new AssertionError();
}
/**
* 将字符串的首字母大写
*/
public static String capitalize(String str) {
StringBuilder sb = new StringBuilder();
if (str != null && str.length() > 0) {
sb.append(str.substring(0, 1).toUpperCase());
if (str.length() > 1) {
sb.append(str.substring(1));
}
return sb.toString();
}
return str;
}
/**
* 生成随机颜色
*/
public static Color getRandomColor() {
int r = (int) (Math.random() * 256);
int g = (int) (Math.random() * 256);
int b = (int) (Math.random() * 256);
return new Color(r, g, b);
}
/**
* 添加时间日期样式
* @param pattern 时间日期样式
*/
public static void registerDateTimePattern(String pattern) {
patterns.add(pattern);
}
/**
* 取消时间日期样式
* @param pattern 时间日期样式
*/
public static void unRegisterDateTimePattern(String pattern) {
patterns.remove(pattern);
}
/**
* 添加类型转换器
* @param converter 类型转换器对象
*/
public static void registerTypeConverter(TypeConverter converter) {
converters.add(converter);
}
/**
* 取消类型转换器
* @param converter 类型转换器对象
*/
public static void unRegisterTypeConverter(TypeConverter converter) {
converters.remove(converter);
}
/**
* 将字符串转换成时间日期类型
* @param str 时间日期字符串
*/
public static Date convertStringToDateTime(String str) {
if (str != null) {
for (String pattern : patterns) {
Date date = tryConvertStringToDate(str, pattern);
if (date != null) {
return date;
}
}
}
return null;
}
/**
* 按照指定样式将时间日期转换成字符串
* @param date 时间日期对象
* @param pattern 样式字符串
* @return 时间日期的字符串形式
*/
public static String convertDateTimeToString(Date date, String pattern) {
return new SimpleDateFormat(pattern).format(date);
}
private static Date tryConvertStringToDate(String str, String pattern) {
DateFormat dateFormat = new SimpleDateFormat(pattern);
dateFormat.setLenient(false); // 不允许将不符合样式的字符串转换成时间日期
try {
return dateFormat.parse(str);
}
catch (ParseException ex) {
}
return null;
}
/**
* 将字符串值按指定的类型转换成转换成对象
* @param elemType 类型
* @param value 字符串值
*/
public static Object changeStringToObject(Class<?> elemType, String value) {
Object tempObj = null;
if(elemType == byte.class || elemType == Byte.class) {
tempObj = Byte.parseByte(value);
}
else if(elemType == short.class || elemType == Short.class) {
tempObj = Short.parseShort(value);
}
else if(elemType == int.class || elemType == Integer.class) {
tempObj = Integer.parseInt(value);
}
else if(elemType == long.class || elemType == Long.class) {
tempObj = Long.parseLong(value);
}
else if(elemType == double.class || elemType == Double.class) {
tempObj = Double.parseDouble(value);
}
else if(elemType == float.class || elemType == Float.class) {
tempObj = Float.parseFloat(value);
}
else if(elemType == boolean.class || elemType == Boolean.class) {
tempObj = Boolean.parseBoolean(value);
}
else if(elemType == java.util.Date.class) {
tempObj = convertStringToDateTime(value);
}
else if(elemType == java.lang.String.class) {
tempObj = value;
}
else {
for(TypeConverter converter : converters) {
try {
tempObj = converter.convert(elemType, value);
if(tempObj != null) {
return tempObj;
}
}
catch (Exception e) {
}
}
}
return tempObj;
}
/**
* 获取文件后缀名
* @param filename 文件名
* @return 文件的后缀名以.开头
*/
public static String getFileSuffix(String filename) {
int index = filename.lastIndexOf(".");
return index > 0 ? filename.substring(index) : "";
}
}
定义好Action接口及其相关类后,我们可以继续改写写前端控制器的代码:
package com.test.servlet;
import java.io.IOException;
import java.io.PrintWriter;
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 com.lovo.action.Action;
import com.lovo.action.ActionResult;
import com.lovo.action.ResultContent;
import com.lovo.action.ResultType;
@WebServlet("*.do")
public class FrontController extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final String DEFAULT_PACKAGE_NAME = "com.lovo.action."; // 默认的Action类的包名前缀
private static final String DEFAULT_ACTION_NAME = "Action"; // 默认的Action类的类名后缀
private static final String DEFAULT_JSP_PATH = "/WEB-INF/jsp"; // 默认的JSP文件的路径
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String contextPath = request.getContextPath() + "/";
// 获得请求的服务路径
String servletPath = request.getServletPath();
// 从servletPath中去掉开头的斜杠和末尾的.do就是要执行的动作(Action)的名字
int start = 1; // 去掉第一个字符斜杠从第二个字符开始
int end = servletPath.lastIndexOf(".do"); // 找到请求路径的后缀.do的位置
String actionName = end > start ? servletPath.substring(start, end) + DEFAULT_ACTION_NAME : "";
String actionClassName = DEFAULT_PACKAGE_NAME + actionName.substring(0, 1).toUpperCase() + actionName.substring(1);
try{
//通过反射来创建Action对象并调用
Action action = (Action)Class.forName(actionClassName).newInstance();
// 执行多态方法execute得到ActionResult
ActionResult result = action.execute(request, response);
ResultType resultType = result.getResultType();// 结果类型
ResultContent resultContent = result.getResultContent();// 结果内容
//根据ResultType决定如何处理
switch(resultType){
case Forward: //跳转
request.getRequestDispatcher(DEFAULT_JSP_PATH+resultContent.getUrl()).forward(request,response);
break;
case Redirect: //重定向
response.sendRedirect(resultContent.getUrl());
break;
case Ajax: //Ajax
PrintWriter pWriter = response.getWriter();
pWriter.println(resultContent.getJson());
pw.close();
break;
case Chain://Chain
request.getRequestDispatcher(contextPath);
break;
case RedirectChain:
response.sendRedirect(contextPath+resultContent.getUrl());
break;
default:
}
}catch(Exception e){
e.printStackTrace();
throw new ServletException(3);
}
}
}
迄今为止,我们还没有编写任何的配置文件,但是大家可能已经注意到前端控制器中的硬代码(hard code)了。我们在前端控制器中设置的几个常量(默认的Action类的包名前缀、默认的Action类的类名后缀以及默认的JSP文件的路径)都算是硬代码,但是我们也可以将其视为一种约定,我们约定好Action类的名字和路径,JSP页面的名字和路径就可以省去很多的配置,甚至可以做到零配置,这种理念并不新鲜,它叫做约定优于配置(CoC,Convenient over Configuration)。当然,对于符合约定的部分我们可以省去配置,对于不合符约定的部分可以用配置文件或者注解加以说明。继续修改我们的前端控制器,代码如下:
@MultipartConfig
该注解主要是为了辅助 Servlet 3.0 中 HttpServletRequest 提供的对上传文件的支持。该注解标注在 Servlet 上面,以表示该 Servlet 希望处理的请求的 MIME 类型是 multipart/form-data。getContextPath、getServletPath、getRequestURI的区别
举例:
在浏览器中输入请求路径: http://localhost:8080/news/main/list.jsp
则执行下面向行代码后打印出如下结果:
1、 System.out.println(request.getContextPath()); //可返回站点的根路径。也就是项目的名字
打印结果:/news
2、System.out.println(request.getServletPath());
打印结果:/main/list.jsp
3、 System.out.println(request.getRequestURI());
打印结果:/news/main/list.jsp
4、 System.out.println(request.getRealPath(“/”));
打印结果:F:\Tomcat 6.0\webapps\news\test
package com.test.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.lovo.action.Action;
import com.lovo.action.ActionResult;
import com.lovo.action.ResultContent;
import com.lovo.util.CommonUtil;
/**
* 前端控制器
*/
@WebServlet(urlPatterns = { "*.do" }, loadOnStartup = 0,
initParams = {
@WebInitParam(name = "packagePrefix", value = "com.test.action."),
@WebInitParam(name = "jspPrefix", value = "/WEB-INF/jsp/"),
@WebInitParam(name = "actionSuffix", value = "Action")
}
)
@MultipartConfig
public class FrontController extends HttpServlet {
private static final long serialVersionUID = 1L;
rivate static final String DEFAULT_PACKAGE_NAME = "com.test.action.";
private static final String DEFAULT_JSP_PATH = "/WEB-INF/content/";
private static final String DEFAULT_ACTION_NAME = "Action";
private String packagePrefix = null; // 包名的前缀
private String jspPrefix = null; // JSP页面路径的前缀
private String actionSuffix = null; // Action类名的后缀
@Override
public void init(ServletConfig config) throws ServletException {
String initParam = config.getInitParameter("packagePrefix");
packagePrefix = initParam != null ? initParam : DEFAULT_PACKAGE_NAME;
initParam = config.getInitParameter("jspPrefix");
jspPrefix = initParam != null ? initParam : DEFAULT_JSP_PATH;
initParam = config.getInitParameter("actionSuffix");
actionSuffix = initParam != null ? initParam : DEFAULT_ACTION_NAME;
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String contextPath = req.getContextPath() + "/";
String servletPath = req.getServletPath();
try {
Action action = (Action) Class.forName(getFullActionName(servletPath)).newInstance();
ActionResult actionResult = action.execute(req, resp);
ResultContent resultContent = actionResult.getResultContent();
switch(actionResult.getResultType()) {
case Redirect:
resp.sendRedirect(contextPath + resultContent.getUrl());
break;
case Forward:
req.getRequestDispatcher(getFullJspPath(servletPath) + resultContent.getUrl())
.forward(req, resp);
break;
case Ajax:
PrintWriter pw = resp.getWriter();
pw.println(resultContent.getJson());
pw.close();
break;
case Chain:
req.getRequestDispatcher(contextPath + resultContent.getUrl())
.forward(req, resp);
break;
case RedirectChain:
resp.sendRedirect(contextPath + resultContent.getUrl());
break;
default:
}
}
catch (Exception e) {
e.printStackTrace();
resp.sendRedirect("error.html");
}
}
// 根据请求的服务路径获得对应的Action类的名字
private String getFullActionName(String servletPath) {
int start = servletPath.lastIndexOf("/") + 1;
int end = servletPath.lastIndexOf(".do");
return packagePrefix + getSubPackage(servletPath) + CommonUtil.capitalize(servletPath.substring(start, end)) + actionSuffix;
}
// 根据请求的服务路径获得对应的完整的JSP页面路径
private String getFullJspPath(String servletPath) {
return jspPrefix + getSubJspPath(servletPath);
}
// 根据请求的服务路径获得子级包名
private String getSubPackage(String servletPath) {
return getSubJspPath(servletPath).replaceAll("\\/", ".");
}
// 根据请求的服务路径获得JSP页面的子级路径
private String getSubJspPath(String servletPath) {
int start = 1;
int end = servletPath.lastIndexOf("/");
return end > start ? servletPath.substring(start, end > 0 ? end + 1 : 0) : "";
}
}
这一次,我们让前端控制器在解析用户请求的小服务路径时,将请求路径和Action类的包以及JSP页面的路径对应起来,也就是说,如果用户请求的小服务路径是/user/order/save.do,那么对应的Action类的完全限定名就是com.test.action.user.order.SaveAction,如果需要跳转到ok.jsp页面,那么JSP页面的默认路径是/WEB-INF/jsp/user/order/ok.jsp。这样做才能满足对项目模块进行划分的要求,而不是把所有的Action类都放在一个包中,把所有的JSP页面都放在一个路径下。
然而,前端控制器的任务到这里还远远没有完成,如果每个Action都要写若干的req.getParameter(String)从请求中获得请求参数再组装对象而后调用业务逻辑层的代码,这样Action实现类中就会有很多重复的样板代码,代码有很多种坏味道,重复是最坏的一种!解决这一问题的方案仍然是反射,通过反射我们可以将Action需要的参数注入到Action类中。需要注意的是,反射虽然可以帮助我们写出通用性很强的代码,但是反射的开销也是不可忽视的,我们的自定义MVC框架还有很多可以优化的地方,不过先放放,先解决请求参数的注入问题。
先封装一个反射的工具类,代码如下所示:
package com.test.util;
public interface TypeConverter {
public Object convert(Class<?> elemType, String value) throws Exception;
}
package com.test.util;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
/**
* 反射工具类
*/
public class ReflectionUtil {
private ReflectionUtil() {
throw new AssertionError();
}
/**
* 根据字段名查找字段的类型
* @param target 目标对象
* @param fieldName 字段名
* @return 字段的类型
*/
public static Class<?> getFieldType(Object target, String fieldName) {
Class<?> clazz = target.getClass();
String[] fs = fieldName.split("\\.");
try {
for(int i = 0; i < fs.length - 1; i++) {
Field f = clazz.getDeclaredField(fs[i]);
target = f.getType().newInstance();
clazz = target.getClass();
}
return clazz.getDeclaredField(fs[fs.length - 1]).getType();
}
catch(Exception e) {
// throw new RuntimeException(e);
}
return null;
}
/**
* 获取对象所有字段的名字
* @param obj 目标对象
* @return 字段名字的数组
*/
public static String[] getFieldNames(Object obj) {
Class<?> clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
List<String> fieldNames = new ArrayList<>();
for(int i = 0; i < fields.length; i++) {
if((fields[i].getModifiers() & Modifier.STATIC) == 0) {
fieldNames.add(fields[i].getName());
}
}
return fieldNames.toArray(new String[fieldNames.size()]);
}
/**
* 通过反射取对象指定字段(属性)的值
* @param target 目标对象
* @param fieldName 字段的名字
* @throws 如果取不到对象指定字段的值则抛出异常
* @return 字段的值
*/
public static Object getValue(Object target, String fieldName) {
Class<?> clazz = target.getClass();
String[] fs = fieldName.split("\\.");
try {
for(int i = 0; i < fs.length - 1; i++) {
Field f = clazz.getDeclaredField(fs[i]);
f.setAccessible(true);
target = f.get(target);
clazz = target.getClass();
}
Field f = clazz.getDeclaredField(fs[fs.length - 1]);
f.setAccessible(true);
return f.get(target);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 通过反射给对象的指定字段赋值
* @param target 目标对象
* @param fieldName 字段的名称
* @param value 值
*/
public static void setValue(Object target, String fieldName, Object value) {
Class<?> clazz = target.getClass();
String[] fs = fieldName.split("\\.");
try {
for(int i = 0; i < fs.length - 1; i++) {
Field f = clazz.getDeclaredField(fs[i]);
f.setAccessible(true);
Object val = f.get(target);
if(val == null) {
Constructor<?> c = f.getType().getDeclaredConstructor();
c.setAccessible(true);
val = c.newInstance();
f.set(target, val);
}
target = val;
clazz = target.getClass();
}
Field f = clazz.getDeclaredField(fs[fs.length - 1]);
f.setAccessible(true);
f.set(target, value);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}
这个工具类中封装了四个方法,通过这个工具类可以给对象的指定字段赋值,也可以获取对象指定字段的值和类型,对于对象的某个字段又是一个对象的情况,上面的工具类也能够提供很好的处理,例如person对象关联了car对象,car对象关联了producer对象,producer对象有name属性,可以用ReflectionUtil.get(person, “car.producer.name”)来获取name属性的值。有了这个工具类,我们可以继续改写前端控制器了:
package com.test.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Array;
import java.util.Enumeration;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.lovo.action.Action;
import com.lovo.action.ActionResult;
import com.lovo.action.ResultContent;
import com.lovo.util.CommonUtil;
import com.lovo.util.ReflectionUtil;
/**
* 前端控制器(门面模式[提供用户请求的门面])
*/
@WebServlet(urlPatterns = { "*.do" }, loadOnStartup = 0,
initParams = {
@WebInitParam(name = "packagePrefix", value = "com.lovo.action."),
@WebInitParam(name = "jspPrefix", value = "/WEB-INF/jsp/"),
@WebInitParam(name = "actionSuffix", value = "Action")
}
)
@MultipartConfig
public class FrontController extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final String DEFAULT_PACKAGE_NAME = "com.lovo.action.";
private static final String DEFAULT_JSP_PATH = "/WEB-INF/content/";
private static final String DEFAULT_ACTION_NAME = "Action";
private String packagePrefix = null; // 包名的前缀
private String jspPrefix = null; // JSP页面路径的前缀
private String actionSuffix = null; // Action类名的后缀
@Override
public void init(ServletConfig config) throws ServletException {
String initParam = config.getInitParameter("packagePrefix");
packagePrefix = initParam != null ? initParam : DEFAULT_PACKAGE_NAME;
initParam = config.getInitParameter("jspPrefix");
jspPrefix = initParam != null ? initParam : DEFAULT_JSP_PATH;
initParam = config.getInitParameter("actionSuffix");
actionSuffix = initParam != null ? initParam : DEFAULT_ACTION_NAME;
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String contextPath = req.getContextPath() + "/";
String servletPath = req.getServletPath();
try {
Action action = (Action) Class.forName(getFullActionName(servletPath)).newInstance();
injectProperties(action, req);// 向Action对象中注入请求参数
ActionResult actionResult = action.execute(req, resp);
ResultContent resultContent = actionResult.getResultContent();
switch (actionResult.getResultType()) {
case Redirect:
resp.sendRedirect(contextPath + resultContent.getUrl());
break;
case Forward:
req.getRequestDispatcher(
getFullJspPath(servletPath) + resultContent.getUrl())
.forward(req, resp);
break;
case Ajax:
PrintWriter pw = resp.getWriter();
pw.println(resultContent.getJson());
pw.close();
break;
case Chain:
req.getRequestDispatcher(contextPath + resultContent.getUrl())
.forward(req, resp);
break;
case RedirectChain:
resp.sendRedirect(contextPath + resultContent.getUrl());
break;
default:
}
}
catch (Exception e) {
e.printStackTrace();
resp.sendRedirect("error.html");
}
}
// 向Action对象中注入属性
private void injectProperties(Action action, HttpServletRequest req) throws Exception {
Enumeration<String> paramNamesEnum = req.getParameterNames();
while(paramNamesEnum.hasMoreElements()) {
String paramName = paramNamesEnum.nextElement();
Class<?> fieldType = ReflectionUtil.getFieldType(action, paramName.replaceAll("\\[|\\]", ""));
if(fieldType != null) {
Object paramValue = null;
if(fieldType.isArray()) { // 如果属性是数组类型
Class<?> elemType = fieldType.getComponentType(); // 获得数组元素类型
String[] values = req.getParameterValues(paramName);
paramValue = Array.newInstance(elemType, values.length); // 通过反射创建数组对象
for(int i = 0; i < values.length; i++) {
Object tempObj = CommonUtil.changeStringToObject(elemType, values[i]);
Array.set(paramValue, i, tempObj);
}
}
else { // 非数组类型的属性
paramValue = CommonUtil.changeStringToObject(fieldType, req.getParameter(paramName));
}
ReflectionUtil.setValue(action, paramName.replaceAll("\\[|\\]", ""), paramValue);
}
}
}
}
到这里,我们的前端控制器还不能够支持文件上传。Java Web应用的文件上传在Servlet 3.0规范以前一直是个让人闹心的东西,需要自己编写代码在Servlet中通过解析输入流来找到上传文件的数据,虽然有第三方工具(如commons-fileupload)经封装了这些操作,但是一个Web规范中居然没有文件上传的API难道不是很搞笑吗?好在Servlet 3.0中有了@MultiConfig注解可以为Servlet提供文件上传的支持,而且通过请求对象的getPart或getParts方法可以获得上传的数据,这样处理文件上传就相当方便了。
我们先定义一个接口来让Action支持文件上传,凡是要处理文件上传的Action类都要实现这个接口,然后我们通过接口注入的方式,将上传文件的数据以及上传文件的文件名注入到Action类中,这样Action类中就可以直接处理上传的文件了。
支持文件上传的接口代码:
package com.test.action;
import javax.servlet.http.Part;
/**
* 支持文件上传的接口
*/
public interface Uploadable {
/**
* 设置文件名
* @param filenames 文件名的数组
*/
public void setFilenames(String[] filenames);
/**
* 设置上传的附件
* @param parts 附件的数组
*/
public void setParts(Part[] parts);
}
修改后的前端控制器:
package com.lovo.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import com.lovo.action.Action;
import com.lovo.action.ActionResult;
import com.lovo.action.ResultContent;
import com.lovo.action.ResultType;
import com.lovo.action.Uploadable;
import com.lovo.util.CommonUtil;
import com.lovo.util.ReflectionUtil;
/**
* 前端控制器(门面模式[提供用户请求的门面])
* @author 骆昊
*
*/
@WebServlet(urlPatterns = { "*.do" }, loadOnStartup = 0,
initParams = {
@WebInitParam(name = "packagePrefix", value = "com.lovo.action."),
@WebInitParam(name = "jspPrefix", value = "/WEB-INF/jsp/"),
@WebInitParam(name = "actionSuffix", value = "Action")
}
)
@MultipartConfig
public class FrontController extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final String DEFAULT_PACKAGE_NAME = "com.lovo.action.";
private static final String DEFAULT_JSP_PATH = "/WEB-INF/content/";
private static final String DEFAULT_ACTION_NAME = "Action";
private String packagePrefix = null; // 包名的前缀
private String jspPrefix = null; // JSP页面路径的前缀
private String actionSuffix = null; // Action类名的后缀
@Override
public void init(ServletConfig config) throws ServletException {
String initParam = config.getInitParameter("packagePrefix");
packagePrefix = initParam != null ? initParam : DEFAULT_PACKAGE_NAME;
initParam = config.getInitParameter("jspPrefix");
jspPrefix = initParam != null ? initParam : DEFAULT_JSP_PATH;
initParam = config.getInitParameter("actionSuffix");
actionSuffix = initParam != null ? initParam : DEFAULT_ACTION_NAME;
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String contextPath = req.getContextPath() + "/";
String servletPath = req.getServletPath();
try {
Action action = (Action) Class.forName(getFullActionName(servletPath)).newInstance();
try {
injectProperties(action, req);
} catch (Exception e) {
}
if(action instanceof Uploadable) { // 通过接口向实现了接口的类注入属性(接口注入)
List<Part> fileparts = new ArrayList<>();
List<String> filenames = new ArrayList<>();
for(Part part : req.getParts()) {
String cd = part.getHeader("Content-Disposition");
if(cd.indexOf("filename") >= 0) {
fileparts.add(part);
filenames.add(cd.substring(cd.lastIndexOf("=") + 1).replaceAll("\\\"", ""));
}
}
((Uploadable) action).setParts(fileparts.toArray(new Part[fileparts.size()]));
((Uploadable) action).setFilenames(filenames.toArray(new String[filenames.size()]));
}
ActionResult actionResult = action.execute(req, resp);
if(actionResult != null) {
ResultContent resultContent = actionResult.getResultContent();
ResultType resultType = actionResult.getResultType();
switch(resultType) {
case Redirect:
resp.sendRedirect(contextPath + resultContent.getUrl());
break;
case Forward:
req.getRequestDispatcher(getFullJspPath(servletPath) + resultContent.getUrl()).forward(req, resp);
break;
case Ajax:
PrintWriter pw = resp.getWriter();
pw.println(resultContent.getJson());
pw.close();
break;
case Chain:
req.getRequestDispatcher(contextPath + resultContent.getUrl()).forward(req, resp);
break;
case RedirectChain:
resp.sendRedirect(contextPath + resultContent.getUrl());
break;
default:
}
}
}
catch (Exception e) {
e.printStackTrace();
resp.sendRedirect("error.html");
}
}
// 根据请求的小服务路径获得对应的Action类的名字
private String getFullActionName(String servletPath) {
int start = servletPath.lastIndexOf("/") + 1;
int end = servletPath.lastIndexOf(".do");
return packagePrefix + getSubPackage(servletPath) + CommonUtil.capitalize(servletPath.substring(start, end)) + actionSuffix;
}
// 根据请求的小服务路径获得对应的完整的JSP页面路径
private String getFullJspPath(String servletPath) {
return jspPrefix + getSubJspPath(servletPath);
}
// 根据请求的小服务路径获得子级包名
private String getSubPackage(String servletPath) {
return getSubJspPath(servletPath).replaceAll("\\/", ".");
}
// 根据请求的小服务路径获得JSP页面的子级路径
private String getSubJspPath(String servletPath) {
int start = 1;
int end = servletPath.lastIndexOf("/");
return end > start ? servletPath.substring(start, end > 0 ? end + 1 : 0) : "";
}
// 向Action对象中注入属性
private void injectProperties(Action action, HttpServletRequest req) throws Exception {
Enumeration<String> paramNamesEnum = req.getParameterNames();
while(paramNamesEnum.hasMoreElements()) {
String paramName = paramNamesEnum.nextElement();
Class<?> fieldType = ReflectionUtil.getFieldType(action, paramName.replaceAll("\\[|\\]", ""));
if(fieldType != null) {
Object paramValue = null;
if(fieldType.isArray()) { // 如果属性是数组类型
Class<?> elemType = fieldType.getComponentType(); // 获得数组元素类型
String[] values = req.getParameterValues(paramName);
paramValue = Array.newInstance(elemType, values.length); // 通过反射创建数组对象
for(int i = 0; i < values.length; i++) {
Object tempObj = CommonUtil.changeStringToObject(elemType, values[i]);
Array.set(paramValue, i, tempObj);
}
}
else { // 非数组类型的属性
paramValue = CommonUtil.changeStringToObject(fieldType, req.getParameter(paramName));
}
ReflectionUtil.setValue(action, paramName.replaceAll("\\[|\\]", ""), paramValue);
}
}
}
}
具体的Action实现和逻辑处理略过!!!