Tuscany Home
 

Webx基于OSGi的改造尝试

From Tuscany中文社区

Jump to: navigation, search

目录

[编辑] 概述

[编辑] 目的

本文的目的就是讲述怎样把WebX框架改造成基于OSGi的框架的思路和过程,基于OSGi完全实现WebX实现的功能,使得WebX框架能够支持OSGi的组件式开发。

[编辑] 名词解释

  • webx——一个基于J2EE的SSH(Struts、Spring和Hibernate)轻量级框架。
  • OSGi——OSGi technology is the dynamic module system for Java™.OSGi technology is Universal Middleware.OSGi technology provides a service-oriented, component-based environment for developers and offers standardized ways to manage the software lifecycle. These capabilities greatly increase the value of a wide range of computers and devices that use the Java™ platform.(引自OSGi Alliance

[编辑] 改造思路

[编辑] Spring

springframework提供的osgi方案叫做dynamic module,这个方案提供了一个基于osgi内核、osgi bundle模式的ioc模式的应用。spring的强大的IoC特性通过dynamic module(以下称spring-osgi)在osgi之上得到了很好的应用。

[编辑] spring-osgi的工作原理

spring-osgi的构成分为两个关键部分:

  • osgi bundle的manifest.mf内容,增加Spring-Context:标记,其后的值指向一个spring的xml文件;
  • spring的xml文件中增加了支持osgi的扩展标记;
    • 通过扩展标记将bean发布为osgi服务
    • 通过扩展标记引用来自不同的bundle的服务

Image:OSGi-Spring001.gif

图中:bundle将软件分割为若干个部分,每个部分将其最有价值的部分发布为OSGi服务。同时引用其它bundle提供的OSGi服务,没有spring-osgi的时候,这个过程只能由程序自行实现,但是现在spring-osgi的IoC为开发者实现了。

springframework提供的一个重要的bundle:

org.springframework.osgi.bundle.extender(版本:1.0.0.rc2)

该bundle启动时,会“扫描”正在运行的各个bundle,并试图发现manifest.mf里面的Spring-Context,然后加载其指向的xml资源。

[编辑] spring-osgi的IoC特点

与Spring原来的IoC相比,OSGi的服务的加载被分割到每个bundle中,并且其加载顺序并不由spring决定,所以spring- osgi通过listener克服了这种无序加载所带来的困难,也就是说,当开发者声明引用一个服务的时候,spring-osgi实际上仅仅是为引用方增加了一个listener,当被引用服务“出现”的时候,它才被真正引用近来。

这个listener会“检查”每个服务,并用指定的java接口类型进行匹配,匹配成功的,就是目标/被引用服务。

[编辑] spring-osgi扩展标记的使用

  1. 首先声明扩展标记的namespace:http://www.springframework.org/schema/osgi
  2. 按照传统spring bean的声明方式,开发bean
  3. 通过<osgi:service/>将任何bean声明为osgi服务;
  4. 同时,必须在manifest.mf中,将该bean的接口以及实现类声明为Export-Package,否则,将会在运行的时候产生奇怪的错误;
  5. 以传统的方式引用一个服务,如<bean><property/></bean>这种形势;
  6. 用<osgi:reference/>声明一个对来自任何bundle的服务的引用;该项声明与5所指的引用者通过name/id来匹配。

[编辑] Struts

[编辑] struts1与spring的传统集成

在struts1的配置文件中,包含了一个plugin标签和一个controller标签,这两个标签实现了Struts1和Spring的集成

  • struts1配置文件中plugin标签使用的类是由spring提供的(org.springframework.web.struts.ContextLoaderPlugIn)

这个类负责加载指定的spring xml(资源)文件。

值得注意的是:在一个使用springframework的web应用中,还可以通过在web.xml中声明 org.springframework.web.context.ContextLoaderListener来加载制定的spring资源。如果同时使用这两种方法,应当将目标资源区分开,以避免重复加载。

  • struts1配置文件中controller标签使用的类也是由Spring提供的(org.springframework.web.struts.DelegatingRequestProcessor)

这个类负责将原来对Action的引用转为对spring bean的引用。这个controller是struts1集成spring的核心。

[编辑] 如何在osgi模式下集成struts1与spring

在新的环境下,最重要的区别是: 没有了servlet context里面的那个ApplicationContext,所以我们面临的问题是:创建我们自己的controller,由它来负责获得ApplicationContext,并从中“定位”所需的Action对象。

Image:OSGi-Spring002.png

图中:我们实现了一个RequestProcessor,它重载父类了processActionCreate方法,从spring-osgi的ApplicationContext中获得Action对象。这里的ApplicationContext实际上是一个OsgiBundleXmlApplicationContext类型,在我们的设计里,是RequestProcessor的一个静态成员。

下一步,我们需要将struts1的主配置文件(如:struts-config.xml)中的controller该为我们自己的类。 原来的plugin不再需要!

[编辑] ApplicationContext在哪里

在前面的文章里,我提到通过一个Activator动态加载jsp编译器和struts环境,但是在集成spring-osgi的模式下,我们必须将这种方式调整一下。 spring-osgi不但兼容传统spring的所有特性,同时还提供了一个BundleContextAware接口,它与ApplictionContextAware接口的用途相近,使开发者可以在spring-osgi模式下,获得OSGi的BundleContext指针。 Image:OSGi-Spring003.png

图中:我们将创建一个对象,它同时继承BundleContextAware和ApplicationContextAware接口,由于先前的“动态代码”需要BundleContext指针,我们需要将这些代码放到setBundleContext方法中;同时,为了让我们的struts所使用的controller访问到ApplicationContext,必须在setApplicationContext中保存ApplicationContext指针。我们在controller中设置一个静态成员,并在此时为这个成员“付值”。

[编辑] Action访问服务层

在本应用中,我们将原来的服务层,如BookService设计为OSGi服务。我们并不需要对原来的类作任何修改,并且,这种“服务化”将由spring-osgi替我们做。 由于在这个应用中,Action仍然作为普通的spring bean存在。我们就可以通过spring-osgi的IoC将OSGi服务“注入”Action对象中。

[编辑] Hibernate

默认情况下Hibernate通过hibernate.cfg.xml中的mapping resource来加载归入SessionFactory中管理的PO(Persistent Object),在加载时Hibernate通过Hibernate类所在的 classloader加载Mapping resource中配置的hbm映射文件,并通过cglib将hbm映射文件中指定的class生成proxy。

改为基于OSGi后产生冲突的地方就在于PO分散到不同的工程中去了,同时要保持模块化的封装性,就不能所有人都来修改封装 Hibernate模块里的hibernate.cfg.xml;另外一个冲突点在于封装Hibernate的模块的classloader无法加载到位于其他模块的PO对象,只要解决了这两个冲突,基于OSGi使用Hibernate的问题就解决了。

要解决这两个冲突,需要解决的就是如何将其他模块的PO注册到封装Hibernate的模块中,以及Hibernate的Configuration如何加载其他模块的PO和映射文件。基于OSGi的可扩展性,要将其他模块的PO注册到封装Hibernate的模块中,通过扩展点方式即可实现,我们注意到Hibernate的Configuration.addClass加载class的方式可达到和mapping resource完全一样的效果,扩展点中需要的仅为po的classname。

[编辑] 改造过程

[编辑] 环境准备

我们用到的类库中,有些是官方已经提供OSGi的支持了,有些官方还没有提供OSGi的支持,第三方提供了支持OSGi的类库,还有一些官方和第三方都没有提供对OSGi支持的类库,这些需要我们通过增加原数据的描述,把它发布成bundle。 我们的OSGi核心框架采用Eclipse的实现:Equinox,从目前来看它是较好的OSGi实现。本文使用的Eclipse的版本是3.3.1.1。

[编辑] Eclipse提供的bundle

[编辑] equinox运行环境

equinox运行环境需要下面这些bundle:

org.eclipse.core.contenttype_3.2.100.v20070319.jar
org.eclipse.core.jobs_3.3.1.R33x_v20070709.jar
org.eclipse.core.runtime_3.3.100.v20070530.jar
org.eclipse.core.runtime.compatibility.auth_3.2.100.v20070502.jar
org.eclipse.equinox.app_1.0.1.R33x_v20070828.jar
org.eclipse.equinox.common_3.3.0.v20070426.jar
org.eclipse.equinox.http.helper_1.0.0.200801161414.jar
org.eclipse.equinox.http.jetty_1.0.1.R33x_v20070816.jar
org.eclipse.equinox.http.servlet_1.0.1.R33x_v20070816.jar
org.eclipse.equinox.jsp.jasper_1.0.1.R33x_v20070816.jar
org.eclipse.equinox.launcher_1.0.1.R33x_v20070828.jar
org.eclipse.equinox.preferences_3.2.100.v20070522.jar
org.eclipse.equinox.registry_3.3.1.R33x_v20070802.jar
org.eclipse.osgi_3.3.1.R33x_v20070828.jar
org.eclipse.osgi.services_3.1.200.v20070605.jar
org.eclipse.osgi.util_3.1.200.v20070605.jar

另外Eclipse作为第三方,还提供了常用的类库的bundle,如下:

org.mortbay.jetty_5.1.11.v200706111724.jar

javax.servlet_2.4.0.v200706111738.jar
javax.servlet.jsp_2.0.0.v200706191603.jar

org.apache.commons.el_1.0.0.v200706111724.jar
org.apache.commons.logging_1.0.4.v200706111724.jar
org.apache.jasper_5.5.17.v200706111724.jar

[编辑] SpringFramework提供的bundle

Spring已经提供了OSGi的支持,这里我们用到的模块如下:

spring-aop-2.5.1.jar
spring-beans-2.5.1.jar
spring-context-2.5.1.jar
spring-core-2.5.1.jar
spring-jdbc-2.5.1.jar
spring-orm-2.5.1.jar
spring-tx-2.5.1.jar

spring-osgi-core-1.0-rc2.jar
spring-osgi-extender-1.0-rc2.jar
spring-osgi-io-1.0-rc2.jar

另外SpringFramework作为第三方,还提供了常用的类库的bundle,如下:

aopalliance.osgi-1.0-SNAPSHOT.jar

commons-beanutils.osgi-1.7.0-20071105.105336-272.jar
commons-collections.osgi-3.2-20071105.105336-303.jar

[编辑] 我们提供的bundle

[编辑] 普通bundle

我们作为第三方提供的bundle如下:

javax.servlet.jsp.jstl_1.1.2.jar
jta_1.1.0.jar
org.apache.log4j_1.2.13.jar
org.apache.commons.lang_2.1.0.jar
org.apache.commons.digester_1.7.0.jar
org.apache.taglibs.standard_1.1.2.jar
com.mysql.jdbc.Driver_5.0.4.jar
org.apache.struts_1.2.9.jar
org.extremecomponents_1.0.1.jar
com.mchange.v2.c3p0_0.9.1.jar

以上的这些bundle都是把本jar包提供的package添加到Export-Package中,并且增加了一些它所依赖的package。

另外在org.apache.log4j_1.2.13.jar里我们加入了如下的log4j.properties文件:

log4j.rootCategory=info,stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern= %d %p [%c] - <%m>%n

[编辑] 特殊bundle

[编辑] org.hibernate_3.2.5.jar

我们这里使用的是hibernate的版本是3.2.5.ga,在运行时hibernate需要如下的库:

asm.jar
asm-attrs.jar
cglib-2.1.3.jar
dom4j-1.6.1.jar
antlr-2.7.6.jar

这些库都可以在hibernate下载包中的lib目录里找到。为了简单起见,我们把这些库放到hibernate Bundle里,而不把每个库打成单独的bundle。

因为在执行过程中会出现了一个错误:

java.lang.NoClassDefFoundError:org/hibernate/proxy/HibernateProxy

这个错误对于延迟加载而言会有不小的影响,因此我们需要追查此错误的原因,根据错误堆栈信息,可追查出是cglib中的AbstractClassGenerator.create方法执行时出现了错误,找出AbstractClassGenerator的源码,跟踪后发现是在此类中无法找到HibernateProxy,是不是有些奇怪呢?仔细来分析下,为什么明明在同一个Bundle下的两个jar,却加载不到呢?问题必然还是出在 classloader上,仔细看create方法,会看到其中有个getClassLoader(),跟踪调试看getClassLoader()就会发现,当为Bulletin生成Proxy时,这个时候的ClassLoader变成了Bulletin所在Bundle的ClassLoader了,不用说,用这个ClassLoader去加载HibernateProxy自然是加载不到了,找到了问题的根源,我们修改AbstractClassGenerator类,把它的ClassLoader改为使用当前类所在的ClassLoader,修改它的getClassLoader方法,增加如下粗体部分的代码:

   public ClassLoader getClassLoader() {
        ClassLoader t = classLoader;
       if(t==null){
       	t=this.getClass().getClassLoader();
       }
       if (t == null) {
           t = getDefaultClassLoader();
       }
       if (t == null) {
           t = getClass().getClassLoader();
       }
       if (t == null) {
           t = Thread.currentThread().getContextClassLoader();
       }
       if (t == null) {
           throw new IllegalStateException("Cannot determine classloader");
       }
       return t;
   }

这个文件我们单独编译放在bundle的根目录里。

[编辑] 修改现有的bundle
  • 因为在logging里需要log4j的package,所以在org.apache.commons.logging_1.0.4.v200706111724.jar里面的META-INF/MANIFEST.MF中增加下面内容:
Import-Package: org.apache.log4j
  • 在eclipse自带的org.eclipse.equinox.jsp.jasper_1.0.1.R33x_v20070816.jar里面的META-INF/MANIFEST.MF中增加相应的Import-Package:
org.apache.struts.taglib.html,org.apache.struts.taglib.logic

[编辑] 数据库连接

在本节中,我们要完成bundle:book.hibernate,此bundle主要作用是提供整个应用的数据库连接,提供其它bundle增加hibernate支持的方法,并且向外暴露service:sessionFactory,其它bundle只要引用此service就可以进行数据库的相关操作了。

[编辑] 增加扩展点

通过上面Hibernate改造思路的讲述,下面我们增加扩展点schema文件hibernateExtension.exsd,通过可视化编辑器增加po,类型为String,修改后的文件主要内容是:

       ……
       <sequence minOccurs="1" maxOccurs="unbounded">
          <element ref="po" minOccurs="1" maxOccurs="unbounded"/>
       </sequence>
 ……
 <element name="po">
    <complexType>
       <attribute name="class" type="string" use="required">
          <annotation>
             <documentation>
             </documentation>
          </annotation>
       </attribute>
    </complexType>
 </element>
 ……

然后增加扩展点的定义文件plugin.xml,内容为:

<?xml version="1.0" encoding="UTF-8"?>
<plugin>
  <extension-point id="hibernateExtension" name="book.hibernate.extension"
                   schema="schema/hibernateExtension.exsd"/>
</plugin>

[编辑] 实现我们的SessionFactoryBean

扩展点增加后,我们需要在运行期获取到此扩展点的所有扩展,并且把扩展的类加载到SessionFactory中,因此我们继承了Spring的LocalSessionFactoryBean类,为了监听bundle注册的改变,我们实现IRegistryChangeListener接口,如下:

public class MySessionFactory extends LocalSessionFactoryBean implements IRegistryChangeListener{
 …………
}

在设置注册对象时,我们获取到该扩展点的所有扩展:

   public void setRegistry(IExtensionRegistry registry) {
       logger.info("************setRegistry");
       registry.addRegistryChangeListener(this, "book.hibernate");
       this.registry = registry;
       IExtension[] extensions = registry.getExtensionPoint(
               "book.hibernate.hibernateExtension").getExtensions();
       for (int i = 0; i < extensions.length; i++) {
           poExtensions.add(extensions[i]);
       }
   }

获取到扩展以后,需要把class加入到Hibernate的Configuration中,OSGi中每个Bundle都是独立的ClassLoader,那也就是说Hibernate所在的这个Bundle是无法加载到扩展点中 className对应的类的,所幸的是IExtension的IConfigurationElement提供了初始化扩展中class的方法,也就是说可以实现在扩展点的模块中直接创建提供扩展的模块中的类,实现如下:

   private void addHibernateConfig()
           throws HibernateException {
       logger.info("************addHibernateConfig");
       Class poClass = null;
       try {
           for (Iterator iter = poExtensions.iterator(); iter.hasNext();) {
               IExtension extension = (IExtension) iter.next();
               IConfigurationElement[] elements = extension
                       .getConfigurationElements();
               for (int j = 0; j < elements.length; j++) {
                   logger.info("************elements:"+elements[j]);
                   poClass = elements[j].createExecutableExtension("class")
                           .getClass();
                   configuration.addClass(poClass);
               }
           }
       } catch (Throwable t) {
           String message = "Hibernate config isn't add.";
           logger.error(message, t);
           throw new HibernateException(message + t);
       }
       logger.info("************addHibernateConfig OK");
   }

那么什么时候应用这些扩展点呢?我们通过重载LocalSessionFactoryBean的postProcessConfiguration方法,这个时候我们把扩展点中的类的实例加入SessionFactory的管理范围内:

   protected void postProcessConfiguration(Configuration config) throws HibernateException {
       this.configuration = config;
       this.addHibernateConfig();
   }

上面的实现方式解决了静态时Hibernate加载其他模块中的PO初始化SessionFactory的问题,但记住要保持好基于OSGi的系统的动态性的特征,要保持动态化,就得监听Hibernate模块这个扩展点的扩展实现的变化情况,当系统中增加了新的需要归入SessionFactory管理的PO时,需要动态的加入到目前的SessionFactory中,当已有的PO从系统中删除时,也需要动态的将其从目前的SessionFactory中删除,不过Hibernate的SessionFactory是不支持动态的删除和增加其中管理的PO的,当发生变化时,只能重新初始化SessionFactory了。所以我们实现了IRegistryChangeListener中的registryChanged 方法:

   public void registryChanged(IRegistryChangeEvent event) {
       IExtensionDelta[] deltas = event.getExtensionDeltas("book.hibernate",
               "hibernateExtension");
       if (deltas.length == 0)
           return;
       for (int i = 0; i < deltas.length; i++) {
           switch (deltas[i].getKind()) {
           case IExtensionDelta.ADDED:
               poExtensions.add(deltas[i].getExtension());
               break;
           case IExtensionDelta.REMOVED:
               poExtensions.remove(deltas[i].getExtension());
               break;
           default:
               break;
           }
       }
       refreshSessionFactory();
   }

按照上面的步骤完成后,Hibernate会根据映射文件中的classname去实例化该Class,而不是直接使用我们通过 Configuration.addClass时传递给Hibernate的Class,直接使用Class.forName的方法实例化扩展出的PO会出错,因为Hibernate类所在的ClassLoader和扩展的类所在的ClassLoader并不同,碰到这样的情况,就得使用OSGi提供给Bundle的DynamicImport-Package了,使用此属性OSGi就可以在运行期动态地为这个Bundle获取其他Bundle Export的Package,使用这种方法的话要求PO所在的Bundle将PO的Package Export,打开Bundle的MANIFEST.MF文件,在其中加上这一行:

DynamicImport-Package: *

[编辑] 需要的配置文件

本bundle需要如下配置文件:

  • hibernate的配置文件:hibernate.properties
hibernate.dialect=org.hibernate.dialect.MySQLDialect
hibernate.show_sql=false
hibernate.connection.release_mode=after_transaction
  • 数据库连接的配置文件:jdbc.properties
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/book
jdbc.username=root
jdbc.password=root
  • Spring的配置文件:springBeans.xml
  <osgi:reference id="iExtensionRegistry" interface="org.eclipse.core.runtime.IExtensionRegistry"/>

  <bean id="propertyConfigurer" 
    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
      <property name="locations">
          <list>
              <value>config/jdbc.properties</value>
              <value>config/hibernate.properties</value>
          </list>
      </property>
  </bean>

  <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
      <property name="driverClass" value="${jdbc.driverClassName}" />
      <property name="jdbcUrl" value="${jdbc.url}" />
      <property name="user" value="${jdbc.username}" />
      <property name="password" value="${jdbc.password}" />
      <property name="initialPoolSize" value="20" />
      <property name="minPoolSize" value="20" />
      <property name="maxPoolSize" value="50" />
      <property name="acquireIncrement" value="5" />
      <property name="maxIdleTime" value="10" />
      <property name="maxStatements" value="0" />
      <property name="testConnectionOnCheckin" value="true" />
      <property name="testConnectionOnCheckout" value="true" />
      <property name="idleConnectionTestPeriod" value="30" />
  </bean>

  <bean id="sessionFactory" class="book.hibernate.MySessionFactory">
      <property name="dataSource" ref="dataSource" />
      <property name="configLocations">
          <list></list>
      </property>
      <property name="hibernateProperties">
          <props>
              <prop key="hibernate.dialect">${hibernate.dialect}</prop>
              <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
              <prop key="hibernate.connection.release_mode">
                   ${hibernate.connection.release_mode}
              </prop>
          </props>
      </property>
      <property name="registry" ref="iExtensionRegistry" />
  </bean>

  <osgi:service id="factory" ref="sessionFactory" interface="org.hibernate.SessionFactory"/>

[编辑] bundle元数据

加入spring的配置:

Spring-Context: config/springBeans.xml

[编辑] 通用功能

在本节中我们要完成bundle:book.common,主要是把应用中一些公共的类放在这里,使得系统结构清晰。

把webx中的下列代码移至这里:

com.sitechasia.webx.book.utils
com.sitechasia.webx.book.vo
com.sitechasia.webx.book.web.aop
com.sitechasia.webx.components.xtable.cell
com.sitechasia.webx.components.xtable.controller
com.sitechasia.webx.components.xtable.toolbar
com.sitechasia.webx.components.xtable.util

并且在MANIFEST.MF文件中Export-Package下导出。

[编辑] 持久化层

本节我们要完成bundle:book.dao,主要完成持久化的工作。

[编辑] 代码

把webx的下列代码移至此处,不需做任何改动:

com.sitechasia.webx.book.dao
com.sitechasia.webx.book.dao.impl
com.sitechasia.webx.book.model

并且把hibernate的hbm.xml配置文件移动到类的目录中,并且文件名一定要用类名.hbm.xml,涉及到的配置文件如下:

BookDO.hbm.xml
CategoryDO.hbm.xml

[编辑] 增加扩展

扩展book.hibernate中定义的扩展点,把类加入到sessionFactory的管理范围中,扩展点的配置文件plugin.xml的内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<plugin>
  <extension point="book.hibernate.hibernateExtension">
     <po class="com.sitechasia.webx.book.model.CategoryDO"/>
  </extension>
  <extension point="book.hibernate.hibernateExtension">
     <po class="com.sitechasia.webx.book.model.BookDO"/>
  </extension>
</plugin>

[编辑] Spring配置

本bundle中需要引用book.hibernate中定义的sessionFactory,在定义的两个dao的bean里都要引用sessionFactory,并且把这两个bean发布为服务,供book.service使用

<osgi:reference id="sessionFactory" interface="org.hibernate.SessionFactory"/>

<bean id="categoryDao" class="com.sitechasia.webx.book.dao.impl.CategoryDaoImpl">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

<bean id="bookDao" class="com.sitechasia.webx.book.dao.impl.BookDaoImpl">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

<osgi:service id="cDao" ref="categoryDao" interface="com.sitechasia.webx.book.dao.ICategoryDao"/>

<osgi:service id="bDao" ref="bookDao" interface="com.sitechasia.webx.book.dao.IBookDao"/>

同样需要在META-INF\MANIFEST.MF中指定这个配置文件:

Spring-Context: config/springBeans.xml

[编辑] 服务层

本节我们要完成bundle:book.service,这里是主要的业务代码,利用book.dao提供的功能进行持久化工作。

[编辑] 代码部分

把webx中的下列代码移动到这里,无需做任何改动:

com.sitechasia.webx.book.service
com.sitechasia.webx.book.service.impl

[编辑] Spring配置

本bundle中需要book.dao提供的两个dao服务,在定一个service bean中根据需要引用这两个服务,并且把定义的两个bean发布为服务,供book.web使用,具体的定义如下:

<osgi:reference id="categoryDao" interface="com.sitechasia.webx.book.dao.ICategoryDao"/>
     
<osgi:reference id="bookDao" interface="com.sitechasia.webx.book.dao.IBookDao"/>

<bean id="category" class="com.sitechasia.webx.book.service.impl.CategoryServiceImpl">
    <property name="categoryDao" ref="categoryDao"/>
</bean>
     
<bean id="book" class="com.sitechasia.webx.book.service.impl.BookServiceImpl">
    <property name="categoryDao" ref="categoryDao"/>
    <property name="bookDao" ref="bookDao"/>
</bean>

<osgi:service id="categoryService" ref="category" 
              interface="com.sitechasia.webx.book.service.ICategoryService" />
     
<osgi:service id="bookService" ref="book" 
              interface="com.sitechasia.webx.book.service.IBookService" />

同样需要在META-INF\MANIFEST.MF中指定这个配置文件:

Spring-Context: config/springBeans.xml

[编辑] web层

本节我们要完成bundle:book.web,主要是提供web应用程序的入口,页面的资源,通过调用服务层来实现要完成的功能。

[编辑] 代码部分

[编辑] 已有代码

  • 把webx的下面代码(除了package:aop)移动到此处,不需做任何改动:
com.sitechasia.webx.book.web
  • 把webx中的WebRoot目录移动到此处(我们把目录名为小写:webroot),删除WEB-INF下的classes和lib目录,并且修改web.xml文件,删除web-app元素内的所有内容,即:
<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">
</web-app>
  • 由于使用的所有的其它的类库都是通过bundle的方式访问,并且访问不到其它bundle的META-INF下的tld文件,所以需要把用到的下列文件中的tld文件拷贝到webroot\WEB-INF下面:
standard-1.1.2.jar
    c.tld
    c-1_0.tld
    c-1_0-rt.tld
    fmt.tld
    fmt-1_0.tld
    fmt-1_0-rt.tld
    fn.tld
    permittedTaglibs.tld
    scriptfree.tld
    sql.tld
    sql-1_0.tld
    sql-1_0-rt.tld
    x.tld
    x-1_0.tld
    x-1_0-rt.tld
struts-1.2.9.jar
    struts-bean.tld
    struts-html.tld
    struts-logic.tld
    struts-nested.tld
    struts-tiles.tld
extremecomponents-1.0.1.jar
    extremecomponents.tld

[编辑] 创建controller

根据前面改造思路所述,我们创建了类DelegatingRequestProcessor作为controller:

public class DelegatingRequestProcessor extends RequestProcessor {
    private Log log = LogFactory.getLog(this.getClass());
     public static ApplicationContext ac;
     protected Action processActionCreate(HttpServletRequest request,
       HttpServletResponse response, ActionMapping mapping)
       throws IOException {
       String prefix = mapping.getModuleConfig().getPrefix();
       String path = mapping.getPath();
       String beanName = prefix + path;
       Action action = (Action) ac.getBean(beanName);
       action.setServlet(this.servlet);
       return action;
     }
}

[编辑] 创建MyContextAware

我们创建类MyContextAware来加载各种资源:

public class MyContextAware implements BundleContextAware,ApplicationContextAware {
          …………
}

我们将setBundleContext方法作为程序的入口,该方法是由spring-osgi调用的:

public void setBundleContext(BundleContext bc) {
    try {
        httpServiceTracker = new HttpServiceTracker(bc);
       httpServiceTracker.open();
    } catch (Exception ex) {
       ex.printStackTrace();
    }
}

以下是HttpServiceTracker的代码,用来加载各种资源:

private class HttpServiceTracker extends ServiceTracker {
   public HttpServiceTracker(BundleContext context) {
       super(context, HttpService.class.getName(), null);
   }

   public Object addingService(ServiceReference reference) {
       final HttpService httpService = (HttpService) context.getService(reference);
       try {
           log.info("************start init book.web");
           HttpContext commonContext = new BundleEntryHttpContext(context
               .getBundle(), "/webroot");
           httpService.registerResources("/web", "/index.html",
               commonContext);
           httpService.registerResources("/web/scripts/extremetable",
               "/scripts/extremetable", commonContext);
           httpService.registerResources("/web/styles/extremetable",
               "/styles/extremetable", commonContext);
           httpService.registerResources("/web/images/extremetable",
               "/images/extremetable", commonContext);

           Properties params = new Properties();
           params.put("extremecomponentsPreferencesLocation",
               "/config/extremetable.properties");
           Servlet adaptedJspServlet = new ContextPathServletAdaptor(
               new JspServlet(context.getBundle(), "/webroot"), "/web");
           httpService.registerServlet("/web/*.jsp",
                       new ContextInitParametersServletAdaptor(
                           adaptedJspServlet, params), null,
                       commonContext);

           Dictionary initparams = new Hashtable();
           initparams.put("servlet-name", "action");
           initparams.put("config", "/WEB-INF/config/struts-config.xml");
           initparams.put("debug", "2");
           initparams.put("detail", "2");
           Servlet adaptedActionServlet = new ContextPathServletAdaptor(
               new ActionServlet(), "/web");
           httpService.registerServlet("/web/*.do", adaptedActionServlet,
               initparams, commonContext);
           log.info("************init book.web OK");
           } catch (Exception ex) {
           }
           return httpService;
   }
}

[编辑] 配置文件

[编辑] Struts配置文件

把webx中的

resource/modules/book/struts-conf/struts-conf.xml

文件移动到目录:

webroot/WEB-INF/config

并且把webx中的

resource/modules/struts.xml

的内容移动到struts-conf.xml文件中(不需要plugin部分),并且把controller的改为我们上面增加的类DelegatingRequestProcessor,具体内容如下:

<struts-config>
   <global-exceptions>
       <exception handler="com.sitechasia.webx.core.exception.support.StrutsExceptionHandler" 
            key="GLOBAL_ERROR_CODE" path="/commons/error.jsp" type="java.lang.Exception" />
   </global-exceptions>

   <action-mappings>
       <action path="/book" scope="request" validate="false" parameter="method">
         <forward name="list" path="/pages/com/sitechasia/webx/book/book_list.jsp"/>
         <forward name="save" path="/book.do?method=list"/>
         <forward name="doDeleteByIds" path="/book.do?method=list"/>
         <forward name="doEdit" path="/pages/com/sitechasia/webx/book/book_edit.jsp"/>
       </action>
       <action path="/category" scope="request" validate="false" parameter="method">
         <forward name="list" path="/pages/com/sitechasia/webx/book/category_list.jsp"/>
         <forward name="doSave" path="/category.do?method=list"/>
         <forward name="doDeleteByIds" path="/category.do?method=list"/>
         <forward name="doEdit" path="/pages/com/sitechasia/webx/book/category_edit.jsp"/>
       </action>
   </action-mappings>

   <controller>
       <set-property property="processorClass" 
                     value="com.sitechasia.webx.book.web.DelegatingRequestProcessor" />
   </controller>

   <message-resources parameter="MessageResources" />

</struts-config>

[编辑] Spring配置

我们需要引用两个服务层暴露的service,并且在两个action中引用,把MyContextAware描述为一个bean,通过Spring来实例化。

<osgi:reference id="categoryService" interface="com.sitechasia.webx.book.service.ICategoryService"/>
<osgi:reference id="bookService" interface="com.sitechasia.webx.book.service.IBookService"/>
<bean id="myContextAware" class="com.sitechasia.webx.book.web.MyContextAware"/>
<bean id="category" name="/category" class="com.sitechasia.webx.book.web.CategoryAction">
    <property name="categoryService" ref="categoryService"/>
</bean>
<bean id="book" name="/book" class="com.sitechasia.webx.book.web.BookAction">
   <property name="bookService" ref="bookService"/>
</bean>

同样需要在META-INF\MANIFEST.MF中指定这个配置文件:

Spring-Context: config/springBeans.xml

[编辑] 总结

上面这些只是把一些主要的工作做完了,除此之外还有一些细节,比如事务、权限验证、错误处理等方面还有待解决。

[编辑] 参考资料

Personal tools