OSGi R4服务平台核心规范 :第十一章 URL处理服务规范(1)

 由  满江红开放技术研究组织 发布

11.URL处理服务规范

版本号: 1.0

11.1.简介

本规范定义了如何注册一个新的URL类型,如何将一个java.io.InputStream类转换为一个指定的Java类。

本规范定义了对Java运行时的通过bundle来对URL资源类型和内容处理器的扩充处理标准机制。在OSGi平台中,支持对URL类型的动态扩展,这是OSGi服务平台的一个具有重要作用的特性。

这部分规范是非常必要的,这是由于标准Java中的通过新的类型和内容类型来扩展URL类的机制和OSGi服务平台的动态特性不相适应。在Java中,一次性的处理注册新的资源类型和URL内容类型,而且一旦注册之后,就不会取消对资源类型或者内容类型的注册。这样的单一的注册模式使得我们不可能在不同的独立的bundle之间来使用这样的机制。因此,OSGi服务框架必须要实现一种新的机制来覆盖原有的机制。

R4规范提出了一个标准的Connector服务,具有类似的功能,参考IO Connector服务规范[P219]。

11.1.1.要点

  • 多样性(Multiple Access) – 多个bundle都可以注册ContentHandler类和URLStreamHandler类
  • 支持现有类型(Existing Schemes Availability) – 不能覆盖在OSGi服务平台中的现有资源类型。
  • 生命周期监控(life cycle Monitored) – 必须支持bundle的生命周期。当注册了资源类型处理器和内容类型处理处理器的bundle停止之后,服务不再可用
  • 简单性(Simplicity) – 如果bundle需要提供一个新的URL资源类型或者内容类型处理器,最小化bundle需要完成的工作。

11.1.2.名词

  • 资源类型(Scheme) – 指定协议的标志符。例如,“http”表示超文本传输协议。资源类型是java.net.URLStreamHandler的子类。
  • 内容类型(Content Type) – 内容的类型标志符。内容类型一般都是引用了MIME类型。内容类型处理器是java.net.ContentHandler类的子类。
  • 统一资源定位(URL) –java.net.URL类的实例,具有一个资源类型名称和足够的参数来表示一个资源。
  • 工厂对象(Factory)– 创建其他对象的对象。使用工厂对象的目的在于对调用者隐藏实现类(可能有多个)。创建的对象是一个指定类型的子类或者是实现类。
  • 代理对象(Proxy) – 在Java中注册的一个类,将调用转发到在服务注册中心中的实现类。
  • java.net.URLStreamHandler –java.net.URLStreamHandler类的实例,可以创建一个指定协议的连接的URLConnection对象。
  • 单一操作(Singleton Operation) – 只能执行一次的操作。
  • URLStreamHandlerService – OSGi服务接口,包含了的URLStreamHandler类的公有方法,因此,框架可以调用这些方法。
  • AbstractURLStreamHandlerService – 接口URLStreamHandlerService的一个实现,通过调用超类的实现(java.net.url.URLStreamHandler)来实现这个接口的方法. 同时,这个类也实现了通过java.net.URLStreamHandlerSetter对java.net.URL类的设置。
  • URLStreamHandlerSetter – 接口,其中定义了对java.net.URL类设置的抽象方法。这个接口和一个代理以及安全检查相关。
  • java.net.URLStreamHandlerFactory – 工厂对象,注册了java.net.URL类。用于查找实现了在Java环境下没有的资源类型的java.net.URLStreamHandler类。在Java环境下只能注册一个java.net.URLStreamHandlerFactory类。
  • java.net.URLConnection – 指定基于类型的协议的连接。当调用java.net.URL.openConnection时,通过java.net.URLStreamHandler对象来创建一个java.net.URLConnection类。
  • java.net.ContentHandler – 将一个字节流转换而来的Java对象。这个Java类是基于字节流的MIME类型。
  • java.net.ContentHandlerFactory – 工厂对象,通过创建一个需要的实例来扩展java.net.URLConnection提供的java.net.ContentHandler类。java.net.URLConnection只能够注册一个java.net.ContentHandlerFactory 类。
  • MIME Type – 字节流格式的名称。参考 [49] MIME多用途网际邮件扩充协议。

