65.9K
CodeProject 正在变化。 阅读更多。
Home

模块化 J2EE,使用 Spring DM、Zk 和 EclipseLink

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (9投票s)

2011 年 4 月 21 日

CPOL

15分钟阅读

viewsIcon

58801

downloadIcon

417

本文介绍如何使用这些开源框架来创建一个基本的模块化 Java 企业应用程序

目录

引言

让我们玩吧!想象一下玩《战神》(0) 这种动作游戏,难度设置为“噩梦”。现在,你能想象轻松击败最终 Boss “宙斯”吗?也许如果游戏中有 Bug 或者作弊,你或许能想象获胜,但过程会很艰难!我们如何在不通过所有任务的情况下打败他呢?

这是不可能的;为了在噩梦难度下面对宙斯,我们需要通过从第一个到最后一个的所有任务。在这篇文章中,我们将做到这一点:我们将玩 J2EE 级别的第一个任务,但我们会以噩梦难度来玩。

本文介绍如何使用这些开源框架来创建一个基本的模块化 Java 企业应用程序:一个 OSGi 实现、Spring、Zk、EhCache、EclipseLink 以及其他一些。我会尽量保持简单。但是!这个“噩梦”和戏剧性的故事在哪里?稍后你就能自己回答这个问题了。

啊!我忘了告诉你们,Zk 框架不支持 OSGi 或 EhCache =)。别担心,你们会体验到的。另外,我之所以说“Zk”和“EclipseLink”,并不意味着我偏爱某个框架,因为本文并不偏袒任何一个。这是一种挑战,是将特定框架投入实际应用的挑战,仅此而已。

你可能会说,“这听起来很有趣,但我是个新手。‘OSGi’和‘EhCache’是什么,还有……?”先做一些研究,然后再回来。这里有一个完整的章节,专门用于在你脑海中清晰地描绘所有这些东西。

在你脑海中

如果你真的研究了 OSGi,或者已经了解了它,或者只是听说过 OSGi(1),你可以跳过这个简短的描述

“OSGi,嗯,它只是一个允许我们创建模块化应用程序的框架,因此我们可以轻松地添加/删除插件,并且应用程序可以无问题地保持运行。专家称这些插件为 bundle,它类似于 Eclipse”

有了这个有趣的框架,我们可以在开发中更有条理,但别忘了 Eclipse 是我们的“厨房”。那我们其他的食材呢,比如 Zk(2)?

“嗯…… Zk!它是一个 Java 的 Web 框架,类似于 Adobe Flex。但是,Zk 不使用 Flash 或 MoonLight,而是生成 HTML。等等,它就像带 Ajax 工具包的 ASP.Net。”

是的,很酷,拖放、效果、无回发等……非常好的 RIA。接下来是 Spring 和 Spring DM(4,5)

“Spring 和 Spring DM,一站式(或两站式?)”

首先,这不是描述,而是一个口号。其次,是的;它有很多模块,可能涵盖了开发 Java 应用程序(桌面或 Web)所需的一切。最后但同样重要的是 EclipseLink 和 EHCache (6,7)

“EclipseLink 是一个用于持久化的 ORM 框架,用于插入、更新等……它比传统的 JDBC 更强大。EhCache 的名字就说明了它的用途,用于在内存或硬盘中缓存东西(对象)。”

此外,EclipseLink 默认支持 OSGi,而 EhCache 则需要我们与它搏斗才能支持 OSGi。关于这些框架还有其他一些秘密,但我们稍后会发现。

现在在你脑海中的大图景是……噔噔噔噔

image001.jpg

别担心,这只是一个游戏,我们在这里组装它,以便最终能得到像这样或像这样

image002.jpg

听到(读到)“太棒了!你已经完成了 J2EE Level:Nightmare 的第一个任务!”真是太棒了。这也意味着我们可以在关闭这个视觉小说后,弄脏我们的双手来设置“厨房”(环境)。

环境设置

还记得我们在前几章中提到的“Eclipse”这个词吗?现在是时候用简单的步骤来设置它了,这样我们就能熟悉这个新世界(哦,厨房)。

不要下载集成的版本,使用“UPDATE SITE INSTALLATION”,这样我们就可以在一个环境中拥有所有这些工具。或者,如果你喜欢探索这些工具,随心所欲——只是不要迷失方向。

最后,我们的环境看起来是这样的

image003.gif

我们可以看到 Zk 的东西,在左上角是“Spring Elements”。

