分类 OSGi 下的文章

ClassNotFound问题解决一

  • 替换当前线程ClassLoader,并在finally中还原

    public void fixClassNotFound() {
      ClassLoader classLoader=Thread.currentThread().
    getContextClassLoader();
      try {
          Thread.currentThread()
          .setContextClassLoader(yourClassLoader);
          // Do something
      } finally {
          Thread.currentThread()
          .setContextClassLoader(classLoader);
      }
    }
    

ClassNotFound问题解决二

  • 通过创建BundleClassLoader的方式来加载类
  • 通过使用DynamicInport-Package:some.package.*的方式来动态导入
  • 通过设置org.osgi.framework.bootdelegation的方式来配置全局类加载代理,适用于javax等JDK内包含但不以java开头的包
  • 通过设置org.osgi.framework.system.packages.extra来配置系统额外导出包
  • 通过写一个Fragment Bundle的方式来导出某个包,该Bundle的Fragment-Host为system.bundle

获取PackageAdmin

OSGi框架的核心包已经将PackageAdmin注册为服务,只要OSGi框架没有停止,该服务就可以被安全的获取

    ServiceReference<PackageAdmin> reference = bundleContext.getServiceReference(PackageAdmin.class);
    if(reference != null) {
        try {
            PackageAdmin packageAdmin = bundleContext.getService(reference);
            Bundle[] fragments = packageAdmin.getFragments(bundleContext.getBundle());
            if(fragments != null) {
                for(Bundle fragment : fragments) {
                    // 读取Fragment的MANIFEST.MF文件内容
                    String bundleInstaller = fragment.getHeaders().get(BUNDLE_INSTALLER);
                    if(bundleInstaller != null) {
                        // Do something
                    }
                }
            }
                
        }finally {
            // 使用完需要unget,已保证内部的计数器平衡
            bundleContext.ungetService(reference);
        }
    }

BundleTracker/BundleTrackerCustomizer

  • 跟踪Bundle的启动和停止
  • addingBundle/removeBundle应成对使用
  • addingBundle返回为null时,removeBundle绝对不会被调用
  • 可用于Bundle的监测,BundleClassLoader的缓存等
  • 做Bundle缓存时,为了线程可见性,建议使用volatile Map或ConcurrentMap

BundleTracker在测试方面的应用

步骤
1 跟踪每个Bundle的启动

  • 读取Bundle的MANIFEST.MF文件,探测Bundle是否导入了org.junit包
  • 如果导入了org.junit包,则使用Javassist扫描标记了@RunWith注解的测试用例
  • 反射初始化测试用例或从Spring上下文中查找实例
  • 在新线程中执行测试用例

BundleTrackerCustomizer实现片段

@Override
public ApplicationContextTracker addingBundle(Bundle bundle, BundleEvent event) {
    if(needTest(bundle)) { // 判断Bundle是否包含测试用例
        Set<Class<?>> testClasses = findTestClasses(bundle);
        if(testClasses != null && !testClasses.isEmpty()) {
            boolean requireApplicationContext = false;
            for(Class<?> testClass : testClasses) {
                RunWith runWith - testClass.getAnnotation(RunWith.class);
                if(SpringOsgiTestRunner.class.equals(runWith).value()) {
                    requireApplicationContext = true;
                    break;
                }
            }
            if(requireApplicationContext) {
                ApplicationContextTracker applicationContextTracker = new ApplicationContextTracker(bundle);
                applicationContextTracker.open();
                
                sharedExecutor.execute(new TestExecutor(bundle.getBundleId(), testClasses, runListenerClasses));
                return applicationContextTracker;
            }esle{
                sharedExecutor.execute(new TestExecutor(bundle.getBundleId(), testClasses, runListenerClasses));
            }
        }
        
    }
    return null;
}
@Override
public void removeBundle(Bundle bundle, BundleEvent event, ApplicationContextTracker applicationContextTracker) {
    applicationContextTracker.close();
}

ServiceTracker/ServiceTrackerCustomizer

  • addingService/removeService应成对使用
  • 作服务缓存时,为了线程可见性,建议使用volatile Map或ConcurrentMap
  • ServiceTracker.waitForService(long timeout)不可在BundleActivator的start/stop方法中使用,可能会阻塞OSGi框架“主线程”
  • waitForService在超时后返回null而不是抛出异常

ServiceTracker在提供扩展方面的应用

  • 本例介绍了表达式执行管理器(ExpressionExecutorManager)如何跟踪表达式执行器(ExpressionExecutor)
  • 用户实现ExpressionExecutor接口并发布为服务时,将被纳入ExpressionExecutorManager管理范围

ExepressionExecutorManager片段