Multipurpose Internet Mail Extension.

下图非常复杂,这是由于Java使用的复杂的策略来实现扩展流处理和内容处理。

11.1.3操作

实现了一个新的URL资源类型的bundle应该在OSGi框架中的URLStreamHandlerService接口下注册一个服务对象。这个接口包含了java.net.URLStreamHandler类的公有方法,因此可以通过代理器(通常在Java运行时注册的对象)来调用这些方法。

OSGi框架实现中,必须在java.net中实现这个服务对象。这是由于只能调用一次方法java.net.URL.setStreamHandlerFactory。这样过去和以后的bundle都不能使用它了。

对一个内容标记的流进行转化的bundle应该在java.net.ContentHandler下注册一个服务对象。OSGi框架应该提供这些对象给java.net.URLConnection类。

11.2.java.net中的工厂模式

Java提供了OSGi框架和很多OSGi服务平台运行的bundle所使用的java.net.URL类,使用URL类的一个关键好处在于简化了将URL字符串转化为对资源请求的过程。

java.net.URL类具有可扩展性,允许动态的通过java.net.URLStreamHandlerFactory添加新的资源类型(协议)和内容类型。现有的应用也可以通过这些新的处理器来使用新的资源类型和内容类型,使用方式和Java运行环境提供的处理器是相同的。在Javadoc的URLStreamHandler 和ContentHandler中描述了这种机制。参阅文献[47] Java。

例如,通过URL http://www.osgi.org/sample.txt来定位一个OSGi web服务的一个文件,这个文件使用HTTP协议来获得(通常Java运行环境都提供了这样的资源类型)。这样的一个URL:rsh://www.acme.com/agent.zip定位了一个ZIP文件,可以通过Java环境中没有定义的RSH资源类型来获得。在成功使用RSH资源之前,必须要在java.net.URL类下注册一个java.net.URLStreamHandlerFactory对象。

只使用在Java中现有的java.net.URL资源处理器会导致一些问题:

  • 工厂对象是单一操作 – 一个java.net.URLStreamHandlerFactory类只能使用java.net.URL注册一次。同样一个java.net.ContentHandlerFactory也只能使用java.net.URLConnection注册一次。也就是说,不能对工厂对象的取消注册或者是替代一个工厂对象。
  • 资源类型的缓存(Caching Of Schemes) – 当java.net.URL类第一次使用一个原来没有使用过的资源类型,java.net.URL类从当前注册的java.net.URLStreamHandlerFactory类中请求一个java.net.URLStreamHandler类。缓存返回的java.net.URLStreamHandler类,然后对这个类型的并发请求使用了同样的java.net.URLStreamHandler类。这也就是说,一旦构造了指定类型的处理器,就不能删除这个处理器,不能替换为一个新的java.net.ContentHandler类。

这两方面的问题都影响到了OSGi操作模型,这个模型允许一个bundle在不同的生命周期阶段中涉及暴露的服务、删除服务、更新代码、从另一个bundle中提供同样的服务来替代等等。bundle并不能兼容现在的Java机制。

11.3.框架规程

在OSGi框架中必须注册一个的java.net.URLStreamHandlerFactory对象和一个java.net.ContentHandlerFactory对象,其中包含的方法分别为:

  java.net.URL.setURLStreamHandlerFactory
  java.net.URLConnection.setContentHandlerFactory

当注册了这两个工厂对象,OSGi框架服务注册中心必须跟踪URLStreamHandlerService服务和java.net.ContentHandler服务的注册。