现在,为了在 Eclipse 中创建我们的 OSGi 开发平台,我们转到菜单 Window > Preferences > Target Platform。之后,我们创建一个名为“J2EE Nightmare”的新目标,并添加所有支持 OSGi 的 JAR,这意味着在我们 MANIFEST 文件中具有属性的 JAR 可以显示在 OSGi 世界中,例如

image004.jpg

(Target creation)
 
[START-CODE]
Manifest-Version: 1
Ant-Version: Apache Ant 1.8.1
Bnd-LastModified: 1302002802162
Bundle-ManifestVersion: 2
Bundle-Name: zk
Bundle-SymbolicName: zk
Bundle-Version: 0
Created-By: 1.6.0_20 (Sun Microsystems Inc.)
Export-Package: metainfo.mesg,metainfo.tld,metainfo.zk,org.zkoss.zk,org...
[END-CODE]

你注意到什么了吗?没有?向上滚动并重新阅读“我忘了告诉你,Zk 框架不支持 OSGi”,现在我们看到 Zk 的 *MINEFIST* 文件包含 OSGi 声明!这是什么意思?是的,我已经为你添加了,只需下载所有 JAR 并添加它们。为了区分这些 JAR,我添加了前缀“adrabi-osgi-”,如果你愿意,可以自行更改。

你可以在这里下载所有这些 JAR:https://sourceforge.net/projects/modularj2ee/files/platform/,名为 *platform_1.0.0.zip* 的 zip 文件。

测试时间!我们想创建一个新的 OSGi 运行配置,并且我们取消勾选三个 **bundles**

  • com.springsource.junit
  • org.springframework.test
  • org.springframework.osgi.test

这些是测试 bundle,也是你的作业 :D

image005.gif

另外,我们在 Settings 中勾选“Clear the configuration area before launching”。现在点击“Run”按钮,一秒钟后,你会看到一个非常漂亮的红色警告

[BEGIN-CODE]
log4j:WARN No appenders could be found for logger (org.springframework.osgi.web.tomcat.internal.Activator).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See https://logging.apache.ac.cn/log4j/1.2/faq.html#noconfig for more info.
[END-CODE]

如果你匆忙回来查看错过了什么,请放轻松,因为这是一个正常的警告,我们将在开始之前修复它。

开始之前

我们首先为 Log4j 创建一个 fragment(fragment 是 bundle 的一部分)。

日志 Fragment

我们转到菜单 New > Project ... > Plug-in Development > Fragment Project。

image006.gif

我们给项目命名并选择“an OSGi framework”。之后(你还记得吗?)“**fragment 是 bundle 的一部分**”,我们需要一个宿主 bundle!它是 log4j “com.springframework.apache.log4j”(点击浏览器按钮并输入“com”以便你能列出它们)

image007.gif

在这个版本中,我们删除了“.qualifier.”。然后我们在该项目的根目录下创建一个名为 *log4j.properties* 的文件,配置如下

[BEGIN-CODE]
# show log in console
log4j.rootLogger=info, A
log4j.appender.A=org.apache.log4j.ConsoleAppender
log4j.appender.A.layout=org.apache.log4j.PatternLayout
log4j.appender.A.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n      
[END-CODE]

我们再次运行我们的 OSGi 配置,看到如下内容

[BEGIN-CODE]
osgi> 0    [Tomcat Catalina Start Thread] INFO  org.springframework.osgi.web.tomcat.internal.Activator 
 - Starting Apache Tomcat/5.5.23 ...
       1    [Tomcat Catalina Start Thread] INFO  org.springframework.osgi.web.tomcat.internal.Activator  
 - Using default XML configuration bundleresource://29.fwk7579563/conf/default-server.xml
       75   [Start Level Event Dispatcher] INFO  org.springframework.scheduling.timer.TimerTaskExecutor  
 - Initializing Timer
...
[END-CODE]

再见了,红色警告 =)。我们可以快速跳到配置验证。

验证 Fragment

我们为 bundle “com.springsource.javax.validation”创建另一个 fragment,但现在我们需要做一些更复杂的事情。我们还需要在 META-INF 中创建一些目录

  • 包含文件 *javax.validation.spi.ValidationProvider* 的 *services* 文件夹,其中包含 *org.hibernate.validator.HibernateValidator*。
  • 包含 Spring bean 的 *jsr303-services-cfg.xml* 和 OSGi 服务 *jsr303-osgi-cfg.xml* 的 *spring* 文件夹。

image008.jpg

而且,事情变得更有趣了,我们为默认的 Validation 工厂“JSR-303”创建了一个 Proxy(阅读有关 Proxy 的设计模式),如下所示