public final class ExpressionExecutorManager implements BundleContextAware, InitializingBean, DisposableBean {
    private volatile Map<String, ExpressionExecutor> expressionExecutors = new HashMap<String, ExpressionExecutor>();
    private ServiceTracker<ExpressionExecutor, ExpressionExecutor> serviceTracker;
    private BundleContext bundleContext;

    @Override
    public void setBundleContext(BundleContext bundleContext) {
        this.bundleContext = bundleContext;
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        serviceTracker = new ServiceTracker<ExpressionExecutor, ExpressionExecutor>(bundleContext,
            ExpressionExecutor.class, null) {
            @Override
            public ExpressionExecutor addingService(ServiceReference<ExpressionExecutor> reference) {
                ExpressionExecutor executor = super.addingService(reference);
                expressionExecutors.put(executor.getName(), executor);
                return executor;
            }

            @Override
            public void removedService(ServiceReference<ExpressionExecutor> reference, ExpressionExecutor executor) {
                expressionExecutors.remove(executor.getName());
                super.removedService(reference, executor);
            }
        };
        serviceTracker.open();
    }
    @Override
    public void destroy() throws Exception {
        serviceTracker.close();
    }
    public ExpressionExecutor getExpressionExecutor(String name) {
        if (MvelExpressionExecutor.getInstance().getName().equals(name)) {
            return MvelExpressionExecutor.getInstance();
        }
        return expressionExecutors.get(name);
    }
}

ServiceFactory服务工厂

  • 针对每个Bundle,单独发布一个服务,在创建该服务的时候,可以获得试用服务的Bundle对象,从而进行客户化
  • 样例,假设针对不同的Bundle发布不同的javax.sql.DataSource服务

    public void start(BundleContext bundleContext) throws Exception {
      bundleContext.registerService(DataSource.class.getName(),
          new DataSourceFactory(), null);
    }
    
    private static class DataSourceFactory implements ServiceFactory<DataSource> {
      @Override
      public DataSource getService(Bundle bundle, ServiceRegistration<DataSource>   
          registration) {
          return createDataSource(bundle);
      }
    
      @Override
      public void ungetService(Bundle bundle, ServiceRegistration<DataSource> 
          registration, DataSource dataSource) {
          if (dataSource instanceof Destroyable) {
              ((Destroyable) dataSource).destroy();
          }
      }
    }

ManagedServiceFactory/ConfigurationAdmin

  • ManagedServiceFactory于ConfigurationAdmin通常配合使用
  • 可动态更新参数
  • 动态配置的代码非常复杂,涉及监听器、多线程等复杂的操作,如果有需求,应避免直接使用OSGi CM API,而开发一个通用Bundle来支持
  • 建议参考Gemini Blueprint Core中CM部分源码
  • 建议参考Apache Felix File Install的源码

服务注册

  • BundleContext.registerService
  • 该方法的不同重载版本,第三个属性均为过滤器,为K/V键值对格式
  • 获取服务时,可使用过滤器来匹配
  • BundleContext.getServiceReferences第二个参数就是LDAP过滤器字符串
  • ServiceTracker构造方法包含一个带Filter(可用FrameworkUtil工具类创建)过滤器对象的版本

LDAP过滤器字符串

  • org.osgi.framework.FrameworkUtil.createFilter(String filter)可以创建Filter对象
  • LDAP过滤器
  • 过滤器最好用String.format("(|(%s=%s)(%s=%s))")这样的方式进行创建,否则容易出错
格式 含义
(!(filter)) 不包含
(&(filter1)...(filterN)) 并且
(|(filter1)...(filterN)) 或者

根据类获取Bundle

  • FrameworkUtil里面有一个getBundle(Class<?> clazz)方法,可以根据类获取Bundle
  • 前提,类所在的Bundle必须处于Starting或Active状态,否则将得到null
  • 如果一定要用这种方式获取Bundle,必须确保该Bundle已经启动,例如可以在子Bundle内调用该方法,Class采用一个父Bundle的类,一定可以获取到父Bundle

OSGi基础接口

BundleContext-Bundle上下文

  • installBundle 自定义Bundle装载用,例如Apache Felix Install/Equinox Configurator等Bundle扫描和安装插件都使用此方法。
  • getServiceReference 获取服务引用,不推荐使用,用ServiceTracker代替
  • getService 获取服务
  • getBundles 获取所有Bundle
  • getBundle 获取持有当前上下文的Bundle
  • getBundle(long bundleId) 根据id获取Bundle

BundleActivator-Bundle激活器

  • 仅用于简单的Bundle注册个别服务,如数据源Bundle,缓存服务Bundle等
  • 强烈不推荐在业务开发的过程中使用
  • 使用OSGi Declarative Service/Apache jPOJO/Apache Aries Blueprint/Gemini Blueprint等第三方框架代替
  • 使用BundleActivator应注意,不可在star/stop方法内执行复杂逻辑,应尽快完成,并且不能阻塞,否则将导致其他Bundle启动进行排队

