OSGi R4服务平台核心规范 :第六章 Framework API(3)

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

版权说明

本文档版权归原作译者所有。 在免费、且无任何附加条件的前提下,可在网络媒体中自由传播。

如需部分或者全文引用,请事先征求作译者意见。

如果本文对您有些许帮助,表达谢意的最好方式,是将您发现的问题和文档改进意见及时反馈给作者。当然,倘若有时间和能力,能为技术群体无偿贡献自己的所学为最好的回馈。

本文档可从http://www.redsaga.com获取最新更新信息

6.1.5. public interface BundleActivator

对bundle的启动和停止进行定制。

BundleActivator是一个当bundle启动或者停止时实现的接口。如果需要,框架可以创建bundle的BundleActivator。如果BundleActivator的实例的start方法成功执行,那么就可以保证同一个实例的stop方法将在停止bundle的时候进行调用。

通过头标Bundle-Activator来指定BundleActivator。

在bundle清单文件中只能指定一个BundleActivator。而且片断bundle是不能有BundleActivator的。头标格式如下:

Bundle-Activator: class-name

其中class-name是全名的Java类名称。

指定的BundleActivator必须要有一个公有的不带参数的构造方法,这样就可以通过Class.newInstance()来创建BundleActivator实例。

6.1.5.1. public void start( BundleContext context ) throws Exception

context 待启动bundle的执行上下文
bundle启动时调用,这样,框架就可以执行bundle指定的动作来启动bundle。可以用于注册服务,或者是分配bundle需要的资源。

本方法必须要执行完成,并且在允许时间内返回。
Throws Exception – 如果方法抛出了异常,那么标记bundle为stopped,然后框架将移除bundle的监听器,取消注册bundle所注册的所有服务,并且释放bundle使用的所有服务。
See Also Bundle.start[p.131]

6.1.5.2. public void stop( BundleContext context ) throws Exception

context 待停止bundle的执行上下文
当停止bundle时调用,这样框架就运行bundle指定的动作来停止bundle。通常,方法执行的是start方法的逆操作。bundle返回后,应该没有由这个bundle创建的活动状态的线程。一个停止的bundle不能调用框架的任何对象。

本方法必须要执行完成,并且在允许时间内返回。
Throws Exception – 如果方法抛出了异常,那么标记bundle为stopped,然后框架将移除bundle的监听器,取消注册bundle所注册的所有服务,并且释放bundle使用的所有服务。
See Also Bundle.stop[p.132]

6.1.6. public interface BundleContext

框架内的bundle执行上下文。上下文用于授予其他方法和框架进行交互的访问途径。

通过使用bundel上下文(BundleContext)允许bundle:

  • 预定框架事件。
  • 在框架服务注册中心注册服务对象。
  • 从框架服务注册中心检索服务引用(ServiceReferences)。
  • 从服务引用对象获取并释放服务对象。
  • 在框架中安装新的bundle。
  • 获取框架中安装的bundle列表。
  • 从bundle中获得Bundle对象。
  • 从框架为bundle提供的持久存储区中为文件创建File对象。

当使用BundleActivator.start方法来启动bundle时,创建一个BundleContext对象关联到这个bundle。而当使用BundleActivator.stop方法来停止一个bundle时,传递和创建时同样的一个BundleContext对象来关联上下文。通常,BundleContext都是bundle私有的,也就是说在OSGi环境下,不能在bundle之间共享BundleContext对象。

和对象BundleContext关联的Bundle对象称之为上下文bundle。

只有在执行BundleContext的上下文bundle时,BundleContext对象才是有效的;也就是说,当上下文bundle处于STARTING、STOPPING和ACTIVE状态时。如果随后使用了BundleContext对象,那么必须抛出IllegalStateException异常。而在BundleContext的上下文bundle停止之后,不能再使用这个BundleContext对象。

框架是创建BundleContext对象的惟一来源,也只在创建BundleContext对象的框架内有效。

6.1.6.1. public void addBundleListener( BundleListener listener )

listener 需要添加的监听器(BundleListener)
如果指定的BundleListener对象在上下文bundle中不存在,那么将它添加到上下文bundle的监听器列表中。当bundle的生命周期状态改变之后,监听器就可以获得通知。

