getInitialContext
最近3个星期一直在研究OSGi框架与现有项目的结合,由于该方案可能进度问题,不能够用得上,但研究过程艰辛,该经验需要记录下来。研究方向如下:
1. 与现有的SOA接口集成,包括EJB, WebServie的调用
2. 与J2EE容器集成, 包括开发环境Tomcat 6 和 生产环境 IBM Websphere 6.0
3. 与数据库接口的集成,包括JDBC 和 JNDI 形式调用
开发OSGI与传统的开发中不同,首先开发的环境不同,其次是Classloader。
- 1.传统开发中,项目都集中在同一个Java Project/Dynamic Web Project,而且所有类都在同一个classloader中加载,即Web容器或者Application容器
- 2.OSGi开发中,每个bundle都有各自的classloader, 参考<<OSGi 实战.pdf>>。由于classloader的问题,常常会引起ClassNotFoundException,尤其是当你把OSGi嵌入到Web容器的时候,当bundle中的类需要调用容器中的资源的时候,如用JNDI查找数据源,往往会遇到ClassNotFoundException和ClassCastException问题。
请看以下的代码:
以下代码是摘自org.springframework.jndi.JndiTemplate。
public class JndiTemplate
protected Context createInitialContext() throws NamingException {
Hashtable icEnv = null;
Properties env = getEnvironment();
if (env != null) {
icEnv = new Hashtable(env.size());
CollectionUtils.mergePropertiesIntoMap(env, icEnv);
}
return new InitialContext(icEnv);
}
DAO代码
public class UserDao extends SqlMapClientDaoSupport implements IUserDao
以下是bundle中Spring-DM的配置文件
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="${jndi.url}"/>
</bean>
<!-- Transaction manager for a single JDBC DataSource -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- SqlMap setup for iBATIS Database Layer -->
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocations" value="META-INF/sql-map-config*.xml"/>
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- ========================= DAO DEFINITIONS: IBATIS IMPLEMENTATIONS ========================= -->
<bean id="userDao" class="org.amway.osgi.internal.dao.UserDao">
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
<!-- ========================= OSGI SERVICE DEFINITIONS ========================= -->
<osgi:service ref="userDao" interface="com.amway.osgi.dao.test.IUserDao" />
这里使用Spring-DM注册DAO服务,在spring-dm启动时候会扫描BundleContext中所有bundle的META-INF/spring/*.xml, 并且创建OsgiApplicationContext。
注意JndiTemplate中createInitialContext方法中new InitialContext(icEnv)。追踪源码,会看到Context是通过ContextFactory生成的,而这个ContextFactory由TCCL(Thread Context Class Loader)加载的。
public static Context getInitialContext(Hashtable<?,?> env)
throws NamingException {
InitialContextFactory factory;
InitialContextFactoryBuilder builder = getInitialContextFactoryBuilder();
if (builder == null) {
// No factory installed, use property
// Get initial context factory class name
String className = env != null ?
(String)env.get(Context.INITIAL_CONTEXT_FACTORY) : null;
if (className == null) {
NoInitialContextException ne = new NoInitialContextException(
"Need to specify class name in environment or system " +
"property, or as an applet parameter, or in an " +
"application resource file: " +
Context.INITIAL_CONTEXT_FACTORY);
throw ne;
}
try {
factory = (InitialContextFactory)
helper.loadClass(className).newInstance();
} catch(Exception e) {
NoInitialContextException ne =
new NoInitialContextException(
"Cannot instantiate class: " + className);
ne.setRootCause(e);
throw ne;
}
} else {
factory = builder.createInitialContextFactory(env);
}
return factory.getInitialContext(env);
}
final class VersionHelper12 extends VersionHelper {
ClassLoader getContextClassLoader() {
return (ClassLoader) AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
return Thread.currentThread().getContextClassLoader();
}
}
);
}
ClassLoader oldTCCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(newTCCL);
...
} catch (Throwable e) {
...
} finally {
Thread.currentThread().setContextClassLoader(oldTCCL);
}
bi
@Override
protected Context createInitialContext() throws NamingException {
InitialContext context = null;
// 1 Get current class loader(osgi bundle loader)
Thread current = Thread.currentThread();
ClassLoader old = current.getContextClassLoader();
try {
// 2 Get BridgeServlet class loader(web container class loader)
ClassLoader newCl = BridgeServlet.class.getClassLoader();
// 3 set current class loader to web ctx class loader
current.setContextClassLoader(newCl);
context = new InitialContext();
} catch (NamingException e) {
e.printStackTrace();
} finally {
current.setContextClassLoader(old);
}
return context;
}
public Object lookup(final String name) throws NamingException {
if (logger.isDebugEnabled()) {
logger.debug("Looking up JNDI object with name [" + name + "]");
}
return JndiUtil.execute(new JndiCallback<Object>() {
public Object doInContext(Context ctx) throws NamingException {
Object located = ctx.lookup(name);
if (located == null) {
throw new NameNotFoundException(
"JNDI object with [" + name + "] not found: JNDI implementation returned null");
}
return located;
}
});
}
public static <T> T execute(Hashtable environment,JndiCallback<T> contextCallback) throws NamingException {
T result = null;
InitialContext context = null;
// 1 Get current class loader(osgi bundle loader)
Thread current = Thread.currentThread();
ClassLoader old = current.getContextClassLoader();
try {
// 2 Get BridgeServlet class loader(web container class loader)
ClassLoader newCl = BridgeServlet.class.getClassLoader();
// 3 set current class loader to web ctx class loader
current.setContextClassLoader(newCl);
context = new InitialContext(environment );
result = contextCallback.doInContext(context);
} catch (NamingException e) {
e.printStackTrace();
} finally {
current.setContextClassLoader(old);
}
return result;
}
org.eclipse.equinox.servletbridge.extensionbundle的MANIFEST.MF文件
Fragment-Host: system.bundle; extension:=framework
Export-Package: javax.servlet;version="2.5",javax.servlet.htt
p;version ="2.5",javax.servlet.resources;version="2.5",org.eclipse.eq
uinox.servletbridge;version="1.1"
====================================================================================
ClassLoader original = Thread.currentThread().getContextClassLoader();
try {
System.setProperty("osgi.framework.useSystemProperties", "false"); //$NON-NLS-1$ //$NON-NLS-2$
URL[] frameworkURLs = findFrameworkURLs(initialPropertyMap);
frameworkClassLoader = new ChildFirstURLClassLoader(frameworkURLs, this.getClass().getClassLoader());
Class clazz = frameworkClassLoader.loadClass(STARTER);
Method setInitialProperties = clazz.getMethod("setInitialProperties", new Class[] {Map.class}); //$NON-NLS-1$
setInitialProperties.invoke(null, new Object[] {initialPropertyMap});
registerRestartHandler(clazz);
Method runMethod = clazz.getMethod("startup", new Class[] {String[].class, Runnable.class}); //$NON-NLS-1$
runMethod.invoke(null, new Object[] {args, null});
Method getSystemBundleContext = clazz.getMethod("getSystemBundleContext", new Class[0]); //$NON-NLS-1$
Object bundleContext = getSystemBundleContext.invoke(null, new Object[0] );
frameworkContextClassLoader = Thread.currentThread().getContextClassLoader();
} catch (InvocationTargetException ite) {
Throwable t = ite.getTargetException();
if (t == null)
t = ite;
context.log("Error while starting Framework", t); //$NON-NLS-1$
throw new RuntimeException(t.getMessage());
} catch (Exception e) {
context.log("Error while starting Framework", e); //$NON-NLS-1$
throw new RuntimeException(e.getMessage());
} finally {
Thread.currentThread().setContextClassLoader(original);
}
这里受到了启发。 该处代码是容器里面启动Equinox OSGi的关键地方,该处ChildFirstURLClassLoader是在配置文件config.ini中的osgi.framework中指定的equinox的jar的class loader。之后通过反射去调用该class loader加载的 framework的startup 方法。注意,这是2个不同class loader交互的桥梁.
public interface CrossClassloaderObjectWrapper{
public void setDelegate(Object object);
public void setOutsideClassloader(ClassLoader classloader);
public void setInsideClassloader(ClassLoader classloader);
}
public class DataSourceWrapper implements CrossClassloaderObjectWrapper,
DataSource {
private Object dataSource;
private ClassLoader outsideClassloader;
private Class clazz;
public DataSourceWrapper() {
}
public void setDelegate(Object t) {
dataSource = t;
}
private Class getOutsideClass() {
if (clazz == null) {
try {
if (this.outsideClassloader != null) {
clazz = (Class<DataSource>) this.outsideClassloader
.loadClass("javax.sql.DataSource");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return clazz;
}
public Connection getConnection() throws SQLException {
Method m = ReflectionUtils.findMethod(getOutsideClass(), "getConnection");
Connection connect = (Connection) ReflectionUtils.invokeMethod(m,
dataSource);
return connect;
}
public Connection getConnection(String username, String password)
throws SQLException {
Method m = ReflectionUtils.findMethod(getOutsideClass(), "getConnection",
String.class, String.class);
Connection connect = (Connection) ReflectionUtils.invokeMethod(m,
dataSource, username, password);
return connect;
}
public PrintWriter getLogWriter() throws SQLException {
Method m = ReflectionUtils.findMethod(getOutsideClass(), "getLogWriter");
return (PrintWriter) ReflectionUtils.invokeMethod(m, dataSource);
}
public void setLogWriter(PrintWriter out) throws SQLException {
Method m = ReflectionUtils.findMethod(getOutsideClass(), "setLogWriter",
PrintWriter.class);
ReflectionUtils.invokeMethod(m, dataSource, out);
}
public void setLoginTimeout(int seconds) throws SQLException {
Method m = ReflectionUtils.findMethod(getOutsideClass(), "setLoginTimeout",
Integer.class);
ReflectionUtils.invokeMethod(m, dataSource, seconds);
}
public int getLoginTimeout() throws SQLException {
Method m = ReflectionUtils.findMethod(getOutsideClass(), "getLoginTimeout");
return (Integer) ReflectionUtils.invokeMethod(m, dataSource);
}
public <T> T unwrap(Class<T> iface) throws SQLException {
Method m = ReflectionUtils.findMethod(getOutsideClass(), "unwrap", Class.class);
return (T) ReflectionUtils.invokeMethod(m, dataSource, iface);
}
public boolean isWrapperFor(Class<?> iface) throws SQLException {
Method m = ReflectionUtils.findMethod(getOutsideClass(), "isWrapperFor",
Class.class);
return (Boolean) ReflectionUtils.invokeMethod(m, dataSource, iface);
}
public void setOutsideClassloader(ClassLoader classloader) {
outsideClassloader = classloader;
}
public void setInsideClassloader(ClassLoader classloader) {
// TODO Auto-generated method stub
}
}
public class ServletJndiTemplate extends JndiTemplate {
@Override
public Object lookup(final String name) throws NamingException {
if (logger.isDebugEnabled()) {
logger.debug("Looking up JNDI object with name [" + name + "]");
}
final DataSourceWrapper wrapper = new DataSourceWrapper();
Object object = JndiUtil.execute(new JndiCallback<Object>() {
public Object doInContext(Context ctx) throws NamingException {
Object located = ctx.lookup(name);
if (located == null) {
throw new NameNotFoundException(
"JNDI object with [" + name + "] not found: JNDI implementation returned null");
}
wrapper.setDelegate(located);
ClassLoader cl = Thread.currentThread().getContextClassLoader();
System.out.println("JndiUtil.execute: current class loader:" + cl);
wrapper.setOutsideClassloader(cl);
return located;
}
});
return wrapper;
}
}
<?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:osgi="http://www.springframework.org/schema/osgi"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd">
<!-- ========================= RESOURCE DEFINITIONS ========================= -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>META-INF/datasource.properties</value>
</list>
</property>
</bean>
<!-- ========================= Datasource: jdbc ========================= -->
<!-- <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean> -->
<bean id="osgiJndiTemplate" class="org.amway.osgi.jndi.internal.ServletJndiTemplate">
</bean>
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiTemplate" ref="osgiJndiTemplate"/>
<property name="jndiName" value="${jndi.url}"/>
</bean>
<!-- <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="${jndi.url}"/>
</bean> -->
<!-- Transaction manager for a single JDBC DataSource -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- SqlMap setup for iBATIS Database Layer -->
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocations" value="META-INF/sql-map-config*.xml"/>
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- ========================= DAO DEFINITIONS: IBATIS IMPLEMENTATIONS ========================= -->
<bean id="userDao" class="org.amway.osgi.internal.dao.UserDao">
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
<!-- ========================= OSGI SERVICE DEFINITIONS ========================= -->
<osgi:service ref="userDao" interface="com.amway.osgi.dao.test.IUserDao" />
</beans>
參考:
http://www.eclipse.org/forums/index.php/t/28276/
http://www.ibm.com/developerworks/cn/opensource/os-cn-eclosgisb/
http://www.eclipse.org/equinox/documents/quickstart.php
http://www.eclipse.org/equinox/server/http_in_container.php
http://www.ibm.com/developerworks/cn/java/j-lo-classloader/
<<OSGi and Equinox - Creating Highly Modular Java System>>
<<OSGi 实战.pdf>>
<<Manning.OSGi.in.Action.2011>>