海鷗航際

          JAVA站
          posts - 11, comments - 53, trackbacks - 1, articles - 102

          導(dǎo)航

          <2007年4月>
          25262728293031
          1234567
          891011121314
          15161718192021
          22232425262728
          293012345

          常用鏈接

          留言簿(4)

          隨筆檔案

          文章分類(lèi)

          文章檔案

          相冊(cè)

          搜索

          •  

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          基于J2EE的Blog平臺(tái)

          Posted on 2005-01-12 13:36 海天一鷗 閱讀(572) 評(píng)論(1)  編輯  收藏 所屬分類(lèi): J2EE


          作者:xuefengl(dev2dev ID)
          摘要:

            本文講述如何在J2EE平臺(tái)上創(chuàng)建一個(gè)Blog系統(tǒng),以.Text的功能和界面為原型,Springframework為框架,實(shí)現(xiàn)一個(gè)運(yùn)行在WebLogic Server上的靈活的多層結(jié)構(gòu)的Blog平臺(tái)。

          目錄:

          1. 設(shè)計(jì)目標(biāo)
          2. 開(kāi)發(fā)環(huán)境
          2.1. 選擇平臺(tái)和框架
          2.2. 配置服務(wù)器
          2.3. 編寫(xiě)Ant腳本
          3. 系統(tǒng)設(shè)計(jì)
          3.1. 持久層設(shè)計(jì)
          3.1.1. 設(shè)計(jì)Domain對(duì)象
          3.1.2. 配置iBatis
          3.1.3. 使用DAO模式
          3.2. 邏輯層設(shè)計(jì)
          3.3. Web層設(shè)計(jì)
          3.3.1. 使用MVC模式
          3.3.2. 實(shí)現(xiàn)Skin
          4. 附加功能
          4.1.1. 實(shí)現(xiàn)圖片上傳
          4.1.2. 生成縮略圖
          4.1.3. 實(shí)現(xiàn)RSS
          4.1.4. 實(shí)現(xiàn)全文搜索
          4.1.5. 發(fā)送Email
          5. 測(cè)試
          6. 中文支持
          7. 總結(jié)
          8. 源代碼下載
          9. 相關(guān)資源下載
          10. 參考
          11. 關(guān)于作者

          設(shè)計(jì)目標(biāo) (目錄)

            Blog(WebLog)在Internet上越來(lái)越流行。許多網(wǎng)友都有了自己的Blog,通過(guò)Blog展示自己,結(jié)識(shí)更過(guò)的網(wǎng)友。比較著名的Blog平臺(tái)是基于ASP.net的開(kāi)源項(xiàng)目.Text。但是它的邏輯全部以存儲(chǔ)過(guò)程的形式放在數(shù)據(jù)庫(kù)中。雖然存儲(chǔ)過(guò)程能大大提高數(shù)據(jù)操作的效率,但是存儲(chǔ)過(guò)程本身是結(jié)構(gòu)化的程序,無(wú)法發(fā)揮面向?qū)ο蟮耐Γ膊槐阌趯?shí)現(xiàn)代碼復(fù)用。因此,我決定實(shí)現(xiàn)一個(gè)基于J2EE體系的多層結(jié)構(gòu)的Blog平臺(tái),功能和界面和.Text非常類(lèi)似,暫命名為Crystal Blog。實(shí)現(xiàn)的功能有:發(fā)表和編輯文章;多用戶(hù)支持;全文檢索;RSS支持;圖片管理;SMTP郵件發(fā)送等常見(jiàn)功能。界面如下:


          選擇平臺(tái)和框架 (目錄)
            由于使用J2EE平臺(tái),我們準(zhǔn)備采用WebLogic Server 8.1作為運(yùn)行平臺(tái),使用WebLogic Workshop8.1這個(gè)強(qiáng)大的集成化IDE作為開(kāi)發(fā)工具。
            數(shù)據(jù)庫(kù)選擇MS SQL Server 2000 SP3,建立一個(gè)名為blog的數(shù)據(jù)庫(kù)存儲(chǔ)所有的用戶(hù)數(shù)據(jù)。由于我們并沒(méi)有針對(duì)特定數(shù)據(jù)庫(kù)編碼,稍后我們會(huì)使用其他數(shù)據(jù)庫(kù)測(cè)試。在系統(tǒng)設(shè)計(jì)之前,選擇一個(gè)優(yōu)秀的框架能大大提高開(kāi)發(fā)效率。Spring是一個(gè)輕量級(jí)的J2EE框架。它覆蓋了從后臺(tái)數(shù)據(jù)庫(kù)的JDBC封裝到前臺(tái)Web框架的幾乎所有方?面。并且,Spring的各個(gè)模塊耦合非常松散,我們既可以用它作為整個(gè)應(yīng)用程序的框架,也可以?xún)H僅使用它的某一個(gè)模塊。此外,Spring非常強(qiáng)大的集成功能使我們可以輕易地集成Struts編寫(xiě)的Web端,或者使用Hibernate作為后端的O/R Mapping方案。
            Spring的核心思想便是IoC和AOP,Spring本身是一個(gè)輕量級(jí)容器,和EJB容器不同,Spring的組件就是普通的Java Bean,這使得單元測(cè)試可以不再依賴(lài)容器,編寫(xiě)更加容易。Spring負(fù)責(zé)管理所有的Java Bean組件,同樣支持聲明式的事務(wù)管理。我們只需要編寫(xiě)好Java Bean組件,然后將它們“裝配”起來(lái)就可以了,組件的初始化和管理均由Spring完成,只需在配置文件中聲明即可。這種方式最大的優(yōu)點(diǎn)是各組件的耦合極為松散,并且無(wú)需我們自己實(shí)現(xiàn)Singleton模式。
            由于后臺(tái)要使用關(guān)系數(shù)據(jù)庫(kù)存儲(chǔ)數(shù)據(jù),使用O/R Mapping必不可少。iBatis是又一個(gè)類(lèi)似于Hibernate的O/R Mapping方案,特點(diǎn)是小巧,配置簡(jiǎn)單,查詢(xún)靈活,完全符合我們的要求。
            除了Spring和iBatis,用到的第三方組件還有:用于全文搜索的Lucene引擎,用于文件上傳的common-file-upload 1.0,用于輸出RSS的RSSLibJ1.0 RC2。
            由于使用Spring這個(gè)輕量級(jí)框架,就無(wú)需EJB服務(wù)器,只需要Web服務(wù)器即可。因此,系統(tǒng)可以運(yùn)行在WebLogic Server,Tomcat和Resin等支持Servlet和JSP的Web服務(wù)器上。

          系統(tǒng)設(shè)計(jì) (目錄)
            很顯然,多層結(jié)構(gòu)的J2EE架構(gòu)能保證系統(tǒng)的靈活性和可擴(kuò)展性。我們?nèi)匀徊捎帽硎緦?邏輯層/持久層三層設(shè)計(jì)。

            整個(gè)系統(tǒng)以Spring為基礎(chǔ),持久層采用DAO模式和iBatis O/R Mapping,封裝所有數(shù)據(jù)庫(kù)操作;中間層是由Spring管理的普通的JavaBean,采用Fa?ade模式;表示層使用Spring提供的MVC框架。由于Spring對(duì)其他框架的良好集成,我們采用Velocity作為View。由于Velocity不能調(diào)用Java代碼,從而強(qiáng)制使用MVC模式而不是在View中嵌入邏輯代碼。

          配置服務(wù)器 (目錄)
            在WebLogic中新建一個(gè)Configuration,命名為blog,添加一個(gè)數(shù)據(jù)源,命名為jdbc/blog:

            整個(gè)應(yīng)用程序的目錄結(jié)構(gòu)如下:

          crystalblog/
          + doc/ (存放API文檔)
          + report/ (存放JUnit測(cè)試結(jié)果)
          + src/ (存放java源程序)
          + web/ (web目錄)
          | + manage/ (存放blog管理頁(yè))
          | + skin/ (存放blog界面頁(yè))
          | + upload/ (存放用戶(hù)上傳的圖片)
          | + WEB-INF/
          |   + classes/ (存放編譯的class文件)
          |   + lib/ (存放用到的所有jar文件)
          |   + search/ (存放Lucene的index)
          |   + c.tld (使用jstl必須的文件)
          |   + dispatcher-servlet.xml (Spring配置文件)
          |   + web.xml (標(biāo)準(zhǔn)web配置文件)
          + blog.war (打包的可部署應(yīng)用)
          + build.xml (ant腳本)

          編寫(xiě)Ant?腳本 (目錄)
            Ant是一個(gè)非常棒的執(zhí)行批處理任務(wù)的工具。使用Ant能使編譯、測(cè)試、打包、部署和生成文檔等一系列任務(wù)全自動(dòng)化,從而大大節(jié)省開(kāi)發(fā)時(shí)間。
            首先我們把用到的所有.jar文件放到/web/WEB-INF/lib中,然后編寫(xiě)compile任務(wù),生成的class文件直接放到web/WEB-INF/classes目錄下。如果編譯成功,就進(jìn)行單元測(cè)試,單元測(cè)試的結(jié)果以文本文件存放在report目錄中。如果測(cè)試通過(guò),下一步便是打包成blog.war文件。接著把應(yīng)用部署到服務(wù)器上,直接將web目錄的內(nèi)容復(fù)制到%BEA_HOME%/user_projects/domains/blogdomain/applications/blog/目錄下即可。如果要在Tomcat上部署,直接將整個(gè)web目錄復(fù)制到%TOMCAT%/webapps/blog/下。
            最后,如果需要,可以用javadoc生成api文檔。

          系統(tǒng)設(shè)計(jì) (目錄)
            Crystal Blog共分成三層結(jié)構(gòu):后臺(tái)數(shù)據(jù)持久層,采用DAO模式;中間邏輯層,采用Facade模式;前端Web層,采用MVC結(jié)構(gòu),使用JSP作為視圖。以下是Rational Rose的UML圖:

          設(shè)計(jì)Domain對(duì)象 (目錄)

            設(shè)計(jì)Domain對(duì)象
            Domain層是抽象出的實(shí)體。根據(jù)我們要實(shí)現(xiàn)的功能,設(shè)計(jì)以下實(shí)體,它們都是普通的Java Bean:
            Account:封裝一個(gè)用戶(hù),包括用戶(hù)ID,用戶(hù)名,口令,用戶(hù)設(shè)置等等。
            Category:封裝一個(gè)分類(lèi),一共有3種Category,分別用來(lái)管理Article,Image和Link,一個(gè)Account對(duì)應(yīng)多個(gè)Category。
            Article:封裝一篇文章,包括Title,Summary,Content等等,一個(gè)Category對(duì)應(yīng)多個(gè)Article。
            Feedback:封裝一個(gè)回復(fù),包括Title,Username,Url和Content,一個(gè)Article對(duì)應(yīng)多個(gè)Feedback。
            Image:封裝一個(gè)圖片,Image只包含圖片信息(ImageId,Type),具體的圖片是以用戶(hù)上傳到服務(wù)器的文件的形式存儲(chǔ)的。一個(gè)Category對(duì)應(yīng)多個(gè)Image。
            Link:封裝一個(gè)鏈接,和Category是多對(duì)一的關(guān)系。有Title,Url,Rss等屬性。
            Message:封裝一個(gè)消息,使其他用戶(hù)在不知道Email地址的情況下能夠通過(guò)系統(tǒng)發(fā)送郵件給某個(gè)用戶(hù)。

            最后,為了唯一標(biāo)識(shí)每條數(shù)據(jù)庫(kù)記錄,我們需要一個(gè)主鍵。在MS SQL Server和Oracle中可以使用自動(dòng)遞增的主鍵生成方式。但是很多數(shù)據(jù)庫(kù)不支持自動(dòng)遞增的主鍵,考慮到移植性,我們自己定義一個(gè)Sequence表,用于生成遞增的主鍵。Sequence表有且僅有7條記錄,分別記錄Account到Message對(duì)象的當(dāng)前最大主鍵值。系統(tǒng)啟動(dòng)時(shí),由SqlConfig負(fù)責(zé)初始化Sequence表。
            SequenceDao負(fù)責(zé)提供下一個(gè)主鍵,為了提高效率,一次緩存10個(gè)主鍵。

          配置iBatis (目錄)
            接下來(lái),使用iBatis實(shí)現(xiàn)O/R Mapping。首先從http://www.ibatis.com下載iBatis 2.0,將所需的jar文件復(fù)制到web/WEB-INF/lib/目錄下。iBatis使用XML配置數(shù)據(jù)庫(kù)表到Java對(duì)象的映射,先編寫(xiě)一個(gè)sql-map-config.xml:

          <?xml version="1.0" encoding="utf-8" ?>
          <!DOCTYPE sqlMapConfig
            PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"
            "http://www.ibatis.com/dtd/sql-map-config-2.dtd">
          <sqlMapConfig>
            <settings cacheModelsEnabled="false" enhancementEnabled="true"
              lazyLoadingEnabled="true" maxRequests="32"
              maxSessions="10" maxTransactions="5"
              useStatementNamespaces="false"
            />
            <transactionManager type="JDBC">
              <dataSource type="JNDI">
                <property name="DataSource" value="jdbc/blog" />
              </dataSource>
            </transactionManager>
            <!-- 如果有其他xml配置文件,可以包含進(jìn)來(lái) -->
            <sqlMap resource="Account.xml" />
          </sqlMapConfig>

            將sql-map-config.xml放到web/WEB-INF/classes/目錄下,iBatis就能搜索到這個(gè)配置文件,然后編寫(xiě)一個(gè)初始化類(lèi):

          public class SqlConfig {
              private SqlConfig() {}
              private static final SqlMapClient sqlMap;
              static {
                  try {
                      java.io.Reader reader = Resources.getResourceAsReader ("sql-map-config.xml");
                      sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader);
                  } catch (Exception e) {
                      e.printStackTrace();
                      throw new RuntimeException("Error initializing SqlConfig. Cause: " + e);
                  }
              }
              public static SqlMapClient getSqlMapInstance () {
                  return sqlMap;
              }
          }


            SqlMapClient封裝了訪問(wèn)數(shù)據(jù)庫(kù)的大部分操作,可以直接使用SqlConfig.getSqlMapInstance()獲得這個(gè)唯一實(shí)例。

          使用DAO模1 式 (目錄)
            為了分離邏輯層和數(shù)據(jù)庫(kù)持久層,定義一系列DAO接口:AccountDao,CategoryDao,ArticleDao……其實(shí)現(xiàn)類(lèi)對(duì)應(yīng)為SqlMapAccountDao,SqlMapCategoryDao,SqlMapArticleDao……這樣就使得邏輯層完全脫離了數(shù)據(jù)庫(kù)訪問(wèn)代碼。如果將來(lái)需要使用其它的O/R Mapping方案,直接實(shí)現(xiàn)新的DAO接口替代現(xiàn)有的SqlMapXxxDao即可。
            以SqlMapAccountDao為例,實(shí)現(xiàn)一個(gè)login()方法是非常簡(jiǎn)單的:

          public int login(String username, String password) throws AuthorizationException {
              try {
                  Map map = new HashMap();
                  map.put("username", username);
                  map.put("password", password);
                  Integer I = (Integer)sqlMap.queryForObject("login", map);
                  if(I==null)
                      throw new RuntimeException("Failed: Invalid username or password.");
                  return I.intValue();
              }
              catch(SQLException sqle) {
                  throw new RuntimeException("Sql Exception: " + sqle);
              }
          }

            在Account.xml配置文件中定義login查詢(xún):

          <select id="login" parameterClass="java.util.Map" resultClass="int">
            select [accountId] from [Account] where
            [username] = #username# and password = #password#
          </select>

          邏輯層設(shè)計(jì) (目錄)
            由于DAO模式已經(jīng)實(shí)現(xiàn)了所有的數(shù)據(jù)庫(kù)操作,業(yè)務(wù)邏輯主要是檢查輸入,調(diào)用DAO接口,因此業(yè)務(wù)邏輯就是一個(gè)簡(jiǎn)單的Facade接口:

          public class FacadeImpl implements Facade {
              private AccountDao  accountDao;
              private ArticleDao  articleDao;
              private CategoryDao categoryDao;
              private FeedbackDao feedbackDao;
              private ImageDao    imageDao;
              private LinkDao     linkDao;
              private SequenceDao sequenceDao;
          }

            對(duì)于普通的getArticle()等方法,F(xiàn)acade僅僅簡(jiǎn)單地調(diào)用對(duì)應(yīng)的DAO接口:

          public Article getArticle(int articleId) throws QueryException {
              return articleDao.getArticle(articleId);
          }

            對(duì)于需要身份驗(yàn)證的操作,如deleteArticle()方法,F(xiàn)acade需要首先驗(yàn)證用戶(hù)身份:

          public void deleteArticle(Identity id, int articleId) throws DeleteException {
              Article article = getArticleInfo(articleId);
              if(article.getAccountId()!=id.getAccountId())
                  throw new AuthorizationException("Permission denied.");
              articleDao.deleteArticle(articleId);
          }

            要分離用戶(hù)驗(yàn)證邏輯,可以使用Proxy模式,或者使用Spring的AOP,利用MethodInterceptor實(shí)現(xiàn),不過(guò),由于邏輯很簡(jiǎn)單,完全可以直接寫(xiě)在一塊,不必使用過(guò)于復(fù)雜的設(shè)計(jì)。
            至此,我們的Blog已經(jīng)實(shí)現(xiàn)了所有的后臺(tái)業(yè)務(wù)邏輯,并且提供統(tǒng)一的Facade接口。前臺(tái)Web層僅僅依賴(lài)這個(gè)Facade接口,這樣,Web層和后臺(tái)耦合非常松散,即使替換整個(gè)Web層也非常容易。

          Web層設(shè)計(jì) (目錄)
          使用MVC模式 (目錄)
            對(duì)于復(fù)雜的Web層,使用MVC模式是必不可少的。雖然Spring能輕易集成Struts,WebWorks等Web框架,但Spring本身就提供了一個(gè)非常好的Web框架,能完全實(shí)現(xiàn)MVC模式。
            Spring使用一個(gè)DispatcherServlet,所有的特定請(qǐng)求都被轉(zhuǎn)發(fā)到DispatcherServlet,然后由相應(yīng)的Controller處理,Controller返回一個(gè)ModelAndView對(duì)象(因?yàn)镴ava語(yǔ)言的方法調(diào)用只能返回一個(gè)結(jié)果,而且不支持ref參數(shù),所以將Model和View對(duì)象合在一起返回),Model是一個(gè)Java對(duì)象,通常是Map,View是視圖的邏輯名字,通常是JSP文件名,但也可以使用Velocity等作為視圖。返回的View通過(guò)viewResolver得到真正的文件名。
            首先配置Spring的MVC,在web.xml中聲明DispatcherServlet,處理所有以.c結(jié)尾的請(qǐng)求:

          <web-app>
              <servlet>
                  <servlet-name>dispatcher</servlet-name>
                  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
                  <load-on-startup>1</load-on-startup>
              </servlet>
              <servlet-mapping>
                  <servlet-name>dispatcher</servlet-name>
                  <url-pattern>*.c</url-pattern>
              </servlet-mapping>
          </web-app>


            Spring會(huì)在WEB-INF下查找一個(gè)名為dispatcher-servlet.xml的文件,我們需要?jiǎng)?chuàng)建這個(gè)文件:

          <?xml version="1.0" encoding="utf-8"?>
          <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
           "http://www.springframework.org/dtd/spring-beans.dtd">
          <beans>
          </beans>


           用到的所有的Java Bean組件都要在這個(gè)文件中聲明和配置,以下是配置URL映射的Bean:

          <bean id="urlMapping"
           class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
              <property name="mappings">
                  <props>
                      <prop key="/article.c">articleController</prop>
                  </props>
              </property>
          </bean>

            凡是匹配/article.c的Request都會(huì)被名為articleController的Bean處理,同樣需要聲明這個(gè)articleController:

          <bean id="articleController" class="example.ViewArticleController">
          </bean>


           ViewArticleController處理請(qǐng)求,然后生成Model,并選擇一個(gè)View:

          public class ViewArticleController implements Controller {
              private Facade facade;
              public void setFacade(Facade facade) { this.facade = facade; }
          public ModelAndView handleRequest(HttpServletRequest request,
              HttpServletResponse response) throws Exception {
                  // 獲得參數(shù):
                  int articleId = Integer.parseInt(request.getParameter("articleId"));
                  // 使用facade處理請(qǐng)求:
                  Article article = facade.getArticle(articleId);
                  // 生成Model:
                  Map map = new HashMap();
                  map.put("article", article);
                  // 返回Model和視圖名“skin/blueskysimple/article”:
                  return new ModelAndView("skin/blueskysimple/article", map);
              }
          }


            最后,skin/bluesky/article視圖會(huì)將結(jié)果顯示給用戶(hù)。
            我們注意到,ViewArticleController并不自己查找或者創(chuàng)建Facade,而是由容器通過(guò)setFacade(Facade)方法設(shè)置的,這就是所謂的IoC(Inversion of Control)或者Dependency Injection。容器通過(guò)配置文件完成所有組件的初始化工作:

          <!-- 聲明一個(gè)Facade -->
          <bean id="facade" class="example.Facade" />
          <!-- 聲明一個(gè)Controller -->
          <bean id="articleController" class="example.ViewArticleController">
              <!-- 為articleController設(shè)置facade屬性 -->
          <property name="facade">
                  <!-- 將名為facade的Bean的引用傳進(jìn)去 -->
                  <ref bean="facade" />
              </property>
          </bean>

            以上配置文件實(shí)現(xiàn)的功能大致為:

          Facade facade = new Facade();
          ViewArticleController articleController = new ViewArticleController();
          articleController.setFacade(facade);

            但是我們不必編寫(xiě)以上代碼,只需在xml文件中裝配好我們的組件就可以了。所有組件由Spring管理,并且,缺省的創(chuàng)建模式是Singleton,確保了一個(gè)組件只有一個(gè)實(shí)例。
            此外,所有自定義異常都是RuntimeException,Spring提供的AOP使我們能非常方便地實(shí)現(xiàn)異常處理。在Web層定義ExceptionHandler,處理所有異常并以統(tǒng)一的error頁(yè)面把出錯(cuò)信息顯示給用戶(hù),因此,在代碼中只需拋出異常,完全不必在Controller中處理異常:

          <bean id="handlerExceptionResolver" class="org.crystalblog.web.ExceptionHandler" />

          使用Velocity作為View
            采用Velocity作為View的最大的優(yōu)點(diǎn)是簡(jiǎn)潔,能通過(guò)簡(jiǎn)單明了的語(yǔ)法直接在Html中輸出Java變量。Velocity本身是一個(gè)模板引擎,通過(guò)它來(lái)渲染Model輸出Html非常簡(jiǎn)單,比如顯示用戶(hù)名:

          <b>${account.username}</b>
            換成JSP不得不寫(xiě)成:
          <b><%=account.getUsername()%></b>
          <%...%>標(biāo)記在Dreamwaver中無(wú)法直接看到其含義。如果需要在標(biāo)簽中嵌入JSP就更晦澀了:
          <a href="somelink?id=<%=id %>">Link</a>

            這種嵌套的標(biāo)簽往往使得可視化Html編輯器難以正常顯示,而Velocity用直接嵌入的語(yǔ)法解決了“ <%...%>”的問(wèn)題。
            在Spring中集成Velocity是非常簡(jiǎn)單的事情,甚至比單獨(dú)使用Velocity更簡(jiǎn)單,只需在dispatcher-servlet.xml中申明:

          <bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
          </bean>
          <bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
              <property name="resourceLoaderPath"><value>/</value></property>
              <property name="configLocation"><value>/WEB-INF/velocity.properties</value></property>
          </bean>

            雖然標(biāo)準(zhǔn)的Velocity模板以.vm命名,但我們的所有Velocity模板頁(yè)均以.html作為擴(kuò)展名,不但用Dreamwaver編輯極為方便,甚至可以直接用IE觀看頁(yè)面效果。

          實(shí)現(xiàn)Skin (目錄)
            許多Blog系統(tǒng)都允許用戶(hù)選擇自己喜歡的界面風(fēng)格。要實(shí)現(xiàn)Skin功能非常簡(jiǎn)單,為每個(gè)Skin編寫(xiě)Velocity模板,存放在web/skin/目錄下,然后在Controller中返回對(duì)應(yīng)的視圖:

          String viewpath = skin.getSkin(account.getSkinId()) + "viewArticle";

            由于使用了MVC模式,我們已經(jīng)為每個(gè)頁(yè)面定義好了Model,添加一個(gè)新的skin非常容易,只需要編寫(xiě)幾個(gè)必須的.html文件即可,可以參考現(xiàn)有的skin。
            SkinManager的作用是在啟動(dòng)時(shí)自動(dòng)搜索/skin/目錄下的所有子目錄并管理這些skin,將用戶(hù)設(shè)定的skinId映射到對(duì)應(yīng)的目錄。目前只有一個(gè)skin,您可以直接用可視化Html編輯器如Dreamwaver改造這個(gè)skin。

          附加功能 (目錄)
          實(shí)現(xiàn)圖片上傳 (目錄)
            用戶(hù)必須能夠上傳圖片,因此需要文件上傳的功能。比較常見(jiàn)的文件上傳組件有Commons FileUpload(http://jakarta.apache.org/commons/fileupload/a>)和COS FileUploadhttp://www.servlets.com/cos),Spring已經(jīng)完全集成了這兩種組件,這里我們選擇Commons FileUpload。
            由于Post一個(gè)包含文件上傳的Form會(huì)以multipart/form-data請(qǐng)求發(fā)送給服務(wù)器,必須明確告訴DispatcherServlet如何處理MultipartRequest。首先在dispatcher-servlet.xml中聲明一個(gè)MultipartResolver:

          <bean id="multipartResolver"
                 class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
                  <!-- 設(shè)置上傳文件的最大尺寸為1MB -->
                  <property name="maxUploadSize">
                  <value>1048576</value>
              </property>
          </bean>

            這樣一旦某個(gè)Request是一個(gè)MultipartRequest,它就會(huì)首先被MultipartResolver處理,然后再轉(zhuǎn)發(fā)相應(yīng)的Controller。
            在UploadImageController中,將HttpServletRequest轉(zhuǎn)型為MultipartHttpServletRequest,就能非常方便地得到文件名和文件內(nèi)容:

          public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
              // 轉(zhuǎn)型為MultipartHttpRequest:
              MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
              // 獲得文件:
              MultipartFile file = multipartRequest.getFile("file");
              // 獲得文件名:
              String filename = file.getOriginalFilename();
              // 獲得輸入流:
              InputStream input = file.getInputStream();
              // 寫(xiě)入文件...
          }

          生成縮略圖 (目錄)
            當(dāng)用戶(hù)上傳了圖片后,必須生成縮略圖以便用戶(hù)能快速瀏覽。我們不需借助第三方軟件,JDK標(biāo)準(zhǔn)庫(kù)就包含了圖像處理的API。我們把一張圖片按比例縮放到120X120大小,以下是關(guān)鍵代碼:

          public static void createPreviewImage(String srcFile, String destFile) {
              try {
                  File fi = new File(srcFile);
          // src
                  File fo = new File(destFile);
          // dest
                  BufferedImage bis = ImageIO.read(fi);

                  int w = bis.getWidth();
                  int h = bis.getHeight();
                  double scale = (double)w/h;
                  int nw = IMAGE_SIZE;
          // final int IMAGE_SIZE = 120;
                  int nh = (nw * h) / w;
                  if( nh>IMAGE_SIZE ) {
                      nh = IMAGE_SIZE;
                      nw = (nh * w) / h;
                  }
                  double sx = (double)nw / w;
                  double sy = (double)nh / h;

                  transform.setToScale(sx,sy);
                  AffineTransformOp ato = new AffineTransformOp(transform, null);
                  BufferedImage bid = new BufferedImage(nw, nh, BufferedImage.TYPE_3BYTE_BGR);
                  ato.filter(bis,bid);
                  ImageIO.write(bid, "jpeg", fo);
              } catch(Exception e) {
                  e.printStackTrace();
                  throw new RuntimeException("Failed in create preview image. Error: " + e.getMessage());
              }
          }

          實(shí)現(xiàn)RSS (目錄)
            RSS是一個(gè)標(biāo)準(zhǔn)的XML文件,Rss閱讀器可以讀取這個(gè)XML文件獲得文章的信息,使用戶(hù)可以通過(guò)Rss閱讀器而非瀏覽器閱讀Blog,我們只要?jiǎng)討B(tài)生成這個(gè)XML文件便可以了。RSSLibJ是一個(gè)專(zhuān)門(mén)讀取和生成RSS的小巧實(shí)用的Java庫(kù),大小僅25k,可以從http://sourceforge.net/projects/rsslibj/下載rsslibj-1_0RC2.jar和它需要的EXMLjar兩個(gè)文件,然后復(fù)制到web/WEB-INF/lib/下。
            使用RSSLibJ異常簡(jiǎn)單,我們先設(shè)置好HttpServletResponse的Header,然后通過(guò)RSSLibJ輸出XML即可:

          Channel channel = new Channel();
          channel.setDescription(account.getDescription());
          baseUrl = baseUrl.substring(0, n);
          channel.setLink("http://server-name/home.c?accountId=" + accountId);
          channel.setTitle(account.getTitle());
          List articles = facade.getArticles(accountId, account.getMaxPerPage(), 1);
          Iterator it = articles.iterator();
          while(it.hasNext()) {
              Article article = (Article)it.next();
              channel.addItem("http://server-name/article.c?articleId=" + article.getArticleId(),
                  article.getSummary(), article.getTitle()
              );
          }
          // 輸出xml:
          response.setContentType("text/xml");
          PrintWriter pw = response.getWriter();
          pw.print(channel.getFeed("rss"));
          pw.close();

          實(shí)現(xiàn)全文搜索 (目錄)
            全文搜索能大大方便用戶(hù)快速找到他們希望的文章,為blog增加一個(gè)全文搜索功能是非常必要的。然而,全文搜索不等于SQL的LIKE語(yǔ)句,因?yàn)殛P(guān)系數(shù)據(jù)庫(kù)的設(shè)計(jì)并不是為全文搜索設(shè)計(jì)的,數(shù)據(jù)庫(kù)索引對(duì)全文搜索無(wú)效,在一個(gè)幾百萬(wàn)條記錄中檢索LIKE '%A%'可能會(huì)耗時(shí)幾分鐘,這是不可接受的。幸運(yùn)的是,我們能使用免費(fèi)并且開(kāi)源的純Java實(shí)現(xiàn)的Lucene全文搜索引擎,Lucene可以非常容易地集成到我們的blog中。
            Lucene不提供直接對(duì)文件,數(shù)據(jù)庫(kù)的索引,只提供一個(gè)高性能的引擎,但接口卻出人意料地簡(jiǎn)單。我們只需要關(guān)心以下幾個(gè)簡(jiǎn)單的接口:
            Document:代表Lucene數(shù)據(jù)庫(kù)的一條記錄,也代表搜索的一條結(jié)果。
            Field:一個(gè)Document包含一個(gè)或多個(gè)Field,類(lèi)似關(guān)系數(shù)據(jù)庫(kù)的字段。
            IndexWriter:用于創(chuàng)建新的索引,也就是向數(shù)據(jù)庫(kù)添加新的可搜索的大段字符串。
            Analyzer:將字符串拆分成單詞(Token),不同的文本對(duì)應(yīng)不同的Analyzer,如HtmlAnalyzer,PDFAnalyzer。
            Query:封裝一個(gè)查詢(xún),用于解析用戶(hù)輸入。例如,將“bea blog”解析為“同時(shí)包含bea和blog的文章”。
            Searcher:搜索一個(gè)Query,結(jié)果將以Hits返回。
            Hits:封裝一個(gè)搜索結(jié)果,包含Document集合,能非常容易地輸出結(jié)果。
            下一步,我們需要為Article表的content字段建立全文索引。首先為L(zhǎng)ucene新建一個(gè)數(shù)據(jù)庫(kù),請(qǐng)注意這個(gè)數(shù)據(jù)庫(kù)是Lucene專(zhuān)用的,我們不能也不必知道它的內(nèi)部結(jié)構(gòu)。Lucene的每個(gè)數(shù)據(jù)庫(kù)對(duì)應(yīng)一個(gè)目錄,只需要指定目錄即可:

          String indexDir = "C:/search/blog";
          IndexWriter indexWriter = new IndexWriter(indexDir, new StandardAnalyzer(), true);
          indexWriter.close();


            然后添加文章,讓Lucene對(duì)其索引:

          String title = "文章標(biāo)題" // 從數(shù)據(jù)庫(kù)讀取
          String content = "文章內(nèi)容" // 從數(shù)據(jù)庫(kù)讀取
          // 打開(kāi)索引:
          IndexWriter indexWriter = new IndexWriter(indexDir, new StandardAnalyzer(), false);
          // 添加一個(gè)新記錄:
          Document doc = new Document();
          doc.add(Field.Keyword("title", title));
          doc.add(Field.Text("content", content));
          // 建立索引:
          indexWriter.addDocument(doc);
          // 關(guān)閉:
          indexWriter.close();

           要搜索文章非常簡(jiǎn)單:
           然后添加文章,讓對(duì)其索引:

          String title = "文章標(biāo)題" // 從數(shù)據(jù)庫(kù)讀取
          String content = "文章內(nèi)容" // 從數(shù)據(jù)庫(kù)讀取
          // 打開(kāi)索引:
          IndexWriter indexWriter = new IndexWriter(indexDir, new StandardAnalyzer(), false);
          // 添加一個(gè)新記錄:
          Document doc = new Document();
          doc.add(Field.Keyword("title", title));
          doc.add(Field.Text("content", content));
          // 建立索引:
          indexWriter.addDocument(doc);
          // 關(guān)閉:
          indexWriter.close();

            要搜索文章非常簡(jiǎn)單:

          Searcher searcher = new IndexSearcher(dir);
          Query query = QueryParser.parse(keyword, "content", new StandardAnalyzer());
          Hits hits = searcher.search(query);
          if(hits != null){
              for(int i = 0;i < hits.length(); i++){
                  Document doc = hits.doc(i);
                  System.out.println("found in " + doc.get("title"));
                  System.out.println(doc.get("content"));
              }
          }
          searcher.close();

            我們?cè)O(shè)計(jì)一個(gè)LuceneSearcher類(lèi)封裝全文搜索功能,由于必須鎖定數(shù)據(jù)庫(kù)所在目錄,我們把數(shù)據(jù)庫(kù)設(shè)定在/WEB-INF/search/下,確保用戶(hù)不能訪問(wèn),并且在配置文件中初始化目錄:

          <bean id="luceneSearcher" class="org.crystalblog.search.LuceneSearcher">
              <property name="directory">
                 <value>/WEB-INF/search/</value>
              </property>
          </bean>

          效果如下:


          (圖4:search)

          發(fā)送Email (目錄)
            Blog用戶(hù)可以讓系統(tǒng)將來(lái)訪用戶(hù)的留言發(fā)送到注冊(cè)的Email地址,為了避免使用SMTP發(fā)信服務(wù)器,我們自己手動(dòng)編寫(xiě)一個(gè)SendMail組件,直接通過(guò)SMTP協(xié)議將Email發(fā)送到用戶(hù)信箱。
            SendMail組件只需配置好DNS服務(wù)器的IP地址,即可向指定的Email信箱發(fā)送郵件。并且,SendMail使用緩沖隊(duì)列和多線程在后臺(tái)發(fā)送Email,不會(huì)中斷正常的Web服務(wù)。具體代碼請(qǐng)看SendMail.java。

          測(cè)試 (目錄)
            服務(wù)器配置為:P4 1.4G,512M DDR,100M Ethernet,Windows XP Professional SP2。
            測(cè)試服務(wù)器分別為WebLogic Server 8.1,Tomcat 4.1/5.0,Resin 2.1.1。
            測(cè)試數(shù)據(jù)庫(kù)為MS SQL Server 2000 SP3。如果你使用Oracle或者DB2,MySQL等其他數(shù)據(jù)庫(kù)并測(cè)試成功,請(qǐng)將SQL初始化腳本和詳細(xì)配置過(guò)程發(fā)一份給我,謝謝。
            由于時(shí)間有限,沒(méi)有作進(jìn)一步的調(diào)優(yōu)。WebLogic Server和iBatis有很多優(yōu)化選項(xiàng),詳細(xì)配置可以參考相關(guān)文檔。

          中文支持 (目錄)
            測(cè)試發(fā)現(xiàn),中文不能在頁(yè)面中正常顯示,為了支持中文,首先在web.xml加入Filter,用于將輸入編碼設(shè)置為gb2312:

          <filter>
              <filter-name>encodingFilter</filter-name>
              <filter-class>org.crystalblog.web.filter.EncodingFilter</filter-class>
              <init-param>
                  <param-name>encoding</param-name>
                  <param-value>gb2312</param-value>
              </init-param>
          </filter>
          <filter-mapping>
              <filter-name>encodingFilter</filter-name>
              <url-pattern>/*</url-pattern>
          </filter-mapping>


            然后用文本工具搜索所有的.htm,.html,.properties文件,將“iso-8859-1”替換為“gb2312”,現(xiàn)在頁(yè)面中文已經(jīng)能正常顯示,但是Lucene仍不能正常解析中文,原因是標(biāo)準(zhǔn)的StandardA?nalyzer只能解析英文,可以從網(wǎng)上下載一個(gè)支持中文的Analyzer。

          總結(jié) (目錄)
            Spring的確是一個(gè)優(yōu)秀的J2EE框架,通過(guò)Spring強(qiáng)大的集成和配置能力,我們能輕松設(shè)計(jì)出靈活的多層J2EE應(yīng)用而無(wú)需復(fù)雜的EJB組件支持。由于時(shí)間倉(cāng)促,水平有限,文中難免有不少錯(cuò)誤,懇請(qǐng)讀者指正。

          源代碼下載 (目錄)
            源代碼可以從http://www.javasprite.com/crystal/download.asp下載。

          相關(guān)資源下載 (目錄)
          JDK 1.4.2可以從http://java.sun.com下載。
          Spring framework 1.1可以從http://www.springframework.org下載。
          iBatis 2.0可以從http://www.ibatis.com下載。
          Tomcat 4.1/5.0、Ant 1.6可以從http://www.apache.org下載。
          Resin 2.1.1可以從http://www.caucho.com下載。
          WebLogic Server / Workshop 8.1可以從http://commerce.bea.com下載。
          JUnit 3.8可以從http://www.junit.org下載。
          MySQL 4及其JDBC驅(qū)動(dòng)可以從http://www.mysql.com下載。
          MS SQL Server 2000 JDBC驅(qū)動(dòng)可以從http://www.microsoft.com/downloads/details.aspx?FamilyID=07287b11-0502-461a-b138-2aa54bfdc03a&DisplayLang=en下載。
          RSSLibJ 1.0可以從http://sourceforge.net/projects/rsslibj/下載。

          參考 (目錄)
          “Spring Reference”,Rod Johnson等。
          “iBatis SQL Maps Guide”。
          “Apache Ant 1.6.2 Manual”。
          “Lucene Getting Started”。
          Springframework的JPetStore示例是非常棒的設(shè)計(jì),本文參考了JPetStore的許多設(shè)計(jì)模式。

          關(guān)于作者
          廖雪峰,北京郵電大學(xué)本科畢業(yè),對(duì)J2EE/J2ME有濃厚興趣,歡迎交流:asklxf@163.com
          個(gè)人網(wǎng)站:http://www.javasprite.com,歡迎訪問(wèn)。
          個(gè)人Blog站點(diǎn):http://blog.csdn.net/asklxf/,歡迎訪問(wèn)。

          Feedback

          # re: 基于J2EE的Blog平臺(tái)   回復(fù)  更多評(píng)論   

          2007-04-10 20:43 by wj
          你好~~我現(xiàn)在做的畢業(yè)設(shè)計(jì)與這個(gè)類(lèi)似,請(qǐng)問(wèn)能否將你的源代碼給我參考下?謝謝?。?
          我的email:supercoca@gmail.com
          主站蜘蛛池模板: 南溪县| 环江| 宜川县| 兰西县| 英山县| 万荣县| 当雄县| 潞城市| 荆门市| 辽宁省| 陇南市| 柳林县| 新巴尔虎右旗| 潞城市| 金华市| 菏泽市| 丰城市| 浦江县| 东港市| 宣城市| 涿鹿县| 海原县| 晴隆县| 古蔺县| 邹平县| 丰原市| 湄潭县| 五莲县| 舟曲县| 泌阳县| 肃北| 遂川县| 中西区| 顺昌县| 静宁县| 北宁市| 抚州市| 剑川县| 黔东| 正蓝旗| 瑞昌市|