一个URL流处理器服务(URL Stream Handler Service)必须要关联一个服务注册属性,属性名称为URLHANDLERPROTOCOL。这个属性url.handler.protocol的值必须是一个资源类型数组(String [])。

内容处理器服务必须关联一个服务注册属性,属性名称为URLCONTENTMIMETYPE。这个属性的值必须是一个MIME类型名称数组(String []),其中字符串的格式为类型/子类型。参阅文献[49]MIME 多用途网际邮件扩充协议。

11.3.1.构建代理和处理器

当URL中使用一个原来没有使用过的资源类型时,查询已经注册的java.net.URLStreamHandlerFactory对象(应该已经在OSGi框架中注册)。然后,OSGi框架必须查询服务注册中心注册在URLStreamHandlerService下的而且匹配请求资源类型的服务。

如果找到了一个或者多个服务对象,那么构造一个代理对象。必须使用代理对象,这是由于提供java.net.URLStreamHandler实现类的服务对象是未注册的,而且Java中没有提供一种机制来撤销一个从java.net.URLStreamHandlerFactor类返回的java.net.URLStreamHandler类。

一旦创建了代理对象,这个代理对象必须要跟踪服务注册中心中和它相关资源类型的注册和取消注册。代理对象必须关联和资源类型匹配,而且任何时候都具有最大的服务属性org.osgi.framework.Constants.SERVICE_RANKING(参考服务属性[P.107])都是具有最大值的服务。如果一个代理对象关联到一个URL流处理服务,则当具有更大的这个服务属性值的的服务注册之后,代理对象必须要关联到这样的最新注册的处理器。

代理对象必须将所有的方法请求发送到相关的URL流处理服务,直到这个服务变成未注册状态。一旦创建了代理对象,就不能撤销这个对象,这是由于Java运行时缓存了这个对象。尽管如此,服务对象是可以撤销的,代理对象可以不关联任何URLStreamHandlerService或者java.net.ContentHandle对象而单独存在。

在这种情况下,代理对象必须要处理接下来的请求,直到注册了另外一个相应的服务。如果发生了这样的情况,代理类必须要能够处理这样的错误。

如果是URL流处理代理对象,如果在方法的声明中允许抛出这样的异常,那么就必须要抛出java.net.MalformedURLException异常。否则,抛出异常java.lang.IllegalStateException

如果是内容处理器代理对象,必须返回这些数据的输入流(InputStream)。

bundle必须要确保它们的URLStreamHandlerService或者是java.net.ContentHandler服务对象在变为未注册状态的情况下也可以抛出异常。

内容处理服务的代理和URL流处理服务代理的操作稍微有些不同。如果一个注册的ContentHandlerFactory对象返回了null,在这种情况下,工厂对象将没有机会来为那种类型的内容提供一个ContentHandler对象。因此,对于一种类型的内容,如果没有内置的处理器,也没有一个注册的处理器,必须构造一个ContentHandler代理类,这个代理类从URLConnection对象中返回一个输入流对象(InputStream)作为内容对象,直到注册了一个这样类型的处理器。

11.3.2.内置处理器

Java实现中提供了一系列的java.net.URLStreamHandler类的子类来处理这样的协议,例如:HTTP,FTP,NEWS等等。大部分Java实现提供了一种机制来通过类名称添加新的处理器,添加的处理器可以通过类路径查找到。

如果对于一个内置处理器(或者是一个可以通过类名称构造机制查找获得的),一个注册的java.net.URLStreamHandlerFactory工厂对象返回了null,那么对于这样类型的请求就不会再次调用,这是由于Java实现将会使用内置的处理器,或者使用类名称构造。

因此,不能保证一定会使用注册的URLStreamHandlerService服务对象,内置的处理器也许会优先来处理,这样做是为了保证兼容性。在OSGi执行环境中定义的内置的处理器,是不能对其进行覆盖的。同样的,内容处理器工厂对象的实现也是采用了同样的技术,当然也具有同样的问题存在。