[BEGIN-CODE]
public class ValidatorFactoryProxy implements ValidatorFactory
{
       private ValidatorFactory factory = null;
       
       public ValidatorFactoryProxy()
       {
             this.factory = Validation.buildDefaultValidatorFactory();
       }
 
       @Override
       public Validator getValidator()
       {
             return this.factory.getValidator();
       }
 
       @Override
       public ValidatorContext usingContext()
       {
             return this.factory.usingContext();
       }
 
       @Override
       public MessageInterpolator getMessageInterpolator()
       {
             return this.factory.getMessageInterpolator();
       }
 
       @Override
       public TraversableResolver getTraversableResolver()
       {
             return this.factory.getTraversableResolver();
       }
 
       @Override
       public ConstraintValidatorFactory getConstraintValidatorFactory()
       {
             return this.factory.getConstraintValidatorFactory();
       }
 
       @Override
       public <T> T unwrap(Class<T> type)
       {
             return this.factory.unwrap(type);
       }
}
[END-CODE]

你可能还注意到另一个秘密是我们使用了 **Hibernate Validation**。我们只剩下最后一件事要做了,那就是添加

[BEGIN-CODE]
 
Require-Bundle: com.springsource.org.hibernate.validator;bundle-version="4.1.0"
 
[END-CODE]

在 MANIFEST 文件中,我们继续构建一个核心组件。

核心组件

我们保持核心非常简单。它只是用于配置将在所有应用程序中使用的基本服务

  • 我们配置数据源服务和事务。
  • 还有,缓存服务。

我们现在创建一个 **bundle** 而不是 fragment,在 Eclipse 中称为“**Plug-in Project**”(记住选择目标平台为“an OSGi framework”),给我们的项目命名,最后在 *META-INF* 文件夹中创建两个名为“config”和“spring”的目录,

  • “config”文件夹包含:ehcache 和 jdbc 的配置文件。
  • “spring”文件夹包含:Spring bean 和 OSGi 服务的配置文件。

我们将展示一些文件,其他文件可以下载项目示例并浏览它们

confg/jdbc.properties 文件

[BEGIN-CODE]
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc\:hsqldb\:mem\:jlnDatabase
jdbc.username=sa
jdbc.password=
jdbc.databasePlatform=org.eclipse.persistence.platform.database.HSQLPlatform
loadTimeWeaver.weaver=org.eclipse.equinox.weaving.springweaver.EquinoxAspectsLoadTimeWeaver
[END-CODE]

spring/core-data-cfg.xml 文件

[BEGIN-CODE]
<?xml version="1.0" encoding="UTF-8"?>
<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:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation=
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
 <a href="http://www.springframework.org/schema/context">http://www.springframework.org/schema/context</a> http://www.springframework.org/schema/context/spring-context.xsd
             http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
 
       <!-- Config properties  -->
       <context:property-placeholder location="classpath:/META-INF/config/jdbc.properties"/>
 
       <!-- Config data source -->
       <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" 
           init-method="createDataSource" destroy-method="close">
             <property name="driverClassName" value="${jdbc.driverClassName}"></property>
             <property name="url" value="${jdbc.url}"></property>
             <property name="username" value="${jdbc.username}"></property>
             <property name="password" value="${jdbc.password}"></property>
       </bean>
 
       <!-- Config entity manager -->
       <bean id="entityManagerFactory" 
           class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
             <property name="dataSource" ref="dataSource"></property>
             <property name="jpaVendorAdapter">
                    <bean class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
                           <property name="databasePlatform" value="${jdbc.databasePlatform}">
                           </property>
                    </bean>
             </property>
             <property name="loadTimeWeaver">
                    <bean class="${loadTimeWeaver.weaver}"></bean>
             </property>
             <property name="jpaProperties">
                    <props>
                           <prop key="eclipselink.logging.level">FINEST</prop>
                           <prop key="eclipselink.ddl-generation">create-tables</prop>
                    </props>
             </property>
             <property name="persistenceXmlLocation" 
                  value="classpath*:/META-INF/persistence/**/persistence.xml"></property>
       </bean>
       
       <!-- Config transaction -->
       <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
             <property name="entityManagerFactory" ref="entityManagerFactory"></property>
       </bean>
</beans>
 
[END-CODE]

此配置用于使用 HSQL(8) 作为数据库,并使用 DBCP(9) 组件进行“**连接池**”,注意它还有另一个秘密,让我们继续在服务组件中工作。

服务组件

我们编写精美的代码来构建我们的组件服务、事务、缓存服务,以及服务接口和实现。我们还需要一些实体!我们将在下一节“无聊的故事”中快速完成。