如果上下文bundle中已经包含了监听器l(l==listener),那么本方法不做任何处理。
IllegalStateException – 如果这个BundleContext不再有效。

SecurityException – 如果这个监听器是一个同步bundle监听器(SynchronousBundleListener),而且Java运行环境支持权限但是调用者没有相应的管理权限:AdminPermission[context bundle,LISTENER]
See Also BundleEvent[p.148] , BundleListener[p.150]

6.1.6.2. public void addFrameworkListener( FrameworkListener listener )6.1.6.2. public void addFrameworkListener( FrameworkListener listener )

listener 待添加的框架监听器(FrameworkListener)对象
如果指定的FrameworkListener对象在上下文bundle中不存在,那么将它添加到上下文bundle的监听器列表中。如果发生了框架事件,监听器就可以获得通知。

如果上下文bundle中已经包含了监听器l(l==listener),那么本方法不做任何处理。
Throws IllegalStateException – 如果这个BundleContext不再有效。
See Also FrameworkEvent[p.168] , FrameworkListener[p.170]

6.1.6.3. public void addServiceListener( ServiceListener listener, String filter ) throws InvalidSyntaxException

listener 待添加的服务监听(ServiceListener)对象
filter 过滤条件
将带有指定过滤器的指定的服务监听对象添加到上下文bundle的监听器列表中。关于过滤语法可以参考Filter一节。当一个服务的生命周期发生改变之后,服务监听器对象可以获得通知。

如果上下文bundle中已经包含了监听器l(l==listener),那么本方法将这个监听器l的过滤器用参数中指定的过滤器替代。

如果满足过滤器条件,那么就调用监听器。为了在服务类的基础上过滤,过滤器应该参考Constants.OBJECTCLASS属性。如果过滤器为null,那么可以认为所有的服务都匹配过滤器。

使用过滤器时,有可能服务的整个生命周期服务事件都不会通知监听器。例如,如果过滤器之匹配x属性的值为1,如果服务注册时,x属性的值没有设置为1。随后,当将x属性的值修改为1,那么过滤器就可以匹配了,如果发生了类型为MODIFIED的服务事件,将调用这个监听器。但是对于类型为REGISTERED的服务事件,将不会调用监听器。

如果Java运行环境支持权限,而只有当注册服务的bundle具有服务权限使用至少一个注册的服务类来获得服务,服务监听器对象才会获得服务事件通知。
Throws InvalidSyntaxException – 如果过滤器包含了无法处理的过滤器字符串。

IllegalStateException – 如果上下文bundle不再有效。
See Also ServiceEvent[p.173],ServiceListener[p.176],ServicePermission[p.176]

6.1.6.4. public void addServiceListener( ServiceListener listener )

listener 待添加的服务监听器对象(ServiceListener)
将指定的服务监听对象添加到上下文bundle的监听器列表中。

这个方法和调用如下方法:

BundleContext.addServiceListener(ServiceListener listener, String filter)其中filter为null

的结果是相同的。
Throws IllegalStateException – 如果上下文bundle不再有效。
See Also addServiceListener(ServiceListener, String)[p.138]

6.1.6.5. public Filter createFilter( String filter ) throws InvalidSyntaxException

filter 过滤器字符串
创建一个Filter对象。使用这个Filter对象来匹配ServiceReference对象或者是Dictionary对象。

如果不能分析这个过滤器对象,那么抛出InvalidSyntaxException异常,并且带有可读的信息来描述哪一部分是不可读的。
Returns 对过滤字符串进行压缩包装的Filter对象。
Throws InvalidSyntaxException – 如果过滤字符串中包含了不能分析的过滤器字符串。

NullPointerException – 如果过滤器为null。

IllegalStateException – 如果指定的BundleContext不再有效。
See Also Framework specification for a description of the filter string

syntax., FrameworkUtil.createFilter(String)[p.170]

6.1.6.6. public ServiceReference[] getAllServiceReferences( String clazz, String filter ) throws InvalidSyntaxException

clazz 服务注册的类名称,或者是所有的服务。
filter 过滤条件
返回一个服务引用对象(ServiceReference)数组。返回的服务引用数组中包含了符合条件的服务,即匹配指定的过滤器,并且是在参数中指定的类下注册的服务。

调用方法时返回的结果是有效的,但是由于框架是一个的动态的环境,服务任何时候都有可能改变或者是取消注册。

