一、契子
很早以前就開(kāi)始構(gòu)思可動(dòng)態(tài)部署的Web應(yīng)用,模塊化應(yīng)用無(wú)疑是一種趨勢(shì),Portal應(yīng)用可謂是一個(gè)小革新,它的功能引起了很多人的注意,OSGi 無(wú)疑會(huì)為這帶來(lái)本質(zhì)上的升級(jí)。
二、目標(biāo)
這篇blog中的例子從JPetStoreOsgi衍生,通過(guò)擴(kuò)展(修改)Spring mvc中的某些對(duì)象,實(shí)現(xiàn)模塊的動(dòng)態(tài)部署,當(dāng)然,這只是很簡(jiǎn)單的案例,不過(guò)足以達(dá)到我的預(yù)期目標(biāo):有2個(gè)非常簡(jiǎn)單的模塊module1和module2,它們都有自己的Spring mvc配置文件,可以在運(yùn)行時(shí)簡(jiǎn)單的通過(guò)OSGi控制臺(tái),安裝它們,并完成它們各自的功能。
三、準(zhǔn)備工作
[點(diǎn)擊這里下載 DynamicModule 工程包]
由于整個(gè)Workspace太大,所以僅僅只是把更新的5個(gè)Bundle的Project上傳了,先 下載JPetStoreOsgi ,然后將所有關(guān)于JPetStore的Project刪除,導(dǎo)入這5個(gè)Project
四、Spring MVC
目前還沒(méi)有用于OSGi環(huán)境的MVC框架,所以選用Spring MVC做為演示框架
org.phrancol.osgi.demo.mvc.springmvc 是整個(gè)應(yīng)用的MVC Bundle,以下簡(jiǎn)稱 MVCBundle
- org.phrancol.osgi.demo.mvc.springmvc.core.HandlerRegister

public interface HandlerRegister
{

/** *//**
* 當(dāng)bundle的ApplicationContext生成后,獲取HandlerMapping,并注冊(cè)
* @param context Spring為Bundle生成的ApplicationContext
* @param bundle
*/
public void registerHandler(ApplicationContext context, Bundle bundle);

/** *//**
* 當(dāng)Bundle被停止或是卸載的時(shí)候,注銷這個(gè)bundle的HandlerMapping
* 當(dāng)然這個(gè)功能沒(méi)有實(shí)現(xiàn)(它可以實(shí)現(xiàn)),因?yàn)樗粚儆谘菔痉秶?br />
* @param bundle
*/
public void unRegisterHandler(Bundle bundle);

}
- 擴(kuò)展DispatcherServlet - org.phrancol.osgi.demo.mvc.springmvc.core.OsgiDispatcherServlet
同時(shí),它還充當(dāng)一個(gè)HandlerMapping注冊(cè)管理器的角色,通過(guò)一個(gè)BundleHandlerMappingManager來(lái)管理bundle的HandlerMapping,包括動(dòng)態(tài)添加/刪除等,它會(huì)重寫DispatcherServlet 的getHandler方法,從BundleHandlerMappingManager獲取Handler.....這里的代碼比較簡(jiǎn)單,一看就能明白。BundleHandlerMappingManager只是一個(gè)Map的簡(jiǎn)單操作,代碼省略
public class OsgiDispatcherServlet extends DispatcherServlet implements