为了简化内置处理器的发现,使得可以通过名称来解释构造,在bundle查找服务注册中心之前,框架必须要使用下一节描述的方法。

11.3.3.查找内置处理器

如果定义了如下系统属性:java.protocol.handler.pkgs或者java.content.handler.pkgs,那么必须要在内置处理器中使用这些属性。属性值为一系列的包名称,采用垂直线(‘|’, \u007C)来分隔,查找的时候按照从左到右的顺序,例如,

  org.osgi.impl.handlers | com.acme.url

包名称是作为资源类型或者内容类型的前缀,来形成一个处理资源类型或者内容类型的类名。

某种资源类型的URL流处理器名称就是将一个字符串“.Handler”附加到资源类型名称的后面。名称前缀是包名。例如,上述例子中,对于rsh资源类型的处理器的查找按照如下顺序进行:

  org.osgi.impl.handlers.rsh.Handler
  com.acme.url.rsh.Handler

MIME类型名称中包含了‘/’字符,和其他字符,其他字符不能是Java类名称中不允许出现的字符。将一个MIME名称转化为一个类名称的处理必须要按照以下规定进行:

1.首先,将名称中的所有斜线转化为点号(‘.’,\u002E)。所遇其他Java类名称中不允许出现的字符必须要转化为下划线(‘_’,\u005F)。

2.转换完成之后,将这个名称附加到在属性java.content.handler.pkgs中指定的包列表名称之后,例如,如果转化前的类型为application/zip,而包列表为前面所示的例子,那么查找的类名称如下:

11.3.4.保护方法和代理器

不能在服务注册中心注册java.net.URLStreamHandler类的实现,这是由于类URLStreamHandler的方法都是protected的,这样代理器就不能使用这些方法。同样,URLStreamHandler类检查那些只有从URLStreamHandlerFactory对象中返回的URLStreamHandler才可以调用setURL方法。这也就是说,服务注册中心的URLStreamHandler类不能调用setURL方法,当实现parseURL方法时,是必须要调用这个方法的。

因此,创建了接口URLStreamHandlerService和URLStreamHandlerSetter。接口URLStreamHandlerService提供了URLStreamHandler的公有方法,除了没有setURL方法,而且parseURL方法有一个新的类型参数,类型为URLStreamHandlerSetter。通常,可以将URLStreamHandler的子类转化为URLStreamHandlerService服务类,只需要对代码作很小的改动。通过将相关方法设置为public,parseURL方法为了调用URLStreamHandlerService 对象传递的URLStreamHandlerSetter对象的setURL方法,需要对parseURL方法作一些改动,相当于URLStreamHandler类的setURL方法。

为了帮助URLStreamHandler实现类的转换,提供了一个AbstractURLStreamHandlerService类。分别完成了相关方法的公有化,AbstractURLStreamHandlerService类中的一个私有变量保存了URLStreamHandlerSetter对象。为了让setURL方法能够顺利执行,覆盖了setURL方法来调用保存URLStreamHandlerSetter的setURL方法,而不是URLStreamHandler.setURL方法。这也就是说URLStreamHandler类的子类应该改为AbstractURLStreamHandlerService类的一个子类而且需要重新编译。

通常,parseURL方法的代码如下格式:

  class URLStreamHandlerImpl {

  ...

  protected URLStreamHandlerSetter realHandler;

  ...

  public void parseURL(URLStreamHandlerSetter realHandler,

  URL u, String spec, int start, int limit) {

  this.realHandler = realHandler;

  parseURL(u, spec, start, limit);

  }

  
  protected void setURL(URL u,

  String protocol, String host,

  int port, String authority,

  String userInfo, String path,

  String query,String ref) {

  realHandler.setURL(u, protocol, host,

  port, authority, userInfo, path,

  query, ref);

  }

  ...

  }

 URLStreamHandler.parseURL将调用setURL方法,这个方法必须要通过代理器调用。这也就是为什么覆盖setURL方法委托给URLStreamHandlerSetter。

