Web開發“菊花寶典”OperaMasks系列之二(連載)

 

OperaMasks2.0開發實例入門之二:用戶註冊

1. 前言

用戶註冊和登錄是比較常用的功能,本文演示了一個比較簡單的登錄和註冊系統原型。

2. 開發環境

Jdk1.5 + ApusicStudio5.0m4 + OperaMasks2.0m1 + Apusi5.1 tp4

3. 實例介紹

我們先看看示例的運行效果,先做個感性的認識。
1)登錄頁面
 
2)註冊頁面
 
3)註冊完成頁面
 
4)登錄完成頁面

4. 架構設計

本例雖然比較簡單,但是仍然採用三層架構的設計:
1)表示層:利用AOM2.0的IoVC技術,優雅的實現了MVC模式,解決了開發上view層和model層互相糾纏和view層包含業務邏輯等問題。同時,因爲利用AOM2.0的LiteBean技術,控制頁面的數據,導航,注入並調用業務邏輯層的相關業務,然後由AOM2.0的框架,調用相應的表示層視圖,進行顯示。
2)業務邏輯層:本例的邏輯層包括兩個方面的業務邏輯,
a)一個方面:是針對單個用戶的業務邏輯處理接口UserPersonalService,這個接口提供了諸如用戶登錄邏輯,註銷邏輯,是否已登錄等等。
b)另一方面:是針對用戶管理的業務邏輯處理接口UsersManagerService,這個接口提供了諸如增加用戶,刪除用戶,查詢用戶等等面向管理方面的業務處理邏輯。
3)數據訪問層:本例的數據雖然使用ArrayList,不過仍然設計爲採用DAO方式進行數據訪問,這也是一般情況大多數類似應用的數據層訪問方式,例如使用HibernianDAO訪問數據庫。本例並沒有用諸如MySQL等數據庫存儲數據,但並不影響本文的思路。這裏,我們使用ArrayList來存儲數據,通過UserDAO接口來訪問。

5. 數據訪問層 (org.operamasks.example.registration.model.dao)

5.1. UserDAO

首先,我們定義一個UserDAO接口,該接口定義了一些訪問用戶數據的一些方法。
其相應的代碼如下:
package org.operamasks.example.registration.dao;
import java.util.List;
import org.operamasks.example.registration.businessobject.User;
public interface UserDAO {  
public void addUser(User user);//增加
public void modifyUser(User user);//修改
public void removeUser(String id);//刪除
public User getUser(String id);//查找
public List<User> getAllUsers();//查找
public boolean validate(User user);//校驗
}

5.2. UserDAOListImpl

接着,我們設計一個實現該接口的類:UserDAOListImpl,這個UerDAO具體訪問一個ArrayList,該list存放的是所有已註冊用戶的信息,那麼在實際開發中,這裏應使用其他工業方式來進行,例如:public class YourUserDAOImple extends HibernateDaoSupport implements UserDAO等等。
那麼,限於篇幅,我這裏就不具體貼出這個供演示用的UserDAO的全部代碼了,下面是個示意,在本文的的附件中,有這個示意類的全部代碼。
package org.operamasks.example.registration.dao;
import ......
@ManagedBean(name="dao", scope = ManagedBeanScope.SESSION)
public class UserDAOListImpl implements UserDAO {
......
}
(1)值得注意的地方是是,這裏,我把這個類利用@ManagedBean聲明爲一個LiteBean,爲什麼要用這個標註呢,我這裏的目的,將會利用AOM2強大的依賴注入功能,注入接口的具體實現類,而不出現諸如new XXXImpl這樣的代碼,實現業務層代碼只與接口相關,而實現類則由AOM2負責自動幫你初始化和管理具體實現類,這裏我們思維跨越一下,提前偷窺一下,看看業務層調用這個DAO的方式:
package org.operamasks.example.registration.business;
import ......
@ManagedBean(name = "usersManagerService", scope = ManagedBeanScope.SESSION)
public class UsersManagerServiceImpl implements UsersManagerService {
  @ManagedProperty("#{dao}")
private UserDAO dao;
public void createUser(User user) {
  dao.addUser(user);
  }
     public ...... //其他業務方法
}
(1)觀察這個實現類的代碼,我們不會看到dao = new UserDAOImpl()這樣的語句,那麼後面調用dao的方法爲什麼不會有問題呢?原來這就是@ManagedProperty的功勞,通過該Annotation的作用,從AOM框架中取得一個命名爲dao的UserDAO類型的實例,而這個名字“dao”,就對應的是UserDAOImpl類裏的@ManagedBean中那個name=“dao”。
那麼,當你下次覺得這個訪問List的UserDAOImpl不爽,要換成自己的DAO類來訪問真實數據庫,那麼,直接把你寫好的實現類,替換掉這個類,就OK了,業務邏輯層不需要修改任何代碼,也不需要修改任何配置文件,那麼初始化業務邏輯層的時候,AOMLiteBean管理容器會自動尋找並實例化UserDAO的具體實現。
 

5.3. User

User類是一個普通的POJO類,沒啥可以闡述的:
package org.operamasks.example.registration.model.businessobject;
@SuppressWarnings("serial")
public class User implements java.io.Serializable {
private String id; //用戶帳號
private String name; //真實姓名
private String password; //密碼
private String email; //電子郵件
public User(){
}
.....
}

6. 業務邏輯層(org.operamasks.example.registration.business.*)

業務邏輯層,我們定義了兩個接口以及他們各自的實現類:
1)UserPersonalService及其實現類UserPersonalServiceImpl。
2) UsersManagerService及其實現類UsersManagerServiceImpl。

6.1. UserPersonalService

這個接口規定了用於針對單個用戶的一些業務邏輯處理,例如:登陸,註銷,是否登陸,獲得當前User對象等方法。 其相應的代碼如下:
package org.operamasks.example.registration.business;
import org.operamasks.example.registration.businessobject.User;
//針對個人的業務處理邏輯  
public interface UserPersonalService {
public boolean login(String userid, String password);// 登錄
public void logout();  // 註銷
public boolean isLogined(); // 是否已登錄
public User getUser();  // 獲得當前用戶所代表的商業對象User
}

6.2. UserPersonalServiceImpl

接着,我們設計一個實現該接口的類:UserPersonalServiceImpl,這個實習進行業務邏輯的處理,通過DAO把數據放入數據庫。 代碼:
 
package org.operamasks.example.registration.business;
import ......
@ManagedBean(name = "userPersonalService", scope = ManagedBeanScope.SESSION)
public class UserPersonalServiceImpl implements UserPersonalService {
    //數據訪問層UserDAO(接口),利用@ManagedPrperty從AOM注入UserDAO的具體實例。
    @ManagedProperty("#{dao}")
    private UserDAO dao;  
    @Accessible
    private User user; //當前用戶的商業對象User的實例,登錄後實例化,登出後置空。
    @Accessible
    private boolean logined; //當前用戶的User的實例,登錄後實例化,登出後置空。
  