无聊的故事:领域

正如我们所说,我们将快速完成。我也懒得去思考和输入一个大的实体。因此,我们将使用本文的一个现成实体:http://www.vogella.de/articles/JavaPersistenceAPI/article.html。不用猜了!

好的,我们为实体组件创建另一个 **bundle**,我们称之为“jln-entities”,并创建实体 Todo

[BEGIN-CODE]
@Entity
public class Todo implements Serializable
{
       
       /**
        *
        */
       private static final long serialVersionUID = 1L;
       
       @Id
       @GeneratedValue(strategy = GenerationType.IDENTITY)
       private Long id;
       
       @Size(min=10, max=100)
       @Pattern(regexp="^#[\\w+\\s]+")
       private String summary;
       
       @Size(min=10, max=500)
       private String description;
 
       public Long getId()
       {
             return id;
       }
       
       public String getSummary() {
             return summary;
       }
 
       public void setSummary(String summary) {
             this.summary = summary;
       }
 
       public String getDescription() {
             return description;
       }
 
       public void setDescription(String description) {
             this.description = description;
       }
 
       @Override
       public String toString() {
             return "Todo [summary=" + summary + ", description=" + description
                           + "]";
       }
}
[END-CODE]

就这样,只是注意“@Size,@Pattern”用于验证。接下来,我们创建一个名为“jln-persistence-1.0.0”的核心组件的 **fragment**,它包含 *META-INF* 文件夹中“persistence”下的两个目录 *1.0.0*。还记得 *core-data-cfg.xml* 文件中的这行话吗

[BEGIN-CODE]
<property name="persistenceXmlLocation" value="classpath*:/META-INF/persistence/**/persistence.xml"></property>
[END-CODE]

我们将名为 *persistence.xml* 的文件放在 *1.0.0* 文件夹中,配置非常简单

[BEGIN-CODE]
<?xml version="1.0" encoding="UTF-8" ?
<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_2_0.xsd"
       version="2.0" xmlns="http://java.sun.com/xml/ns/persistence">
       <persistence-unit name="jln" transaction-type="RESOURCE_LOCAL">
             <class>com.cp.adrabi.jln.model.Todo</class>
       </persistence-unit>
</persistence>
[END-CODE]

完成了!我们回到服务,用于 CRUD Todo,在导出包“com.cp.adrabi.jln.model”后,我们在 bundle “jln-entities”的 *MANIFEST* 文件中添加这行

[BEGIN-CODE]
 
Export-Package: com.cp.adrabi.jln.model
 
[END-CODE]

最后,我们的 Todo 服务实现也完成了,添加了缓存注解

[BEGIN-CODE]
@Service("todoService")
@Transactional
@Repository
public class TodoServiceImpl implements TodoService
{
 
       private final static String SELECT_ALL_TODO = "select t from Todo t";
       
       @PersistenceContext
       private EntityManager entityManager;
       
       @Override
       @Cacheable(cacheName="todoCache")
       public Todo getTodoById(Long id)
       {
             return this.entityManager.find(Todo.class, id);
       }
 
       @Override
       @Cacheable(cacheName="todoCache")
       public List<Todo> getListTodo()
       {
             return this.entityManager.createQuery( SELECT_ALL_TODO, Todo.class).getResultList();
       }
 
       @Override
       @TriggersRemove(cacheName="todoCache",removeAll=true, when=When.AFTER_METHOD_INVOCATION)
       public void saveTodo(Todo todo)
       {
             this.entityManager.persist(todo);
       }
 
       @Override
       @TriggersRemove(cacheName="todoCache", removeAll=true, when=When.AFTER_METHOD_INVOCATION)
       public void updateTodo(Todo todo)
       {
             this.entityManager.merge(todo);
       }
 
       @Override
       @TriggersRemove(cacheName="todoCache", removeAll=true, when=When.AFTER_METHOD_INVOCATION)
       public void removeTodoById(Long id)
       {
             Object tmp = this.entityManager.getReference(Todo.class, id);
             this.entityManager.remove(tmp);
       }
}
[END-CODE]

其余的只是 Spring 配置文件和 MANIFEST 文件。记住,**永远**不要忘记查看 **所有** 这些 bundle 和 fragment 的 *MANIFEST* 文件,因为它们包含许多许多包和 bundle 的导入/导出。

但我们不确定这个服务是否能正常工作!为了确保服务能够工作并连接到数据库等……我们需要做一些作业来测试。

作业:测试

为了准备测试环境,我们需要在菜单 Window > Preferences > Java Tab > Build Path Tab > User Libraries 中创建一个用户库,给它命名并添加所有 JAR。