11.4.提供新的模式

下面的例子提供了一种资源类型,这种资源类型返回了URL的路径部分。第一部分是URLStreamHandlerService类的一个实现。服务启动之后,在OSGi框架中这个类将自己注册。

然后如果必须创建一个新的java.net.URLConnection 对象,那么OSGi框架调用方法openConnection方法。在这个例子中,返回的是DataConnection对象。

  public class DataProtocol

  extends AbstractURLStreamHandlerService

  implements BundleActivator {

  
  public void start( BundleContext context ) {

  Hashtable properties = new Hashtable();

  properties.put( URLConstants.URL_HANDLER_PROTOCOL,

  new String[] { "data" } );

  context.registerService(

  URLStreamHandlerService.class.getName(),

  this, properties );

  }

  
  public void stop( BundleContext context ) {}

  
  public URLConnection openConnection( URL url ) {

  return new DataConnection(url);

  }

  }

下面的例子中,DataConnection类继承了java.net.URLConnection类,而且覆盖了构造方法,这样就可以为超类、connect方法和getInputStream方法提供URL对象。最后一个方法返回了URL的路径部分,返回的形式为一个java.io.InputStream对象。

  class DataConnection extends java.net.URLConnection {

  DataConnection( URL url ) {super(url);}

  
  public void connect() {}

  
  public InputStream getInputStream() throws IOException {

  String s = getURL().getPath();

  byte [] buf = s.getBytes();

  return new ByteArrayInputStream(buf,1,buf.length-1);

  }

  
  public String getContentType() {

  return "text/plain";

  }

  }

11.5.内容处理器

内容处理器(Content Handler)应该继承java.net.ContentHandler类,而且实现getContent方法。这个方法必须要从java.net.URLConnection参数中获得一个InputStream对象,而且将输入流中的子介转化为应用类型。在这个例子中,MIME类型是text/plain,返回的对象是一个字符串对象。

  public class TextPlainHandler extends ContentHandler

  implements BundleActivator {

  
  public void start( BundleContext context ) {

  Hashtableproperties = new Hashtable();

  properties.put( URLConstants.URL_CONTENT_MIMETYPE,

  new String[] { "text/plain" } );

  context.registerService(

  ContentHandler.class.getName(),

  this, properties );

  }

  public void stop( BundleContext context ) {}

  
  public Object getContent( URLConnection conn )

  throws IOException {

  InputStream in = conn.getInputStream();

  InputStreamReader r = new InputStreamReader( in );

  StringBuffer sb = new StringBuffer();

  int c;

  while ( (c=r.read()) >= 0 )
 
  sb.append( (char) c );

  r.close(); in.close();

  return sb.toString();

  }

  }

11.6.安全问题

指定一种协议类型并且添加内容处理器会导致程序可能会直接影响到Java虚拟机中的核心代码类的运行。在网络应用中,广泛使用了java.net.URL类,而且OSGi框架本身也可以使用这个类。

因此,当提供了注册处理器的能力之后,必须要小心谨慎。支持的两种类型的处理器是URLStreamHandlerService和java.net.ContentHandler。只有可信的bundle才可以进行注册这些服务,并且具有对这些类的权限:ServicePermission[URLStreamHandlerService|ContentHandler, REGISTER]。由于其他bundle都可以通过java.net.URL类和java.net.URLConnection类来使用这些服务,最好还是对于所有bundle拒绝这样的服务(ServicePermission[,GET]),而只有框架才可以获得它们。这样可以阻止一些恶意代码攻击,恶意代码可以通过java.net.URL类完成权限检查,然后就可以直接使用URLStreamHandlerServices类。


wmz 2015-06-16 19:02

http://osgi.jxtech.net 是一个非常优秀的OSGi开发平台,能快速上手,相当好用。

顶(1) 踩(0) 回复
查看评论