HandlerRegister
{

private static final Log log = LogFactory
.getLog(OsgiDispatcherServlet.class);

/**//* HandlerMapping管理對(duì)象 */
private BundleHandlerMappingManager bundleHandlerMappingManager;

private BundleContext bundleContext;


public OsgiDispatcherServlet(BundleContext bundleContext)
{
this.bundleContext = bundleContext;
this.bundleHandlerMappingManager = new BundleHandlerMappingManager();
}

protected WebApplicationContext createWebApplicationContext(

WebApplicationContext parent) throws BeansException
{
ClassLoader contextClassLoader = Thread.currentThread()
.getContextClassLoader();

try
{
ClassLoader cl = BundleDelegatingClassLoader
.createBundleClassLoaderFor(bundleContext.getBundle(),
getClass().getClassLoader());
Thread.currentThread().setContextClassLoader(cl);
LocalBundleContext.setContext(bundleContext);

ConfigurableWebApplicationContext wac = new OSGiXmlWebApplicationContext(
bundleContext);
wac.setParent(parent);
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());

if (getContextConfigLocation() != null)
{
wac
.setConfigLocations(StringUtils
.tokenizeToStringArray(
getContextConfigLocation(),
ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
wac.addApplicationListener(this);
wac.refresh();
return wac;

} finally
{
Thread.currentThread().setContextClassLoader(contextClassLoader);
}
}


/** *//**
* 重寫這個(gè)方法是很有必要的
*/
protected HandlerExecutionChain getHandler(HttpServletRequest request,

boolean cache) throws Exception
{
HandlerExecutionChain handler = (HandlerExecutionChain) request
.getAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);

if (handler != null)
{

if (!cache)
{
request.removeAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
}
return handler;
}

for (Iterator _it = this.bundleHandlerMappingManager

.getBundlesHandlerMapping().values().iterator(); _it.hasNext();)
{
List _handlerMappings = (List) _it.next();


for (Iterator it = _handlerMappings.iterator(); it.hasNext();)
{
HandlerMapping hm = (HandlerMapping) it.next();

if (logger.isDebugEnabled())
{
logger.debug("Testing handler map [" + hm
+ "] in OsgiDispatcherServlet with name '"
+ getServletName() + "'");
}
handler = hm.getHandler(request);

if (handler != null)
{

if (cache)
{
request.setAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE,
handler);
}
return handler;
}
}
}
return null;
}


/** *//**
* 這個(gè)功能實(shí)現(xiàn)起來(lái)有點(diǎn)牽強(qiáng),但是以演示為主,一笑而過(guò)
*/
protected View resolveViewName(String viewName, Map model, Locale locale,

HttpServletRequest request) throws Exception
{
long bundleId = this.bundleHandlerMappingManager.getBundleId(request);
Bundle bundle = this.bundleContext.getBundle(bundleId);
ViewResolver viewResolver = new OsgiInternalResourceViewResolver(
bundle, getWebApplicationContext(), viewName);
View view = viewResolver.resolveViewName(viewName, locale);
return view;
}


public void registerHandler(ApplicationContext context, Bundle bundle)
{
Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, HandlerMapping.class, true, false);

if (!matchingBeans.isEmpty())
{
List _list = new ArrayList(matchingBeans.values());
String bundleId = new Long(bundle.getBundleId()).toString();
this.bundleHandlerMappingManager.registerHandlerMapping(bundleId,
_list);
}
}

public void unRegisterHandler(Bundle bundle)
{
String bundleId = new Long(bundle.getBundleId()).toString();
this.bundleHandlerMappingManager.unRegisterHandlerMapping(bundleId);
}
}
- 擴(kuò)展InternalResourceViewResolver - org.phrancol.osgi.demo.mvc.springmvc.core.OsgiInternalResourceViewResolver
為了方便,這部份的代碼寫得有些不地道(演示為主~),重寫getPrefix()方法,主要是為了獲取jsp文件
public class OsgiInternalResourceViewResolver extends

InternalResourceViewResolver
{
private static final Log log = LogFactory.getLog(OsgiInternalResourceViewResolver.class);
private static final String PREFIX = "/web/jsp/spring/";
private static final String SUFFIX = ".jsp";
private String viewName;
private Bundle bundle;

public OsgiInternalResourceViewResolver(Bundle bundle, ApplicationContext applicationContext , String viewName)
{
this.bundle = bundle;
setPrefix(PREFIX);
setSuffix(SUFFIX);
setViewClass(new JstlView().getClass());
setApplicationContext(applicationContext);
this.bundle = bundle;
this.viewName = viewName;
}

protected String getPrefix()
{
String _prefix= "/"+bundle.getSymbolicName()+PREFIX;
return _prefix;
}

}
- MVCBundle需要設(shè)置一個(gè)Activator,用于將OsgiDispatcherServlet注冊(cè)為OSGi Service