过滤器是用来选择注册服务,那些注册服务包含了和过滤器匹配的属性键值对。关于过滤器语法参阅Filter一节。

如果过滤器为null,那么表示匹配所有的注册服务。如果不能解析过滤字符串,那么抛出一个InvalidSyntaxException异常,其中包含了对那么不能解析部分的可读信息。

按照如下步骤来选择ServiceReference对象:

1. 如果过滤字符串不为null,那么分析过滤字符串,然后得到了满足过滤器的ServiceReference对象集合。

2. 如果Java运行环境支持权限控制,那么第一步中生成的集合将由于对权限的检查而缩减,如果调用者没有权限获得至少一个注册服务的类,那么将从集合中删除这个ServiceReference对象;如果对于某一个ServiceReference对象,调用者没有正确的权限,那么删除这个对象。

3. 如果参数中clazz的值为非空,那么进一步检查集合中引用的服务是否注册了指定类。可以通过服务的Constants.OBJECTCLASS属性来获得所有的服务接口类列表和服务注册类列表。

4. 返回剩下的ServiceReference对象数组。
Returns ServiceReference对象数组

或者如果没有注册服务,返回null
Throws InvalidSyntaxException – 如果过滤器包含不能分析的不合法的过滤字符串。

IllegalStateException – 如果BundleContext对象已经不再有效
Since 1.3

6.1.6.7. public Bundle getBundle( )

返回BundleContext关联的Bundle对象,这个bundle称之为上下文bundle。
Returns 和BundleContext关联的Bundle对象。
Throws IllegalStateException – 如果BundleContext对象已经不再有效。

6.1.6.8. public Bundle getBundle( long id )

id 待获得的bundle的标识符
返回指定标识符的bundle。
Returns 一个Bundle对象; 返回null,如果指定标识符不能匹配任何安装的bundle。

6.1.6.9. public Bundle[] getBundle( )

返回所有的已经安装的bundle。

这个方法返回调用方法时安装在OSGi环境下的所有的bundle列表。但是,由于框架是一个动态的环境,任何时候都有可能安装或者是卸载bundle。
Returns Bundle对象数组,对于每一个安装的bundle,对应一个Bundle对象。

6.1.6.10. public File getDataFile( String filename )

filename 访问的文件的相对路径名
在框架为bundle提供的持久存储区中创建一个指定文件名称的File对象。如果平台不支持这种文件系统,那么返回null。

如果使用一个空串作为文件名参数来调用这个方法,那么可以获得一个框架为bundle提供存储区的根目录的File对象。

如果Java运行环境支持权限控制,那么框架要确保bundle具有文件权限(java.io.FilePermission)来读取、写入、删除所有的上下文bundle的持久存储区中的文件(递归)。
Returns 表示请求文件的File对象, 或者如果平台不支持文件系统,返回null。
Throws IllegalStateException – 如果BundleContext对象已经不再有效。

6.1.6.11. public String getProperty( String key )

key 请求的属性名称
返回指定属性的值。如果没有在框架中找到指定的属性,那么查找系统属性,如果没有找到属性,返回null。

框架定义了如下标准属性键:

  • Constants.FRAMEWORK_VERSION[p.161] — OSGi框架版本。
  • Constants.FRAMEWORK_VENDOR[p.161] — 框架实现者。
  • Constants.FRAMEWORK_LANGUAGE[p.160] — 使用的语言。参阅ISO 639 的标准。
  • Constants.FRAMEWORK_OS_NAME[p.160] —主服务器操作系统名称。
  • Constants.FRAMEWORK_OS_VERSION[p.160] — 主服务器操作系统版本号。
  • Constants.FRAMEWORK_PROCESSOR[p.160] — 主服务器处理器名称。
所有的bundle必须要有权限读取这些属性。

注意:最后的四个标准属性是用于Constants.BUNDLE_NATIVECODE清单头标选择本地语言代码的匹配算法。
Returns 请求的属性值;

或者如果没有定义这个属性,则返回null。
Throws SecurityException – 如果Java运行环境支持权限控制而调用者没有相应的权限来读取这个属性。

6.1.6.12. public Object getService( ServiceReference reference )

reference 服务的一个引用(ServiceReference)
返回指定服务的一个服务对象。

