开发Java Web项目时,如果不采用Struts、WebWork、SpringMVC等MVC框架,而使用原始的Servlet API时,该怎么开发呢?
Struts 1.x采用了一个有“总控制器”作用的Servlet处理所有的请求,而Struts 2.x则采用了Filter。两者各有优缺点。我个人比较喜欢Struts2的一种URI约定形式,类似于“.../member!add.action ”、“.../abc/articles!list.action”的形式。在这里,我们本例也采取了Struts2的这种URI约定形式。即:
目录/目录/.../类名!方法名.后缀
我们也采用Struts2的Filter主控方式,开发一个javax.servlet.Filter的子类,用来处理所有请求,再在这个Filter里获取访问的URI,从URI字符串中提取业务实体类和访问方法,然后根据Java的反射API,动态地创建类对象,动态地执行相应的业务方法。这个Filter我命名为"org.mvcgo.filter.ControllerFilter"。
在web.xml文件中,配置这个Filter,代码如下:
<filter> <filter-name>ControllerFilter</filter-name> <filter-class>org.mvcgo.filter.ControllerFilter</filter-class> <init-param> <param-name>suffix</param-name> <param-value>go</param-value> </init-param> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> <init-param> <param-name>package</param-name> <param-value>org.mvcgo.action</param-value> </init-param> </filter> <filter-mapping> <filter-name>ControllerFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
上面的代码中,ControllerFilter内置参数suffix、encoding分别表示URI的后缀和编码,package表示“根包”,所谓“根包”,这是学习了Struts2的URI命名规则,如上面配置的"org.mvcgo.action",若有个Action类名为"org.mvcgo.action.infos.MemberGo.java",则相应的访问路径为.../PROJECT_NAME/infos/member.go。这个参数都会在我写的"org.mvcgo.filter.ControllerFilter"类中用到。
下面,我们来分析一下“org.mvcgo.filter.ControllerFilter”类,类的头部代码如下:
package org.mvcgo.filter; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.mvcgo.action.BaseGo; import org.mvcgo.utils.StringUtil; public class ControllerFilter implements Filter{...}
在ControllerFilter里定义的成员属性如下:
private FilterConfig filterConfig; //配置ControllerFilter时,对应于init-param中的param-name private static final String SUFFIX_NAME = "suffix"; //前缀的"param-name" private static final String ENCODING_NAME = "encoding"; //编码的"param-name" private static final String PACKAGE_NAME = "package"; //Go的基包的"param-name" //init-param的默认初始化值 private String SUFFIX_VALUE_DEFAULT = "go"; //默认前缀 private String ENCODING_VALUE_DEFAULT = "UTF-8"; //默认编码 private String PACKAGE_VALUE_DEFAULT;
重写Filter的init方法,执行初始化:
/** * 重写Filter的init方法,执行初始化操作 */ public void init(FilterConfig filterConfig) throws ServletException { this.filterConfig = filterConfig; //初始化Go访问的后缀 String suffix = filterConfig.getInitParameter(SUFFIX_NAME); if(null != suffix && !"".equals(suffix)) { this.SUFFIX_VALUE_DEFAULT = suffix; } //初始化编码 String encoding = filterConfig.getInitParameter(ENCODING_NAME); if(null != encoding && !"".equals(encoding)){ this.ENCODING_VALUE_DEFAULT = encoding; } //初始化Go类的基包 String basePackage = filterConfig.getInitParameter(PACKAGE_NAME); if(null != basePackage && !"".equals(basePackage)) { this.PACKAGE_VALUE_DEFAULT = basePackage; } else { throw new ControllerDeployException("读取web.xml中ControllerFilter的初始化参数失败,请检查" + PACKAGE_NAME + "是否配置正确..."); } }
初始化完后,会自动执行doFilter方法,代码如下:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //强制转换request和response HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; //设置HttpServletRequest的编码格式 httpRequest.setCharacterEncoding(ENCODING_VALUE_DEFAULT); //获取请求的URI,如请求http://127.0.0.1:8080/mvcgo/info/user!save.go?id=7,会得到"/mvcgo/info/user!save.go" String requestURI = httpRequest.getRequestURI(); System.out.println("url = " + requestURI); if(requestURI.endsWith("." + SUFFIX_VALUE_DEFAULT)) { String packageClassName = PACKAGE_VALUE_DEFAULT; //包类名,即类的全名 String[] segs = requestURI.split("/"); for(int i = 2 ; i < segs.length - 1 ; i++) { packageClassName = packageClassName + "." + segs[i]; } String classMethodSuffix = segs[segs.length - 1]; String classMethod = classMethodSuffix.substring(0, classMethodSuffix.lastIndexOf("." + SUFFIX_VALUE_DEFAULT)); String className = null; //业务类名 String methodName = null; //方法名 if(classMethod.indexOf("!") != -1) { String[] methodNamesArray = classMethod.split("!"); className = methodNamesArray[0]; methodName = methodNamesArray[1]; } else { className = classMethod; } //格式化并构建Go类的类名 className = StringUtil.upFirstChar(className) + StringUtil.upFirstChar(SUFFIX_VALUE_DEFAULT.toLowerCase()); //首字母大写 packageClassName = packageClassName + "." + className; try { Class c = Class.forName(packageClassName); Method[] methods = c.getMethods(); Object target = c.newInstance(); //业务的Go类,支持继承BaseGo和为不继承BaseDao的两种方式 if(target instanceof BaseGo) { //如果该类继承了BaseGo父类 Method method = c.getMethod(methodName); Method initMethod = c.getSuperclass().getMethod("initHttpServlet", HttpServletRequest.class, HttpServletResponse.class); //执行BaseGo初始化 initMethod.invoke(target, httpRequest, httpResponse); //执行Go类的业务方法 method.invoke(target); } else { //不继承BaseGo父类的情况 Method method = c.getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class); //执行Go类的业务方法 method.invoke(target, httpRequest, httpResponse); } } catch (ClassNotFoundException e) { throw new GoNotFoundException("Go类:"+ packageClassName +"找不到", e); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchMethodException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } } else { chain.doFilter(request, response); } }
从上面的代码可看到,我们的业务BaseGo类支持两种形式,有不继承Go类的情况,如:
package org.mvcgo.action.school; import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class StudentGo { public void add(HttpServletRequest request, HttpServletResponse response){ System.out.println("student " + request.getQueryString()); } public void save(HttpServletRequest request, HttpServletResponse response){ String category = request.getParameter("category"); System.out.println(category); response.sendRedirect("../test01.jsp"); } }
上面的代码中,每个业务方法,都在ControllerFilter类中注入了HttpServletRequest和HttpServletResponse,这样,我们就可以方便地利用Servlet的API来处理我们网页请求了。
为了实现更方便的Go编码,于是开发一个可重用的BaseGo类,代码如下:
package org.mvcgo.action; import java.lang.reflect.Method; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.mvcgo.utils.ParameterFilter; public class BaseGo { protected HttpServletRequest request; protected HttpServletResponse response; public void initHttpServlet(HttpServletRequest request, HttpServletResponse response, Class clazz, Method method) { this.request = request; this.response = response; } }
这时,我们的StudentGo类,继承这个BaseGo,那些业务方法,可直接写成如下形式,而无须带参数。
public void add() throws IOException{ System.out.println("student " + request.getQueryString()); } public void save() throws IOException{ String category = request.getParameter("category"); System.out.println(category); response.sendRedirect("../test01.jsp"); }
本例,运行服务器,在浏览器中访问时路径为
.../PROJECT_NAME(项目名)/school/student!add.go
.../PROJECT_NAME(项目名)/school/student!save.go
本例小小的代码,无以跟Struts、WebWork待这些大框架比美,仅演示了在脱离大框架的情况下,用Servlet、Filter等原始的J2EE API开发Java Web项目时的一种方案,其中ControllerFilter、BaseGo等类都可继续开发扩充更多功能,以方便Web开发者的使用。