public void start(BundleContext bundleContext) throws Exception
{
DispatcherServlet ds = new OsgiDispatcherServlet(bundleContext);
bundleContext.registerService(DispatcherServlet.class.getName(), ds,
null);
}
- MVCBundle中的SpringmvcHttpServiceRegister還是需要的,它需要生成一個(gè)所謂的容器Context

public class SpringmvcHttpServiceRegister implements HttpServiceRegister
{
public void serviceRegister(BundleContext context,

ApplicationContext bundleApplicationContext)
{

try
{

ServiceReference sr = context.getServiceReference(HttpService.class
.getName());
HttpService httpService = (HttpService) context.getService(sr);
HttpContext defaultContext = httpService.createDefaultHttpContext();
Dictionary<String, String> initparams = new Hashtable<String, String>();
initparams.put("load-on-startup", "1");

/** *//**/
ContextLoaderServlet contextloaderListener = new BundleContextLoaderServlet(
context, bundleApplicationContext);
httpService.registerServlet("/initContext", contextloaderListener,
initparams, defaultContext);
/**/
DispatcherServlet dispatcherServlet = (DispatcherServlet) context
.getService(context
.getServiceReference(DispatcherServlet.class
.getName()));

/**//* 這里給了 DispatcherServlet 一個(gè)空的配置文件,可以節(jié)省好多代碼*/
dispatcherServlet
.setContextConfigLocation("META-INF/dispatcher/DynamicModule-servlet.xml");
initparams = new Hashtable<String, String>();
initparams.put("servlet-name", "DynamicModule");
initparams.put("load-on-startup", "2");
httpService.registerServlet("/*.do", dispatcherServlet, initparams,
defaultContext);

} catch (Exception e)
{
e.printStackTrace(System.out);
}
}
}
通過(guò)以上工作,Spring MVC就被簡(jiǎn)單的改造完了......當(dāng)然他僅僅只是能實(shí)現(xiàn)我所要演示的功能
五、模塊
新建一個(gè)模塊bundle - org.phrancol.osgi.demo.mvc.springmvc.module2 ,Bundle-SymbolicName設(shè)置為module2
先看看它的bean配置
<beans>

<bean id="module2Register"
class="org.phrancol.osgi.demo.mvc.util.BundleServiceRegister">
<constructor-arg>
<bean
class="org.phrancol.osgi.demo.mvc.springmvc.module2.SpringmvcHttpServiceRegister" />
</constructor-arg>
</bean>

<!-- ========================= DEFINITIONS OF PUBLIC CONTROLLERS ========================= -->

<bean id="module2HandlerMapping"
class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />

<bean name="/DynamicModule/module2.do"
class="org.phrancol.osgi.demo.mvc.springmvc.module2.TheSecondModuleController">
</bean>

</beans>
也使用了一個(gè)SpringmvcHttpServiceRegister,它就是用來(lái)注冊(cè)這個(gè)bundle 中的jsp和資源的

public class SpringmvcHttpServiceRegister implements HttpServiceRegister
{
public void serviceRegister(BundleContext context,

ApplicationContext bundleApplicationContext)
{

try
{

ServiceReference sr = context.getServiceReference(HttpService.class
.getName());

/**//* 在上一個(gè)例子中,HttpContext的用法不對(duì),這個(gè)用法才是正確的 */
HttpService httpService = (HttpService) context.getService(sr);
HttpContext defaultContext = httpService.createDefaultHttpContext();
httpService.registerResources("/module2", "/module2",
defaultContext);

/**//*
* 這個(gè)JspServlet對(duì)象中的參數(shù)"module2/web",可以理解為 The root path of module
* application,它是干什么用的,請(qǐng)參考它的JavaDoc,建議從Eclipse的CVS中準(zhǔn)備一份Equinox的源代碼
*/
JspServlet jspServlet = new JspServlet(context.getBundle(),
"/module2/web");
httpService.registerServlet("/module2/*.jsp", jspServlet, null,
defaultContext);

HandlerRegister dispatcherServlet = (HandlerRegister) context
.getService(context
.getServiceReference(DispatcherServlet.class
.getName()));
dispatcherServlet.registerHandler(bundleApplicationContext, context
.getBundle());


} catch (Exception e)
{
e.printStackTrace(System.out);
}
}
}
來(lái)看看org.phrancol.osgi.demo.mvc.springmvc.module2.TheSecondModuleController ,只有很簡(jiǎn)單的一個(gè)輸出

