原文地址:
http://cometd.org/documentation/cometd-java/server/services/integration-spring
Spring的集成
由 sbordet 提交于星期二,2009/11/17-15:39。
Bayeux 服務與Spring集成
CometD 服務與Spring的集成尤其有趣,因為大多時候你的 Bayeux 服務將需要其他 bean 來執(zhí)行他們的服務。
并不是所有的 Bayeux 服務都像EchoService這樣簡單,加入Spring的依賴注入 (和其他框架一樣) 集成將大大簡化開發(fā)過程。
下面您可以找到 3 種建議的方法集成Spring。
Late Spring初始化
這種方法是延遲Spring初始化,直到 CometD servlet 初始化完成。
這種方法在這些情況下適用:要求Spring beans可以被延遲初始化,因為沒有其他服務或框架需要預先進行Spring的初始化。
這種方法要求Spring和 Bayeux 服務的一些代碼粘合在一起。
要求一些代碼粘合在一起的原因有兩個:
Bayeux 對象不是由Spring創(chuàng)建(是 CometD servlet),所以任何用于Bayeux對象的bean必須在CometD servlet初始化后才初始化。
在 web.xml中的servlets 和 listeners的初始化順序在現(xiàn)有的Servlet 規(guī)范中不能被指定 (只有在servlet 3.0 中能完全指定順序)
所以,為了保證方便初始化,這段粘合的代碼是必需的。
下面您可以找到配置文件和集成Spring所需的代碼。
首先,web.xml 文件:
<?xmlversion="1.0" encoding="UTF-8"?>
<web-appxmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<servlet>
<servlet-name>cometd</servlet-name>
<servlet-class>org.cometd.server.continuation.ContinuationCometdServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>cometd</servlet-name>
<url-pattern>/cometd/*</url-pattern>
</servlet-mapping>
<listener>
<listener-class>com.acme.cometd.spring.LateSpringBayeuxInitializer</listener-class>
</listener>
</web-app>
請注意我們是使用listener初始化Spring的。此listener完全取代了Spirng的 org.springframework.web.context.ContextLoaderListener,但它是基于相同的類的,這個類是用來執(zhí)行web 應用程序啟動時上下文初始化的。
這意味著:
LateSpringBayeuxInitializer會裝載位于 /WEB-INF/applicationContext.xml中的Spring bean,或裝載在 servlet上下文的初始化參數(shù)contextConfigLocation,和每一個通常Spring需要的配置。
其次, 在Spring的 applicationContext.xml 文件中,定義我們先前寫好的 EchoService:
<?xmlversion="1.0" encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="echoService"class="com.acme.cometd.EchoService">
<constructor-arg><refbean="bayeux" /></constructor-arg>
</bean>
</beans>
請注意這里我們引用的是"bayeux"bean,這在Spring xml文件中是沒有定義的,他是在 LateSpringBayeuxInitializer 中定義的,見下文。
最后,在 web.xml的 LateSpringBayeuxInitializer中像下面這樣編寫代碼以整合Spring:
public class LateSpringBayeuxInitializerimplements ServletContextListener, ServletContextAttributeListener
{
private volatile ContextLoader loader;
public void contextInitialized(ServletContextEvent event)
{
}
public void contextDestroyed(ServletContextEvent event)
{
ContextLoader loader = this.loader;
if (loader != null)
loader.closeWebApplicationContext(event.getServletContext());
}
public void attributeAdded(ServletContextAttributeEvent event)
{
if (Bayeux.ATTRIBUTE.equals(event.getName()))
{
Bayeux bayeux = (Bayeux) event.getValue();
StaticListableBeanFactory factory = new StaticListableBeanFactory();
factory.addBean("bayeux", bayeux);
GenericApplicationContext bayeuxApplicationContext = newGenericApplicationContext(new DefaultListableBeanFactory(factory));
bayeuxApplicationContext.refresh();
loader = new BayeuxContextLoader(bayeuxApplicationContext);
ApplicationContext applicationContext =loader.initWebApplicationContext(event.getServletContext());
customizeBayeux(bayeux, applicationContext);
}
}
public void attributeRemoved(ServletContextAttributeEvent event)
{
}
public void attributeReplaced(ServletContextAttributeEvent event)
{
}
protected void customizeBayeux(Bayeux bayeux, ApplicationContextapplicationContext)
{
}
private static class BayeuxContextLoader extends ContextLoader
{
private final ApplicationContext parentApplicationContext;
public BayeuxContextLoader(ApplicationContext parentApplicationContext)
{
this.parentApplicationContext = parentApplicationContext;
}
@Override
protected ApplicationContext loadParentContext(ServletContextservletContext) throws BeansException
{
return parentApplicationContext;
}
}
}
請注意這里可以重寫 customizeBayeux() 方法和進一步自定義 Bayeux 對象,例如,從Spring的應用程序上下文中查找一個 org.cometd.SecurityPolicy 對象并把它設置在 Bayeux 對象上?;蛘?,用相同的方式,向 Bayeux 對象添加您Spring配置的 Bayeux 擴展。
Lazy Spring 初始化
這種方法允許正常初始化Spirng,但需要 Bayeux 服務標記成Lazy(懶加載) (且所有其他的bean或服務都要依賴于 Bayeux 服務),當然,也同樣需要 CometD servlet正確初始化。
這種方法在這些情況下適用:有其他框架 (例如,Struts2) 需要預先進行Spring初始化。
這種方法需要編寫一些代碼從Spring配置文件中訪問 Bayeux 對象。
首先,web.xml 文件:
<?xml version="1.0"encoding="UTF-8"?>
<web-appxmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<servlet>
<servlet-name>cometd</servlet-name>
<servlet-class>org.cometd.server.continuation.ContinuationCometdServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>cometd</servlet-name>
<url-pattern>/cometd/*</url-pattern>
</servlet-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>com.acme.cometd.spring.LazySpringBayeuxInitializer</listener-class>
</listener>
</web-app>
請注意我們使用的是普通的spring偵聽器(ContextLoaderListener)進行Spring的初始化,但我們還添加了另一個偵聽器,LazySpringBayeuxInitializer。
LazySpringBayeuxInitializer是我們需要從Spring配置文件中訪問 Bayeux 對象所編寫的代碼:
public classLazySpringBayeuxInitializer implements ServletContextAttributeListener
{
public voidattributeAdded(ServletContextAttributeEvent event)
{
if(Bayeux.ATTRIBUTE.equals(event.getName()))
{
Bayeux bayeux = (Bayeux)event.getValue();
BayeuxHolder.setBayeux(bayeux);
}
}
public voidattributeRemoved(ServletContextAttributeEvent event)
{
}
public voidattributeReplaced(ServletContextAttributeEvent event)
{
}
}
LazySpringBayeuxInitializer類用到的 BayeuxHolder 類, 其實是非常簡單的:
public classBayeuxHolder
{
private static volatile Bayeux bayeux;
public static void setBayeux(Bayeux bayeux)
{
BayeuxHolder.bayeux = bayeux;
}
public static Bayeux getBayeux()
{
return bayeux;
}
}
到此,我們需要在Spring的配置文件 applicationContext.xml 中做如下配置:
<?xmlversion="1.0" encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="nonLazyService"class="com.acme..." />
<bean id="bayeux"class="com.acme.cometd.spring.BayeuxHolder"factory-method="getBayeux" lazy-init="true" />
<bean id="echoService"class="com.acme.cometd.EchoService" lazy-init="true">
<constructor-arg><reflocal="bayeux" /></constructor-arg>
<constructor-arg><reflocal="nonLazyService" /></constructor-arg>
</bean>
</beans>
請注意Spring的配置文件中可以有正常的bean (如 nonLazyService),這些bean可以被注入這些lazy(懶加載的) bean中,如: EchoService。
另外,請注意這里使用的 BayeuxHolder,是lazy(懶加載)初始化的,以便檢索在 CometD servlet 初始化完成后被LazySpringBayeuxInitializer存儲的 Bayeux 對象。
請注意EchoService是非常重要的,它也是懶加載的,這樣它才能被注入到其他懶加載初始化的bean,或非單例bean (如原型范圍的bean) ; 如果被注入到單例且非懶加載的bean中,因為他還沒初始化(CometD servlet還沒初始化),所以你會得到一個錯誤。
Full Spring初始化
這種方法是在Spring配置文件中直接初始化 Bayeux 對象,并注入 servlet 上下文中,這上下文是 CometD servlet 創(chuàng)建的。
這種方法更多地有點依賴于 CometD 實現(xiàn),且如果CometD 實現(xiàn)更改了,它也要跟著改。
它可能需要或可能不需要中間代碼,取決于正在開發(fā)的應用程序的其他細節(jié)(參見下文)。
它也需要一點規(guī)范,因為現(xiàn)在的Bayeux 對象可以在這兩個位置: Spring配置文件和 web.xml 文件,而您不想將Bayeux 的配置拆分在不同的地方。
Web.xml 文件,如下所示:
<?xmlversion="1.0" encoding="UTF-8"?>
<web-appxmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<servlet>
<servlet-name>cometd</servlet-name>
<servlet-class>org.cometd.server.continuation.ContinuationCometdServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>cometd</servlet-name>
<url-pattern>/cometd/*</url-pattern>
</servlet-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
Spring的配置文件 applicationContext.xml 如下所示:
<?xmlversion="1.0" encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="nonLazyService"class="com.acme..." />
<bean id="bayeux"class="org.cometd.server.continuation.ContinuationBayeux">
<property name="timeout"value="15000" />
</bean>
<beanclass="org.springframework.web.context.support.ServletContextAttributeExporter">
<property name="attributes">
<map>
<entrykey="org.cometd.bayeux">
<reflocal="bayeux" />
</entry>
</map>
</property>
</bean>
<bean id="echoService"class="com.acme.cometd.EchoService" lazy-init="true">
<constructor-arg><reflocal="bayeux" /></constructor-arg>
<constructor-arg><reflocal="nonLazyService" /></constructor-arg>
</bean>
</beans>
注意這里的Bayeux 對象是在applicationContext.xml文件中引用的,并且,這個Bayeux對象是通過spring的ServletContextAttributeExporter訪問的。
這里只是創(chuàng)建一個沒有初始化的Bayeux對象,只有在CometD servlet初始化后Bayeux對象才能夠使用;正因為如此,懶加載的echoService(任何的Bayeux服務,引用到的服務,其他的依賴服務)才很重要。
因為echoService是懶加載的,當應用程序第一次用到它,就必須要告訴spring去實例化和初始化它。
如果你使用的其他框架需要用到spring的依賴注入,那么這個步驟應該由這個框架來完成。例如:你用spring配合structs2,你不需要告訴spring實例化和初始化你的CometD服務,因為structs2能識別有需要注入的CometD服務(如:在structs2的action中),所以structs2要求spring提供。
因此,上述的配置就完全足夠了。
不管怎樣,如果你不需要其他框架,你就要手動告訴spring實例化和初始化你的CometD服務。
這樣做有點類似于非spring服務集成的描述:用一個servlet或偵聽器來配置你的懶加載CometD服務。
見下面的例子:
public classConfigurationServlet extends GenericServlet
{
public void init() throws ServletException
{
// Grab Spring's ApplicationContent
ApplicationContext context =WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
// Trigger CometD serviceinitialization
context.getBean("echoService");
}
public void service(ServletRequest request,ServletResponse response) throws ServletException, IOException
{
throw new ServletException();
}
}
在web.xml的這個servlet中必須給load-on-startup標簽元素設置一個高的值。
<?xmlversion="1.0" encoding="UTF-8"?>
<web-appxmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<servlet>
<servlet-name>cometd</servlet-name>
<servlet-class>org.cometd.server.continuation.ContinuationCometdServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>cometd</servlet-name>
<url-pattern>/cometd/*</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>configuration</servlet-name>
<servlet-class>com.acme.cometd.ConfigurationServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>