构建整合Hibernate,Spring和BlazeDS的Flex开发环境
1.服务器我打算使用预装了BlazeDS的Turn Key Server。
下载BlazeDS的Turnkey Server。
2.安装配置Spring。
参考http://coenraets.org/flex-spring/文章,首先下载Flex-Spring.zip文件,将Java文件展开到BlazeDS的Samples的Src目录下。
然后下载最新的Spring的包spring.jar,复制到Samples的WEB-INF的lib目录下。
接下来编辑Samples的Web.xml文件,添加Spring的配置项目
<!-- Spring configuration file (Not needed if you don't use Spring) -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<!-- Spring ContextLoaderListener (Not needed if you don't use Spring) -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
为了让Flex-Remoting能够将Spring的Bean正确传给Flex客户端,我们需要配置基于Spring的Factory。
编辑{Samples}\WEB-INF\flex\services-config.xml文件,添加
<!-- Spring factory: not needed if you don't use Spring) -->
<factories>
<factory id="spring" class="flex.samples.factories.SpringFactory"/>
</factories>
为了使用springfactory,需要将预编译好的flex-spring-factory.jar添加到web-inf的lib目录下
前面下载的Flex-spring.zip包中带了两个Spring的例子程序,为了让Spring能够正确地组装例子对应的Spring Bean,我们需要在WEB-INF目录下创建applicationContext.xml
<beans>
<bean id="rateFinderBean" class="flex.samples.spring.mortgage.SimpleRateFinder"/>
<bean id="mortgageBean" class="flex.samples.spring.mortgage.Mortgage">
<property name="rateFinder" ref="rateFinderBean"/>
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:hsql://localhost:9002/flexdemodb"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>
<bean id="productDAOBean" class="flex.samples.spring.store.SimpleProductDAO">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
接下来我们要公开这些Spring Bean的接口给Flex的客户端,编辑Remoting-Config.xml文件
<destination id="mortgageService">
<properties>
<factory>spring</factory>
<source>mortgageBean</source>
</properties>
</destination>
<destination id="productService">
<properties>
<factory>spring</factory>
<source>productDAOBean</source>
</properties>
</destination>
接下来,创建一个Flex应用,指定服务器的路径为http://localhost:8400/samples,创建下面的客户端
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:RemoteObject id="ro" destination="mortgageService"/>
<mx:TextInput id="amount" x="113" y="10"/>
<mx:Button label="Calculate" click="ro.calculate(Number(amount.text))" x="155" y="40"/>
<mx:TextInput id="monthlyPayment" text="{ro.calculate.lastResult}" x="113" y="70"/>
</mx:Application>
运行后即可。
基于ProductDAO的Spring例子类似上面的例子,要注意的是在运行前确保执行了SampleDB下的StartDB命令运行了HSQL的例子数据库。
3.安装配置Hibernate
安装配置Hibernate,主要是参考了http://www.adobe.com/devnet/flex/articles/flex_hibernate.html这篇文章。
首先,安装Mysql,然后创建consultant_db,用户名,密码指定为root,root。
然后下载Hibernate的包
- Hibernate Core 3.3.1
- Hibernate Annotations 3.4.0 GA
- Hibernate EntityManager 3.4.0 GA
- Hibernate Validator 3.1.0
将Hibernate的包全部复制到web-inf的lib下。
为了登录日志,我们使用Log4j,在Src目录下创建一个log4j.xml文件,内容如下
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration debug="false"
xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<param name="target" value="System.out" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern"
value="%d{HH:mm:ss.SSS} %-5p %-40.70C{6} %x - %m%n" />
</layout>
</appender>
<appender name="ADC_DEMO"
class="org.apache.log4j.RollingFileAppender">
<!-- Change the log path and file name here! -->
<param name="File" value="c:/temp/logs/adc.log" />
<param name="Append" value="true" />
<param name="MaxFileSize" value="2MB" />
<param name="MaxBackupIndex" value="8" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern"
value="%d{HH:mm:ss.SSS} %-5p %-40.70C{6} %x - %m%n" />
</layout>
</appender>
<appender name="LOG_APACHE"
class="org.apache.log4j.RollingFileAppender">
<!-- Change the log path and file name here! -->
<param name="File" value="c:/temp/logs/apache.log" />
<param name="Append" value="true" />
<param name="MaxFileSize" value="2MB" />
<param name="MaxBackupIndex" value="8" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern"
value="%d{HH:mm:ss.SSS} %-5p %-40.70C{6} %x - %m%n" />
</layout>
</appender>
<logger name="com.adobe" additivity="false">
<level value="DEBUG" />
<appender-ref ref="ADC_DEMO" />
<appender-ref ref="STDOUT" />
</logger>
<!-- Hibernate -->
<logger name="org.hibernate">
<level value="WARN" />
</logger>
<!-- Apache Commons -->
<logger name="org.apache.commons">
<level value="INFO" />
<appender-ref ref="LOG_APACHE" />
<appender-ref ref="STDOUT" />
</logger>
<!-- Apache Jasper -->
<logger name="org.apache.jasper">
<level value="INFO" />
<appender-ref ref="LOG_APACHE" />
<appender-ref ref="STDOUT" />
</logger>
<!-- Apache Catalina -->
<logger name="org.apache.catalina">
<level value="INFO" />
<appender-ref ref="LOG_APACHE" />
<appender-ref ref="STDOUT" />
</logger>
<!-- Apache Coyote -->
<logger name="org.apache.coyote">
<level value="INFO" />
<appender-ref ref="LOG_APACHE" />
<appender-ref ref="STDOUT" />
</logger>
<logger name="net">
<level value="WARN" />
<appender-ref ref="ADC_DEMO" />
<appender-ref ref="STDOUT" />
</logger>
<!--
DEFAULT: All log message are send to Appender ADC_DEMO and STDOUT.
Log Level Order: DEBUG, INFO, WARN, ERROR, FATAL
-->
<root>
<priority value="DEBUG" />
<appender-ref ref="STDOUT" />
<appender-ref ref="ADC_DEMO" />
</root>
</log4j:configuration>
然后创建Hibernate配置文件,在Src目录下创建META-INF目录,然后新建一个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="consultant_db">
<properties>
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" />
<property name="hibernate.connection.url" value="jdbc:mysql://localhost/consultant_db" />
<property name="hibernate.connection.username" value="root" />
<property name="hibernate.connection.password" value="root" />
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect" />
<property name="hibernate.connection.pool_size" value="6" />
<property name="hibernate.connection.autoReconnect" value="true" />
<property name="hibernate.generate_statistics" value="false" />
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.use_sql_comments" value="false" />
<property name="hibernate.hbm2ddl.auto" value="update" />
</properties>
</persistence-unit>
</persistence>
然后将例子中的consultant及其Service的Java文件及其目录结构复制到Src目录下。注意因为例子程序使用了标记,我们需要将JAVA编译器的兼容性调整为Java 5.0以上。创建好类之后,我们还要配置remoting-config.xml,公开服务器端接口
<!-- ADC Demo application -->
<destination id="consultantService">
<properties>
<source>com.adobe.demo.ConsultantService
</source>
</properties>
</destination>
最后,创建对应的Flex客户端,直接将下载的例子代码覆盖后,运行即可。注意:我们创建Flex客户端及Java服务器端集成工程时,最好参考这篇英文文章的配置,可以很方便地实现服务器及客户端的Debug调试。
4.基于Spring Security 2.0的安全认证
这个环境的配置主要是基于http://www.adobe.com/devnet/flex/articles/flex_security.html这篇文章。注意这篇文章的最新的程序有了一些改进,改进版本可以从http://code.google.com/p/gridshore下载。
首先编辑web.xml文件,添加过滤器定义
<filter> <filter-name>SpringSecurityFilterChain</filter-name> <filter-class>org.Springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>SpringSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
接着在将例子中的spring-security.xml,spring-business.xml, spring-intergration.xml复制到WEB-INF目录下,并将这三个文件的引用加入到web.xml中
<!-- Spring configuration file (Not needed if you don't use Spring) -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/applicationContext.xml,
/WEB-INF/spring-business.xml,
/WEB-INF/spring-integration.xml,
/WEB-INF/spring-security.xml
</param-value>
</context-param>
接着将Server端的代码复制到WEB-INF的Src目录下。注意,spring用aspectj来实现aop,所以需要将aspectj的jar放到lib目录下。
接下来配置Hibernate特性,在classes目录下创建Database.properties文件,内容如下
hibernate.sql.dialect=org.hibernate.dialect.MySQL5Dialect
hibernate.sql.generateddl=true
hibernate.sql.show=true
jdbc.driverclass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost/consultant_db
jdbc.username=root
jdbc.password=root
然后,在Persistent.xml中添加下面内容
<persistence-unit name="books" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>nl.gridshore.samples.books.domain.Book</class>
<class>nl.gridshore.samples.books.domain.Author</class>
<properties>
<property name="hibernate.archive.autodetection" value="class"/>
</properties>
</persistence-unit>
接下来,我们同样也要公开对应的Service,在Remoting-config.xml中添加下面Service
<destination id="bookManager"> <properties> <factory>spring</factory> <source>bookManager</source> </properties> </destination> <destination id="authenticationHelper"> <properties> <source>nl.gridshore.samples.books.web.security.AuthenticationHelper </source> </properties> </destination>
最后一个需要注意的地方就是我们需要将tomcat的servlet-api.jar复制到WEB-INF的lib目录下,否则
ApplicationContext appContext = WebApplicationContextUtils.getWebApplicationContext(FlexContext.getServletConfig().getServletContext());
这句话运行时会报错。
5.一点关于Spring Security的补充
上面的例子程序使用的用户名和密码是直接定义在XML文件中的,但是实际环境中经常是需要将用户信息保存在数据库中,为此我们要修改认证Provider来使用jdbc-user-service。
编辑spring-security.xml,添加内容
<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:jdbc-user-service data-source-ref="dataSource"
users-by-username-query="SELECT U.username, U.password, U.accountEnabled AS 'enabled' FROM User U where U.username=?"
authorities-by-username-query="SELECT U.username, R.name as 'authority' FROM User U JOIN Authority A ON u.id = A.userId JOIN Role R ON R.id = A.roleId WHERE U.username=?"
/>
</security:authentication-provider>
JDBC User Service对应的Schema是
create table users(
username varchar_ignorecase(50) not null primary key,
password varchar_ignorecase(50) not null,
enabled boolean not null);
create table authorities (
username varchar_ignorecase(50) not null,
authority varchar_ignorecase(50) not null,
constraint fk_authorities_users foreign key(username) references users(username));
create unique index ix_auth_username on authorities (username,authority);;
如果应用使用的用户名表结构不一样,可以使用自定义的SQL来转换字段名,如上文所示。
如果我们想控制一个用户同一时刻只能有一个Session在线,可以在Web.xml中添加
<listener>
<listener-class>org.springframework.security.ui.session.HttpSessionEventPublisher</listener-class>
</listener>
然后将下面定义添加到Spring-security.xml中
<security:http auto-config="true">
.....
<security:concurrent-session-control max-sessions="1" />
</security:http>
注意:Flex Security那篇文章中的代码缺少设置Detail的方法调用,跟并发session控制一起使用的时候,会导致java.lang.IllegalArgumentException异常,下面是我修改后的认证方法
public AuthorizationData authenticatePrincipal(String username, String password) {
ApplicationContext appContext =
WebApplicationContextUtils.getWebApplicationContext(FlexContext.getServletConfig().getServletContext());
AuthenticationManager manager = (AuthenticationManager)appContext.getBean("_authenticationManager");
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(username,password);
//不执行SetDetails方法的话、如果使用了concurrent-session-control定义会引发异常
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetails(FlexContext.getHttpRequest()));
Authentication authentication = manager.authenticate(usernamePasswordAuthenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
return obtainGrantedAuthorities();
}
另外,例子中没有提供登录注销的功能,下面是我实现的Logout的函数
public void sessionLogout(){
HttpSession session=FlexContext.getHttpRequest().getSession(false);
if (session!=null)
session.invalidate();
SecurityContextHolder.getContext().setAuthentication(null);
SecurityContextHolder.clearContext();
}