OperaMasks2.0開發實例入門之二:用戶註冊
本例雖然比較簡單,但是仍然採用三層架構的設計:
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接口來訪問。
首先,我們定義一個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);//校驗
}
接着,我們設計一個實現該接口的類: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的具體實現。
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(){
}
.....
}
業務邏輯層,我們定義了兩個接口以及他們各自的實現類:
1)UserPersonalService及其實現類UserPersonalServiceImpl。
2) UsersManagerService及其實現類UsersManagerServiceImpl。
這個接口規定了用於針對單個用戶的一些業務邏輯處理,例如:登陸,註銷,是否登陸,獲得當前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
}
接着,我們設計一個實現該接口的類: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;
}
}
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;
}
}
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中找到業務邏輯的具體實現並初始化。
這個接口規定了用於管理用戶的一些業務邏輯處理,例如:創建用戶,刪除用戶,查詢用戶等等。 其相應的代碼如下:
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(); //查詢所有用戶
}
代碼如下:
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);
}
}
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在後面默默的幫你搞定。
那麼,表示層主要分爲兩塊介紹,視圖和模型,而控制層就是AOM的FacesServlet,本文我們可以不用關心她。
1) 視圖方面,我們設計了這樣幾個頁面:login.xhtml,register.xhtml,userDatail.xhtml,success.xhtml。
2)模型方面,我們設計了對應的model類:LoginBean,RegisterBean,UserDetailBean。Success因爲只有一個鏈接,所以我們不打算生成其後臺對應的LiteBean。
利用Apusic Stuido來開發AOM應用最方便不過了,拖拖拉拉,這裏點點,那裏擊擊就幫你代碼生成了,並且後臺的LiteBean也幫你把代碼框架生成好了。因此,這幾個視圖的代碼我就不一一貼出了。僅僅講下客戶端校驗。 一些頁面需要一些校驗,比如輸入不能爲空。那麼我們在form的屬性中設置一個屬性,則也會進行客戶端校驗:從而減少和服務器段的交互,代碼如下:
<w:form clientValidate="true">
那麼我通過監控工具FireBug,看看實際效果:
可以看到,校驗發生時,沒有和服務器進行交互,如果是通過服務器的校驗的話,則會是下面這樣樣子(我們用輸錯密碼的方式檢驗一下,密碼是否正確是肯定要通過後臺才能知道的)。
現在,我們來看看我們的模型層的設計,除了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;
}
}
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;
}
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;
}
@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的業務邏輯來判斷用戶帳號是否已存在。
那麼到這裏,本例基本上介紹完了,可以運行這個例子了,可能有的朋友會問了,你的頁面沒有訪問控制,我直接地址欄敲回車,不是一樣的可以訪問嗎,還要登錄幹啥啊。這個問題問的不錯,那麼如果我們對這個例子想加入該功能該如何做呢,其實現方式有很多種,比如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;
}
}
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>
<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這樣的標註放在類上面,這個類就是一個階段監聽器了,你也不需要繼承什麼接口,只寫自己興趣的代碼就行了。
本例設計我們都已經介紹完畢,如果想結合具體代碼一起觀看的,這是應用包下載地址(含源代碼):
注:因爲筆者使用的是OperaMasks2.0 M1版本,而不是M2版本,如果是M2版本,則兩個zip將會是一樣的。