posts - 33, comments - 0, trackbacks - 0, articles - 0
            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

          2011年11月22日

                在第一篇時就說過框架要在URL上作文章,是的,本文就框架怎樣充分利用url上作盡可能詳細的說明。

                做web開發的不可能對url陌生,早在web1.0時代,url作為統一資源定位符,在對web中資源的如何獲得上起到巨大作用。不論用戶請求的時靜態頁面或者是各種圖片、腳本文件,通過url總能從web網站獲取要訪問的資源。Web2.0更是常常使用url作為get請求時參數的傳遞,如http://xxx.xxx.xxx/xxx.jsp?user=admin。以及近幾年很火restful web service 摒棄soap而使用url傳遞請求參數 都說明合理利用url的可行和流行。

                當然不止是使用了url就算好的實踐,而是能夠做到優雅的使用,保證層次分明和整體的簡潔,這才算是好的方式,這也正是本框架對使用url 所追求的目標。

                首先來看幾個例子:

          http://www.cnblogs.com/p2
          http://www.xxx.com/index.do?page=2

          http://www.xxx.com/product/
          http://www.xxx.com/channel.do?channel=product

          http://www.xxx.com/product/mobile
          http://www.xxx.com/channel.do?channel=product&&subChannel=mobile

                相信各位看官不用我說也能明白,這幾組的實現肯定第一種實現的方式更佳。拋開它能屏蔽服務器端使用的技術這一特性不說,它還能夠更好地說明動態網站的層次結構,讓用戶在訪問時能明確知道在網站的什么位置,而不會覺得是陷入了一個迷宮。

                當然上面列舉的例子是網站前端所使用的url表現方式,因為表現方式可以多種多樣,個人喜好不同,本框架在設計時沒有給指定前端url的表現方式,而是定義接口,把這個權利留給使用的用戶。框架將考慮更多 通用性的東西而不是 個性 自由的東西。

           

                下面對框架里默認使用的url Router AMPPathRouter做詳細的介紹,包括設計的思想和實現的方式。首先AMPPathRouter的用途定位為后臺使用。為了理解快速的理解它的工作原理,先來和struts做一下對比。

                Struts關于請求的配置:

           

          <action name="login" class="com.lscmjx.action.LoginAction" method="login">
          <result name="success">
          main.jsp
          </result>
          <result name="failure">
          login.jsp
          </result>
          </action>

              它提交的url會是http://xxx.xxx.xxx/login,訪問web服務器時會把此url傳遞到struts框架交給它處理,之后struts會在struts.xml中尋找login的相關的配置,像上面例子,struts會找到LoginAction的類,并且調用其login的方法。

                寫到這里,我請問這是最好的方式嗎?當然不是,至少我在使用struts時就認為這是相當撇腳的設計。上面例子只是列舉一個login方法,假如一個系統中要對后臺調用的方法是100個,那豈不是就需要在struts.xml中寫100個與之類似的配置。想想都頭大,這樣繁瑣的工作,應該是由框架自己去處理,而不是人工給配置。

           

                再來看實現相同功能的Unicorn web框架的配置。

           

          <action class="com.mh.action.UserAction"></action>

                當然提交的url肯定需要包含多一些的信息,來保證能通過url正確調用框架Action里的方法。這里提交的url方式:http://xxx.xxx.xxx/UserAction/login/

                通過在url里附加調用的Action類的信息,可以省略為不同的方法都在xml里配置的麻煩。假如UserAction里有100個方法,框架也只需這一行的配置。

                有了大體的認識之后,來看框架的核心部分AMPPathRouter的具體實現。

           

            /**
          * 檢查url是否是此Router類要處理的,/Action/Method/Param 格式的將會被檢查合格,返回true
          *
          @param relativeUri
          *
          @param actionMap
          *
          @return
          */
          public boolean checkUrl(String relativeUri, Map<String, ActionSupport> actionMap) {
          Pattern pattern = Pattern.compile("^/\\w+/\\w+/\\S*");
          Matcher matcher = pattern.matcher(relativeUri);
          if(matcher.matches()) {
          String actionName = relativeUri.split("/")[1];
          ActionSupport actionSupport = actionMap.get(actionName);
          if(null != actionSupport) {
          String actionMethodName = relativeUri.split("/")[2];
          Class<?> actionClass = actionSupport.getClass();
          Method[] methods = actionClass.getMethods();
          for(int i = 0; i < methods.length; i++) {
          Method method = methods[i];
          String methodName = method.getName();
          if(methodName.equals(actionMethodName)) {
          return true;
          }
          }
          } else {
          return false;
          }
          }
          return false;
          }
          /**
          * 匹配規則為:
          * 1、符合/Action/method/param格式,
          * 2、并且Action在actionMap中的確存在
          * 3、method在此Action中存在
          */
          @Override
          public boolean route(String relativeUri, UrlFilter urlFilter) {
          Map<String, ActionSupport> actionMap = urlFilter.getActionMap();
          if(!this.checkUrl(relativeUri, actionMap)) {
          return false;
          }
          // 攔截Action/Method/Param方式的請求,并構建ActionSupport類的屬性
          String[] params = relativeUri.split("/");
          try {
          ActionSupport actionSupport = actionMap.get(params[1]);
          Class<?> action = actionSupport.getClass();
          Method method = action.getMethod(params[2], new Class[] {});
          if(params.length > 3) {
          this.boxingRequest(urlFilter.getRequest(), params[3]);
          }
          // 只要找到ActionSupport的子類,則初始化其所具有的屬性
          Object newInstance = action.newInstance();
          this.initActionSupport(newInstance, urlFilter);
          String result = (String) method.invoke(newInstance, new Object[] {});
          if (null == result || ActionSupport.AJAX.equals(result) || ActionSupport.FORWARD.equals(result) || ActionSupport.WEB_SERVICE.equals(result)) {
          return true;
          }
          if(ActionSupport.REDIRECT.equals(result)) {
          urlFilter.getResponse().sendRedirect(result);
          return true;
          }
          } catch (NoSuchMethodException e) {
          e.printStackTrace();
          } catch (SecurityException e) {
          e.printStackTrace();
          } catch (IllegalAccessException e) {
          e.printStackTrace();
          } catch (IllegalArgumentException e) {
          e.printStackTrace();
          } catch (InvocationTargetException e) {
          e.printStackTrace();
          } catch (InstantiationException e) {
          e.printStackTrace();
          } catch (Exception e) {
          e.printStackTrace();
          }
          return false;
          }
          /**
          * 把url中得param加入到request的attribute里
          *
          @param request
          *
          @param parameter
          */
          private void boxingRequest(HttpServletRequest request, String parameter) {
          String[] parameters = parameter.split("&");
          for (int i = 0; i < parameters.length; i++) {
          String param = parameters[i];
          String[] key_value = param.split("=");
          if(key_value.length == 2) {
          request.setAttribute(key_value[0], key_value[1]);
          }
          }
          }
          /**
          * 初始化ActionSupport類中所需的request、response、session、application等對象
          *
          @param obj
          *
          @param urlFilter
          */
          private void initActionSupport(Object obj, UrlFilter urlFilter) {
          ActionSupport action = (ActionSupport) obj;
          action.setRequest(urlFilter.getRequest());
          action.setResponse(urlFilter.getResponse());
          action.setSession(urlFilter.getSession());
          action.setApplication(urlFilter.getApplication());
          }

           

                這便是AMPPathRouter的全部內容,其中在把請求分發到ActionSupport的子類 并調用相關方法時 是通過反射實現,其他地方地方都是相當容易理解的。

                空說無憑,把框架應用到實戰中才是硬道理:

           

                好了,下一篇介紹Action 和 json。

          posted @ 2011-11-25 18:18 馬航 閱讀(269) | 評論 (0)編輯 收藏

                上篇說過,所有提交到web程序的url都被此UrlFilter攔截。攔截到請求后,UrlFilter則召集它的好多個得力干將Router 們, 詢問他們:“誰能處理此URL啊 ?”

          這時一位叫做AMPRouter 首當其沖 說:“這個url交給我了”。這時filter就會把此url全權交給AMPRouter來辦,至于如何去處理,filter也不再過問,它覺得:“我把任務都交給你了,怎么解決是你的事”。

                根據單一職責的原則,UrlFilter就負責上面情景中的分發urlRouter中的差事,url如何分發交給Router處理。并且Router實際是一個接口,使用框架的用戶完全可以自己實現Router,這樣用戶可以自主定義的url分發的策略。另外呢,框架初始化的一些操作它也是 推脫不掉的,像根據unicorn-config.xml初始化系統中的RouterAction'。下面是具體的代碼:

           

          @Override
          public void init(FilterConfig config) throws ServletException {
          	application = config.getServletContext();
          	String loadPath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
          	String classPath = loadPath.substring(1, loadPath.length());
          	ArrayList<String> actions = XMLReader.getNodeValues(classPath + "unicorn-config.xml", "actions");
          	this.initActions(actions);
          	ArrayList<String> routers = XMLReader.getNodeValues(classPath + "unicorn-config.xml", "routers");
          	this.initRouters(routers);
          }

           

          @Override
          public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
          			throws IOException, ServletException {
          	HttpServletRequest request = (HttpServletRequest) servletRequest;
          	String path = request.getContextPath();
          	String uri = request.getRequestURI();
          	String relativeUri = uri.substring(path.length(), uri.length());
          	this.request = request;
          	this.session = request.getSession();
          	this.response = (HttpServletResponse) servletResponse;
          	// 用戶自定義的Router優先級最高,url先通過用戶定義的
          	Iterator<IPathRouter> iterator = routerList.iterator();
          	while(iterator.hasNext()) {
          		IPathRouter router = iterator.next();
          		if(router.route(relativeUri, this)) {
          			return ;
          		}
          	}
          	// 攔截不到的繼續訪問
          	filterChain.doFilter(servletRequest, servletResponse);
          }
          

                  其中Router類的初始化,Action類的初始化于這個類似:

          private void initRouters(ArrayList<String> routers) {
          	routerList = new ArrayList<IPathRouter>();
          	for (int i = 0; i < routers.size(); i++) {
          		String routerName = routers.get(i);
          		try {
          			Class<?> clz = Class.forName(routers.get(i));
          			// 單例模式通過方法獲取對象實例
          			IPathRouter router = (IPathRouter) clz.newInstance();
          			routerList.add(router);
          		} catch (ClassNotFoundException e) {
          			e.printStackTrace();
          		} catch (IllegalAccessException e) {
          			e.printStackTrace();
          		} catch (SecurityException e) {
          			e.printStackTrace();
          		} catch (IllegalArgumentException e) {
          			e.printStackTrace();
          		} catch (InstantiationException e) {
          			e.printStackTrace();
          		}
          	}
          	// 最后把框架默認的Router加入進來
          	routerList.add(new AMPPathRouter());
          }
          

                  其中unicorn-config.xml文件的編寫,拿其中我一個項目里的這個文件來舉例:

          <?xml version="1.0" encoding="UTF-8" ?>
          <config>
          <routers>
          <router class="com.mh.router.MySessionCheckRouter"></router>
          </routers>
          <actions>
          <action class="com.mh.action.UserAction"></action>
          <action class="com.mh.action.InformationAction"></action>
          <action class="com.mh.action.UploadInformationIconAction"></action>
          <action class="com.mh.action.TempPicAction"></action>
          <action class="com.mh.action.MobileAction"></action>
          </actions>
          </config>

                這里即定義了Action,也定義了自己的Router,并且從名稱上可以看出,這個SessionCheckRouter是要判斷所有提交到服務器的指定url的請求 是否已經登錄過,沒有登錄,可能會把此請求遣送會登錄頁。以及初始化所有的Action,在Router處理完請求,分發給action時,可以從filter里面去取。

           


          posted @ 2011-11-25 12:35 馬航 閱讀(273) | 評論 (0)編輯 收藏

                承接上篇的簡單介紹,下面詳細介紹整個框架的大致結構。

                先來看一下整個框架包的結構:

           

                可以看出框架包含的包很少,包的結構也超簡單。這里 涉及FilterActionSupportRouter等三個概念,他們之間的關系,通過下圖來表示:

           

                圖也不規范,說不上來是哪個UML圖,不過通過它也能看出一個請求到達時,框架基本的處理流程。首先由Filter攔截到所有請求,然后把請求交給所有注冊的Router類,如果請求的Url正好是一個Router要攔截的,則把此請求交給這個Router,框架不再把請求向下傳遞。Router得到請求后,分析Url,通過Url里的信息把請求交給對應的ActionSupport的子類來處理。

                這里攔截采用Filter來處理,這跟多數的web框架一樣,使用FilterServlet有更多的能力進行請求的分發。首先在一個web工程的web.xml文件中配置框架的UrlFilter類來攔截所有的請求。需要注意的一點是dispatcher 要設置為request,如果設置了forward的話,由框架內部進行的forward又會被框架攔截,從而造成無限的循環。Url-pattern設置為/*,表示所有的請求都會攔截,從而把對url分發的權利交由框架本身,而不是采用jsp規范里的url分發策略。框架在處理所有請求的url 時,依次交給各個Router類來處理,如果Router類判斷是符合自己的url格式,則分發給 action 處理。如果不能處理再交給下一級的Router,最后url經由所有Router處理完,剩下的資源文件的url,如http://xxx.xxx.xxx.jpg,則框架調用filterdoChain()方法,通過filter的過濾去訪問web里的資源。

          <filter>

                <filter-name>unicornWeb</filter-name>

                <filter-class>com.mh.mvc.filter.UrlFilter</filter-class>

          </filter>

          <filter-mapping>

              <filter-name>unicornWeb</filter-name>

              <url-pattern>/*</url-pattern>

              <dispatcher>REQUEST</dispatcher>

          </filter-mapping>

                大致的原理就是這樣,在下篇介紹框架的詳細實現。

          posted @ 2011-11-25 11:43 馬航 閱讀(350) | 評論 (0)編輯 收藏

                我承認有點標題黨了,不過題目中所說的幾項技術確實有其相似之處,欲知事情原委,且聽我詳細道來。

                項目一開始只是不滿 struts 龐大的體積,于是想自己根據其原理實現一個tiny 版。后來的開發中覺得,完全可以把上述的ajax、Restuful web service的一些思想加入進來。經過幾周的努力,便開發出了一個基本成型的web 框架,暫且起名為unicorn(獨角獸,吼吼)。下文開始便對這個自編寫的框架做一些列的介紹,并且初步打算是將其開源,希望能一起交流和完善它。

                首先,為了能快速了解它是什么,先來看一下配置文件:

          <?xml version="1.0" encoding="UTF-8" ?>
          <config>
          <routers>
          <router class="com.mh.router.MySessionCheckRouter"></router>
          </routers>
          <actions>
          <action class="com.mh.action.UserAction"></action>
          <action class="com.mh.action.InformationAction"></action>
          <action class="com.mh.action.MobileAction"></action>
          </actions>
          </config>

                上面就是整個工程的配置文件,可以看出需要配置的東西非常少,只需要制定action類 和 router類有哪些就Ok。框架奉行約定大于配置的思想,至于請求如何分發,這個不需要人工配置,框架自動解決。這里要介紹兩個概念Action 和 Router,熟悉Struts的肯定都知道Action,Action替代Servlet、JSP時代的Servlet,所有提交的請求由struts分發給不同的Action來處理。這里道理也是一樣的,Action就是經過框架處理后的請求接受者。再來說一下Router,字面意思路由器,學過計算機網絡的都知道,ip數據包在網絡上之所以能夠順利到達,就是因為路由器根據路由表來來確定出來傳輸的途徑。這里Router也是這個作用,根據訪問服務器的URL來制定分發策略。Router是完全可以自定義的,用戶可以定義自己的Router來制定URL分發的策略,并且用戶自定義的Router比系統默認的Router有更高的優先權。

          二、URL上做文章

          /UserAction/login/username=admin&&password=admin

                先來介紹系統MethodRouter的處理方式。上面的url根據"/"分為三個部分,第一部分是請求的Action類,第二部分是類中的方法Method,第三部分是提交的參數Param。這一點受上篇文章優酷的架構里URL設計的啟發。

                經過這樣的設計,就明白在上述配置文件中為何可以如此簡單了。

                當然也可以不以這樣的方式,框架提供自定義Router的支持。比如你想這樣處理URL:/前臺頁面/子欄目/子欄目

                想實現上面的方式,就可以自己定義Router,在Router里面獲取上述的URL,然后做處理、forward到相應的jsp頁面。

          三、使用Json傳輸數據

                Ajax請求很容易處理json數據,ajax可以與系統輕松交互。

                當初Web Service使用SOAP的xml格式傳輸數據,如今也有人指責這是大費周折。Restful方式提倡遵循HTTP語義,完全使用URL結合GET、POST、PUT、DELETE來傳輸請求,結果在roil陣營里廣泛使用,認為是web service更優雅的方式。所以本框架也吸取他們的優點,也完全可以通過url傳輸請求的數據,如上述URL中的Param部分。不過沒有遵循Restful強調的Http語義,全部使用Get和POST的請求方式,當然也可以制定為其他,這完全看你的心情,因為這對功能實現無關緊要。而且我覺得統一使用一種,更避免了需要指定請求方式的麻煩。

                數據的返回使用json格式,比SOAP更為輕量簡潔和優雅,而且有更多的平臺直接支持。如在android平臺,本身就支持json格式的處理, 如果使用web service 的SOAP,你可能還要導入KSOAP的第三方庫。

                在非瀏覽器的客戶端,可以借助編寫的工具類,來完成web service方式的操作,

          public interface IWebService {
          	public List<LiteInformationDTO> getInformationsOfOwnerApp(String ownerApp, int start, int limit) throws SocketTimeoutException;
          }
          

                經過這樣的封裝,已經與使用web service毫無差別,而且還會更加高效,因為處理json總比處理SOAP的xml要容易。

           

                先簡單寫這么多,之后的續篇詳細介紹。

          posted @ 2011-11-22 16:22 馬航 閱讀(284) | 評論 (0)編輯 收藏

          主站蜘蛛池模板: 岑巩县| 榆中县| 万荣县| 河西区| 闵行区| 星子县| 佛坪县| 潜江市| 韩城市| 凉山| 新乡市| 嵩明县| 封丘县| 万州区| 若尔盖县| 江阴市| 嫩江县| 淮北市| 冕宁县| 双流县| 盘锦市| 遵化市| 房山区| 富顺县| 紫金县| 平谷区| 翼城县| 高州市| 林口县| 潞西市| 五莲县| 莱阳市| 曲周县| 化州市| 阿拉尔市| 南阳市| 岳普湖县| 县级市| 道孚县| 繁峙县| 德清县|