一个简单的MVC框架
1、ActionMap,action name与action object之间的映射
public class ActionMap {
private Map<String, Object> actionMap = new HashMap<String, Object>();
public Object getAction(String actionName){
return actionMap.get(actionName);
}
public void initFromProperties(Configuration conf) throws InstantiationException, IllegalAccessException, ClassNotFoundException{
System.out.println("Init actionMap from Properties " + conf.getFileName() + " ...");
Set<Entry<Object, Object>> keyValues = conf.entrySet();
for(Entry<Object, Object> keyValue : keyValues){
this.actionMap.put(keyValue.getKey().toString(), Class.forName(keyValue.getValue().toString()).newInstance());
}
System.out.println("Init actionMap success, Total size " + this.actionMap.size());
}
}
2、NormalUrl是用来解析url中的Servlet path中的action name与method name的(本来还有一个RestfulUrl的,但又不想在这里实现),约定为 http://www.unclepeng.com:XXXX/appName/actionName/methodName/...
public class NormalUrl {
public static final String DEFAULT_METHOD_NAME = "index";
private String actionName;
private String methodName;
private int level = 2;
public String getActionName() {
return actionName;
}
public String getMethodName() {
return methodName;
}
public static NormalUrl fromServletPath(String str){
if(str.startsWith("/")){
str = str.substring(1);
}
String[] strs = StringUtils.split(str, '/', true);
NormalUrl url = new NormalUrl();
if(strs == null || strs.length == 0){
throw new IllegalArgumentException(String.format("Illegal Servlet path %s", str));
}
url.actionName = StringUtils.split(strs[0], '.', true)[0];
if(strs.length > 1){
url.methodName = StringUtils.split(strs[1], '.', true)[0];
}else{
url.methodName = DEFAULT_METHOD_NAME;
url.level = 1;
}
return url;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
}
[、code]
3、核心DispatcherServlet,在init的时候初始化actionMap,在doGet方法中根据servlet path解析出对应的actionName与methodName,再找到对应的Action Object,然后invoke method
public class DispatcherServlet extends HttpServlet {
private static final long serialVersionUID = - 8330920476525405610L;
public static final String INCLUDE_PATH_INFO =
"javax.servlet.include.path_info";
public DispatcherServlet() {
super();
}
private ActionMap actionMap = new ActionMap();
public static int FILE_NOT_FOUND = 404;
public void destroy() {
super.destroy();
this.actionMap = null;
}
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//Split URL
String path = request.getServletPath();
NormalUrl url = NormalUrl.fromServletPath(path);
String actionName = url.getActionName();
String methodName = url.getMethodName();
//Get action by name, if not found, response 404
Object action = this.actionMap.getAction(actionName);
if(action == null){//action not found
System.err.println(String.format("Action named '%s' not found", actionName));
response.sendError(FILE_NOT_FOUND);
}
//Invoke action method, and forward if invoke result not returns null
try{
Method method = action.getClass().getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
Object o = method.invoke(action, new Object[]{request, response});
if(o!= null){
if(url.getLevel() == 2){
request.getRequestDispatcher("../" + (String)o).forward(request, response);
}else{
request.getRequestDispatcher((String)o).forward(request, response);
}
return;
}
}catch(Exception e){
e.printStackTrace();
response.sendError(FILE_NOT_FOUND);
return;
}
}
public void doPut(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
public void doDelete(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
public void init() throws ServletException {
ServletConfig servletConfig = this.getServletConfig();
String mvcConfigFilePath = servletConfig.getInitParameter("mvc_config");
Configuration conf = null;
if(mvcConfigFilePath != null){//case 1, use properites configuration
try {
conf = new PropertiesConfiguration(mvcConfigFilePath);
actionMap.initFromProperties(conf);
return;
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}else{//case 2, use packages setting
String realPath = this.getServletContext().getRealPath("/");
String packagesPath = servletConfig.getInitParameter("action_packages");
String[] packagePathArray = StringUtils.split(packagesPath, ',', true);
for(String packagePath : packagePathArray){
}
}
}
}
4、web.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>Dispatcher</servlet-name>
<servlet-class>com.upeng.webcommons.mvc.DispatcherServlet</servlet-class>
<init-param>
<param-name>mvc_config</param-name>
<param-value>mvc.properties</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Dispatcher</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
5、示例Action
public class ProductAction {
public String index(HttpServletRequest request, HttpServletResponse response){
return "product.jsp";
}
}
6、mvc.properties配置
product =com.upeng.webcommons.test.ProductAction
7、Test
http://localhost:222/webcommons/product/index.do 或者 http://localhost:222/webcommons/product.do,其中的product为action名,index为action中的方法名,当没指定方法时,默认选择action中的index方法
总结与选型过程:
1、本来想实现基于类的MVC,类似struts2,然后把对应的url parameter注入到action的field中,这样做好处是能跟servletAPI完全解耦,坏处是为每次请求以反射的方式生成一个临时的action object,效率上要打些折扣。
2、也想过实现类似mvc.net 1.0中的把action实现成sigton,然后实现基于方法的MVC把对应的url parameter注入到方法的参数里然后回调,这样做要求action中不能有重名的方法,另外在查找方法的时候要做一次遍历,稍微比直接指定method name跟parameterTypes式的查找慢了那么一点。
但是还需要把string型的url parameters转型为方法参数对应的类型效率上又打了一点点的折扣。综合考虑下便又打消了这个念头
3、这还只是一个MVC雏形,初步实现了url到action乃到action中的method之间的mapping,当然,也可以添加对参数检验,错误处理、国际化等的支持。