image009.gif

终于!我们创建了一个 Java 项目。“**Java Project**”不是 Plug-in 也不是 Fragment,而是一个经典的 Java 项目,我们将其添加到我们的用户库中(别忘了在“Projects”选项卡中将实体和 todo 组件项目添加到此项目)。

image010.gif

我们创建的集成测试继承类 AbstractConfigurableBundleCreatorTests,并重写两个方法

  • Resource[]:getTestFrameworkBundles 用于设置我们的自定义 bundle。
  • 以及 String:getRootPath 来重写默认的类路径。

现在我们构建一些私有内容,用于从 OSGi 获取服务。还记得吗?我们通过 Spring OSGi 使用 Spring 配置文件、core 和 todo 组件注册的服务!

makeService() 方法用于从 OSGi 获取已注册的 Todo 服务

[BEGIN-CODE]
private TodoService makeService()
{
	ServiceReference ref = this.bundleContext.getServiceReference("com.cp.adrabi.jln.todo.services.TodoService");
	return (TodoService) this.bundleContext.getService(ref);
}
[END-CODE]

makeCache() 方法用于从 OSGi 获取已注册的 Cache 服务

[BEGIN-CODE]
private CacheManager makeCache()
{
	ServiceReference ref = this.bundleContext.getServiceReference("net.sf.ehcache.CacheManager");
	return (CacheManager) this.bundleContext.getService(ref);
}
[END-CODE]

其他内容你可以在源代码中发现,现在我们为三个基本对象创建测试:bundleContexttodoServicecacheService。如果这三个对象不工作,我们所做的一切就都白费了 =)

[BEGIN-CODE]
public void testBundleContext()
{
	assertNotNull( this.bundleContext );
}
       
public void testTodoService()
{
	assertNotNull( makeService() );
}
 
public void testCacheService()
{
	assertNotNull(makeCache());
}
[END-CODE]

在你运行这个测试之前,我们需要导出我们所有的 bundle/fragment

image011.gif

这是所有测试的最终截图。你需要自己更改 JARs 文件夹的路径,截图中的路径是 Linux 下的,你可以将其更改为 *c:/...* 或者如果你在 Windows 中,这次集成测试的总测试数为 10,包括缓存测试。

image012.gif

另一点需要注意的是,这仅适用于 JUnit 3。换句话说,AbstractConfigurableBundleCreatorTests == **Green** 当且仅当 JUnit.version == **3**

print “是时候构建演示层了,从 Web Core Component 开始”;

Web 核心组件

Web 核心组件是一个 bundle。我们需要创建一个 Plug-in 项目,给它命名,并将其配置为三个部分:“MVC”、“Security”和“URL rewriting”。但是 Web bundle 的结构与其他 bundle 不同,我们将在 MVC 配置中展示这一点。

又一个无聊的故事:MVC