    public boolean login(String userid, String password) {//登錄
  boolean flag = false;
  User user = dao.getUser(userid);
  if (user != null) {
      if (user.getPassword().equals(password)) {
    this.logined = true;
    this.user = user;
    flag = true;
      }
  }
  return flag;
    }
    public void logout() {//註銷
  this.logined = false;
  this.user = null;
    }
    public User getUser() {
  return this.user;
    }
    public boolean isLogined() {//是否登陸
  return this.logined;
    }
}

 
(1)同樣的,通過@ManagedBean(name="userPersonalService"…將該實現類交個AOM容器管理,這樣當表示層的model調用邏輯時,不用寫new XXX()這樣的代碼,而是直接申明一個UserPersonalService即可,然後直接調用。那麼具體的實例化交給AOM好了。我們看看表示層調用的方式:
 
package org.operamasks.example.registration.litebean;
import ......
@ManagedBean(scope = ManagedBeanScope.REQUEST)
public class LoginBean {
    //利用@Inject,注入UserPersonalService的具體實例。
  @Inject(value = "userPersonalService")
    private UserPersonalService service;

    @Bind    private String userid;
    @Bind    private String password;
    @Bind    private String message;

    //登錄,調用業務邏輯完成。
    @Action(id = "btnLogin")
    public String login() {
  String nextView;
  boolean success = this.service.login(userid, password);
  if (!success) {
      this.message = "用戶帳號或密碼錯誤!";
      nextView = null;
  } else {
      nextView = "view:userDetail";
  }
  return nextView;
    }
}

 
(1)可以看到:我們利用@Inject(value = "userPersonalService"),注入了UserPersonalServiceImpl,而申明時,只涉及接口:private UserPersonalService service; 下面的代碼並沒有出現UserPersonalServiceImpl的實例化過程,這些也是交給AOM自動完成的。 都是注入完成功能,那@Inject 和@ManagedProperty 有什麼區別呢,一般@Inject 是在需要調用這個類的時候才進行初始化,而@ManagedProperty是一旦調用者被初始化了,則被調用者跟着被實例化,兩者的時機稍有不同,讀者可以自行試驗,那麼一般的經驗是,在表示層的Model上使用@Inject來注入資源,在非表示層Model的類上使用@ManagedProperty注入資源,就像本例一樣。
那麼,當下次你決定換掉實現類,而不想改代碼的時候,Model層的調用邏輯是不用發生任何改變的,並且,你有權決定業務層使用Spring來組織和管理,那麼盡情的用Spring。AOM和Spring可以緊密的集成,表示層Model的邏輯在這種情況下,同樣不需要任何改變,AOM的LiteBean容器能夠從Spring中找到業務邏輯的具體實現並初始化。

6.3. UsersManagerService

這個接口規定了用於管理用戶的一些業務邏輯處理,例如:創建用戶,刪除用戶,查詢用戶等等。 其相應的代碼如下:
package org.operamasks.example.registration.business;
import ......
//針對用戶管理的業務處理邏輯
public interface UsersManagerService {
    public User findUser(String id);  //查詢用戶
    public void createUser(User user);  //創建用戶
    public void removeUser(String userid);    //刪除用戶
    public void modifyUser(User user);  //修改用戶
    public boolean validate(User user);    //操作之前,調用此方法進行信息校驗
    public List<User> findAllUsers();  //查詢所有用戶
}

6.4. UsersManagerServiceImple

代碼如下:
 
package org.operamasks.example.registration.business;
import ......
@ManagedBean(name = "usersManagerService", scope = ManagedBeanScope.SESSION)
public class UsersManagerServiceImpl implements UsersManagerService {
    @ManagedProperty("#{dao}")
    private UserDAO dao;
    public void createUser(User user) {
  dao.addUser(user);
    }
    public void modifyUser(User user) {
  dao.modifyUser(user);
    }
    public void removeUser(String userid) {
  dao.removeUser(userid);
    }
    public User findUser(String id) {
  return dao.getUser(id);
    }
    public List<User> findAllUsers() {
  return dao.getAllUsers();
    }
    public boolean validate(User user) {
  return dao.validate(user);
    }
}

 
注意的地方在前面基本上都介紹了,AOM會注入DAO的具體實現,然後可以直接使用了,對於這個業務邏輯類來說,DAO的具體實現是透明的,使用者無需關心,即使當DAO的實現類發生了驚天動地的變化,不管你具體實現類是哪個,其調用者,是完全不知道的,AOM在後面默默的幫你搞定。

7. 表現層

那麼,表示層主要分爲兩塊介紹,視圖和模型,而控制層就是AOM的FacesServlet,本文我們可以不用關心她。
1) 視圖方面,我們設計了這樣幾個頁面:login.xhtml,register.xhtml,userDatail.xhtml,success.xhtml。
2)模型方面,我們設計了對應的model類:LoginBean,RegisterBean,UserDetailBean。Success因爲只有一個鏈接,所以我們不打算生成其後臺對應的LiteBean。

7.1. 視圖

利用Apusic Stuido來開發AOM應用最方便不過了,拖拖拉拉,這裏點點,那裏擊擊就幫你代碼生成了,並且後臺的LiteBean也幫你把代碼框架生成好了。因此,這幾個視圖的代碼我就不一一貼出了。僅僅講下客戶端校驗。 一些頁面需要一些校驗,比如輸入不能爲空。那麼我們在form的屬性中設置一個屬性,則也會進行客戶端校驗:從而減少和服務器段的交互,代碼如下:
<w:form clientValidate="true">
那麼我通過監控工具FireBug,看看實際效果:
 
可以看到,校驗發生時,沒有和服務器進行交互,如果是通過服務器的校驗的話,則會是下面這樣樣子(我們用輸錯密碼的方式檢驗一下,密碼是否正確是肯定要通過後臺才能知道的)。

7.2. 模型:LiteBean

