Dependendo de seus requisitos específicos, em alguns casos, o mecanismo do carregador de serviço do Java pode alcançar o que você procura.
Em resumo, ele permite que os desenvolvedores declarem explicitamente que uma classe subclasse alguma outra classe (ou implementa alguma interface) listando-a em um arquivo no META-INF/services
diretório do arquivo JAR / WAR . Em seguida, ele pode ser descoberto usando a java.util.ServiceLoader
classe que, quando recebe um Class
objeto, gera instâncias de todas as subclasses declaradas dessa classe (ou, se isso Class
representa uma interface, todas as classes que implementam essa interface).
A principal vantagem dessa abordagem é que não há necessidade de varrer manualmente todo o caminho de classe em busca de subclasses - toda a lógica de descoberta está contida na ServiceLoader
classe e apenas carrega as classes explicitamente declaradas no META-INF/services
diretório (nem todas as classes no caminho de classe) .
Existem, no entanto, algumas desvantagens:
- Ele não encontrará todas as subclasses, apenas aquelas que são explicitamente declaradas. Como tal, se você precisar realmente encontrar todas as subclasses, essa abordagem poderá ser insuficiente.
- Exige que o desenvolvedor declare explicitamente a classe no
META-INF/services
diretório Esse é um ônus adicional para o desenvolvedor e pode estar sujeito a erros.
- O
ServiceLoader.iterator()
gera instâncias de subclasse, não seus Class
objetos. Isso causa dois problemas:
- Você não tem nenhuma opinião sobre como as subclasses são construídas - o construtor no-arg é usado para criar as instâncias.
- Como tal, as subclasses devem ter um construtor padrão ou explicitamente declarar um construtor sem argumento.
Aparentemente, o Java 9 abordará algumas dessas deficiências (em particular, as relacionadas à instanciação de subclasses).
Um exemplo
Suponha que você esteja interessado em encontrar classes que implementam uma interface com.example.Example
:
package com.example;
public interface Example {
public String getStr();
}
A classe com.example.ExampleImpl
implementa essa interface:
package com.example;
public class ExampleImpl implements Example {
public String getStr() {
return "ExampleImpl's string.";
}
}
Você declararia que a classe ExampleImpl
é uma implementação Example
criando um arquivo que META-INF/services/com.example.Example
contém o texto com.example.ExampleImpl
.
Em seguida, você pode obter uma instância de cada implementação de Example
(incluindo uma instância de ExampleImpl
) da seguinte maneira:
ServiceLoader<Example> loader = ServiceLoader.load(Example.class)
for (Example example : loader) {
System.out.println(example.getStr());
}
// Prints "ExampleImpl's string.", plus whatever is returned
// by other declared implementations of com.example.Example.