我们现在做的第一件事是在这个 bundle 的 *MANIFEST* 文件中添加这行“**Web-ContextPath: /**”。这行是什么意思?通过添加这行,我们的 Web 应用程序的 URL 将会是这样的:“http://127.0.0.1:8080”,我们不需要在 URL 中添加额外的路径,比如“http://127.0.0.1:8080/jln-blabla/”。

什么是 MVC?更确切地说,“我们的 Web 结构是什么样的?”像这样

image013.jpg

我们创建了一个名为 *WEB-INF* 的新文件夹。其中有两个文件夹 *components* 和 *spring*,在 web.xml 中,我们添加此配置,以便通过 Spring Dynamic Modules 加载 Web 组件的 Spring 配置

<context-param>
             <param-name>contextClass</param-name>
             <param-value>org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext</param-value>
       </context-param>
       <context-param>
             <param-name>contextConfigLocation</param-name>
             <param-value>/WEB-INF/spring/*.xml</param-value>
       </context-param>
[END-CODE]

此配置也用于支持和使用 Zk 框架

[BEGIN-CODE]
<listener>
             <description>Used to cleanup when a session is destroyed</description>
             <display-name>ZK Session cleaner</display-name>
             <listener-class>org.zkoss.zk.ui.http.HttpSessionListener</listener-class>
       </listener>
       <servlet>
             <description>The ZK loader for ZUML pages</description>
             <servlet-name>zkLoader</servlet-name>
             <servlet-class>org.zkoss.zk.ui.http.DHtmlLayoutServlet</servlet-class>
             <init-param>
                    <param-name>update-uri</param-name>
                    <param-value>/zkau</param-value>
             </init-param>
             <load-on-startup>1</load-on-startup>
       </servlet>
       <servlet>
             <description>The asynchronous update engine for ZK</description>
             <servlet-name>auEngine</servlet-name>
             <servlet-class>org.zkoss.zk.au.http.DHtmlUpdateServlet</servlet-class>
       </servlet>
       <servlet-mapping>
             <servlet-name>zkLoader</servlet-name>
             <url-pattern>*.zul</url-pattern>
       </servlet-mapping>
       <servlet-mapping>
              <servlet-name>zkLoader</servlet-name>
             <url-pattern>*.zhtml</url-pattern>
       </servlet-mapping>
       <servlet-mapping>
             <servlet-name>auEngine</servlet-name>
             <url-pattern>/zkau/*</url-pattern>
       </servlet-mapping>
[END-CODE]

还有一件事!Zk 默认不支持 JSR-303 中的验证,因此我们需要导入我们的验证服务以在此处使用。你还记得它吗?记得验证 fragment。我们通过在 Spring OSGi 的 Spring 配置文件 *web-core-osgi-cfg.xml* 中引用它来导入它。

[BEGIN-CODE]
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:osgi="http://www.springframework.org/schema/osgi"
       xsi:schemaLocation=
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
             http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi-1.2.xsd">
 
       <!-- Validator factory -->
       <osgi:reference id="validatorProxy" interface="javax.validation.ValidatorFactory"></osgi:reference>
 
</beans>
[END-CODE]

另外,我们并没有创建一个具有“**prototype**”作用域的抽象 bean,作为所有 bean 控制器的基础。为什么是 prototype?通过使用 prototype,Spring 容器在每次调用时都会为该对象创建新实例。我们为什么需要这个?试想一下,如果我们只有一个这个对象的实例,那么所有网页都会访问相同的对象、相同的引用和相同的实例!**这会导致很多问题**。为了避免这种情况,Zk 会阻止使用“**singleton**”bean 作为控制器的页面创建两个实例。

"java.lang.IllegalStateException: Access denied: component, <Grid gdListTodo>, belongs to another desktop..."

好的,我们的简单抽象 bean 在 *web-core-services-cfg.xml* 文件中

[BEGIN-CODE]
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation=
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
       
       <!-- Abstract composer bean -->
       <bean id="basicComposer" abstract="true" scope="prototype">
             <property name="validatorFatory" ref="validatorProxy"></property>
       </bean>
       
</beans>[END-CODE]

我们通过创建一个所有控制器(具有基本系统以使用 JSR-303 验证)的基础控制器来完成 MVC 配置。对于验证,我们默认使用“Default”组

[BEGIN-CODE]
public class BasicComposer extends GenericForwardComposer
{
       /**
        *
        */
       private static final long serialVersionUID = 1L;
 
       private ValidatorFactory validatorFatory;
       
       private List<WrongValueException> wrongValues = new ArrayList<WrongValueException>();
 
       /**
        * Validation JSR 303
        *
        * @param <T>: anonymous type
        * @param comp: component
        * @param beanClass: bean class to validate
        * @param propertyName: property name to check and validate
        * @param value: value in question
        */
       protected <T> void validateValue(final Component comp, 
                 final Class<T> beanClass, final String propertyName, 
                 final Object value)
       {
             final Set<ConstraintViolation<T>> violations = 
               this.validatorFatory.getValidator().validateValue(
               beanClass,propertyName, value, Default.class);
             
             // only one violation
             if( violations.size() == 1 )
             {
                    this.wrongValues.add(new WrongValueException(
                       comp, violations.iterator().next().getMessage()));
             }
             // many violations
             else if( violations.size() > 1 )
             {
                    StringBuilder builder = new StringBuilder();
                    for(ConstraintViolation<T> cv : violations)
                    {
                           builder.append(cv.getMessage() + "\n");
                    }
                    builder.deleteCharAt(builder.length() - 1);
                    this.wrongValues.add(new WrongValueException(comp, builder.toString()));
             }
       }
       
       /**
        * Fire validation for throwing exceptions
        *
        * @throws WrongValuesException
        */
       protected void fireValidation() throws WrongValuesException
       {
             if(!this.wrongValues.isEmpty())
             {
                    WrongValueException[] wrongs = 
                      this.wrongValues.toArray(new WrongValueException[]{});
                    this.wrongValues.clear();
                    throw new WrongValuesException(wrongs);
             }
       }
       
       public ValidatorFactory getValidatorFatory()
       {
             return validatorFatory;
       }
 
       public void setValidatorFatory(ValidatorFactory validatorFatory)
       {
             this.validatorFatory = validatorFatory;
       }
}
[END-CODE]

