论坛首页 Web前端技术论坛

Flex实战-puremvc+java(spring+hibernate)实现增删改查操作

浏览 9890 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2011-07-25  

  • 关键技术简介

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。

 

  • 效果展示
注:因为我不是UI,对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;
			}
		}

 

  • 尚未实现的功能
Java端并发和异常处理,Flex端异常处理、表单验证等等...

  • 项目环境
eclipse3.5、mysql5、Flashbuilder4

附件“flex_sql.rar”包含flex源码和数据库脚本
附件“ssh2model.rar”包含java源码
附件“jar.rar”包含java项目的jar文件,解压后丢到ssh2model\WebContent\WEB-INF\lib下面即可


请大家提出宝贵意见,共同进步,谢谢!

  • 大小: 38.6 KB
  • 大小: 30.5 KB
  • 大小: 34.2 KB
  • 大小: 35.7 KB
  • 大小: 27.9 KB
  • 大小: 28.3 KB
  • 大小: 26.5 KB
  • 大小: 56.3 KB
  • jar.rar (8.3 MB)
  • 下载次数: 880
   发表时间:2011-07-31  
PureMVC,难用的要死,但是却是了解设计模式非常好的一个框架。。。。。
0 请登录后投票
   发表时间:2011-07-31  
不错。支持楼主!
0 请登录后投票
   发表时间:2011-07-31  
andy_ghg 写道
PureMVC,难用的要死,但是却是了解设计模式非常好的一个框架。。。。。


同感
大量的Command非常痛苦
而且为了解耦使用注册-通知的形式,造成开发维护时根本理不清mvc三层关联关系

不知道Flex还有什么优秀的mvc框架
0 请登录后投票
   发表时间:2011-07-31  
helloworld这种看文档解决的东西,别浪费ITEYE的数据库了
0 请登录后投票
   发表时间:2011-08-01  
asbdzxln118 写道
helloworld这种看文档解决的东西,别浪费ITEYE的数据库了


HelloWorld?看文档解决问题?
敢问你认真看过代码没,没看过别废话。
还有,用真ID示人,别用新号在那唧唧歪歪,最TM看不惯这种喷子!
0 请登录后投票
   发表时间:2011-08-01  
白糖_ 写道
asbdzxln118 写道
helloworld这种看文档解决的东西,别浪费ITEYE的数据库了


HelloWorld?看文档解决问题?
敢问你认真看过代码没,没看过别废话。
还有,用真ID示人,别用新号在那唧唧歪歪,最TM看不惯这种喷子!


可以鉴定这个“喷子”为无证程序员,就算有证也是没经过铁道部认证的。


0 请登录后投票
   发表时间:2011-08-01  
距离实际做的solution项目,差距很大。算是入门级的吧。
0 请登录后投票
   发表时间:2011-08-01  
楼上的貌似很资深,要么也写篇文章出来,让大伙瞧瞧
0 请登录后投票
   发表时间:2011-08-01  
居然还有人用PureMVC!
0 请登录后投票
论坛首页 Web前端技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics