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将会是一样的。