GenericForwardComposer 自动将 ZUL 文件中的组件绑定到 Java 代码。

哦,我们忘了很重要的一件事;我们忘了我们的模板基础(类似于 ASP.NET 中的 MasterPage)。所以我们快速创建一个 *WEB-INF* 文件夹下的 components 文件夹中的 ZUL 文件,如下所示

[BEGIN-CODE]
<?variable-resolver class="org.zkoss.zkplus.spring.DelegatingVariableResolver"?>
<?page title="Modular J2EE, Level Nightmare" contentType="text/html;charset=UTF-8"?>
<zk>
       <window title="Modular J2EE, Level Nightmare" border="normal">
             <label value="I'm GentlePage =) not MasterPage, Yeah! I'm gentle"></label>
             <hbox self="@{insert(content) }" />
       </window>
</zk>[END-CODE]

Gentle Page 的代码很简单。没有 WOW 布局,只需看第一行并牢记在心。这一行用于在 ZUL 文件中使用 Spring bean,换句话说,在 ZkScript 或表达式语言中使用。你对测试主页感到兴奋吗?好的,我们将在安全设置后使用一个小型的 URL 重写来测试我们的主页。

黑客设置(哦,安全)

我们通过一个文件“web-hacking-cfg.xml”中的纯文本密码设置了非常基本的安全措施

[BEGIN-CODE]
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:hacking="http://www.springframework.org/schema/security"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                                        http://www.springframework.org/schema/beans/spring-beans.xsd
                                        http://www.springframework.org/schema/security
                                        http://www.springframework.org/schema/security/spring-security-3.0.xsd">
 
       <hacking:http auto-config="true" use-expressions="true">
             <hacking:intercept-url pattern="/" access="permitAll"/>
             <hacking:intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')"/>
             <hacking:intercept-url pattern="/jln/**" access="hasRole('ROLE_NO_ONE')"/>
             <hacking:form-login login-page="/login"/>
       </hacking:http>
       
       <hacking:authentication-manager alias="authenticationManager">
             <hacking:authentication-provider>
                    <hacking:user-service>
                           <hacking:user name="admin" password="*" authorities="ROLE_ADMIN"/>
                    </hacking:user-service>
             </hacking:authentication-provider>
       </hacking:authentication-manager>
             
</beans>
[END-CODE]

用户是“admin”,密码是“*”(一个星号,用这个密码永远不会被黑 :D),你可以看到 login.zul 页面,它很简单,类似于 Zk wiki 中的演示代码。让我们开始重写。

URL 重写

这个 URL 重写是做什么用的?呃,有很多用途,但在这里我们用它来隐藏真实的 URL 路径和文件扩展名,例如

主页的真实路径是:http://127.0.0.1:8080/jln/index.zul 我们将把它改为 http://127.0.0.1:8080/index,我只展示了一个基本示例,但这可以用于 SEO 等,让我们开始重写……

[BEGIN-CODE]
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE urlrewrite PUBLIC "-//tuckey.org//DTD UrlRewrite 3.2//EN"
        "http://tuckey.org/res/dtds/urlrewrite3.2.dtd">
 
<urlrewrite>
       <rule>
             <from>^/$</from>
             <to type="redirect">/index</to>
       </rule>
       <rule>
             <from>^/([\w-]+)$</from>
             <condition operator="notequal" type="request-url">j_spring_security_check</condition>
             <condition operator="notequal" type="request-url">/zkau.zul</condition>
             <condition operator="notequal" type="request-url">/zkau/*</condition>
             <to type="forward">/jln/$1.zul</to>
       </rule>
             <rule>
             <from>^/admin/([\w-]+)/([\w-]+)$</from>
             <to type="forward">/jln/admin/$1/$2.zul</to>
       </rule>
       <rule>
             <from>^/admin/([\w-]+)/([\w-]+)/(\w+)/(\w+)$</from>
             <to type="forward">/jln/admin/$1/$2.zul?$3=$4</to>
       </rule>
</urlrewrite>
[END-CODE]

是的,这是我们目前所需的所有配置,我们可以说:URL 重写配置现在已完成。

为了能有一个稍微复杂的 URL 重写演示,我们创建一个名为 *jln* 的文件夹,并将我们所有的 Web 页面都放在里面。还有我们的测试 index 页面,如下所示

[BEGIN-CODE]
<?init class="org.zkoss.zk.ui.util.Composition" arg0="/WEB-INF/components/gentlepage.zul" ?>
<zk>
       <window self="@{define(content) }" title="Index Page" border="normal">
             <label value="Welcome"></label>
       </window>
</zk>
[END-CODE]

我们运行我们的 OSGi 配置,然后在浏览器中输入“http://127.0.0.1:8080”或“http://127.0.0.1:8080/index”,结果是

image014.gif

现在,重头戏来了,你需要为一个 Web Fragment 构建完整的 Todo CRUD。

Web 模块

现在我需要重复如何创建一个 fragment。我们将为 Web 核心创建一个具有相同结构的 fragment,但不包含配置文件 *web.xml* 和 *urlrewriting.xml*,并将我们所有的 ZUL 页面创建在 */jln/admin/todo* 文件夹中,这样只有拥有“ROLE_ADMIN”的用户才能访问。如果你忘了,请向上滚动并重新阅读安全配置中的内容。

