Enterprise RIA with Spring 3, Flex 4 and GraniteDS
Adobe Flex is one of the most widely used client technologies for building rich applications and Spring 3 is one of the most popular Java application frameworks. These two technologies make a great combination for building enterprise applications with a modern looking and rich user interface.
There are various options to integrate them, that each have their pros and cons, such as Web/REST services and the Spring-Flex project promoted by Adobe and SpringSource. There are lots of articles and resources about them, here I will focus on an alternative approach using the open source project GraniteDS.
GraniteDS is based on a cleanroom implementation of the AMF3 remoting and messaging protocols, and has been historically the first open source implementation of the AMF3 protocol in Java. It has been providing out-of-the-box integration with Spring very early and this integration has continually been improved with each new version of GraniteDS, following a few core principles :
- Provide a fully integrated Spring/Flex/GraniteDS RIA platform that makes configuration and integration code mostly inexistant. The platform includes in particular the tools and Flex client libraries necessary to easily use all features of Spring and its associated technologies (persistence, security...).
- Promote type-safety as much as possible in both Java and AS3 applications, ensuring that most integration issues can be detected early at compile/build time.
These core design choices make GraniteDS very different from for example Adobe BlazeDS that has only a server-side part. In this article I will show this concept of RIA platform at work by building a simple application using the following features :
- Flex AMF remoting to Spring services.
- Support for Hibernate/JPA detached entities directly in the Flex application. Bye bye DTOs and lazy initialization exceptions.
- Support for the Bean Validation API (JSR-303) with the corresponding Flex validators.
- Support for Spring Security 3 and Flex components that integrate with server-side authorization.
- Support for 'real-time' data push.
As a side note, GraniteDS still supports the classic Flex RemoteObject API and is thus a close drop-in replacement for BlazeDS with some useful enhancements, but provides an alternative Flex API called Tide that is easier to use and brings the full power of the platform.
Project setup
We have to start somewhere, and the first step is to create the Spring application. This is no big deal, we could just start by a standard Spring MVC application, and just add a few GraniteDS elements in the Spring application context. To make things easier, I'm going to use a Maven archetype (Maven 3 recommended) :
mvn archetype:generate
-DarchetypeGroupId=org.graniteds.archetypes
-DarchetypeArtifactId=graniteds-tide-spring-jpa-hibernate
-DgroupId=org.example
-DartifactId=gdsspringflex
-Dversion=1.0-SNAPSHOT
This creates a basic project skeleton that includes persistence, security and real-time data push features. As a starting point you can simply build the project and run it with the Maven jetty plugin :
cd gdsspringflexThen browse http://localhost:8080/gdsspringflex/gdsspringflex.swf, and log in with admin/admin or user/user.
mvn install
cd webapp
mvn jetty:run-war
The structure of the project is a classic multi-module Maven project with a Flex module, a Java module and a Web application module. It uses the very nice flexmojos plugin to build the Flex application with Maven.
<b>gdsspringflex</b>
|- pom.xml
|- flex
|- pom.xml
|- src/main/flex
|- Main.mxml
|- Login.mxml
|- Home.mxml
|- java
|- pom.xml
|- src/main/java
|- org/example/entities
|- AbstractEntity.java
|- Welcome.java
|- org/example/services
|- ObserveAllPublishAll.java
|- WelcomeService.java
|- WelcomeServiceImpl.java
|- webapp
|- pom.xml
|- src/main/webapp
|- WEB-INF
|- web.xml
|- dispatcher-servlet.xml
|- spring
|- app-config.xml
|- app-jpa-config.xml
|- app-security-config.xml
If we forget about the default generated application sources in the Flex and Java modules, and focus only on configuration, the most interesting files are web.xml and the app*config.xml Spring configuration files.
web.xml basically includes Spring 3 listeners, a Spring MVC dispatcher servlet mapped on /graniteamf/* that will handle AMF requests, and a Gravity servlet for Jetty mapped on /gravityamf/* (Gravity is the name of the GraniteDS Comet-based messaging implementation).
<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>GraniteDS Tide/Spring</display-name>
<description>GraniteDS Tide/Spring Archetype Application</description>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/app-config.xml,
/WEB-INF/spring/app-*-config.xml
</param-value>
</context-param>
<!-- Spring listeners -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<!-- Spring MVC dispatcher servlet that handles incoming AMF requests on the /graniteamf endpoint -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/graniteamf/*</url-pattern>
</servlet-mapping>
<!-- Gravity servlet that handles AMF asynchronous messaging request on the /gravityamf endpoint -->
<servlet>
<servlet-name>GravityServlet</servlet-name>
<servlet-class>org.granite.gravity.jetty.GravityJettyServlet</servlet-class>
<!--servlet-class>org.granite.gravity.tomcat.GravityTomcatServlet</servlet-class-->
<!--servlet-class>org.granite.gravity.jbossweb.GravityJBossWebServlet</servlet-class-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>GravityServlet</servlet-name>
<url-pattern>/gravityamf/*</url-pattern>
</servlet-mapping></span>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>
The reason why there is a specific servlet for Gravity is that it is optimized to use the specific asynchronous capabilities of the underlying servlet container to get better scalability, and this can not be achieved with the default Spring MVC dispatcher servlet. That's also why this is necessary to configure different servlet implementations depending on the target container (Tomcat, JBossWeb...).
Next is the main Spring 3 configuration that is mostly basic Spring MVC stuff :
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:graniteds="http://www.graniteds.org/config"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.graniteds.org/config http://www.graniteds.org/public/dtd/2.1.0/granite-config-2.1.xsd">
<!-- Annotation scan -->
<context:component-scan base-package="org.example"/>
<!-- Spring MVC configuration -->
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>
<!-- Configuration of GraniteDS -->
<graniteds:flex-filter url-pattern="/*" tide="true"/>
<!-- Simple messaging destination for data push -->
<graniteds:messaging-destination id="welcomeTopic" no-local="true" session-
selector="true"/>
</beans>
The main thing concerning GraniteDS is the flex-filter declaration. There is also an example messaging topic that is used by the default Hello World application. app-jpa-config.xml contains the JPA configuration and does not include anything about GraniteDS. Lastly Spring Security :
<beansOnce again mostly Spring stuff, we just find here the tide-identity bean that is used to integrate Spring Security with the Tide Identity Flex component.
xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:graniteds="http://www.graniteds.org/config"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd
http://www.graniteds.org/config http://www.graniteds.org/public/dtd/2.1.0/granite-config-2.1.xsd"
default-autowire="byName"
default-lazy-init="true">
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider>
<security:user-service>
<security:user name="admin" password="admin" authorities="ROLE_USER,ROLE_ADMIN" />
<security:user name="user" password="user" authorities="ROLE_USER" />
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
<security:global-method-security secured-annotations="enabled" jsr250-annotations="enabled"/>
<!-- Configuration for Tide/Spring authorization -->
<graniteds:tide-identity/>
<!-- Uncomment when there are more than one authentication-manager :
<graniteds:security-service authentication-manager="authenticationManager"/>
-->
</beans>
We're done for the server-side setup. GraniteDS detects automatically most of the Spring configuration at startup and configures itself accordingly, so these 10 lines of XML are generally enough for most projects. If you have a look at the various Maven POMs, you will find the dependencies on the server-side GraniteDS jars and client-side GraniteDS swcs. You can also have a look at the Flex mxml code of the example application generated by the archetype, but for now I will start from scratch.
Remoting to Spring services
First the traditional Hello World and its incarnation as a Spring 3 service :
@RemoteDestination
public interface HelloService {
public String hello(String name);
}
@Service("helloService")
public class HelloServiceImpl implements HelloService {
public String hello(String name) {
return "Hello " + name;
}
}
You have probably noticed the @RemoteDestination annotation on the interface, meaning that the service is allowed to be called remotely from Flex. Now the Flex application :
<s:ApplicationYou can rebuild and restart the project with :
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns="*"
preinitialize="init()">
<fx:Script>
<![CDATA[
import org.granite.tide.Component;
import org.granite.tide.spring.Spring;
import org.granite.tide.events.TideResultEvent;
import org.granite.tide.service.DefaultServiceInitializer;
private function init():void {
Spring.getInstance().initApplication();
Spring.getInstance().getSpringContext().serviceInitializer = new DefaultServiceInitializer('/gdsspringflex');
}
[In]
public var helloService:Component;
private function hello(name:String):void {
helloService.hello(name,
function(event:TideResultEvent):void {
message.text = "Message: " + (event.result as String);
}
);
}
]]>
</fx:Script>
<s:VGroup width="100%">
<s:Label text="Name"/>
<s:TextInput id="tiName"/>
<s:Button label="Hello" click="hello(tiName.text)"/>
<s:Label id="message"/>
</s:VGroup>
</s:Application>
mvn clean install
cd webapp
mvn jetty:run-war
Well, that's not exactly the shortest Hello World application, but let's see the interesting bits :
- The init() method is called in the preinitialize handler. It does two things: initialize the Tide framework with Spring support, and declare a service initializer with the context root of our application. The task of the service initializer is to setup all remoting/messaging stuff, such as server endpoint uris, channels, etc... Basically it replaces the traditional Flex static services-config.xml file. Other implementations can easily be built for example to retrieve the channels configuration dynamically from a remote file (useful with an AIR application for example).
- A client proxy for the helloService Spring bean is injected in the mxml by using the annotation [In]. By default the variable name should match the Spring service name, otherwise we would have to specify the service name in the [In("helloService")] annotation. This may seem like a 'magic' injection but as we asked for an instance of Component, the framework knows for sure that you want a client proxy for a remote bean.
- The hello function demonstrates the basic Tide remoting API. It simply calls a remote method on the client proxy with the required arguments and provide callbacks for result and fault events, much like jQuery, so you don't have to deal manually with event listeners, asynchronous tokens, responders and all the joys of RemoteObject.
Now that we got the basics working, we can improve this a little. The Flex project POM is configured to automatically generate (with the GraniteDS Gas3 generator embedded in flexmojos) typesafe AS3 proxies for all Java interfaces named *Service and annotated with @RemoteDestination. That means we could also simply write this :
import org.example.services.HelloService;
[In]
public var helloService:HelloService;
This looks like a minor cosmetic change, but now you benefit from code completion in your IDE and from better error checking by the Flex compiler. Going even further, the whole injection can be made completely typesafe and not rely on the service name any more by using the annotation [Inject] instead of [In] (note that we can now give our injected variable any name) :
[Inject]And as the injection in the client now uses the interface name, we don't have to give a name to the Spring service any more :
public var myService:HelloService;
private function hello(name:String):void {
myService.hello(name,
function(event:TideResultEvent):void {
message.text = "Message: " + (event.result as String);
}
);
}
@Service
public class HelloServiceImpl implements HelloService {
public String hello(String name) {
return "Hello " + name;
}
}
Now you can do any refactoring you want on the Java side, like changing method signatures, Gas3 will then regenerate the AS3 proxies and the Flex compiler will immediately tell you what's wrong. Another interesting thing is that the Flex mxml now looks like a Spring bean, making very easy for Spring developers to get started with Flex.
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)





Comments
Nuttapong Maneenate replied on Sat, 2011/02/12 - 9:59am
Lucas Marino replied on Tue, 2011/02/22 - 11:05pm
Arthur Vernon replied on Wed, 2011/02/23 - 5:22pm
William Draï replied on Thu, 2011/02/24 - 3:55pm
in response to:
Nuttapong Maneenate
@Arthur : Right, the recommended solution is to keep only the observer. Unfortunately there is currently no way of knowing where the update comes from (local, remoting or push).
Chris Jansen replied on Fri, 2011/02/25 - 11:24am
Richard Van Der Laan replied on Tue, 2011/03/08 - 6:09am
Hi there,
I am unable to extract the extended data from the validation exception, as described in the example:
The problem is that fault (type mx.rpc.Fault) has no extendedData property and the property cannot dynamically be accessed. Is this due to an API change? Or am I doing something wrong?Deepak Srivastav replied on Fri, 2011/03/11 - 8:01am
iain starks replied on Fri, 2011/04/01 - 5:43am
Eric Be replied on Mon, 2011/05/02 - 11:12pm
Lou Leal replied on Mon, 2011/07/18 - 3:18pm
in response to:
Richard Van Der Laan
Dmitry Kv replied on Tue, 2011/08/09 - 11:03am
in response to:
Lou Leal
Khent Johnson replied on Fri, 2011/09/02 - 2:41pm
Sanjay Patel replied on Wed, 2012/08/29 - 7:53am
Thanks. This is very nice article to get started.
Can you please add or guide how to get flexmojos wrapper goal working?
Gary Huitson replied on Wed, 2012/09/12 - 10:03am
in response to:
Richard Van Der Laan
I think theres a mistake in the example code
The line should read...
Alert.show(event.extendedData.invalidValues[0].message);
The extendedData is a property of event not event.fault