- 关键技术简介
Blaseds是一款开源免费的插件,主要作用是实现flex调用java代码;
puremvc是一款针对flex的开源免费的mvc框架,主要作用是实现Flex代码解耦;
Hibernate框架是java持久层经典框架,帮助程序员更方便安全地访问数据库;
spring是java非常流行的框架,主要作用是管理java对象,其强大的低耦合机制能够与许多框架进行无缝整合(比如前面提到的Blaseds和Hibernate)。
- 实现机制
本实例实现用户管理的增删改查操作,前端完全使用flash展示,后台使用java服务器与mysql数据库交互。
Flex端使用puremvc进行解耦,Mediator监听页面事件和页面数据存取,Command处理页面复杂操作并调用Proxy方法,Proxy实现Java代码调用(使用RemoteObject的形式)。
Java端分为持久层(Dao层)和业务层(Service层),Flex调用的是Service层代码,使用spring mvc捕获flex的调用请求,Service再调用Dao层进行增删改查操作,最后将结果返回给Flex。
- 效果展示
①主页面
②下一页
③添加页面
④编辑页面
⑤详细页面
⑥删除操作
- Java端实现
Java端主要使用spring和Hibernate实现Dao层和Service层代码,具体spring和Hibernate配置网上有很多,在这就不赘述了。
关于Blaseds与spring整合的例子也很多,大家可以到我的博文中参考一下:http://blessht.iteye.com/admin/blogs/1131148。
①下面看Flex远程调用Service的接口定义:
public interface LoginService {
//新增
public void insertLoginInfo(Logininfo login);
//删除
public void deleteLoginInfo(int id);
//修改
public void updateLoginInfo(Logininfo login);
//详细
public LoginInfoDto getLoginInfo(int id);
//分页查询用户信息(用于显示到表格中)
public List<LoginTableDto> findLoginInfo(int pageNo,int pageSize);
//获取用户信息表的总数据
public long getLoginInfoTotalCounts();
//进入编辑页面时调用的方法
public Logininfo getLogin(int id);
//暂时无用
public List<FlexComboBoxDto> getSexList();
}
②看比较关键的web.xml配置:
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <display-name>ssh2model</display-name> <description>ssh2model Application</description> <!-- spring mvc --> <servlet> <servlet-name>Spring MVC Dispatcher Servlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value></param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Spring MVC Dispatcher Servlet</servlet-name> <url-pattern>/messagebroker/*</url-pattern> </servlet-mapping> <!-- 著名 Character Encoding filter --> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <!-- spring IOC --> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:com/bless/base/config/spring/springApplicationContext.xml, classpath:com/bless/*/config/spring/spring-*-*.xml </param-value> </context-param> <!-- blaseds监听器:用于获取服务器内置对象(request,application等) --> <listener> <listener-class>flex.messaging.HttpFlexSession</listener-class> </listener> <!--Spring ApplicationContext 载入 ,必须--> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- Spring 刷新Introspector防止内存泄露 --> <listener> <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class> </listener> <!-- session超时定义,单位为分钟 --> <session-config> <session-timeout>20</session-timeout> </session-config> <!--添加远程支持,用于远程数据服务--> <servlet> <display-name>RDSDispatchServlet</display-name> <servlet-name>RDSDispatchServlet</servlet-name> <servlet-class>flex.rds.server.servlet.FrontEndServlet</servlet-class> <init-param> <param-name>useAppserverSecurity</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>messageBrokerId</param-name> <param-value>_messageBroker</param-value> </init-param> <load-on-startup>10</load-on-startup> </servlet> <servlet-mapping id="RDS_DISPATCH_MAPPING"> <servlet-name>RDSDispatchServlet</servlet-name> <url-pattern>/CFIDE/main/ide.cfm</url-pattern> </servlet-mapping> </web-app>
③最后看service的IOC配置文件:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:flex="http://www.springframework.org/schema/flex" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/flex http://www.springframework.org/schema/flex/spring-flex-1.0.xsd"> <!-- 为了把请求路由给 MessageBroker,添加以下 tag--> <flex:message-broker /> <bean id="loginServiceBean" class="com.bless.logininfo.service.impl.LoginServiceImpl"> <!-- 指定当前bean被远程调用 --> <flex:remoting-destination /> <property name="loginDao" ref="loginInfoDaoBean"></property> </bean> </beans>
④最后将Java项目部署到服务器上即可。
- Flex端实现
Flex端因为使用了puremvc,关于puremvc我在这不做过多介绍,因为我也是新手,可能很多东西写的都是错误,在以后对它有更深认识之后我会与大家分享使用经验。
整个项目的包结构如下图所示:
上图*.mxml就是flex的页面展示了,功能与jsp和html相似,也是由页面标签、事件等组成。mxml可以像纯jsp一样,将所有代码写在里面,但是这样会造成项目难以维护,所以为了尽量减少耦合度,我在mxml中基本没有写ActionScript代码,而是交给puremvc的Mediator处理。
下面以index.mxml为例,我在index.mxml中定义了一些变量,方便Mediator赋值,也就是说mxml不做逻辑操作,只是为了展示数据:
<?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600" creationComplete="facade.init_index(this)"> <!--整体布局--> <s:layout> <s:VerticalLayout horizontalAlign="center" verticalAlign="middle"/> </s:layout> <!--ActionScript代码块--> <fx:Script> <![CDATA[ import mx.collections.ArrayCollection; //定义puremvc核心Facade private var facade:ApplicatioinFacade = ApplicatioinFacade.getInstance(); //总条数 [Bindable] public var totals:int = 0; //当前页数 [Bindable] public var pageNo:int = 1; //一页显示条数 [Bindable] public var pageSize:int = 6; //表格中的结果集 [Bindable] public var logintablelist:ArrayCollection; //被选中的值id public var selectId:int = 0; protected function table_loginInfo_clickHandler(event:MouseEvent):void { if(table_loginInfo.selectedItem != null){ selectId = table_loginInfo.selectedItem.id; } } ]]> </fx:Script> <!-- <s:HGroup width="70%" height="30" verticalAlign="middle" horizontalAlign="center"> <s:Label text="用户名:"/> <s:TextInput id="txt_loginCode"/> <s:Label text="姓名:"/> <s:TextInput id="txt_name"/> <s:Label text="性别:"/> <s:ComboBox width="100" id="txt_sex"/> <s:Button label="检索" id="btn_search"/> <s:Button label="重置" id="btn_reset"/> </s:HGroup> --> <!--操作区--> <s:HGroup width="70%" height="30" verticalAlign="middle"> <mx:LinkButton label="刷新" id="refresh"/> <mx:LinkButton label="添加" id="insert"/> <mx:LinkButton label="编辑" enabled="{table_loginInfo.selectedIndex==-1?false:true}" id="edit"/> <mx:LinkButton label="详情" enabled="{table_loginInfo.selectedIndex==-1?false:true}" id="detail"/> <mx:LinkButton label="删除" enabled="{table_loginInfo.selectedIndex==-1?false:true}" id="link_delete"/> </s:HGroup> <!--表格区--> <mx:DataGrid width="70%" id="table_loginInfo" dataProvider="{logintablelist}" click="table_loginInfo_clickHandler(event)"> <mx:columns> <mx:DataGridColumn headerText="用户名" dataField="loginCode"/> <mx:DataGridColumn headerText="姓名" dataField="name"/> <mx:DataGridColumn headerText="性别" dataField="sex"/> <mx:DataGridColumn headerText="联系电话" dataField="phone"/> <mx:DataGridColumn headerText="电子邮箱" dataField="email"/> </mx:columns> </mx:DataGrid> <s:HGroup width="70%" height="30" verticalAlign="middle"> <s:Label text="总共"/> <s:Label text="{totals}"/> <s:Label text="条数据"/> <s:Label text=" 第"/> <s:NumericStepper id="curPage" minimum="1" maximum="{(totals%pageSize)!=0?((uint)(totals/pageSize)+1):(totals/pageSize)}" value="{pageNo}"/> <s:Label text="页"/> <mx:LinkButton label="上一页" id="befor_page" enabled="{(pageNo==1)?false:true}"/> <mx:LinkButton label="下一页" id="after_page" enabled="{(totals>((pageNo-1)*pageSize+(logintablelist.length)))?true:false}"/> </s:HGroup> </s:Application>
大家可以看到index.mxml中本应有很多事件的,不然怎么能实现增删改查操作,其实上面所有事件都写到了这里:
public class IndexMediator extends Mediator
{
public static const NAME:String = "IndexMediator";
public function IndexMediator(viewComponent:Object=null)
{
super(IndexMediator.NAME, viewComponent);
var indexApp:index = viewComponent as index;
//刷新
indexApp.refresh.addEventListener(MouseEvent.CLICK,function click():void{
sendNotification(LoadLoginCommand.NAME,indexApp);
});
//上下页选择事件
indexApp.curPage.addEventListener(Event.CHANGE,function change():void{
loadTable(indexApp.curPage.value);
});
//上一页事件
indexApp.befor_page.addEventListener(MouseEvent.CLICK,function click():void{
loadTable(indexApp.pageNo - 1);
});
//下一页事件
indexApp.after_page.addEventListener(MouseEvent.CLICK,function click():void{
loadTable(indexApp.pageNo + 1);
});
//打开添加操作事件
indexApp.insert.addEventListener(MouseEvent.CLICK,function click():void{
sendNotification(LoginAddOpenCommand.NAME,indexApp);
});
//打开编辑操作事件
indexApp.edit.addEventListener(MouseEvent.CLICK,function click():void{
sendNotification(LoginEditCommand.NAME,indexApp);
});
//打开详细操作事件
indexApp.detail.addEventListener(MouseEvent.CLICK,function click():void{
sendNotification(LoginDetailCommand.NAME,indexApp);
});
//执行删除操作事件
indexApp.link_delete.addEventListener(MouseEvent.CLICK,function click():void{
Alert.show("你确定该条数据吗?","删除提示",Alert.OK|Alert.NO,indexApp,function alert(e:CloseEvent):void{
//如果点击Cancel则不执行任何操作,否则执行删除操作
if(e.detail == Alert.OK){
sendNotification(LoginDeleteCommand.NAME,indexApp);
}
});
});
}
public function get indexApp():index{
return viewComponent as index;
}
//列举监听事件
override public function listNotificationInterests() : Array
{
return [
SearchLoginProxy.findLoginInfo,
SearchLoginProxy.getLoginInfoTotalCounts,
SearchLoginProxy.deleteLoginInfo
];
}
//处理监听事件
override public function handleNotification( note : INotification ) : void{
switch (note.getName()){
case SearchLoginProxy.findLoginInfo:
indexApp.logintablelist = note.getBody() as ArrayCollection;
break;
case SearchLoginProxy.getLoginInfoTotalCounts:
indexApp.totals = note.getBody() as int;
break;
case SearchLoginProxy.deleteLoginInfo:
sendNotification(LoadLoginCommand.NAME,indexApp);
break;
default:
break;
}
}
private function loadTable(pageNo:int):void{
indexApp.pageNo = pageNo;
sendNotification(LoadLoginCommand.NAME,indexApp);
}
}
那么上面IndexMediator类的构造方法就是关键了,构造方法必须传入index.mxml的实现对象,其实实现步骤很简单:
①index.mxml页面启动是触发creationComplete事件,该事件调用facade的init_index方法并且将当前对象作为参数传过去
②在facade类中注册一个名叫InitCommand的类,通过sendNotification向InitCommand发送通知
public class ApplicatioinFacade extends Facade
{
public static const INIT:String = "init";
//得到ApplicationFacade单例的工厂方法
public static function getInstance() : ApplicatioinFacade
{
if ( instance == null ) instance = new ApplicatioinFacade( );
return instance as ApplicatioinFacade;
}
//注册Command,建立Command与Notification之间的映射
override protected function initializeController( ) : void{
super.initializeController();
registerCommand(InitCommand.NAME,InitCommand);
}
public function init_index(index_:index):void{
sendNotification(InitCommand.NAME,index_);
}
}
③InitCommand接到通知后运行execute方法,execute方式内完成MVC的注册(当然也包括IndexMediator的注册)。其实到这IndexMediator就已经开始监听index.mxml的事件了。
public class InitCommand extends SimpleCommand
{
public static const NAME:String = "InitCommand";
override public function execute(notification:INotification):void{
var index_:index = notification.getBody() as index;
//向facade中注册mvc
facade.registerMediator(new IndexMediator(index_));
facade.registerProxy(new SearchLoginProxy());
//注册command
facade.registerCommand(LoadLoginCommand.NAME,LoadLoginCommand);
facade.registerCommand(LoginDetailCommand.NAME,LoginDetailCommand);
facade.registerCommand(LoginDeleteCommand.NAME,LoginDeleteCommand);
facade.registerCommand(LoginEditCommand.NAME,LoginEditCommand);
facade.registerCommand(LoginAddReturnCommand.NAME,LoginAddReturnCommand);
facade.registerCommand(LoginEditSubmitCommand.NAME,LoginEditSubmitCommand);
facade.registerCommand(LoginAddOpenCommand.NAME,LoginAddOpenCommand);
facade.registerCommand(LoginEditCloseCommand.NAME,LoginEditCloseCommand);
//发送通知初始化页面
sendNotification(LoadLoginCommand.NAME,index_);
}
}
- puremvc运行流程简介
以打开新增页面为例,我简单介绍一下puremvc的调用原理:
①首先IndexMediator负责index.mxml的事件监听,当用户点击“新增”链接时向LoginAddOpenCommand发送通知,并且将index.mxml对象传给该command使用:
//打开添加操作事件
indexApp.insert.addEventListener(MouseEvent.CLICK,function click():void{
sendNotification(LoginAddOpenCommand.NAME,indexApp);
});
②发送通知后,facade调用LoginAddOpenCommand的execute方法,该方法创建一个insert.mxml对象,同时创建LoginAddMediator(该Mediator用于监听insert.mxml页面),并且以模式弹出窗的形式显示在界面中,这时需要初始化“性别”下拉列表,所以必须调用SearchLoginProxy的getSexList方法:
public class LoginAddOpenCommand extends SimpleCommand
{
public static const NAME:String = "LoginAddOpenCommand";
override public function execute(notification:INotification):void{
//新建添加窗口
var i:insert = new insert();
PopUpManager.addPopUp(i,notification.getBody() as index,true);
PopUpManager.centerPopUp(i);
facade.registerMediator(new LoginAddMediator(i));
(facade.retrieveProxy(SearchLoginProxy.NAME) as SearchLoginProxy).getSexList();
}
}
③SearchLoginProxy的getSexList方法主要封装“性别”下拉列表的数据,封装完成后会发出一个通知
//查询性别列表
public static const getSexList:String ="getSexList";
public function getSexList():void{
var list:ArrayCollection = new ArrayCollection([
{label:"保密",data:0},
{label:"男",data:1},
{label:"女",data:2}
]);
sendNotification(SearchLoginProxy.getSexList,list);
}
④在LoginAddMediator中会注册并收听到Proxy发过来的通知,最后通过LoginAddMediator将值赋给insert.mxml的combox组件:
//列举监听事件
override public function listNotificationInterests() : Array
{
return [
SearchLoginProxy.insertLoginInfo,
SearchLoginProxy.getSexList
];
}
//处理监听事件
override public function handleNotification( note : INotification ) : void{
switch (note.getName()){
case SearchLoginProxy.insertLoginInfo:
(insertApp.parentDocument as index).pageNo = 1;
sendNotification(LoginAddReturnCommand.NAME,insertApp);
break;
case SearchLoginProxy.getSexList:
//给“性别”combox赋初始值
insertApp.sexList = note.getBody() as ArrayCollection;
insertApp.cb_sex.selectedIndex = 0;
default :
break;
}
}
- 尚未实现的功能
- 项目环境