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






4.92/5 (9投票s)
本文介绍如何使用这些开源框架来创建一个基本的模块化 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。关于这些框架还有其他一些秘密,但我们稍后会发现。
现在在你脑海中的大图景是……噔噔噔噔
别担心,这只是一个游戏,我们在这里组装它,以便最终能得到像这样或像这样
听到(读到)“太棒了!你已经完成了 J2EE Level:Nightmare 的第一个任务!”真是太棒了。这也意味着我们可以在关闭这个视觉小说后,弄脏我们的双手来设置“厨房”(环境)。
环境设置
还记得我们在前几章中提到的“Eclipse”这个词吗?现在是时候用简单的步骤来设置它了,这样我们就能熟悉这个新世界(哦,厨房)。
- 首先,我们在 Eclipse 中安装 Spring Tools,这里 http://blog.springsource.com/2009/06/24/installing-sts-into-eclipse-35/
- 然后,我们在这里安装 Zk Studio http://www.zkoss.org/product/zkstudio.dsp
不要下载集成的版本,使用“UPDATE SITE INSTALLATION”,这样我们就可以在一个环境中拥有所有这些工具。或者,如果你喜欢探索这些工具,随心所欲——只是不要迷失方向。
最后,我们的环境看起来是这样的
我们可以看到 Zk 的东西,在左上角是“Spring Elements”。
现在,为了在 Eclipse 中创建我们的 OSGi 开发平台,我们转到菜单 Window > Preferences > Target Platform。之后,我们创建一个名为“J2EE Nightmare”的新目标,并添加所有支持 OSGi 的 JAR,这意味着在我们 MANIFEST 文件中具有属性的 JAR 可以显示在 OSGi 世界中,例如
(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
另外,我们在 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。
我们给项目命名并选择“an OSGi framework”。之后(你还记得吗?)“**fragment 是 bundle 的一部分**”,我们需要一个宿主 bundle!它是 log4j “com.springframework.apache.log4j”(点击浏览器按钮并输入“com”以便你能列出它们)
在这个版本中,我们删除了“.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* 文件夹。
而且,事情变得更有趣了,我们为默认的 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。
终于!我们创建了一个 Java 项目。“**Java Project**”不是 Plug-in 也不是 Fragment,而是一个经典的 Java 项目,我们将其添加到我们的用户库中(别忘了在“Projects”选项卡中将实体和 todo 组件项目添加到此项目)。
我们创建的集成测试继承类 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]
其他内容你可以在源代码中发现,现在我们为三个基本对象创建测试:bundleContext
、todoService
和 cacheService
。如果这三个对象不工作,我们所做的一切就都白费了 =)
[BEGIN-CODE]
public void testBundleContext()
{
assertNotNull( this.bundleContext );
}
public void testTodoService()
{
assertNotNull( makeService() );
}
public void testCacheService()
{
assertNotNull(makeCache());
}
[END-CODE]
在你运行这个测试之前,我们需要导出我们所有的 bundle/fragment
这是所有测试的最终截图。你需要自己更改 JARs 文件夹的路径,截图中的路径是 Linux 下的,你可以将其更改为 *c:/...* 或者如果你在 Windows 中,这次集成测试的总测试数为 10,包括缓存测试。
另一点需要注意的是,这仅适用于 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 结构是什么样的?”像这样
我们创建了一个名为 *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”,结果是
现在,重头戏来了,你需要为一个 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 页面的精美截图
下一个截图显示了 JSR-303 验证的实际应用
其他内容请在源代码和 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。
“任务完成”
参考文献
- 战神:http://en.wikipedia.org/wiki/God_of_War_(video_game)
- OSGi:http://en.wikipedia.org/wiki/OSGi
- Zk:http://en.wikipedia.org/wiki/OSGi
- 搜索引擎:http://www.google.com/support/webmasters/bin/answer.py?answer=35769
- Spring Framework:http://en.wikipedia.org/wiki/Spring_Framework
- Spring DM:http://www.springsource.org/osgi
- EclipseLink:http://www.eclipse.org/eclipselink/
- EHCache:http://ehcache.org/
- HSQL:http://hsqldb.org/
- DBCP:http://commons.apache.org/dbcp/