我可以用反射之类的方法做到吗?
我可以用反射之类的方法做到吗?
Answers:
我已经搜索了一段时间,似乎有不同的方法,这里是一个摘要:
如果您不介意添加依赖项,则反射库非常受欢迎。它看起来像这样:
Reflections reflections = new Reflections("firstdeveloper.examples.reflections");
Set<Class<? extends Pet>> classes = reflections.getSubTypesOf(Pet.class);
ServiceLoader(根据埃里克森的回答),看起来像这样:
ServiceLoader<Pet> loader = ServiceLoader.load(Pet.class);
for (Pet implClass : loader) {
System.out.println(implClass.getClass().getSimpleName()); // prints Dog, Cat
}
请注意,要使其正常工作,您需要定义Pet
为ServiceProviderInterface(SPI)并声明其实现。你这样做,通过创建一个文件resources/META-INF/services
的名称examples.reflections.Pet
和声明的所有实现Pet
它
examples.reflections.Dog
examples.reflections.Cat
包级注释。这是一个例子:
Package[] packages = Package.getPackages();
for (Package p : packages) {
MyPackageAnnotation annotation = p.getAnnotation(MyPackageAnnotation.class);
if (annotation != null) {
Class<?>[] implementations = annotation.implementationsOfPet();
for (Class<?> impl : implementations) {
System.out.println(impl.getSimpleName());
}
}
}
和注释定义:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PACKAGE)
public @interface MyPackageAnnotation {
Class<?>[] implementationsOfPet() default {};
}
并且您必须package-info.java
在该包内名为的文件中声明包级注释。以下是示例内容:
@MyPackageAnnotation(implementationsOfPet = {Dog.class, Cat.class})
package examples.reflections;
请注意,只有那时ClassLoader已知的软件包才能通过调用加载Package.getPackages()
。
另外,还有其他基于URLClassLoader的方法将始终限于已加载的类,除非您执行基于目录的搜索。
通常,这样做很昂贵。要使用反射,必须加载该类。如果要加载类路径上可用的每个类,则将花费时间和内存,因此不建议这样做。
如果要避免这种情况,则需要实现自己的类文件解析器,该类解析器的运行效率更高,而不是反射。字节码工程库可能有助于此方法。
该服务提供机制是枚举可插拔服务过程中的常规手段,并已成为更成熟的在Java 9.使用引进Jigsaw项目(模块)的ServiceLoader
Java 6中,或实现自己的早期版本。我在另一个答案中提供了一个示例。
META-INF/services/java.sql.Driver
Spring有一个非常简单的方法来实现这一点:
public interface ITask {
void doStuff();
}
@Component
public class MyTask implements ITask {
public void doStuff(){}
}
然后,您可以自动连接类型列表,ITask
Spring会使用所有实现填充它:
@Service
public class TaskService {
@Autowired
private List<ITask> tasks;
}
当前列出实现给定接口的所有类的最健壮的机制是ClassGraph,因为它可以处理尽可能广泛的类路径规范机制,包括新的JPMS模块系统。(我是作者。)
try (ScanResult scanResult = new ClassGraph().whitelistPackages("x.y.z")
.enableClassInfo().scan()) {
for (ClassInfo ci : scanResult.getClassesImplementing("x.y.z.SomeInterface")) {
foundImplementingClass(ci); // Do something with the ClassInfo object
}
}
是的,第一步是确定您关心的“所有”类。如果您已经有了此信息,则可以对它们中的每一个进行枚举,并使用instanceof来验证关系。相关文章在这里:http : //www.javaworld.com/javaworld/javatips/jw-javatip113.html
埃里克森说的最好。这是一个相关的问答线程- //www.velocityreviews.com/forums/t137693-find-all-implementing-classes-in-classpath.html
Apache BCEL库允许您读取类而不加载它们。我相信它将更快,因为您应该能够跳过验证步骤。使用类加载器加载所有类的另一个问题是,您将遭受巨大的内存影响,并且会无意中运行您可能不想执行的任何静态代码块。
Apache BCEL库链接-http: //jakarta.apache.org/bcel/
使用ClassGraph,它非常简单:
用Groovy代码查找以下实现my.package.MyInterface
:
@Grab('io.github.classgraph:classgraph:4.6.18')
import io.github.classgraph.*
new ClassGraph().enableClassInfo().scan().withCloseable { scanResult ->
scanResult.getClassesImplementing('my.package.MyInterface').findAll{!it.abstract}*.name
}
scan().withCloseable { ... }
使用Groovy进行调用,或者使用Java中的try-with-resources:github.com/classgraph/classgraph/wiki/…。此外,最后一部分应该是.name
,而不是.className
,因为这.getName()
是获取类名称的正确方法。从一个ClassInfo
对象。
@ kaybee99的答案的新版本,但现在返回用户要求的内容:实现...
Spring有一个非常简单的方法来实现这一点:
public interface ITask {
void doStuff();
default ITask getImplementation() {
return this;
}
}
@Component
public class MyTask implements ITask {
public void doStuff(){}
}
然后,您可以自动连接类型列表,ITask
Spring会使用所有实现填充它:
@Service
public class TaskService {
@Autowired(required = false)
private List<ITask> tasks;
if ( tasks != null)
for (ITask<?> taskImpl: tasks) {
taskImpl.doStuff();
}
}