flex+java项目创建
当前的Web技术对它们的需求不断增长。 他们必须能够管理用户帐户,上载内容和流视频。 这种需求要求RIA开发人员寻求简化开发工作流程的技术,同时提供常见的功能。 开发人员面临的挑战是选择正确的技术来提供这些服务。
Adobe Flex是一项客户端技术,可为开发人员提供丰富的API调用集,用于创建GUI,绘制图形,播放和流媒体以及连接到Web服务。 在服务器端,Java技术提供了诸如连接到关系数据库管理系统(RDBMS),对服务请求进行多线程处理以及随需求增加而进行最佳扩展之类的功能。 结合使用这两种技术可提供功能强大的技术堆栈,可满足RIA应用程序的需求。
本文演示了如何编写一个简单但功能强大的RIA,该RIA将Flex用于客户端,将Java技术用于服务器,将MySQL用于后端数据库。
样例应用
该示例应用程序(可从下面的下载部分获得)提供了一个丰富的UI,该UI支持通过AdobeFlash®(SWF)应用程序创建,读取,更新和删除(CRUD)联系人信息。 此三层Web体系结构如图1所示,其中,客户端由嵌入在Web页面中的SWF文件表示,服务器应用程序在Java Servlet容器(在本例中为Apache Tomcat)中运行。 MySQL的。 这三个层结合在一起,创建了一个分布式电源应用程序。
图1.联系人应用程序
为了在Flash应用程序和Java servlet容器之间进行通信,Adobe BlazeDS框架提供了对象远程处理(一种RPC形式),它允许Adobe ActionScript™对象调用Java对象,反之亦然。 Java服务器应用程序与关系数据库之间的通信由Hibernate Object Relational Mapping(ORM)框架处理。 Hibernate允许将Java对象转换为SQL代码,反之亦然。
应用程序:服务器层
第一步是创建一个Java类,其中包含存储联系信息所需的信息。 该示例应用程序包含一个具有基本信息的简单模型。 Contact
对象所需的属性和数据类型为:
- String emailAddress
- String firstName
- long id
- String lastName
- String phoneNumber
- long serialVersionUID
+ Contact()
+ Contact(String first, String last, String email, String number)
+ String getEmailAddress()
+ String getFirstName()
+ long getId()
+ String getLastName()
+ String getPhoneNumber()
+ void setEmailAddress(String address)
+ void setFirstName(String first)
+ void setId(long newId)
+ void setLastName(String last)
+ void setPhoneNumber(String number)
+ StringtoString()
注释业务对象
Java Contact
类被认为是充当业务对象的POJO(普通的旧Java对象),这意味着它表示业务域的特征和行为。 Contact
对象中的数据需要保留到数据库中。 解决方案是使用诸如Hibernate之类的ORM框架,该框架在将对象映射到数据库表中的记录并再次返回时执行大量工作。 如果使用Java Persistence API(JPA)批注,则只需很少的代码即可完成ORM。 清单1演示了带注释的Java类Contact
。
清单1. Java Contact类
package bcit.contacts;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
@Entity
@Table(name="contact")
@NamedQueries( {
@NamedQuery(name = "contact.findAll", query = "from Contact"),
@NamedQuery(name = "contact.getById", query =
"select c from Contact c where c.id = :id")
} )
public class Contact {
private static final long serialVersionUID = 123456789L;
public Contact() {
firstName = "N/A";
lastName = "N/A";
emailAddress = "N/A";
phoneNumber = "N/A";
}
public Contact(String first, String last, String email, String number) {
firstName = first;
lastName = last;
emailAddress = email;
phoneNumber = number;
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", nullable = false, updatable=false)
private long id;
@Column(name = "lastName", nullable = false, unique = false)
private String lastName;
@Column(name = "firstName", nullable = false, unique = false)
private String firstName;
@Column(name = "emailAddress", nullable = false, unique = false)
private String emailAddress;
@Column(name = "phoneNumber", nullable = false, unique = false)
private String phoneNumber;
public void setPhoneNumber(String number) { phoneNumber = number; }
public String getPhoneNumber() { return phoneNumber; }
public String getEmailAddress() { return emailAddress; }
public void setEmailAddress(String address) { emailAddress = address; }
public String getFirstName() { return firstName; }
public void setFirstName(String first) { firstName = first; }
public String getLastName() { return lastName; }
public void setLastName(String last) { lastName = last; }
public long getId() { return id; }
public void setId(long newId) { id = newId; }
@Override
public String toString() {
return id + " " + firstName + " " + lastName + " " + emailAddress
+ " " + phoneNumber;
}
}
该类很简单,但是注释有很多事情要做:
-
@Column
:将该属性标记为数据库中的一列,并进行选择,包括该列的名称,其是否唯一以及一列是否可为空 -
@Entity
:将类声明为实体Bean,这意味着它是持久化的POJO -
@GeneratedValue
:指定生成主键的策略; 选择是AUTO
,IDENTITY
,SEQUENCE
和TABLE
-
@Id
:指出该属性是每个Java对象的唯一标识符(即主键) -
@NamedQueries
:列出一组命名查询 -
@NamedQuery
:将预定义查询声明为字符串文字,以后可以引用该文字来执行 -
@Table
:将Java类指定为数据库中的表
每次需要保留内存中的Java对象时,Hibernate都会将任何Java对象的状态信息转换为SQL更新。 同样,带有结果集SQL语句用于填充Java对象。 结果是所有对象都可以保存为数据库中的记录,并且所有记录都可以检索并转换回Java对象。
注释告知Hibernate应该将类中的哪些内容视为持久性的。 但是它们只是图片的一部分。
业务服务:数据库连接
需要一个服务类来执行对Hibernate的调用才能执行ORM。 清单2显示了ContactsService
类,它充当应用程序服务。
清单2. ContactsService类
public class ContactsService {
private static Logger logger = Logger.getLogger(ContactsService.class);
private static final String PERSISTENCE_UNIT = "contacts";
private static EntityManagerFactory emf = null;
static {
logger.info("LOADING CONTACTSSERVICE CLASS.");
emf = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT);
}
public ContactsService() {
super();
}
public void addContact(Contact c) {
if(c == null) {
return;
}
EntityManager em = emf.createEntityManager();
logger.info("PERSISTENCE ENTITYMANAGER ACQUIRED.");
logger.info("ABOUT TO ADD CONTACT: fName: " + c.getFirstName()
+ ", lName: " + c.getLastName() + ", email:" + c.getEmailAddress()
+ ", phone: " + c.getPhoneNumber());
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
em.merge(c);
tx.commit();
} catch (Exception e) {
logger.error("CONTACT APP PERSISTING ERROR: " + e.getMessage());
tx.rollback();
} finally {
logger.info("CONTACT APP CLOSING ENTITY MANAGER.");
em.close();
}
}
public void editContact(Contact c) {
logger.info("CONTACT TO UPDATE: " + c);
addContact(c);
}
public void deleteContact(Long id) {
logger.info("ABOUT TO DELETE CONTACT");
EntityManager em = emf.createEntityManager();
logger.info("PERSISTENCE ENTITYMANAGER ACQUIRED.");
Query contactByIdQuery = em.createNamedQuery("contact.getById");
contactByIdQuery.setParameter("id", id);
Contact c = (Contact) contactByIdQuery.getSingleResult();
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
em.remove(c);
tx.commit();
} catch (Exception e) {
logger.error("CONTACT APP PERSISTING ERROR: " + e.getMessage());
tx.rollback();
} finally {
logger.info("CONTACT APP CLOSING ENTITY MANAGER.");
em.close();
}
}
public List<Contact> getContacts() {
logger.info("ABOUT TO RETRIEVE CONTACTS");
EntityManager em = emf.createEntityManager();
logger.info("PERSISTENCE ENTITYMANAGER ACQUIRED.");
Query findAllContactsQuery =
em.createNamedQuery("contact.findAll");
List<Contact> contacts = findAllContactsQuery.getResultList();
if (contacts != null) {
logger.debug("CONTACT APP RETRIEVED: " + contacts.size()
+ " CONTACT(S)");
}
return contacts;
}
}
每个方法都获得对EntityManager
的引用,该引用代表内存中的缓存。 缓存是确保效率的强大功能,因为从数据库发送和接收数据可能是一项昂贵的操作。 您只需要确保创建的每个高速缓存都已提交到数据库或在不需要时回滚。
在JPA中,赋予缓存的术语是持久性上下文,它由EntityManager
类表示。 每个持久性上下文管理一组实体,这些实体是已使用@Entity
注释进行注释的Java对象。 EntityManagerFactory
类代表一个持久性单元,它负责配置与数据存储(例如,关系数据库)的连接,管理实体类型(即,给定上下文中需要映射到数据存储的所有类) ),最后提供一个持久化上下文的实例(即EntityManager
)。
尽管创建持久性上下文的过程在时间上不昂贵,但是创建持久性单元的过程成本很高。 设置与数据存储的连接,查找所有标注为实体的类以及配置持久性逻辑以将这些类绑定到数据存储中的实体并不是一项快速的操作。 因此,应在应用程序启动时创建一次EntityManagerFactory
实例。 至于持久性上下文,请确保在创建另一个EntityManager
之前将其销毁。 遵循的另一个重要规则是每个请求的实体管理器模式。 此模式将数据库调用(例如,请求和更新)分组,以便可以一次发送它们。 这样做可以确保充分利用JPA的缓存机制。
下一个要求是客户端。
应用程序:客户端层
Flex框架允许您创建用户可以在Adobe Flash Player中播放的应用程序。 Flex包括:
- 声明性的XML UI语言,称为MXML
- ActionScript编程语言
- 运行时库,用于创建UI,Web连接和许多其他功能
- 用于将应用程序编译为SWF文件的开发人员工具
本文引用的客户端应用程序使用Flex版本4。在访问客户端应用程序之前,重要的是要了解Flex应用程序的创建方式以及它们在Flash Player中作为可执行文件的形式。
首先,您可以结合使用MXML标记和ActionScript代码来创建应用程序。 常见的工作流程是使用MXML格式创建大部分GUI(表示),然后使用ActionScript代码进行事件处理和业务逻辑。 因为MXML和ActionScript都是基于文本的,所以创建Flash应用程序只需要一个标准的文本编辑器和Flex SDK。
其次,一旦编写了Flex应用程序,就可以使用MXML编译器来编译代码。 MXML编译器创建SWF文件,然后可以在Web浏览器中运行该文件(通过Flash Player浏览器插件)。
最后,Flash应用程序在使用时间轴范例的ActionScript虚拟机2(AVM2)中运行。 这种范例将执行分解为多个帧,就像电影一样。 您可以在编译时在Flash应用程序中指定每秒的帧数。 此外,Flash Player将执行分解为以下有序任务:
- Flash Player事件,例如计时器和鼠标事件
- 用户密码
- 预渲染逻辑,Flash Player尝试确定由于数据值更改是否需要更新GUI
- 绑定到数据值的用户代码更改
- Flash Player渲染
如果每秒要渲染的帧很少,则可以执行许多用户代码。 但是,如果帧速率很高(例如每秒60帧),则Flash Player可能无法执行很多用户代码,因为用户代码可能花费的时间超过了所允许的时间。 在编写Flash Player时,请记住这一点,这一点很重要。
MXML
MXML是一种功能强大的声明式XML格式,可帮助:
- 由于XML格式的声明性,最大程度地减少了构建GUI所需的代码量
- 通过明确区分表示逻辑和交互逻辑,降低GUI代码的复杂性
- 进行软件开发时促进设计模式的使用
清单3显示了MXML Application
类。
清单3. ContactsApp类
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:contact="bcit.contacts.*" creationComplete="initPage();"
layout="vertical" frameRate="30" pageTitle="Contacts Example"
horizontalAlign="center" verticalAlign="middle"
backgroundColor="#A9C0E7">
<mx:Style>
.mainBoxStyle {
borderStyle: solid;
paddingTop: 5px;
paddingBottom: 5px;
paddingLeft: 5px;
paddingRight: 5px;
}
.textMessages {
fontWeight: bold;
}
</mx:Style>
<mx:RemoteObject id="remotingService" showBusyCursor="false"
destination="contacts" fault="handleFault(event);"
result="handleResult(event);"/>
<mx:Script>
<![CDATA[
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.collections.ArrayCollection;
import bcit.contacts.dto.Contact;
[Bindable]
private var contacts:ArrayCollection = new ArrayCollection();
// For more on the Bindable metadata tag, see the devguide_flex3.pdf
// document, page 1249 (1257 in PDF page numbering)
[Bindable]
private var message:String = "Status: Ready";
private var contact:Contact;
public function setControlBarValid(valid:Boolean):void {
if(valid) {
// if the selected item is -1, then no item is selected but at
// the same time the fields are valid which means the user chose
// to add a contact, not update one
if(contactsDataGrid.selectedIndex == -1) {
createButton.enabled = valid;
} else {
editButton.enabled = valid;
}
} else {
// else nothing is valid
createButton.enabled = false;
editButton.enabled = false;
}
}
private function initPage():void {
editContactForm.setApp(this);
contact = new Contact();
getAllContacts();
resetPage();
}
private function createContact():void {
contact = editContactForm.getContact();
remotingService.addContact(contact);
message = "Status: Contact Added";
getAllContacts();
}
private function editContact():void {
var id:Number = contact.id;
contact = editContactForm.getContact();
contact.id = id;
remotingService.editContact(contact);
message = "Status: Contact Edited";
getAllContacts();
}
private function deleteContact():void {
if(contactsDataGrid.selectedItem != null) {
var c:Contact = contactsDataGrid.selectedItem as Contact;
// no sense in sending the whole contact - just send the id
// to cut down on bandwidth
remotingService.deleteContact(c.id);
message = "Status: Contact Deleted";
}
getAllContacts();
}
private function getAllContacts():void {
loadButton.enabled = false;
remotingService.getContacts();
loadButton.enabled = true;
resetPage();
}
private function populateFormWithContact():void {
contact = contactsDataGrid.selectedItem as Contact;
editContactForm.setContact(contact);
editButton.enabled = true;
deleteButton.enabled = true;
}
private function resetPage():void {
editContactForm.clearForm();
contact = new Contact();
createButton.enabled = false;
editButton.enabled = false;
deleteButton.enabled = false;
contactsDataGrid.selectedIndex = -1;
}
private function handleFault(e:FaultEvent):void {
message = "Status: Error"
+ "\nFault code: " + e.fault.faultCode
+ "\nFault detail: " + e.fault.faultDetail
+ "\nFault string: " + e.fault.faultString;
}
private function handleResult(e:ResultEvent):void {
// can get the results by accessing e.result property
//mx.controls.Alert.show(e.toString());
contacts = e.result as ArrayCollection;
var number:int = contacts.length;
//if(number == 1) {
// message = "Status: Retrieved 1 contact";
//} else {
// message = "Status: Retrieved " + contacts.length + " contacts";
//}
}
]]>
</mx:Script>
<mx:VBox styleName="mainBoxStyle">
<mx:Text id="titleText" text="Single click to select a contact"/>
<contact:ContactsDataGrid id="contactsDataGrid" dataProvider="{contacts}"
itemClick="populateFormWithContact();"
doubleClick="populateFormWithContact();"/>
<contact:EditContactForm id="editContactForm"/>
<mx:ControlBar horizontalAlign="center">
<mx:Button label="List" id="loadButton" click="getAllContacts()"
toolTip="Retrieve contacts from the server"/>
<mx:Button label="Add" id="createButton" click="createContact()"
toolTip="Create a new contact"/>
<mx:Button label="Update" id="editButton" click="editContact()"
toolTip="Edit a selected contact"/>
<mx:Button label="Delete" id="deleteButton" click="deleteContact()"
toolTip="Delete a selected contact"/>
<mx:Button label="Clear Form" id="clearButton" click="resetPage()"
toolTip="Clear the form"/>
</mx:ControlBar>
<mx:TextArea text="{message}" styleName="textMessages" wordWrap="true"
verticalScrollPolicy="auto" horizontalScrollPolicy="off" editable="false"
width="100%"/>
</mx:VBox>
</mx:Application>
这里还有与清单3有关的几点:
- MXML文档的根元素是
Application
类的子类。 -
mx:Style
元素允许CSS属性为UI组件定义本地样式。 可以使用本地样式定义(如清单3所示 ),对外部样式表的引用,在组件中内联样式以及在ActionScript中使用setStyle
方法来完成样式设置。 -
RemoteObject
类表示一个HTTP服务对象,该对象通过服务器执行远程操作。 -
mx:Script
元素在CDATA
节中包含ActionScript代码块。 - 有一种布局(即
VBox
类)。 - 每次在应用程序中声明UI组件(例如
TextArea
)时,都会生成一个实例变量,以后可以使用该组件的id
属性在应用程序内对其进行引用。 - 数据绑定使用花括号执行(例如,
TextArea
元素的text
属性绑定到ActionScriptmessage
实例变量)。
动作脚本
当MXML定义GUI时,ActionScript提供了处理事件,绑定数据(通过[Bindable]
元数据标签)和调用远程服务的功能。 在清单3中 ,方法createContact
, editContact
, deleteContact
和getAllContacts
都在服务器端调用远程方法。 调用远程方法时,可以通过声明回调函数为ActionScript提供处理结果和任何错误的机会。 在清单3中 , handleResult
函数将结果作为Object
接收,并将其强制转换为ArrayCollection
。 BlazeDS在服务器端将List
转换为ArrayCollection
。
清单4给出了ActionScript类Contact
,您可以创建该类来表示Flash一侧的联系人对象。
清单4. ActionScript Contact类
package bcit.contacts.dto {
[RemoteClass(alias="bcit.contacts.Contact")]
public class Contact {
public function Contact() { id = -1; }
public var id:Number;
public var lastName:String;
public var firstName:String;
public var emailAddress:String;
public var phoneNumber:String;
public function toString():String {
return id + ", " + firstName + " " + lastName + " " + emailAddress
+ " " + phoneNumber;
}
}
}
这些ActionScript对象被发送到服务器端,在其中BlazeDS发挥其魔力并将ActionScript对象转换为Java对象。 ActionScript Contact
类被视为数据传输对象(DTO)。
配置应用程序
该应用程序还依赖于配置文件,该文件说明服务器的设置详细信息。 该应用程序内的两个主要配置区域是Hibernate和BlazeDS。
配置Hibernate
您可以使用标准JPA配置文件persistence.xml来配置Hibernate,如清单5所示。
清单5. persistence.xml配置文件的子集
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="contacts" transaction-type="RESOURCE_LOCAL">
<properties>
<property name="hibernate.dialect"
value="org.hibernate.dialect.MySQLDialect" />
<property name="hibernate.default_schema" value="contacts" />
<property name="hibernate.connection.driver_class"
value="com.mysql.jdbc.Driver" />
<property name="hibernate.connection.url"
value="jdbc:mysql://localhost:3306/contacts" />
<property name="hibernate.archive.autodetection" value="class, hbm"/>
<property name="hibernate.connection.username" value="root"/>
<property name="hibernate.connection.password" value="root"/>
</properties>
</persistence-unit>
</persistence>
必须将persistence.xml文件放入Web应用程序的WEB-INF / classes / META-INF文件夹中,以便Hibernate读取它。 设置好这些后,Hibernate需要以下信息:
- 数据库方言(即与之通信的数据库,因为许多数据库SQL方言略有不同)
- 通过默认架构的表空间
- 用于连接数据库的数据库驱动程序
- 数据库URL
- 自动检测应检测到的内容(例如,带注释的类,Hibernate映射XML文件等)
- 用户名和密码
其他信息可以帮助提高Hibernate的性能,但不是必需的。
配置BlazeDS
BlazeDS具有四个配置文件:
- messages-config.xml :定义发布-订阅消息传递信息
- proxy-config.xml :提供HTTP和Web服务的代理服务信息
- remoting-config.xml :定义用于远程服务的信息,例如文章应用程序中的信息
- services-config.xml :顶级配置文件,它引用其他配置文件,并且还提供安全性约束,通道和日志记录
清单6演示了services-config.xml文件。 请注意,对于文章应用程序,仅remoting-config.xml文件是相关的,因为该应用程序仅使用BlazeDS远程服务。
清单6. services-config.xml配置文件的子集
<?xml version="1.0" encoding="UTF-8"?>
<services-config>
<services>
<service-include file-path="remoting-config.xml" />
<service-include file-path="messaging-config.xml" />
<service-include file-path="proxy-config.xml" />
<default-channels>
<channel ref="contacts-amf"/>
</default-channels>
</services>
<channels>
<channel-definition id="contacts-amf" class="mx.messaging.channels.AMFChannel">
<endpoint url="http://localhost:8080/contacts/messagebroker/amf"
class="flex.messaging.endpoints.AMFEndpoint"/>
<properties>
<polling-enabled>false</polling-enabled>
</properties>
</channel-definition>
</channels>
<logging>
<target class="flex.messaging.log.ConsoleTarget" level="Error">
<properties>
<prefix>[BlazeDS] </prefix>
<includeDate>false</includeDate>
<includeTime>false</includeTime>
<includeLevel>false</includeLevel>
<includeCategory>false</includeCategory>
</properties>
<filters>
<pattern>Endpoint.*</pattern>
<pattern>Service.*</pattern>
<pattern>Configuration</pattern>
</filters>
</target>
</logging>
</services-config>
services-config.xml配置文件引用其他配置文件(必须存在),配置BlazeDS日志记录,并设置任何通道。 通道是用于客户端与服务器通信的协议的抽象。 本文应用程序使用标准AMF协议,但不进行轮询。 轮询意味着客户端与服务器持续通信,以确保仍建立连接-该应用程序不需要此功能。
通道终点指定服务器URL。 该终点是编译项目所必需的; 客户端Flash应用程序将其用作硬编码值,以便它知道要连接的服务器。 实际上,您可以直接在MXML或ActionScript代码中定义终点URL。
最后,remoting-config.xml配置文件(如清单7所示)指定处理远程操作所需的适配器类以及响应远程调用的实际类。 (在这种情况下,将bcit.contacts.ContactsService
类作为对远程请求的响应者。)
清单7. remoting-config.xml配置文件的子集
<?xml version="1.0" encoding="UTF-8"?>
<service id="remoting-service"
class="flex.messaging.services.RemotingService">
<adapters>
<adapter-definition id="java-object" default="true"
class="flex.messaging.services.remoting.adapters.JavaAdapter"/>
</adapters>
<default-channels>
<channel ref="contacts-amf"/>
</default-channels>
<destination id="contacts">
<properties>
<source>bcit.contacts.ContactsService</source>
<!--<scope>application</scope>-->
</properties>
</destination>
</service>
结论
本文向您展示了如何编写一个Java服务器端Web应用程序,该应用程序在Tomcat中运行并回答对联系信息的请求。 您还学习了如何使用MXML和ActionScript编写Flex应用程序以创建客户端Flash应用程序。 MySQL充当数据存储,Hibernate(ORM框架)用于将Java对象转换为可以查询和更新MySQL数据库SQL语句。 最后,BlazeDS框架允许Flash应用程序进行远程过程调用并在Java服务器端Web应用程序上执行远程处理。
翻译自: https://www.ibm.com/developerworks/web/library/wa-flex4javaapps/index.html
flex+java项目创建