Bundle

  • getBundleContext 获取上下文,你想一个Bundle访问其他Bundle时使用
  • loadClass

    • 加载本Bundle的类,该方法经常被用来实现BundleClassLoader
    • 扩展点,配置BundleTracker/Javassist/JDK注解来完成Bundle启动时标记了特定注解类的加载,已达到扩展的目的。例如扫描标记了@RunWith的测试用例并启动
  • start/stop

    • 启动和停止,Bundle务必处于Resolve状态才可以start,可用PackageAdmin的resolveBundles来解决依赖
    • 扩展点,配合PackageAdmin和BundleContext,可实现任意Bundle在运行时的API方式启动,甚至是字节数组定义
  • getHeaders

    • 获取MANIFEST.MF文件内定义的所有key/value键值对
    • 扩展点,在MANIFEST.MF文件中定义的自定义属性可以被读取。配合BundleTracker可以识别添加了指定属性的Bundle
  • getResource 获取Bundle内的资源
  • findEntries

    • 查找Bundle内的资源,强烈不推荐使用,在开发环境中会发生无法找到资源的情况
    • 使用BundleWiring.listResource代替
  • adapt 使用bundle.adapt(BundleWiring.class)的方式获取BundleWiring

BundleListener/ServiceListener

  • 不推荐使用
  • BundleListener无法监听在其注册之前启动的Bundle
  • ServiceListener无法监听在其注册之前启动的Bundle
  • 使用BundleTracker/ServiceTracker代替
  • BundleTracker可以监听所有Bundle,即使是BundleTracker注册之前启动的Bundle,依然可以监听到
  • ServiceTracker可以监听所有的服务

ServiceRegistration

  • 手工反注册服务使用,通常 用于框架性质的代码,不常用
  • 不推荐普通用户使用

ServiceReference

  • 服务引用
  • 获取服务之前,必须先拿到该引用
  • 可以通过BundleContext.getServiceReference()或ServiceTracker.addingService方法获取

PackageAdmin-包管理

  • 该接口已过时,但其在OSGi 4.x规范中不会被舍弃
  • 其提供的部分方法在其他接口中没有代替
  • getExportedPackages 获取某个Bundle的导出包
  • refreshPackages 刷新包
  • resolveBundles 解决包依赖,API方式安装Bundle后,在其启动前必须调用该方法
  • getFragments 获取子Bundle,一旦拿到,就可以读取子Bundle的MANIFEST.MF文件,可以读取配置/加载接口实现等等

OSGi的jar与普通jar的区别

  • 为普通的jar增加一个META-INF/MANIFEST.MF文件,该jar就成为了OSGi的Bundle
  • MANIFEST.MF文件中定义了一些导入导出包以及激活器等

MANIFEST.MF的特殊使用

  • DynamicImport-Package

    • 动态导入包,例如开发swing应用,可以导入javax.swing.*
    • 开发Gemini Blueprint应用,不确定需要导入spring的哪些包,可以动态导入org.springframework.*
    • 动态导入包可以解决一些运行时依赖,在对已有的ClassLoader部分代码无法绕过时,通常可以使用动态导入解决
  • Fragment-Host

    • 定义父插件
    • 子插件使用父插件的ClassLoader,可以通过子插件作为扩展

Fragment-Host高阶应用

  • 在父插件里写接口/配置文件的初始化逻辑
  • 在子插件里提供接口实现或配置文件

已监控为例,监控里有一个ExpressionExecutor表达式执行器这样一个类,其通过查找子插件实现类以及ServiceTracker的两种方式来预留给使用者自行完成,而不用修改父插件。在父插件定义接口,在子插件的MANIFEST.MF文件中定义接口实现,并在父插件中读取

ExpressionExecutor: com.your.package.SomeExecutor

OSGi的优势

  • 模块化 针对不同客户发布不同模块的产品
  • 动态能力 无停机更新 热插拔新业务
  • 版本控制 一个Package可以存在多个版本,适用于某些功能必须多版本共存的情况
  • 生态系统良好 Java生态系统里,大部分jar都已OSGi化

OSGi的劣势

  • 有部分jar不是OSGi的,OSGi化需要一定的经验
  • 过于复杂,解决ClassLoader问题需要相当经验
  • 基础API不友好,通常需要借助第三方框架才比较容易使用
  • 除非使用高端服务器,否则与Web应用集成有一定的困难
  • 开发人员容易将Bundle当做普通jar加入到Bundle-ClassPath中使用