Wicket框架的优点就不多说了,总之第一次看到她就让人耳目一新。我的框架就是采用OSGi、Spring、Hibernate、Wicket、Jetty等开源技术搭建的,所以这篇文章就是我在将Wicket通过OSGi框架注入Jetty中的一点心得体会。目前wicket刚刚推出1.3Beta3版,由于1.3版和2.0版比较接近,所以采用1.3版来测试。
首先介绍一下我的整体思路,将Jetty和wicket分别作为OSGi的Bundle,对外提供服务;自己的基于wicket的web应用也作为一个Bundle。另外因为web应用中要用到其它Bundle提供的服务,当然采用IOC方式最方便,所以需要wicket和spring整合一下。
先说Jetty和Wicket这两个Bundle,其实Jetty6.1.5已经兼容OSGi,它的jar包已经可以直接作为Bundle使用,我们目前涉及到的就是其安装目录下的lib/jetty-6.1.5.jar,lib/jetty-util-6.1.5.jar和lib/servlet-api-2.5.6.1.5.jar这三个jar包。前两个是jetty作为嵌入式服务器所必需的,最后一个是jetty的servlet实现。
因为我们采用编程式处理web应用,所以需要将web应用的信息注入到Jetty中。那么为什么不使用配置文件如web.xml呢?因为如果使用配置文件就要解决两个问题:
第一是绝对路径问题,OSGi的Bundle并没有自己固定部署的位置,所以部署时绝对路径可以任选,这样如果采用配置文件声明web应用,那么配置文件就要随着Bundle位置变更而变更,一般Bundle都是jar包的形式保存,势必导致修改的困难。
第二是类加载问题,因为我们要使用IOC和其他Bundle声明的服务,如果采用配置文件方式,类的初始化都由ClassLoader直接处理,因此上面所说的很难办到。
采用编程式处理web应用,就需要建一个Bundle(wanged_core_jetty),用来处理其他Bundle提供的Handler(这个jetty中的主要部件,可以参看
Jetty 6.1.5的配置一文)。先看配置文件bean.xml:
xml 代码
- <bean id="jettyHandlerManager" class="wanged.core.jetty.JettyHandlerManager">
- <property name="handlers" ref="handlers" />
- </bean>
-
- <bean name="jettyServer" class="wanged.core.jetty.JettyServer" init-method="start" destroy-method="stop" />
以及osgi-service.xml:
xml 代码
-
- <osgi:reference id="handlers" interface="org.mortbay.jetty.Handler" cardinality="1..n">
- <osgi:listener ref="jettyServer" />
- </osgi:reference>
这里引用了其他Bundle提供的接口为org.mortbay.jetty.Handler的服务,并注册一个监听器jettyServer来处理Handler的动态绑定与删除问题,以便可以动态添加/删除web应用。下面看看具体代码:
java 代码
- package wanged.core.jetty;
-
- import java.util.Dictionary;
-
- import org.mortbay.jetty.Handler;
- import org.mortbay.jetty.Server;
- import org.mortbay.jetty.handler.HandlerCollection;
- import org.springframework.osgi.service.TargetSourceLifecycleListener;
-
-
-
-
-
-
-
- @SuppressWarnings("unchecked")
- public class JettyServer implements TargetSourceLifecycleListener{
-
- private Server server = new Server(8080);
-
- private HandlerCollection handlers = new HandlerCollection();
-
- public void start(){
- this.server.setHandler(this.handlers);
- try {
- this.server.start();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
-
-
-
- public void bind(Object obj, Dictionary dict) throws Exception {
- Handler h = (Handler)obj;
- this.handlers.addHandler(h);
- h.start();
- }
-
-
-
-
- public void unbind(Object obj, Dictionary dict) throws Exception {
- Handler h = (Handler)obj;
- this.handlers.removeHandler(h);
- h.stop();
- }
-
- public void stop(){
- try {
- this.server.stop();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- }
实现接口
TargetSourceLifecycleListener,监听服务的绑定与删除。另一个类如下:
java 代码
- package wanged.core.jetty;
-
- import java.util.Collection;
-
- import org.mortbay.jetty.Handler;
-
-
-
-
-
-
- public class JettyHandlerManager {
- private Collection<Handler> handlers;
-
- public void setHandlers(Collection<Handler> handlers) {
- this.handlers = handlers;
- }
-
- }
这个类只是简单声明了一个
setHandlers方法,功能可以扩展。到此为止Jetty的相关工作已经处理完了。
下面该说说web应用了,我的web应用放在一个叫wanged_wicket_app的Bundle中。涉及到的类比较多,所以现将类列出再一一解说。首先看SpringWicketFilter:
java 代码
- package wanged.web.wicket;
-
- import org.apache.wicket.protocol.http.IWebApplicationFactory;
- import org.apache.wicket.protocol.http.WicketFilter;
-
- public class SpringWicketFilter extends WicketFilter{
- private IWebApplicationFactory factory ;
-
- public IWebApplicationFactory getApplicationFactory(){
- return factory;
- }
-
- public void setApplicationFactory(IWebApplicationFactory factory){
- this.factory = factory;
- }
-
- }
为什么不直接用WicketFilter呢,因为
WicketFilter需要有一个applicationClass的参数需要定义在init-param中,需要解析web.xml才能得到,而且生成的WebApplication不能使用IOC功能。熟悉Wicket的朋友会问,Wicket不是已经和Spring整合了么?是的,确实整合了,但这种整合无法用在OSGi环境下,所以我们需要自定义一个
WicketFilter的子类来声明一个
注入了Spring上下文的IWebApplicationFactory的实现SpringWebApplicationFactory
:
java 代码
- package wanged.web.wicket;
-
- import org.apache.wicket.protocol.http.IWebApplicationFactory;
- import org.apache.wicket.protocol.http.WebApplication;
- import org.apache.wicket.protocol.http.WicketFilter;
- import org.springframework.beans.BeansException;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.ApplicationContextAware;
-
- public class SpringWebApplicationFactory implements IWebApplicationFactory, ApplicationContextAware {
- private WebApplication webApplication;
-
- private ApplicationContext context;
-
- private String diskStorePath;
-
- public void init() {
- DefaultApplication app = new DefaultApplication();
- app.setApplicationContext(context);
- app.setDiskStorePath(this.diskStorePath);
- this.webApplication = app;
- }
-
- public WebApplication createApplication(WicketFilter filter) {
- return webApplication;
- }
-
- public void setApplicationContext(ApplicationContext context) throws BeansException {
- this.context = context;
- }
-
- public void setDiskStorePath(String path){
- this.diskStorePath = path;
- }
- }
这里引用了一个
DefaultApplication,这是
WebApplication的子类,在Wicket应用中有至关重要的作用,每一个wicket的web应用都需要自定义一个:
java 代码
- package wanged.web.wicket;
-
- import java.io.File;
-
- import org.apache.wicket.protocol.http.SecondLevelCacheSessionStore;
- import org.apache.wicket.protocol.http.WebApplication;
- import org.apache.wicket.protocol.http.pagestore.DiskPageStore;
- import org.apache.wicket.session.ISessionStore;
- import org.apache.wicket.spring.injection.annot.SpringComponentInjector;
- import org.apache.wicket.util.lang.Bytes;
- import org.springframework.beans.BeansException;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.ApplicationContextAware;
-
- import wanged.web.wicket.login.LoginPage;
-
- public class DefaultApplication extends WebApplication implements ApplicationContextAware {
- private ApplicationContext context;
- private String diskStorePath;
-
-
- @SuppressWarnings("unchecked")
- @Override
- public Class getHomePage() {
- return LoginPage.class;
- }
-
- @Override
- public void init() {
- super.init();
- addComponentInstantiationListener(new SpringComponentInjector(this, this.context));
- }
-
- public void setApplicationContext(ApplicationContext context) throws BeansException {
- this.context = context;
- }
-
- protected ISessionStore newSessionStore() {
- return new SecondLevelCacheSessionStore(this, new DiskPageStore(new File(this.diskStorePath), (int) Bytes.megabytes(10).bytes(), (int) Bytes.megabytes(
- 100).bytes(), 25));
- }
-
- public void setDiskStorePath(String path){
- this.diskStorePath = path;
- }
- }
由于Wicket1.3开始采用Filter处理Request,不再使用Servlet,所以我们的环境中只需要配置Filter的相关信息即可。为了能采用IOC方式配置Filter及其映射,引入一个新类FilterMapping:
java 代码
- package wanged.web.wicket;
-
- import javax.servlet.Filter;
-
- public class FilterMapping {
- private Class<Filter> filterClass;
-
- private Filter filter;
-
- private String urlPattern = "/*";
-
- public Class<Filter> getFilterClass() {
- return filterClass;
- }
-
- public void setFilterClass(Class<Filter> filterClass) {
- this.filterClass = filterClass;
- }
-
- public Filter getFilter() {
- return filter;
- }
-
- public void setFilter(Filter filter) {
- this.filter = filter;
- }
-
- public String getUrlPattern() {
- return urlPattern;
- }
-
- public void setUrlPattern(String urlPattern) {
- this.urlPattern = urlPattern;
- }
-
- }
内容很简单,不多废话了。到目前为止,我们只是定义了Wicket相关的类文件,如何注入到Jetty中呢?接下来就是最主要的了:
java 代码
- package wanged.web;
-
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
-
- import org.mortbay.jetty.Handler;
- import org.mortbay.jetty.servlet.Context;
- import org.mortbay.jetty.servlet.DefaultServlet;
- import org.mortbay.jetty.servlet.FilterHolder;
- import org.mortbay.jetty.servlet.SessionHandler;
-
- import wanged.web.wicket.FilterMapping;
-
- public class ContextFactory {
-
- private HashMap<String, String> attributes;
-
- private List<FilterMapping> filterMappings;
-
- private Context ctx = new Context();;
-
- public Context newInstance() {
- return ctx;
- }
-
- public void init() {
- if (this.attributes != null) {
- for (Map.Entry<String, String> entry : this.attributes.entrySet()) {
- ctx.setAttribute(entry.getKey(), entry.getValue());
- }
- }
- ctx.setSessionHandler(new SessionHandler());
- ctx.addServlet(DefaultServlet.class, "/");
-
- for (FilterMapping fm : filterMappings) {
- ctx.addFilter(new FilterHolder(fm.getFilter()), fm.getUrlPattern(), Handler.REQUEST);
- }
- }
-
- public void setFilterMappings(List<FilterMapping> filterMappings) {
- this.filterMappings = filterMappings;
- }
- public void setAttributes(HashMap<String, String> attributes) {
- this.attributes = attributes;
- }
-
- public void setContextPath(String contextPath) {
- this.ctx.setContextPath(contextPath);
- }
-
- }
这个类负责收集我们配置文件中的bean,然后打包成一个Context作为一个对外声明的Handler服务(
注意org.mortbay.jetty.servlet.Context实现了
org.mortbay.jetty.
Handler接口
)。
下面看配置文件web-bean.xml:
xml 代码
- <?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">
-
-
- <bean id="handler" factory-bean="contextFactory" factory-method="newInstance" />
-
-
- <bean id="contextFactory" class="wanged.web.ContextFactory" init-method="init">
- <property name="contextPath" value="/" />
- <property name="filterMappings">
-
- <list>
- <ref bean="gzipFilterMapping" />
- <ref bean="wicketFilterMapping" />
- </list>
- </property>
- </bean>
-
- <bean id="gzipFilter" class="org.mortbay.servlet.GzipFilter" />
-
- <bean id="wicketFilter" class="wanged.web.wicket.SpringWicketFilter">
- <property name="applicationFactory" ref="applicationFactory" />
- </bean>
-
- <bean id="wicketFilterMapping" class="wanged.web.wicket.FilterMapping">
- <property name="filter" ref="wicketFilter" />
- </bean>
-
- <bean id="gzipFilterMapping" class="wanged.web.wicket.FilterMapping">
- <property name="filter" ref="gzipFilter" />
- </bean>
-
- <bean id="applicationFactory" class="wanged.web.wicket.SpringWebApplicationFactory" init-method="init" >
- <property name="diskStorePath" value="F:/TMP" />
- </bean>
-
- </beans>
这里定义了我们所使用到的各种类,同时
SpringWebApplicationFactory在此获得Spring上下文信息。
另一个声明服务的配置文件osgi-service.xml:
xml 代码
- <osgi:service interface="org.mortbay.jetty.Handler" ref="handler" />
-
- <osgi:reference id="roleService" interface="wanged.security.service.RoleService" />
这里的roleService是引用了外部的一个服务,用来测试Sping注入的(可以用一个在web-bean.xml中定义的bean来代替测试)。
以上已经完成了wicket的web应用注入jetty的工作,最后就是写一个LoginPage.java完成整个Bundle:
java 代码
- package wanged.web.wicket.login;
-
- import org.apache.wicket.ajax.AjaxRequestTarget;
- import org.apache.wicket.ajax.markup.html.form.AjaxButton;
- import org.apache.wicket.markup.html.WebPage;
- import org.apache.wicket.markup.html.form.Form;
- import org.apache.wicket.markup.html.form.PasswordTextField;
- import org.apache.wicket.markup.html.form.TextField;
- import org.apache.wicket.model.CompoundPropertyModel;
- import org.apache.wicket.model.IModel;
-
- import wanged.security.entity.Role;
- import wanged.security.service.RoleService;
- import org.apache.wicket.spring.injection.annot.SpringBean;
- import org.mortbay.jetty.Handler;
-
- public class LoginPage extends WebPage {
-
- private LoginUser user;
-
- public LoginPage() {
- super();
- this.user = new LoginUser();
-
- Form form = new AutoSaveForm("loginForm", new CompoundPropertyModel(user));
- add(form);
- form.add(new TextField("username"));
- form.add(new PasswordTextField("password"));
- }
-
- }
-
- class AutoSaveForm extends Form {
-
-
-
-
- @SpringBean
- RoleService roleService;
-
- AutoSaveForm(String id, IModel m) {
- super(id, m);
- AjaxButton button = new AjaxButton("button", this) {
- @Override
- protected void onSubmit(AjaxRequestTarget target, Form form) {
- LoginUser user = (LoginUser)form.getModelObject();
- System.out.println("un: " + user.getUsername());
- System.out.println("pd: " + user.getPassword());
- }
- };
- this.add(button);
- }
- }
运行一下一切OK。感觉好繁琐呀!确实如此,OSGi带给我们的是基于组件的面向服务的开发,用来做小项目确实有点大炮打蚊子的感觉,但如果是做大型项目,就能充分体现出组件和SOA的优势。