Java 9,OSGi以及模块化的未来

  这会为包括OSGi和JPMS的模块化系统带来问题。理想情况下,domain或bean类应该隐藏在一个模块内部:如果它被导出,就会成为公共API,这样会对依赖于它的消费者造成破坏,但是我们希望能够灵活地随意改变我们的内部类。另一方面,如所述的,通过反射访问非导出类型支持框架是非常有效的。

  由于OSGi的类加载器是基于设计的,模块可以获得其他模块非导出包和类型的可见性——只要他们知道该类型的全限定名以及知道是哪个模块发出的请求(请记住几个模块可以包含任何给定的类型名称)。Java长期使用反射的精神有效地减少了隔离,在这里甚至所谓的私有字段都可以通过调用setAccessible方法被公开。

  在OSGi中使用此功能是常见的做法,用来提供根本没有导出模块的实现!相反,它们可能包含引用内部类型的声明,这些内部类型可以通过框架加载。例如,使用JPA做持久化的模块可以引用persistence.xml文件的domain类型,并且在需要时JPA实现模块将会加载引用类型。

  最大的用例是实施服务组件。OSGi规范包含一章节叫声明式服务(DS),定义了一个模块如何声明组件:类的生命周期是由框架管理的。组件可以绑定到OSGi服务注册表中的服务,并且可以自选地为自己提供服务。例如:

  @Component

  public class CartMgrComponent implements CartManager {

  @Reference

  UserAdmin users;

  @Override

  public Cart getOrCreateCart(String user) {

  // ...

  }

  }

  在这个例子中,CartMgrComponent是一个提供CartManager服务的组件。它引用了一个服务——UserAdmin,类的生命周期由DS框架管理。当UserAdmin服务可用,CartMgrComponent就会被创建,并且它会发布CartManager服务,该服务同样可以在其他模块的其他组件中引用。

  这个框架可以工作是因为它加载了CartMgrComponent类,该类已经被@Component注解标记为组件。定义组件和服务是OSGi应用设计和编写的主要方式。

  在JPMS,只有导出包的类型可以被访问,即使是反射。虽然在非导出包中类型是可见的(你可以调用Class.forName获取一个类对象),但在模块外它们是不可访问的。当一个框架试图调用newInstance实例化一个对象,会抛出IllegalAccessException异常。这似乎切断了框架的许多可能性,但是也有一些解决方法。

  一种方法是提供个别类型作为服务,可以通过java.util.serviceloader加载。自Java 6开始serviceloader就是标准平台的一部分,在Java 9它已被更新支持跨模块工作。serviceloader可以访问非导出包的类型,只要提供包含provides声明的模块。不幸的是,serviceloader是古老的,不能为现代化框架,如DS或spring,提供所需的灵活性。

  第二种可能是使用“qualified”导出包。这种导出,只允许指定模块访问,而不是所有模块都通用。例如,你可以导出bean包到Spring Framework模块。但是这可能无法用于其他方面像JPA,因为JPA是一个规范而不是一个指定的模块,并且它可以由不同的模块实现,如Hibernate、EclipseLink等。

  第三种可能是“dynamic”导出,这种包任何人都可以访问,但只能自己使用反射,而且不是在编译时。这是JPMS一个非常新的特性,在邮件列表上它仍然是有争议的。它最接近OSGi的permissive方法,但它仍然需要模块作者为某些包明确添加dynamic导出,这些包中可能包含需要反射地加载的类型。作为一个OSGi用户感觉它是不必要的复杂性。