看来我正在做一件非常相似的事情。虽然最终组件框架,如 OSGi 和 NetBeans 平台(也可以在服务器端使用)是我使用过并且正在用于其他项目的可行解决方案,但当您使用更多功能时,它们会付出代价他们提供的,除了搜索已注册的组件(例如强制依赖检查、版本检查、模块隔离等)。
但是对于扫描捆绑的类,有一个基于注释扫描的更简单的解决方案。在我与 Vaadin 的项目中,我正在创建一个引用“抽象”组件名称的用户界面,这些名称必须与用户可能提供的实际 Java 类相匹配。
实现组件的 Java 类使用定制的注解进行标记:例如
@ViewMetadata(typeUri="component/HtmlTextWithTitle", controlledBy=DefaultHtmlTextWithTitleViewController.class)
public class VaadinHtmlTextWithTitleView extends Label implements HtmlTextWithTitleView
然后我使用 ClassScanner 在类路径中搜索带注释的类:
final ClassScanner classScanner = new ClassScanner();
classScanner.addIncludeFilter(new AnnotationTypeFilter(ViewMetadata.class));
for (final Class<?> viewClass : classScanner.findClasses())
{
final ViewMetadata viewMetadata = viewClass.getAnnotation(ViewMetadata.class);
final String typeUri = viewMetadata.typeUri();
// etc...
}
这是我在 Spring 之上实现的 ClassScanner 的完整实现:
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.ClassUtils;
public class ClassScanner
{
private final String basePackage = "it"; // FIXME
private final ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
@Nonnull
public final Collection<Class<?>> findClasses()
{
final List<Class<?>> classes = new ArrayList<Class<?>>();
for (final BeanDefinition candidate : scanner.findCandidateComponents(basePackage))
{
classes.add(ClassUtils.resolveClassName(candidate.getBeanClassName(), ClassUtils.getDefaultClassLoader()));
}
return classes;
}
public void addIncludeFilter (final @Nonnull TypeFilter filter)
{
scanner.addIncludeFilter(filter);
}
}
这很简单,但很有效。请注意,由于 Java 类加载器的工作方式,您必须指定至少一个要搜索的包。在我的示例中,我硬连线了顶级包“it”(我的东西是“it.tidalwave.*”),很容易将此信息放入可配置的属性中,最终指定多个包。
另一种解决方案可以通过使用来自 NetBeans 平台的两个库来使用。我强调这样一个概念,即这不会将整个平台导入您的项目,包括类加载器设施等,而只是使用两个 jar 文件。因此它不是侵入性的。这些库是 org-openide-util.jar 和 org-openide-util-lookup.jar(我再次强调,您可以使用普通的 .jar 文件而不是特定于 NetBeans 平台的 .nbm 文件)。
基本上,您会使用@ServiceProvider annotation。它在编译期间被触发(使用 Java 6)并生成一个 META-INF/services/ 描述文件,该文件将放置在类路径中。该文件是 Java 的标准特性(我相信是从 1.3 开始),可以使用标准类 ServiceLoader 进行查询。在这种情况下,您将仅在编译期间使用 NetBeans 平台库,因为它们仅用于生成 META-INF/服务。最终,这些库还可以用于更好的方式来查询已注册的服务,通过Lookup class。
这两种解决方案之间存在设计差异。通过我的自定义注释,我发现了类:然后我将它们与反射一起使用来实例化对象。使用@ServiceProvider,系统会自动从类中实例化一个“单例”对象。因此,在前一种情况下,我为要创建的对象注册了类,在第二种情况下,我注册了一个工厂来创建它们。在这种情况下,似乎前一种解决方案需要的通道更少,这就是我使用它的原因(通常我使用@ServiceProvider很多)。
总结一下,列举了三种解决方案:
- 将我提供的 ClassScanner 与 Spring 结合使用。运行时需要 Spring。
- 在代码中使用@ServiceProvider 并使用ServiceLoader 进行扫描。在编译时需要两个 NetBeans 平台库,在运行时只需要 Java 运行时。
- 在代码中使用 @ServiceProvider 并使用 Lookup 进行扫描。运行时需要两个 NetBeans 平台库。
您还可以查看this question 的答案。