現在,我們來看看我們的模型層的設計,除了success.xhtml,其他的頁面都對應一個後臺的LiteBean。
1)LoginBean
首先看登陸頁面的後臺LiteBean :LoginBean
 
package org.operamasks.example.registration.litebean;
import  ......
@ManagedBean(scope = ManagedBeanScope.REQUEST)
public class LoginBean {
    //利用@Inject,注入UserPersonalService的具體實例。
    @Inject(value = "userPersonalService")
    private UserPersonalService service;
    @Bind    private String userid;

    @Bind    private String password;

    @Bind    private String message;

    //登錄,調用業務邏輯完成。
    @Action(id = "btnLogin")
    public String login() {
  String nextView;
  boolean success = this.service.login(userid, password);
  if (!success) {
      this.message = "用戶帳號或密碼錯誤!";
      nextView = null;
  } else {
      nextView = "view:userDetail";
  }
  return nextView;
    }
}

 
代碼相當簡單,主要是一個按鈕事件,該事件調用UserPersonalService的login業務邏輯,進行登陸。成功則用戶會看到userDetail視圖,失敗,則用界面上的一個outputlabel控件會顯示“用戶帳號活密碼錯誤”。
2)UserDetailBean
這個頁面是登陸成功後,進入用戶詳情的頁面,本例設計的比較簡單,就是顯示這個用戶的帳號,郵件,和真實姓名。這裏通過資源注入的方式,取得當前的用戶個人服務類UserPersonalService的實例,取得相關參數。然後用來個@Bind,頁面就能顯示被綁定的數據。
 
package org.operamasks.example.registration.litebean;
import ......
@ManagedBean(scope = ManagedBeanScope.REQUEST)  
public class UserDetailBean {
    @Bind
    @ManagedProperty("#{userPersonalService.user.id}")
    private String userid;

    @Bind
    @ManagedProperty("#{userPersonalService.user.email}")
    private String email;

    @Bind
    @ManagedProperty("#{userPersonalService.user.name}")
    private String name;

}

 
#{userPersonalService.user.id} 這句話是個表達式,如果用代碼方式來翻譯就是:userPersonalService.getUer().getId()。
 
3)RegisterBean
首先,這個註冊頁面的第一個輸入框是要求用戶輸入想要註冊的帳戶名,那麼經常上論壇的我們知道,大部分的註冊,會提供一個在線用戶檢測功能,以免用戶好不容易填到最後並提交,然後等待服務器響應,結果服務器響應後來個“用戶己註冊”這樣的信息,那將是十分不友好的。 本例,也提供了這樣的功能,效果如下,先看下帳號存在時註冊效果:
 
一個可以註冊的帳號,會有個小勾表示通過,可以註冊此帳號。
 
這個實現思路是:
a) 文本框輸入字符後,焦點移開時,會觸發一個onblur事件.
b) 後臺liteBean響應這個事件,並且使用immediate=true避開其他控件的校驗。
c) 在這個事件中,通過代碼調用文本框的校驗方法。
d) 給文本框,加個校驗器,該校驗器的作用是調用UserPsersonalService的業務邏輯,判斷用戶是否存在。
響應onblur事件的代碼片段,其核心是fieldUserId.processValidators(ctx)。
// 帳號輸入框失去焦點事件事件,該事件觸發帳號是否已註冊的校驗。
    @ActionListener(id = "userid", event = "onblur", immediate = true)
    public void userid_onBlur(ActionEvent event) {
  UITextField fieldUserId = (UITextField) event.getComponent();
......
  FacesContext ctx = FacesContext.getCurrentInstance();
  fieldUserId.processValidators(ctx);
  if (fieldUserId.isValid()) {
      img.setUrl(IMG_CHECK_RIGHT);
      this.imgCheckUser.setUrl(img.getUrl());
  }
    }
這是帳號輸入框上的校驗器代碼片段:
 
// 輕鬆注入UsersManagerService的實現類。
    @Inject(value = "usersManagerService")
    private UsersManagerService manager;

......

// 帳號是否存在校驗的校驗器
    @Validate(id = "userid", message = "帳號已存在。")
    public boolean validateUserId(Object value) {
  String uid = value.toString();
  User user = manager.findUser(uid);
  return user == null ? true : false;
    }

 
使用UsersManagerService的業務邏輯來判斷用戶帳號是否已存在。