我们创建的 *ZUL* 文件列出了数据库中的所有 Todo

[BEGIN-END]
<?init class="org.zkoss.zk.ui.util.Composition" arg0="/WEB-INF/components/gentlepage.zul" ?>
<zk>
       <window self="@{define(content) }" title="List Todo" border="normal" apply="${todoListController }">
             <vbox>
                    <grid id="gdListTodo" >
                           <columns>
                                  <column>ID</column>
                                  <column>Summary</column>
                                  <column></column>
                           </columns>
                    </grid>
                    <div>
                           <button id="btnNew" label="New Todo"></button>
                    </div>
             </vbox>
       </window>
</zk>
[END-CODE]

我们终于遇到了传说中的 **controller**!经过所有这些艰苦的工作,我们终于编写了我们的第一个真正的控制器

[BEGIN-CODE]
public class ControllerListTodo extends ControllerBaseTodo
{
 
       /**
        *
        */
       private static final long serialVersionUID = 1L;
 
       private Grid gdListTodo;
       
       @Override
       public void doAfterCompose(Component comp) throws Exception
       {
             super.doAfterCompose(comp);
             this.loadTodoLis();
       }
       
       /**
        * Load todos and add them to grid
        */
       private void loadTodoLis()
       {
             this.gdListTodo.setModel( new ListModelList( this.service.getListTodo() ) );
             this.gdListTodo.setRowRenderer(new TodoRowModel());
       }
 
       /**
        * Redirect to new todo page
        */
       public void onClick$btnNew()
       {
             Executions.sendRedirect("/admin/todo/new");
       }
}
[END-CODE]

我们输入 URL:http://127.0.0.1:8080/admin/todo/list,并获得了该 ZUL 页面的精美截图

image015.gif

下一个截图显示了 JSR-303 验证的实际应用

image016.gif

其他内容请在源代码和 Spring 配置中查看。我认为这就是我们在 J2EE Level: Nightmare 的第一个任务的结束。我们已经欺骗并绕过了所有正式的词汇和定义。稍后在总结中再见。

总结

你在等什么?我因为泄露这些行业秘密会被解雇的!(开玩笑 xD)你现在脑海中首先闪过的可能是我们花了所有时间在配置上

这是真的,但这里的噩梦不在于配置,而在于当我们设置它时,一切都可能变得容易。

  • Spring 是用于 DI 的框架,它有许多模块,如 Spring Dynamic Module;
  • Spring Dynamic Module 是这个地狱般的 JAR 集合 Spring 的一部分,基于 OSGi;
  • OSGi 是用于创建桌面和 Web 应用(如 Zk)的模块化 Java 应用程序的框架;
  • Zk 是一个 Java Web 框架,使用 ZUL 文件,我们通过 URLrewrite 隐藏它;
  • URLrewrite 是一个 API,用于隐藏 ZUL 扩展名并重写 URL,通过 Spring Security 进行安全或非安全访问;
  • Spring-Security 用于通过“内存”保护 URL,也可以使用 JDBC 从 HSQL 等数据库获取用户;
  • HSQL 是“内存数据库”,我们通过 EclipseLink 访问它;
  • EclipseLink 是一个 ORM 持久化框架,我们使用 EhCache 缓存通过它检索的对象
  • EHCache 是一个 API,用于缓存 EclipseLink 通过 DBCP 在内存中检索的对象。
  • DBCP 是一个 API,用于使用 Apache Tomcat 的连接池;
  • 最后,VIP 是 Apache tomcat 作为 Web 服务器;

希望你喜欢这个第一个任务,感谢你阅读这篇文章,纠正我的错误,批评,或提出新的或更好的建议,等等…… :D。

“任务完成”

参考文献

© . All rights reserved.