bundle对服务的使用通过bundle对服务的使用计数来跟踪。每当通过这个方法getService(ServiceReference)获得了一个对某个服务的服务对象,那么上下文bundle对该服务的使用计数加1。 如果bundle对某个服务的使用计数降为0,那么表示bundle不再使用这个服务。

通常如果ServiceReference关联的服务已经取消注册了,那么方法返回null。

通过以下步骤来获得服务对象:

1. 如果已经取消注册服务,那么返回null。

2. 上下文bundle对这个服务的使用计数加1。

3. 如果上下文bundle对服务的使用计数为1,而且服务注册了一个实现服务工厂(ServiceFactory)的类,那么调用ServiceFactory. getService(Bundle, ServiceRegistration)方法来为上下文bundle创建一个服务对象。框架将缓存这个服务对象。当上下文bundle对服务使用计数大于0时,接下来对本方法的调用将返回这个缓存的服务对象。
如果服务工厂对象返回的服务对象不是注册的服务类的实例对象,或者服务工厂对象中抛出了异常,那么返回null,而且发出框架事件FrameworkEvent.ERROR。

4. 返回获得的服务对象。
Returns 和指定ServiceReference关联的服务对象;

如果服务没有注册,或者如果使用服务工厂而没有实现服务注册类,那么返回null值。
Throws SecurityException – 如果Java运行环境支持权限,而调用者没有使用至少一个服务注册类获得服务的权限。

IllegalStateException – 如果BundleContext对象已经不再有效。
See Also ungetService(ServiceReference)[p.147] , ServiceFactory[p.175]

6.1.6.13. public ServiceReference getServiceReference( String clazz )

clazz 服务注册的类名称
返回实现并且注册了指定类的服务的一个引用对象(ServiceReference)。

当调用方法时,ServiceReference对象有效。但是由于框架是一个动态环境,服务随时都可能修改或取消注册,那么返回的ServiceReference对象不一定一直有效。

这个方法等同于使用一个null值的过滤字符串来调用如下方法:BundleContext.getServiceReferences(String, String),这是为了方便调用者只对实现了指定类的服务感兴趣的情况。

如果存在多个这样的服务,那么返回具有最高等级的服务(在Constants.SERVICE_RANKING中指定);也就是说,返回最先注册的服务。
Returns ServiceReference对象;

如果没有任何注册服务实现了指定类,返回null。
Throws IllegalStateException – 如果BundleContext对象已经不再有效。
See Also getServiceReferences(String, String)[p.143]

6.1.6.14. public ServiceReference[] getServiceReferences( String clazz, String filter ) throws InvalidSyntaxException

clazz 服务注册的类名称,或者null表示所有的服务
filter 过滤器条件
返回ServiceReference对象数组。返回的对象都是注册了指定类,符合过滤条件,而且服务注册的类名所在包要和上下文bundle中通过ServiceReference.isAssignableTo(Bundle, String)定义的包匹配。

当调用方法时,ServiceReference对象有效。但是由于框架是一个动态环境,服务随时都可能修改或取消注册,那么返回的ServiceReference对象不一定一直有效。

过滤器用于选择注册服务,选择的服务的属性对象中包含的键值对和过滤器匹配。参阅Filter一节中过滤器语法规则描述。

如果过滤器字符串为null,那么匹配所有的注册服务。如果不能分析过滤字符串,那么抛出InvalidSyntaxException异常,其中包含了过滤字符串不能分析的原因。

按照以下步骤选择ServiceReference对象集合:

1. 如果过滤字符串不为null,那么分析过滤字符串对象,产生一个满足过滤条件的ServiceReference对象集合。
如果过滤字符串为null,那么可以认为所有的注册服务都满足过滤器。

2. 如果Java运行环境支持权限控制,那么通过权限检查来缩减这个集合。如果调用者没有服务权限来获得至少一个注册服务类,那么将移除这个ServiceReference对象。

3. 如果clazz不为null,那么将集合缩减到指定类的的实例并且注册了指定类的服务。可以通过服务的Constants.OBJECTCLASS属性来获得所有的服务接口类列表和服务注册类列表。

4. 最后对集合中的所有ServiceReference对象作最后一次检查。对于每一个ServiceReference对象,使用这个上下文bundle和ServiceReference对象注册的每一个类,调用ServiceReference.isAssignableTo(Bundle, String)方法,如果任何调用返回false,那么从集合中移除这个ServiceReference对象。