8. 權限認證

那麼到這裏,本例基本上介紹完了,可以運行這個例子了,可能有的朋友會問了,你的頁面沒有訪問控制,我直接地址欄敲回車,不是一樣的可以訪問嗎,還要登錄幹啥啊。這個問題問的不錯,那麼如果我們對這個例子想加入該功能該如何做呢,其實現方式有很多種,比如filter,比如用其他的安全框架,本例是用的JSF生命週期的監聽器(PhaseListener)方式,其基本實現思路是:
1)我們先創建一個類AuthorityCheckPhaseListener,並實現PhaseListener接口。由於我們需要在執行其他操作之前先執行我們的訪問控制操作,我們設置:
UserAuthorityCheckListener.java
public PhaseId getPhaseId()
{        return PhaseId.RESTORE_VIEW;    
}
就可以了,告訴AOM,我們需要偵聽RESTORE_VIEW階段(AOM收到請求的第一個階段)。 我們實現一個空的beforePhase方法,什麼也不做,因爲這個方法是接口規定的,必須要寫上,即使是個空方法。
public void beforePhase(PhaseEvent phaseEvent) {}
2)然後在afterPhase方法裏做我們的訪問控制操作:
public final void afterPhase(PhaseEvent phaseEvent) {    //這裏做訪問控制操作}
那麼我們該做些什麼呢:
1) 取得用戶訪問的url
2) 根據配置文件判斷該url是否需要認證
3) 需要認證的情況,則判斷該用戶是否已經登錄(還記得我們的UserPersonalService有這個業務方法嗎),未登錄則遷移到用戶登錄頁面。 看下代碼的具體實現片段吧:
 
package org.operamasks.example.registration;
import ......
@SuppressWarnings("serial")
public class UserAuthorityCheckListener implements PhaseListener {
    ......
    public void afterPhase(PhaseEvent event) {
  FacesContext ctx = FacesContext.getCurrentInstance();
  String url = ctx.getViewRoot().getViewId();
  NavigationHandler nh = ctx.getApplication().getNavigationHandler();
  if (!authorityCheckOk(url)) {
      nh.handleNavigation(ctx, null, "LoginView");
  }
    }

    ......

    public PhaseId getPhaseId() {
  return PhaseId.RESTORE_VIEW;
    }

    private boolean authorityCheckOk(String url) {

  if (notNeedToCheck(url))
      return true;

  ExternalContext etx = FacesContext.getCurrentInstance()
    .getExternalContext();
  Map<String, Object> map = etx.getSessionMap();
  if (map != null) {
      UserPersonalService service = (UserPersonalService) map
        .get("userPersonalService");
      if (service != null) {
    return service.isLogined();
      }
  }
  return false;
    }

    private boolean notNeedToCheck(String url) {
  boolean flag = false;

  for (String page : NOT_NEED_TO_CHECK_PAGE_LIST) {
      if (url.indexOf(page) != -1) {
    flag = true;
    break;
      }
  }
  return flag;
    }
}

 
因爲我們使用的是 AOM2.0的m1版本,沒有Annotation來簡化階段監聽器的開發,那麼我們在使用監聽器的時候,需要在faces-config.xml中怎加一段:
 
<lifecycle>
  <phase-listener>
    org.operamasks.example.registration.UserAuthorityCheckListener
  </phase-listener>
    </lifecycle>

    <navigation-rule>
  <from-view-id>*</from-view-id>
  <navigation-case>
      <from-outcome>LoginView</from-outcome>
      <to-view-id>/login.jsf</to-view-id>
  </navigation-case>
    </navigation-rule>

 
不過,還好的是,OperaMasks2.0 M2版本已經對此進行了優化,增加了相關的Annotation支持,這樣,以後我們就不用在這個配置文件中增加這些內容了(這是這個例子中唯一和配置文件打交道的地方),直接寫一個@PhaseListener這樣的標註放在類上面,這個類就是一個階段監聽器了,你也不需要繼承什麼接口,只寫自己興趣的代碼就行了。

9. 資料

本例設計我們都已經介紹完畢,如果想結合具體代碼一起觀看的,這是應用包下載地址(含源代碼):
注:因爲筆者使用的是OperaMasks2.0 M1版本,而不是M2版本,如果是M2版本,則兩個zip將會是一樣的。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章