Home class name: CategoryLocalHome
为了实现自增主键的功能,需要指定一个Ejb-Gen属性:点击右键弹出菜单,选择Insert EJB Gentag→automatic-key-generation,指定字段名id,数据库类型SQLServer2000即可。
为了提高CMP的性能,我们全部采用本地接口,如果某些CMP字段需要提供只读接口(如id,username字段),只需右键点击字段名,然后选择“Read Only”。
按相同方法建立Artist.ejb,Album.ejb,Song.ejb,Account.ejb,然后创建CMR关系:
打开Category.ejb的设计视图,点击右键,在弹出菜单里选择Add Relation,建立一对多的CMR关系:

类似的,创建Artist-Album和Album-Song的一对多关系。
注意我们还设计了一个AccountUtil类,使用正则表达验证用户信息的有效性,比如用户名,口令是否符合要求。
设计逻辑层
()
首先我们设计一个JndiHelper类,用于封装查找实体Bean的JNDI操作,同时还对实体Bean的Home接口缓存。
我们把所有的逻辑封装到3个Session Bean中:
MusicView.ejb:用于封装浏览,下载等页面操作。
AccountManage.ejb:用于封装注册,登陆等账号操作。
MusicManage.ejb:用于封装管理员的各项操作。
首先建立MusicView.ejb,我们一共设计了以下业务方法:

所有的方法均为远程方法,这样Web层和EJB就可分开部署。当然你也可以将会话Bean接口全部改成本地接口以进一步提高性能。方法看上去虽多,但逻辑相当简单,例如,getAlbums(int artistId)方法:

基本逻辑大致为:
查找相应的实体Bean,获得实体Bean对应CMR关系的集合,在将其包装成值对象返回给客户端。你可以看到,由于没有JDBC操作,代码被大大的简化了,并且由于容器处理了底层数据库连接,使我们的代码更加健壮。
在这里我们采用了值对象(Value Object)模式,用于在Web层和逻辑层之间传送对象。你可以很容易的写出每个实体Bean对应的值对象,比如Album.ejb对应的值对象AlbumVO。
然后创建AccountManage.ejb,用于处理用户帐号相关的操作:

如果要提高系统的安全性,建议使用MD5码存储用户口令,MD5算法是一个单向函数,即使获得数据库表也无法得知用户口令。如果要进一步防止使用预先算好的MD5码攻击,还可以采用加盐处理。为了简单起见,我们直接将用户口令存储在数据库表的字段中,因此login(String username, String password)看起来像这样:

如果找到了相应的实体Bean(即数据库表中存在此记录),再判断password字段,如果相符,验证通过,否则,抛出一个自定义的UnauthorizedException异常。这里也不涉及JDBC操作,因此代码非常简单。
搜索功能是通过实体Bean的Finder方法实现的,标准的EJB QL 2.0并不支持带参数的like关键字(在SUN J2EE SDK中编译就会失败),幸好WebLogic对其进行了扩充。为了实现模糊搜索,这里我们定义了Song.ejb的一个Finder方法findByTitle(String title),对应的EJB QL为“SELECT OBJECT(o) FROM Song AS o WHERE lower(o.title) LIKE concat(%, concat(?1, %))”。如果使用其他厂商的服务器,你需要查看厂商的EJB QL文档然后作相应的修改。
最后一个是MusicManage.ejb,用于管理员创建、删除信息:

创建和删除实体Bean的代码都非常简单,唯一需要的就是捕获相应的异常,我们在deleteSong(int songId)中将RemoveException包装成更一般的ApplicationException异常返回客户端:

需要特别注意的是,对存在一对多CMR关系的实体Bean,如果指定了级联删除,当删除“一”时,对应的“多”会被自动删除,因此执行删除前要异常小心,至少应该提示用户,或者,只允许删除“多”为空的实体Bean,比如删除Artist.ejb时:
home = JndiHelper.getArtistLocalHome();
artist = home.findByPrimaryKey(new Integer(artistId));
Collection c = artist.getAlbums();
if(c.size()>0) throw new Exception("Cannot delete unless it is empty.");
为了安全起见,我们在删除时不允许删除“多”一方不为空的实体。
编译,部署。OK,小功告成!现在我们可以测试一下我们的逻辑层。一个好的办法是使用JUnit测试,但是似乎Workshop尚未集成JUnit。没关系,我们自己写一个客户端Java程序来测试逻辑层。
在MusicClientProject中建立Client.Java类,放在包music.client中。为了测试,我们首先在数据库中创建一些测试数据:
void initData () throws Exception { … }
然后,我们写一个testGetCategories()方法:
void testGetCategoris(int parentCategoryId) throws Exception { … }
类似的,我们对每个业务逻辑都写一个相应的测试方法,直到每一个业务逻辑都正确无误。在一个独立的Java客户端程序中测试EJB要比在JSP中方便得多,你可以方便地设置断点,跟踪以便查看变量,随时使用System.out.println()在控制台输出任何调试信息。
设计表示层
()
表示层用于向用户提供系统交互的接口。采用J2EE的系统表示层一般都是瘦客户端,使用JSP/Servlets技术,使用户能通过浏览器使用系统。在JetMusic站点中,我们将考虑以下几种实现方式:
1.在JSP中使用业务代表模式:
通常,在JSP/Servlets中直接调用EJB并不是一个好主意,这需要大量的查找JNDI的代码,业务代表(Business Delegate)是一个不错的模式,它封装了所有的EJB查找和调用,使得表示层和逻辑层的耦合度能降到最低,并且还可以进行一些缓存。
由于业务代表类将Web和EJB隔开了,因此,对于JSP/Servlets来说,它们根本就不知道EJB的存在。这将带来另一个好处:只要在设计时仔细定义了业务代表的接口,Web层和EJB的开发人员立即可以同时开发各自的模块,Web层的开发人员可以首先对业务代表类进行硬编码,以便返回他们希望的结果。等到EJB开发完成后,再用实际的EJB调用替换即可。
为了封装对MusicView.ejb的所有调用,我们建立一个MusicViewBD的业务代表类:

在index.jsp中即可使用MusicViewBD以便输出Categories:

2.使用NetUI标签实现数据绑定
WebLogic提供了大量的NetUI标签,能够方便地在JSP中绑定数据。我们下一步将创建一个viewCa.jsp页面,此页面向用户展示分类。
我们首先创建一个EJB Control。WebLogic向我们提供的EJB Control完全封装了EJB调用。利用EJB Control,我们不用写一行代码,立刻就可以在JSP中调用EJB。这个EJB Control可以看作是一个用标签封装的业务代表模式的应用。
在MusicWeb工程下新建文件夹music.control,用于存放EJB Control。新建EJB Control,命名为CallMusicView:

选择“Browse application EJBs…”,直接找到“MusicView (remote jndi)”,Workshop会自动填好相应的接口,点击“create”即创建成功。
当我们获得了从EJB返回的Collection后,可以使用Repeater标签显示这个复杂的数据,在这里我们不打算进一步讨论Repeater标签的细节,关于如何使用Repeater标签,可以参考下面的文章:
http://dev2dev.bea.com/products/wlworkshop81/articles/repeater.jsp
以下JSP页面显示了如何使用EJB Control调用EJB的getCategories远程方法,并将返回的Collection结果集用Repeater标签以超链接的形式显示出来,这里还用到了一个netui:anchor标签:

第一个DeclareControl标签申明了一个Control,名为ctrlMusicView,第二个CallControl标签调用控件ctrlMusicView的一个方法getCategories并附带参数MethodParameter,参数值由URL的id确定,然后将结果放在变量categories中。Repeater标签从categories取得数据源,然后循环显示集合中的每个元素。
对应的源代码看起来是这样:

运行结果在IE中显示如下:

类似地,您可以自己“画”出列艺术家,列专辑的页面,然后将它们组合起来。这些JSP页面仅仅是通过简单的鼠标拖放和属性设置完成的,真是太棒了!
3.使用Java Page Flow
Java Page Flow是完全基于开源的Struts模型,但提供了更强的易用性和自动化的状态管理,支持数据绑定,拥有更强大的动作和异常处理。下一步,我们将创建一个名为FileUploadController的页面流,完成一个文件上传的功能。
新建页面流fileUpload,Workshop自动为我们创建了一个FileUploadController.jpf,这个jpf作为控制器,我们再添加一个uploadFile的Action,包含一个名为“UploadForm”的ActionForm,3个JSP文件,然后设置导航如下:

详细代码请下载源代码包。
使用Java Page Flow的最大的优点是完全的MVC架构,有清晰的导航模型,并且配合NetUI标签,实现起来更为简单。
以上讨论了基本的浏览页面,下面我们要实现用户登陆的功能和针对管理员的管理功能。
实现用户登陆
为了跟踪用户,我们创建一个AccountManageBD的业务代理类,并将它放在Session中,封装所有与用户帐号相关的操作。几个关键的方法如下:
String getName() // 返回用户登陆名
boolean isAdmin() // 是否是管理员
void login(String username, String password) // 登陆系统
我们在MusicSessionListener中监听Session事件,以便创建和销毁与Session关联的AccountManageBD对象。然后在/WEB-INF/web.xml中注册MusicSessionListener。
这样,任何时候均可在JSP中调用
AccountManageBD account = (accountManageBD)
session.getAttribute(AccountManageBD.ACCOUNT);
来访问用户当前登陆信息。
创建一个登陆页面流LoginController.jpf实现登陆页面:

类似的,一个registerUser页面流实现用户注册功能:

实现管理功能
为了能使管理员登陆后管理站点,我们把所有的管理页面放在目录{MusicWeb}/admin/下,为了防止未授权用户访问任何/admin/下的任何页面,我们准备一个过滤器AdminFilter,将所有未授权的操作重定向到/login/LoginController.jpf中。核心代码如下:

修改/WEB-INF/web.xml,添加过滤器申明:

最后,你只需要用Dreamwaver之类的网页制作软件强化界面,然后把这些JSP及页面流组织起来,即可发布您的JetMusic站点。
安全
()
正如你所看到的,我们的安全是通过Web层的登陆验证实现的,在逻辑层并未使用基于角色的验证逻辑,对于我们现在的站点来说,可能已经足够了。不过,如果您打算将来向用户提供付费下载,付费收听的业务,那么就需要切实保证系统的安全性。通常,不建议您自己开发安全逻辑,因为开发防攻击的安全逻辑本身就不是一个简单的任务,此外,自定义的安全逻辑可能会导致较低的可重用性。
从Java 1.4和J2EE 1.3开始,Java验证与授权服务JAAS(Java Authentication and Authorization Service)就被引入到核心包中。JAAS实现了验证和授权两类服务,验证服务能够可靠并安全地确定目前是谁在执行代码,在我们的这个应用中,只需要用到JAAS的验证服务。以下是JAAS验证的一般步骤:
1.创建一个LoginContext实例。
2.指定LoginContext的配置文件。
3.加载指定的LoginModule。
4.客户端调用LoginContext的login方法。
5.如果登陆成功,就会将一个通过身份验证的Principal和Subject联系起来。
6.LoginContext将通过身份验证的Subject返回客户机。
在WebLogic中,缺省的用户,组和角色都是在XML配置文件中指定的,我们只需要提供一个登陆页面,包含一个名为j_security_check的Form,一个名为j_username的TextBox和一个名为j_password的PasswordBox,然后在/WEB-INF/web.xml中配置即可使用WebLogic默认的JAAS身份验证。但是,这些用户是静态的,无法满足我们使用数据库动态管理用户的需求。好在JAAS本身就是一个“可插拔”的模块,我们可以轻易地使用自定义的安全模块实现验证功能。
要在WebLogic中实现自定义的安全验证,需要提供一个Security Provider,包括自定义的Authentication Provider,Login Module,Identity Assertion,Principal Validation Provider,Role Mapping Provider,看上去有点复杂,不过,你可以从BEA站点下载一个示例代码:
http://dev2dev.bea.com/codelibrary/code/security_prov81.jsp
然后修改相应的实现类,配置部署,即可实现自定义的JAAS验证。
使用JAAS验证的好处是,验证逻辑从页面中分离,对页面的限制访问是通过/WEB-INF/web.xml中的配置指定的,无需自定义过滤器;在调用EJB时,Web容器能隐含的把Principal传递给EJB容器,从而和EJB的安全限制联系起来。
在我们的JetMusic站点中,目前仅仅使用Session+过滤器的机制,也许可以考虑在下一个版本中加入JAAS验证,以便更清晰地划分表示层逻辑和安全逻辑。
部署
()
可以直接在Workshop中启动WebLogic Server,即可自动完成部署。如果部署成功,WebLogic不会输出提示信息,如果部署失败,会提示异常信息。
常见的异常有:
JDBC连接出错:没有启动数据库,或者没有找到对应JNDI名称的数据库。你需要检查JNDI设置和数据库是否已经启动。
INSERT语句出错:这是创建实体Bean时容器的INSERT语句出错。如果指定了automatic-key-generation,第一次部署时WebLogic自动创建的主键为INT类型,但并不是AUTOINCREASE,需要打开数据库,手动更改主键设置,或者,您可以直接下载示例数据库,然后导入到SQL Server2000即可。
总结
()
我们已经利用J2EE技术在WebLogic Server上基本建立起了一个健壮的,可扩展的JetMusic音乐站点,其中涉及到EJB,JSP,Servlets,NetUI,Java Page Flow,JAAS以及几个常用的EJB设计模式等。相信您能从这个小小的Web应用中看到J2EE体系强大的功能。由于时间仓促,水平有限,文中不免会有一些错误,恳请读者指正。最后希望本文能够给您带来一点收获。最后申明:如果您使用JetMusic系统在网上发布音乐,所引起的一切版权纠纷本文作者概不负责。