5. 返回剩下的ServiceReference对象数组。
Returns ServiceReference对象数组;

如果没有找到满足条件的服务,返回null。
Throws InvalidSyntaxException – 如果过滤器包含不能分析的不合法的过滤字符串。

IllegalStateException – 如果BundleContext对象已经不再有效。

6.1.6.15. public Bundle installBundle( String location ) throws BundleException

location bundle待安装的位置的标识符
从指定位置安装bundle。框架将位置字符串翻译为一个与实现无关的位置信息,从这个位置获得bundle。

每一个安装的bundle都可以通过位置字符串来惟一标识,例如,URL的形式。

按照如下步骤安装bundle:

1. 如果已经安装了同样位置字符串标识的bundle,那么返回已经安装的Bundle对象。

2. 从位置字符串标识的位置读取bundle,如果读取失败,抛出bundle异常(BundleException)。

3. 解析bundle的Bundle-NativeCode依赖,如果失败,抛出bundle异常(BundleException)。

4. 分配bundle关联的资源。分配最低限度的资源,由一个惟一标识符和一块持久存储区域(如果平台支持文件系统)组成。如果本步骤失败,那么抛出bundle异常(BundleException)。

5. 如果bundle已经声明了Bundle-RequiredExecutionEnvironment头标,那么根据列出运行环境对安装的运行环境进行检测。如果当前运行环境没有在其中列出,那么抛出bundle异常(BundleException)。

6. 设置bundle状态为INSTALLED。

7. 发出bundle事件:BundleEvent.INSTALLED。

8. 返回最新或者原来安装的bundle的Bundle对象。

后置条件,没有抛出异常:

  • getState() 返回值在集合{INSTALLED,RESOLVED}中。
  • Bundle对象具有一个惟一ID
后置条件,抛出异常:

  • 没有安装bundle,而且没有bundle存在的记录。
Returns 安装的bundle的Bundle对象。
Throws BundleException – 如果安装失败。

SecurityException – 如果Java运行环境支持权限控制而调用者没有合适的管理权限:AdminPermission[installed bundle,LIFECYCLE]。

IllegalStateException – 如果BundleContext 不再有效。

6.1.6.16. public Bundle installBundle( String location, InputStream input ) throws BundleException

location 待安装的bundle的位置标识符
input bundle读取的输入流对象(InputStream)
从指定输入流中安装bundle。

本方法需要执行BundleContext.installBundle(String location)中列出的所有步骤,另外本方法需要从输入流中读取bundle内容。位置标识符字符串指定了bundle的标识符。

本方法必须要关闭输入流,即使抛出了异常。
Returns 安装的Bundle对象
Throws BundleException – 如果安装失败或者是不能读取提供的输入流。

SecurityException – 如果Java运行环境支持权限控制而调用者没有合适的管理权限:AdminPermission[installed bundle,LIFECYCLE]。

IllegalStateException – 如果BundleContext 不再有效。
See Also installBundle(java.lang.String)[p.144]

6.1.6.17. public ServiceRegistration registerService( String[] clazzes, Object service, Dictionary properties )

clazzes 服务可以定位的类名称。在这个数组中的类名称存储在服务属性中的键Constants.OBJECTCLASS之下
service 服务对象或者是服务工厂对象
properties 服务的属性。属性对象中的键必须是字符串对象。参阅常量(Constants)一节中描述的标准属性键。调用本方法之后,不能改变这个对象。可以通过调用如下方法来修改服务的属性值:ServiceRegistration.setProperties。如果服务没有属性值,那么这个集合可能为null。
通过指定属性,在框架中使用指定类来注册指定的服务对象。返回一个ServiceRegistration对象。这个ServiceRegistration对象是bundle服务注册时私有的,而且不能在bundle之间共享。注册的bundle即为当前上下文bundle。其他的bundle可以通过getServiceReferences方法或者getServiceReference方法来定位服务。

也可以注册实现了ServiceFactory接口的服务对象,这样为其他bundle提供服务对象的方式更加灵活。

注册服务时按照如下步骤进行:

1. 如果注册的服务不是服务工厂(ServiceFactory),那么如果服务不是所有命名的类的接口,那么抛出异常IllegalArgumentException。