public class TheSecondModuleController implements Controller
{
private static final String VIEWSTRING = "Hello, this is the second module !";

public ModelAndView handleRequest(HttpServletRequest request,

HttpServletResponse response) throws Exception
{
Map model = new HashMap();
model.put("viewString", VIEWSTRING);
ModelAndView mv = new ModelAndView("Success", model);
return mv;
}

}
目錄結(jié)構(gòu)也有一點(diǎn)變化 /module1/web/jsp/spring/ *.jsp
模塊1和模塊2是一樣的
六、運(yùn)行
將模塊二導(dǎo)出為bundle jar包,放到C盤根目錄下,啟動(dòng)這個(gè)應(yīng)用(當(dāng)然不要啟動(dòng)modure2),在瀏覽器看看module1的運(yùn)行情況
建模塊化的動(dòng)態(tài)Web應(yīng)用(演示版)/module1.gif)
現(xiàn)在安裝一下module2
建模塊化的動(dòng)態(tài)Web應(yīng)用(演示版)/module2_install.gif)
試著訪問(wèn)一下module2
建模塊化的動(dòng)態(tài)Web應(yīng)用(演示版)/module2_404.gif)
404,正常,啟動(dòng)一下這個(gè)bundle再看看
建模塊化的動(dòng)態(tài)Web應(yīng)用(演示版)/module2.gif)
顯示出來(lái)了,現(xiàn)在可以動(dòng)態(tài)的操作這2個(gè)模塊了......
七、擴(kuò)展
通過(guò)這個(gè)演示,可以領(lǐng)略到OSGi帶給我們的一小部分功能,做一些擴(kuò)展看看
1. 當(dāng)然是各種框架的支持。
2. 強(qiáng)大的bundle資源庫(kù)
3. 絕對(duì)動(dòng)態(tài)的部署框架,可以通過(guò)UI界面來(lái)操作。
4. 可以從URL來(lái)安裝bundle, install http://www.domain.com/sampleBundle.jar ,如果是這樣的,服務(wù)網(wǎng)關(guān)就能體現(xiàn)出來(lái)了,你提供一個(gè)服務(wù)框架,別人可以通過(guò)你的框架運(yùn)行自己的服務(wù)。
5. 個(gè)人猜測(cè),它將取代Portal的運(yùn)行模式
6. ..........
八、結(jié)束語(yǔ)
OSGi在Web應(yīng)用中還有很長(zhǎng)的路要走,它到底會(huì)發(fā)展成什么樣子,就目前的功能還真不好推測(cè)。
現(xiàn)在不管是MVC還是持久層都還沒(méi)有框架對(duì)OSGi的支持,我個(gè)人準(zhǔn)備用業(yè)余時(shí)間研究一下這方面,順便也可以練練手,希望傳說(shuō)中的強(qiáng)人能開(kāi)發(fā)這樣的框架并不吝開(kāi)源~
九、相關(guān)資源
就我目前能找到的一些資源,列出如下:
Struts2有一個(gè)OSGi的插件,但是我看了看,并不能達(dá)到預(yù)期效果,不過(guò)可以看一看
http://cwiki.apache.org/S2PLUGINS/osgi-plugin.html
在持久層方面,db4o似乎有這個(gè)打算,不做評(píng)論
http://www.db4o.com/osgi/
另外它的合作伙伴prosyst已經(jīng)開(kāi)發(fā)出了一個(gè)基于Equinox的OSGi Server,還有個(gè)專業(yè)版,好像要收費(fèi),所以也就沒(méi)下載,不知道是個(gè)什么樣子。
http://www.prosyst.com/
posted on 2007-11-01 15:09
Phrancol Yang 閱讀(6279)
評(píng)論(5) 編輯 收藏 所屬分類:
OSGI