利用抽象工廠創(chuàng)建DAO、利用依賴注入去除客戶端對工廠的直接依賴、將有關Article的各種Servlet全部封裝到一個Servlet中(通過BaseServlet來進行ArticleServlet方法的導向)
總體分析:
1、利用PropertiesBeanFactory抽象工廠根據(jù)beans.properties配置文件創(chuàng)建各種DAO,放入ServletContext中。
2、在BaseServlet中(實際上用戶訪問的是繼承了BaseServlet的ArticleServlet、…)根據(jù)屬性PropertiesBeanFactory取出ArticleServlet、…需要的DAO,向具體ArticleServlet、ChannelServlet、LoginServlet注入需要的某些DAO,以避免客戶端直接依賴于具體的DAO實現(xiàn)類。
3、將各種關于Article功能的Servlet全部集中到一個ArticleServlet中(關于Channel功能的Servlet全部集中到一個ChannelServlet中、……),通過BaseServlet中的process()方法實現(xiàn)導向ArticleServlet(ChannelServlet…)中的不同方法(add()、del()、update()、…方法)的功能(即原先的直接訪問各種AddArticleServlet、DelArticleServlet、UpdateArticleServlet、…)。
大體的思路是這樣的:
首先在web.xml中定義
<servlet>
<servlet-name>InitBeanFactoryServlet</servlet-name>
<servlet-class>cn.com.leadfar.cms.backend.view.InitBeanFactoryServlet</servlet-class>
<init-param>
<param-name>configLocation</param-name>
<param-value>beans.properties</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
1、<load-on-startup>表示容器是否在啟動的時候就加載這個servlet(實例化并調用其init()方法);
2、它的值必須是一個整數(shù),表示該servlet應該被載入的順序;
3、當值為0或者大于0時,表示容器在啟動時加載并初始化這個servlet;當值小于0或者沒有指定時,表示容器在該servlet被使用時才去加載(即用戶第一次請求時);
4、正數(shù)的值越小,該servlet的優(yōu)先級越高,應用啟動時就越先加載;
5、當值相同時,容器會自己選擇順序來加載。
由于<load-on-startup>0</load-on-startup>,所以首先實例化并調用InitBeanFactoryServlet這個servlet的init()方法,同時配置了init-param。
在InitBeanFactoryServletString中,通過String configLocation = config.getInitParameter("configLocation");取出param-value即beans.properties賦值給configLocation ,通過執(zhí)行factory = new PropertiesBeanFactory(configLocation);創(chuàng)建PropertiesBeanFactory對象(PropertiesBeanFactory實現(xiàn)了BeanFactory接口,因為BeanFactory不一定都是通過Properties配置文件來創(chuàng)建bean產(chǎn)品的)。
Beans.properties(properties類型的配置文件)的內容為:
HashMap是線程不安全的對象,而Hashtable<>是線程安全的對象。
(public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {}
)
Properties extends Hashtable<Object,Object>繼承了Hashtable,它有更強大的功能,Properties 調用load方法(HashMap、Hashtable中沒有load方法),可以直接讀取文件,而且可以將文件中的鍵值對直接放到Map中來(Properties就是一個Map),props.load(Thread.currentThread().getContextClassLoader().getResourceAsStream(configurationFile));由于Properties(Map沒有iterator方法,iterator是collection:Set、List特有的)沒有iterator方法,所以
因為DAO是無狀態(tài)的(即~~~,得上網(wǎng)補齊),所以在最開始的時候就根據(jù)Beans.properties文件的配置率先把所有需要用到的DAO全創(chuàng)建好,同時beans.put(key, bean); //緩存DAO對象,將創(chuàng)建的DAO放到PropertiesBeanFactory中的Map中。
創(chuàng)建完PropertiesBeanFactory對象后,InitBeanFactoryServletString調用getServletContext().setAttribute(INIT_FACTORY_NAME, factory);將PropertiesBeanFactory放入ServletContext中(ServletContext是servlet中范圍最大的~~~不會寫了,從網(wǎng)上看看這句話怎么寫吧),意即所有的servlet全能訪問到這個PropertiesBeanFactory(以及它里面的各種DAO),即單例化了DAO,而不需要在每個servlet中都new一個DAO。
接下來分析DAO:
這個DAO是關于CRUD Article對象對應的數(shù)據(jù)庫中的article表的DAO,article表可能建立在MySQL、Oracle、DB2、SQL server中…,所以ArticleDao是個接口,而有ArticleDaoImpl(MySQL)和ArticleDaoImplForOracle…等等實現(xiàn)了ArticleDao接口的具體Dao。
接下來分析ArticleDaoImpl(都是最簡單的CRUD,后面會用MyBatis更好的實現(xiàn),當然我看Hibernate也不錯,學著看吧):
接下來分析ArticleServlet以及BaseServlet
原先有AddArticleServlet、DelArticleServlet、OpenUpdateArticleServlet、UpdateArticleServlet、SearchArticleServlet…一堆關于Article的servlet,現(xiàn)在將與Article有關的Servlet全部合成到ArticleServlet類中(將與Channel有關的Servlet全部合成到ChannelServlet類中)。
BaseServlet的代碼:
ArticleServlet繼承BaseServlet,任何servlet都有一個唯一的入口,即service()方法。在BaseServlet的service()方法中,實現(xiàn)了向ArticleServlet注入ArticleDaoImpl的功能(向ChannelServlet注入ChannelDaoImpl、向LoginServlet注入AdminDaoImpl)
。
首先先從ServletContext中取出BeanFactory,BeanFactory factory = (BeanFactory)getServletContext().getAttribute(InitBeanFactoryServlet.INIT_FACTORY_NAME);
Method[] methods = this.getClass().getMethods();這里的this雖然是在BaseServlet的service中寫的,但實際上是用戶訪問的是ArticleServlet,this表示的是ArticleServlet(ArticleServlet繼承了BaseServlet,而ArticleServlet并沒有重寫service()方法)。this.getClass().getMethods()即得到了ArticleServlet中的方法,
if(m.getName().startsWith("set"))的意思即判斷方法名是不是以set開頭,如果是,就可能是setArticleDao()方法之類的了(看ArticleServlet的代碼就懂了),String propertyName = m.getName().substring(3);將前面的set三個字符截掉,得到ArticleDao,接下來:
StringBuffer sb = new StringBuffer(propertyName);
sb.replace(0, 1, (propertyName.charAt(0)+"").toLowerCase());
propertyName = sb.toString();
Object bean = factory.getBean(propertyName);
將ArticleDao轉換為articleDao,賦值給propertyName,通過propertyName參數(shù)名,來從InitBeanFactoryServlet中獲取articleDao名對應的cn.com.leadfar.cms.backend.dao.impl.ArticleDaoImpl對象。
約定:setters方法所決定的屬性(property)名articleDao,與配置文件Beans.properties中相應的對象命名articleDao一致!
m.invoke(this, bean);最后這句是將依賴對象注入客戶端,m.invoke()中第一個參數(shù)是要調用的對象,this表示ArticleServlet,后邊的bean(bean即為cn.com.leadfar.cms.backend.dao.impl.ArticleDaoImpl)是要調用的方法的參數(shù),實際上相當于調用ArticleServlet對象的setArticleDao(cn.com.leadfar.cms.backend.dao.impl.ArticleDaoImpl)方法。
ArticleServlet的ArticleDao屬性運用了DI(Dependency Injection依賴注入)的方法,即ArticleServlet對象不自己設置自己的ArticleDao屬性,而是依賴~~~注入。
原先有AddArticleServlet、DelArticleServlet、OpenUpdateArticleServlet、UpdateArticleServlet、SearchArticleServlet…一堆關于Article的servlet,現(xiàn)在將與Article有關的Servlet全部合成到ArticleServlet類中(將與Channel有關的Servlet全部合成到ChannelServlet類中)。
super.service(arg0, arg1);的意思是執(zhí)行父類HttpServlet的職責:根據(jù)請求是GET還是POST方法,調用doGet或doPost!但在doGet或doPost()方法中只是簡單的導向執(zhí)行process(req,resp);方法,而ArticleServlet也并沒有重寫doGet()、doPost()、
process(req,resp)方法,
用戶訪問ArticleServlet是ArticleServlet?add、ArticleServlet?del、ArticleServlet?update…,
String method = request.getParameter("method");即取出add、del、update。
如果客戶端只是ArticleServlet而不傳遞method參數(shù),則默認調用execute()方法,即查詢操作。在BaseServlet中execute()方法什么也不做,但是ArticleServlet有重寫了這個方法,在這個方法中執(zhí)行查詢工作。
在這里并沒有用
if(method.equals("add")) {
//執(zhí)行添加動作,同時執(zhí)行完成之后,轉向成功頁面
}else if(method.equals("del")) {
//執(zhí)行刪除界面…
}else if(method.equals("update")) {
//執(zhí)行更新界面…
}
而是使用了反射機制,更便捷的實現(xiàn)了功能:
Method m = this.getClass().getMethod(method, HttpServletRequest.class,HttpServletResponse.class);
獲取ArticleServlet中的add、del、update…之類的方法。
m.invoke(this, request,response);
m.invoke()中第一個參數(shù)是要調用的對象,this表示ArticleServlet,后邊的request,response是要調用的方法的參數(shù),實際上相當于調用ArticleServlet對象的add(HttpServletRequest request, HttpServletResponse response)…之類的方法。
接下來該分析ArticleServlet的代碼了,其實也沒啥可分析的,就是從request中獲取客戶端傳過來的參數(shù),然后調用articleDao的相應CRUD方法:
關于del(HttpServletRequest request, HttpServletResponse response)方法有個小注意點:如果執(zhí)行完刪除操作后,轉向頁面語句寫成:request.getRequestDispatcher("/backend/ArticleServlet").forward(request, response);因為forward服務器端重定向時request中的數(shù)據(jù)不會丟失,雖然表面上寫的是轉向/backend/ArticleServlet頁面,實際上還是/backend/ArticleServlet?method=del&id=…頁面。所以我們這里需要寫成redirect重定向:response.sendRedirect(request.getContextPath()+"/backend/ArticleServlet");
在添加頁面add_article.jsp中,提交時并沒有寫成
<form action="ArticleServlet?method=add" method="post">
而是寫成:
<form action="ArticleServlet" method="post">
<input type="hidden" name="method" value="add">
其實兩者都對,但在post提交的方式中一般不在action后面再加method=add參數(shù),而是將這參數(shù)寫成隱含參數(shù)。當然需要用戶輸入的參數(shù)肯定也是寫成<input type="text" name="title" id="title" value="" size="60" maxlength="200" />這種格式。
在更新界面update_article.jsp中這么寫:
<form action="ArticleServlet" method="post">
<input type="hidden" name="id" value="${article.id }">
<input type="hidden" name="method" value="update">
將LoginServlet、LogoutServlet、CheckCodeServlet這些與登錄有關的Servlet,全部合成到LoginServlet中(LoginServlet也繼承了BaseServlet)!
- LoginServlet中的方法checkcode用于生成驗證碼
- LoginServlet中的方法execute用于登錄用戶名和密碼驗證
- LoginServlet中的方法quit用于退出后臺系統(tǒng)
當在servlet中重寫init(ServletConfig)方法的時候,記得調用super.init(ServletConfig),調用super.init(ServletConfig)的目的,主要是由于在父類(GenericServlet)中有一個ServletConfig實例變量,super.init(ServletConfig)就是給這個實例變量賦值。這樣,在后續(xù)的getServletContext()操作,才可以拿到ServletContext對象:
GenericServlet的部分源代碼如下所示:
----------------------------------------------------
public abstract class GenericServlet
implements Servlet, ServletConfig, java.io.Serializable
{
private transient ServletConfig config;
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
}
public ServletConfig getServletConfig() {
return config;
}
public ServletContext getServletContext() {
return getServletConfig().getServletContext();
}
-----------------------------------------------------
在login.jsp頁面中還有一個小知識點:
function reloadcheckcode(img){
img.src = "LoginServlet?method=checkcode&"+Math.random();
}
在重新載入驗證碼時,只需在后面加個&"+Math.random()隨機數(shù),系統(tǒng)即會自動調用LoginServlet的checkcode()方法(注意“&”符號)。
下面的是LoginServlet的代碼: