构建整合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的包全部复制到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();
    }