2. 框架将服务属性添加到指定的Dictionary中(可能为null):属性名称为Constants.SERVICE_ID的属性描述服务的注册编号,属性名称为Constants.OBJECTCLASS的属性包含了所有的指定类。如果注册bundle已经定义了这些属性,那么这些属性的值将被框架覆盖。

3. 将服务添加到框架服务注册中心,现在其他bundle就可以使用服务。

4. 发出服务事件:ServiceEvent.REGISTERED[p.174]。

5. 返回一个ServiceRegistration对象。
Returns 一个ServiceRegistration对象,注册服务的bundle可以使用这个对象来更新服务属性或者取消注册服务。
Throws IllegalArgumentException – 如果满足以下条件中的一个:参数service为null值;参数service不是一个服务工厂类,而且不是在clazzes参数中指定的所有类的实例;参数properties中包含了同名的属性。

SecurityException –如果Java运行环境支持权限控制而调用者没有服务权限(ServicePermission)来注册所有命名类的服务。

IllegalStateException –如果BundleContext 不再有效。
See Also ServiceRegistration[p.179] , ServiceFactory[p.175]

6.1.6.18. public ServiceRegistration registerService( String clazz, Object service,Dictionary properties )

clazz 服务可以定位的类名
service 服务对象或者是服务工厂对象
properties 服务的属性
通过指定属性,在框架中使用指定类来注册指定的服务对象。

这个方法等同于上文中的registerService(java.lang.String[],java.lang.Object, java.util.Dictionary )[p.145]方法在使用一个类来注册的情况是等同的。在这种情况下,服务的Constants.OBJECTCLASS[p.162]属性是一个字符串数组,而不仅仅是一个单独字符串。
Returns 一个ServiceRegistration对象,注册服务的bundle可以使用这个对象来更新服务属性或者取消注册服务。
Throws IllegalStateException –如果BundleContext 不再有效。
See Also registerService(java.lang.String[], java.lang.Object,java.util.Dictionary)[p.145]

6.1.6.19. public void removeBundleListener( BundleListener listener )

listener 待删除的bundle监听器对象(BundleListener)
从上下文bundle的监听器列表中移除指定的BundleListener对象。

如果在上下文bundle的监听器列表中没有这个监听器,那么方法将不作任何操作。
Throws IllegalStateException – 如果BundleContext不再是有效的。

SecurityException – 如果监听器是同步的,而且Java运行环境支持权限控制而调用者没有管理权限: AdminPermission[context bundle, LISTENER]。

6.1.6.20. public void removeFrameworkListener( FrameworkListener listener )

listener 待删除的框架监听器对象(FrameworkListener)
从上下文bundle的监听器列表中移除指定的FrameworkListener对象。

如果在上下文bundle的监听器列表中没有这个监听器,那么方法将不作任何操作。
Throws IllegalStateException – 如果BundleContext不再是有效的。

6.1.6.21. public void removeServiceListener( ServiceListener listener )

listener 待删除的服务监听器对象(ServiceListener)
从上下文bundle的监听器列表中移除指定的ServiceListener对象。

如果在上下文bundle的监听器列表中没有这个监听器,那么方法将不作任何操作。
Throws IllegalStateException – 如果BundleContext不再是有效的。

6.1.6.22. public boolean ungetService( ServiceReference reference )

reference 待释放的服务引用
释放指定的服务引用所引用的服务对象。如果上下文bundle对这个服务的使用计数为0,那么这个方法返回false,否则,将上下文bundle对服务的使用计数减1。

应该不再使用服务的service对象,而且如果当服务的引用计数减为0时,销毁这个服务的所有引用。按照如下步骤来释放服务对象:

1. 如果上下文bundle对服务的使用计数为0,或者已经取消注册了服务,那么返回false。

2. 将上下文bundle对这个服务的使用计数减1。

3. 如果上下文bundle对这个服务的使用计数在执行第二步后为0,而且注册的是一个服务工厂对象,那么调用服务工厂的服务释放方法:ServiceFactory.ungetService(Bundle, ServiceRegistration, Object)[p.175]。

4. 返回true。
Returns 如果上下文bundle对服务的使用计数为0,或者已经取消注册了服务,那么返回false;

否则返回true。
Throws IllegalStateException – 如果BundleContext不再是有效的。
查看评论