自己动手写mvc
? ??經常做javaee開發的朋友基本上每天與mvc打交道,但是很多朋友卻并沒有自己寫過mvc,甚至只會用,不知道其內部實現原理,同樣地,我身邊有很多朋友也都是知其然,不知其所以然。其實個人認為mvc實現起來不難,我想事實也是,筆者在興趣的驅使下,自己寫了一個小型的框架,實現了mvc,orm。雖然功能不是很強大,但基本可以在簡單項目,至少是練手的項目使用,學習的項目使用是很好的。至于ioc目前正在實現中。Aop在醞釀中。
? ? ? SpringMVC主要使用注解式,struts2也可以用convention插件來實現注解式。個人認為靈活性很好。不過對于配置文件其實也是不錯的選擇。不管哪種方式,其實只是初始化訪問路徑時的來源不一樣而已,配置文件只用解析xml,對于注解自然是要掃描注解包。
? ? ??當有了訪問路徑時,其他的事就是訪問某一地址(請求路徑)時如何轉發到具體的方法(也就是對應的注解)。
? ? ??注解不難理解,說白了說是一種注釋。本身沒有任何的意義,只是標記一下。比如:
@Resource @Autoware ?等。最多帶幾個參數:@Table(name=”user”),這種說明一下對應關系或者其他的一些參數。使用不多說。
先上訪問地址(也就是action方法)代碼:
packagenet.javaing.mvc.annotation;
importjava.lang.annotation.Documented;
importjava.lang.annotation.ElementType;
importjava.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
importjava.lang.annotation.Target;
/**
*?對實體屬性與數據庫字段的一個對應
* @author javaing
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public@interface At {
? ? ?
? ? ? String value() default "";
? ? ?
? ? ? String method() default "get";
? ? ?
? ? ? String suffix() default"servlet";
? ? ?
}
說明:Java中提供了四種元注解,專門負責注解其他的注解,分別如下 1、@Retention元注解,表示需要在什么級別保存該注釋信息(生命周期)。可選的RetentionPoicy參數包括:RetentionPolicy.SOURCE: 停留在java源文件,編譯器被丟掉 ? ?RetentionPolicy.CLASS:停留在class文件中,但會被VM丟棄(默認) ? ?RetentionPolicy.RUNTIME:內存中的字節碼,VM將在運行時也保留注解,因此可以通過反射機制讀取注解的信息 2、@Target元注解,默認值為任何元素,表示該注解用于什么地方??捎玫腅lementType參數包括 ? ?ElementType.CONSTRUCTOR: 構造器聲明 ? ?ElementType.FIELD: 成員變量、對象、屬性(包括enum實例) ? ?ElementType.LOCAL_VARIABLE: 局部變量聲明 ? ?ElementType.METHOD: 方法聲明 ? ?ElementType.PACKAGE: 包聲明 ? ?ElementType.PARAMETER: 參數聲明 ? ?ElementType.TYPE: 類、接口(包括注解類型)或enum聲明 3、@Documented將注解包含在JavaDoc中 4、@Inheried允許子類繼承父類中的注解這樣首先就完成了action注解。
通過上面的文章,可以看到@At注解有三個屬性,value為訪問請求地址,method為請求方式,支持get/post,還有個suffix是后綴,也就是通過什么樣的后綴訪問請求,一個完整的請求地址(也就是瀏覽器中的址,或post請求控制器地址),由value和suffix構成。中間用“.”分開。
有了注解,我們當然要掃描到注解,什么時候掃描?其實用spring的朋友都知道,spring在web.xml中有這么一句話:
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener>相信這個大家都不陌生。
那么同樣地,我也用這個來實現:
package net.javaing.mvc.listener;import java.lang.reflect.Method;import javax.servlet.ServletContextEvent;import javax.servlet.ServletContextListener;import net.javaing.mvc.annotation.At;import net.javaing.mvc.exception.ExistActionException;import net.javaing.mvc.mapping.ActionMapping;import net.javaing.util.ClassUtil;import net.javaing.util.PropertiesUtil;import org.apache.log4j.Logger;public class MvcContextListener implements ServletContextListener { static Logger logger =Logger.getLogger(MvcContextListener.class.getName());@Overridepublic voidcontextDestroyed(ServletContextEvent arg0){ }@Overridepublic voidcontextInitialized(ServletContextEvent arg0){ for (Class<?> clazz :ClassUtil.getClasses(PropertiesUtil.readValue("config/mvc.properties","scan-base-package"))) { Method[]methods=clazz.getDeclaredMethods(); for (Method method :methods) { At at =method.getAnnotation(At.class);if (at != null){ StringmethodName="";if(!at.value().equals("")){methodName=at.value();}else{methodName=method.getName(); } if(at.method().equals("get")){ if(ActionMapping.getGetAction(methodName+"."+at.suffix())==null){ ActionMapping.addGetActionMapping(methodName+"."+at.suffix(),method);logger.info("findnew action:type:get\t\tname:"+methodName+"."+at.suffix()); }else{ try{ throw newExistActionException(methodName+"."+at.suffix());}catch (ExistActionException e) { e.printStackTrace();} } }else{ if(ActionMapping.getPostAction(methodName+"."+at.suffix())==null){ ActionMapping.addPostActionMapping(methodName+"."+at.suffix(),method); logger.info("find newaction:type:post\t\tname:"+methodName+"."+at.suffix()); }else{ try{ thrownew ExistActionException(methodName+"."+at.suffix()); }catch (ExistActionException e) {e.printStackTrace(); } } }} } } }}至于使用,同spring一樣。只是將類名替換:
<listener> <listener-class>net.javaing.mvc.listener. MvcContextListener.</listener-class> </listener>
這樣也就完成了@At注解的掃描。并且保存到了一個map集合對象ActionMapping中。
有的朋友會問,ActionMapping是個什么東東?
我只能說是個map(事實上他也是),用來存放掃描到的地址。先看看代碼:
package net.javaing.mvc.mapping;import java.lang.reflect.Method;import java.util.HashMap;import java.util.Map;/**** Action映射*/public class ActionMapping {private staticMap<String,Method> postMapping = new HashMap<String,Method>(); private static Map<String,Method>getMapping = new HashMap<String,Method>();public static voidaddPostActionMapping(String serletPath,Method method){ postMapping.put("/"+serletPath,method); }public static MethodgetPostAction(String servletPath){ returnpostMapping.get(servletPath); }public static voidaddGetActionMapping(String serletPath,Method method){ getMapping.put("/"+serletPath,method); }public static MethodgetGetAction(String servletPath){ returngetMapping.get(servletPath); } }實現的辦法就是,把掃描到的請求地址,按get/post分類。按請求地址(前面說過,由value+”.”+suffix構成)存放主鍵,對應的方法(method對象)為值,這樣的方式存放。想法就是,當請求地址請求以后,取得請求地址,尋找對應方法。如果掃描出現地址完全一樣(請求方式、請求名稱、后綴完全一樣)會拋出:ExistActionException。當請求地址找不到的時候(不存在的請求地址,有可能是請求方式不對或名稱或后綴不一樣)會拋出:NoSuchActionException。
以下為兩異常定義:
ExistActionException.java:
package net.javaing.mvc.exception;public class ExistActionException extends Exception {private static final longserialVersionUID = -8608538729128495163L;publicExistActionException(String msg) {super("重復的請求地址!"+msg);}NoSuchActionException.java:
package net.javaing.mvc.exception;public class NoSuchActionException extends Exception {private static final longserialVersionUID = -8608538729128495163L;publicNoSuchActionException(String msg) {super("不存在的請求地址!"+msg);}這樣也就完全地完成了地址掃描。
?到現在為止,如果熟悉springmvc的朋友就會發現,筆者自己寫的mvc有很多使用方式和spring很像。只不過注解名稱不一樣而已。多一個了個屬性而已,之所以加一個屬性,是因為本mvc主要設計為網站形式,尤其文章類型的,給用戶呈現的和管理后臺有點一樣的情況,就是用戶呈現的需要頁面靜態化,常用urlrewrite實現,但是開發的時候一般總是先開發功能,完了再統一做地址靜態化,至少我一般這樣。這樣無形中添加了工作量。如果讓action后綴(部分)變成html這樣就不用再處理了,而如果直接全用html做后綴,有時感覺又不太方便或有可能沖突(與真實靜態文件),而且后臺管理方便又一般不那樣做,所以,這純粹是自己新加的一點功能(有些朋友可能不認同這種方式,或感覺沒用)。
????同樣地,本mvc使用也和spring一樣,在web.xml中加入一個servlet:
?
<servlet> <servlet-name>mvc</servlet-name> <servlet-class>net.javaing.mvc.Mvc</servlet-class> </servlet> <servlet-mapping> <servlet-name>mvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>?
根據自己的一些功能,配置自己的mvc。和spring比起來簡單多了,主要參數少。功能小。
關于地址轉發,基本思想是根據請求方式,找到對應的所有方式下的請求地址集合,再從集合中根據請求地址,找到請求方法。然后執行,進行頁面返回。中間涉及幾個用戶不會手動帶參的,也就是request這樣的參數。但是像springmvc這樣直接在后臺使用的參數的處理。以下是具體實現:
Mvc。Java:主要接收地址,進行地址分發,將請求下發
package net.javaing.mvc; import java.io.IOException; import java.lang.reflect.Method; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import net.javaing.mvc.mapping.ActionMapping; public class Mvc extends HttpServlet { private static Mvcs mvc = new Mvcs(); /** * */private static final long serialVersionUID = -6326149137476027612L; @Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Method method = ActionMapping.getGetAction(request.getServletPath()); Object obj=mvc.execute(request, response, method); if(obj instanceof JspView){ request.setAttribute("obj", ((JspView) obj).getObject()); request.getRequestDispatcher(((JspView) obj).getPath()+".jsp").forward(request,response); } } @Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Method method = ActionMapping.getPostAction(request.getServletPath()); Object obj=mvc.execute(request, response, method); if(obj instanceof JspView){ request.setAttribute("obj", ((JspView) obj).getObject()); request.getRequestDispatcher(((JspView) obj).getPath()+".jsp").forward(request,response); } } }?
在上面的類中可以看到,并沒有進行真正的處理,只是轉發給mvcs類。
Mvcs.java
package net.javaing.mvc; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import net.javaing.mvc.annotation.CheckSession; import net.javaing.mvc.exception.NoSuchActionException; import net.javaing.mvc.exception.NullCheckSessionIdException; import net.javaing.util.MethodParamNamesScanner; import net.javaing.util.SetterGetter; public class Mvcs { public Object execute(HttpServletRequest request, HttpServletResponse response, Method method) { if (method == null) { try { throw new NoSuchActionException(""); } catch (NoSuchActionException e) { e.printStackTrace(); } return null; } else { try { if(checkSession(request, method)){ Class<?>[] parameterTypes = method.getParameterTypes(); List<String> paramNames = MethodParamNamesScanner .getParamNames(method); Object[] parameterObjects = new Object[parameterTypes.length]; for (int index = 0; index < parameterTypes.length; index++) { Class<?> clazz = parameterTypes[index]; String paramName = paramNames.get(index); if (clazz.getName().equals( HttpServletRequest.class.getName())) { parameterObjects[index] = request; } else if (clazz.getName().equals( HttpServletResponse.class.getName())) { parameterObjects[index] = response; } else if (clazz.getName().equals(Integer.class.getName())) { parameterObjects[index] = Integer.parseInt(request .getParameter(paramName)); } else if (clazz.getName().equals(Double.class.getName())) { parameterObjects[index] = Double.parseDouble(request .getParameter(paramName)); } else if (clazz.getName().equals(Float.class.getName())) { parameterObjects[index] = Float.parseFloat(request .getParameter(paramName)); } else if (clazz.getName().equals(String.class.getName())) { parameterObjects[index] = request .getParameter(paramName); } else { Object obj = clazz.newInstance(); for (Field filed : clazz.getDeclaredFields()) { Method setMethod = clazz.getDeclaredMethod( SetterGetter.setter(filed.getName()), filed.getType()); setMethod.invoke(obj, request.getParameter(filed.getName())); } parameterObjects[index] = obj; } } return method.invoke(method.getDeclaringClass().newInstance(), parameterObjects); } } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } return null; } } public boolean checkSession(HttpServletRequest request, Method method){ CheckSession checkSession = method.getAnnotation(CheckSession.class); if(checkSession!=null){ if(checkSession.name()==null){ try { throw new NullCheckSessionIdException("\"\""); } catch (NullCheckSessionIdException e) { e.printStackTrace(); } return false; }else if((checkSession.value()==null&&request.getSession().getAttribute(checkSession.name())!=null)||checkSession.value().equals(request.getSession().getAttribute(checkSession.name()))){ return true; }else{ //不符合指定值當然是要返回指定頁面了。 return false; } }else{ return true; } } }這個類會進行真正的處理,將請求轉發給真正的我們要使用的控制器。主要處理:
1、地址分發
2、參數處理,主要表現為request和response。
3、將參數對象化,主要模仿spring的方式,自動裝配對象,而不是像struts2一樣,要通過user.username這種方式處理。
4、添加了一個過濾器,會檢查會話狀態。下次會講到。
這樣也就完成了從頁面請求到控制器的處理。
總結
- 上一篇: 自己动手写文件系统
- 下一篇: mvc框架自个儿搭建