Ao realizar algumas limpezas de código em Eclipse, Lars fez a observação “Isso parece desnecessariamente difícil – por que não existe uma API simples para isso?”.
O código em questão foi a aquisição de uma instância de DebugOptions que é usada literalmente durante todo tempo de execução do Eclipse para determinar se uma opção está presente para permitir a depuração. Na verdade, o uso de DebugOptions em si não era exatamente o problema (embora o getBooleanOption seja incapaz de determinar se é ou não um valor booleano que está presente ou é false – mas isso é outro bug). O problema é que olhar para um serviço OSGi é mais que um single-line, e como tal, é o tipo de coisa que provavelmente causa mais dor do que realmente precisa.
Há três maneiras de obter serviços em OSGi, na ordem inversa da facilidade de uso:
- Adquira BundleContext (como por via BundleActivator ou por meio de um manipulador como FrameworkUtil e, em seguida, usar getService() diretamente.
- Use um ServiceTracker para manter um cache dos serviços disponíveis para retorno rápido.
- Use serviços declarativos para instanciar o componente e ter os serviços injetados diretamente.
O problema com o uso de serviços declarativos é que você deu não só a aquisição do serviço, mas também a vida útil do componente. Ele também impede o uso de métodos ou integração com outras APIs estáticas que esperam para gerenciar a criação do objeto (ou, na verdade, usam a criação do objeto com constructors ou padrões de construtor).
Os outros dois fazem uso de uma quantidade não-trivial de código para executar, tudo para satisfazer um pedido do tipo “Por favor me dê uma instância’.
Felizmente, o Java 8 fornece uma maneira simples de abstrair isso; utilizando o Supplier de interface. Um supplier é algo que, quando perguntado, retorna uma instância de um determinado pedido. Também é usado em um número de diferentes coleções para adiar a aquisição do objeto até que seja necessário. Isso se encaixa com o que estamos tentando fazer – obter uma instância de um serviço. Então, como isso pode se parecer em OSGi?
public class OSGiTracker<T> implements AutoClosable, Supplier<T> {
private final ServiceTracker<T,T> serviceTracker;
private boolean closed = true;
private OSGiTracker(Class<T> target, Class<?> source) {
if (target == null) {
throw new IllegalArgumentException("Target cannot be null");
}
if (source == null) {
throw new IllegalArgumentException("Source cannot be null");
}
Bundle bundle = FrameworkUtil.getBundle(source);
BundleContext context = bundle == null ? null : bundle.getBundleContext();
if (context == null) {
throw new IllegalArgumentException(
"Unable to acquire bundle context for " + source.getCanonicalName());
}
this.serviceTracker = new ServiceTracker<T,T>(context,target,null);
}
public static <T> OSGiTracker<T> supply(Class<T> target, Class<?> source) {
return new OSGiTracker<>(target, source);
}
@Override
public T get() {
if(closed) {
serviceTracker.open();
closed = false;
}
return serviceTracker.getService();
}
protected void finalize() throws Throwable {
close();
super.finalize();
}
public void close() throws Exception {
if(serviceTracker != null && !closed) {
serviceTracker.close();
}
}
}
Isso proporciona um Supplier de um dado serviço, e os dois únicos parâmetros necessários são o alvo genérico necessário (por exemplo, DebugOptions) e a classe de chamada (de modo que o BundleContext possa ser resolvido). A API não fica muito mais simples do que isso. Assim é como ela se parece quando podemos usá-la:
private final Supplier<DebugOptions> options = OSGiTracker.supply(DebugOptions.class, getClass()); private final boolean DEBUG = options.get().getBooleanOption(...)
Não é preciso se preocupar com outras dependências da classe OSGiTracker, e a API do lado do cliente é trivial.
No entanto, na maioria dos casos não há necessidade de manter um ServiceTracker esperando, em uma vez que você esteja usando-o em uma base one-shot. Se o serviço não estiver lá, você quer usar um padrão, sem alterar qualquer outra coisa. Como resultado, há uma implementação alternativa que pode ser utilizada:
public class OSGiSupplier<T> implements Supplier<T> {
private final BundleContext context;
private final ServiceReference<T> serviceReference;
private OSGiSupplier(Class<T> target, Class<?> source) {
if (target == null) {
throw new IllegalArgumentException("Target cannot be null");
}
if (source == null) {
throw new IllegalArgumentException("Source cannot be null");
}
Bundle bundle = FrameworkUtil.getBundle(source);
BundleContext context = bundle == null ? null : bundle.getBundleContext();
if (context == null) {
throw new IllegalArgumentException(
"Unable to acquire bundle context for " + source.getCanonicalName());
}
this.context = context;
this.serviceReference = context.getServiceReference(target);
}
public static <T> OSGiSupplier<T> supply(Class<T> target, Class<?> source) {
return new OSGiSupplier<>(target, source);
}
@Override
public T get() {
try {
T service = context.getService(serviceReference);
if (service != null) {
context.ungetService(serviceReference);
}
return service;
} catch (Throwable t) {
return null;
}
}
}
A API para usar é quase idêntica:
private final Supplier<DebugOptions> options = OSGiSupplier.supply(DebugOptions.class, getClass()); private final boolean DEBUG = options.get().getBooleanOption(...)
Vê a diferença? É o tipo de supplier que estamos usando. Caso contrário, o tipo de campo e seu caso de uso são idênticos. Isso torna mais fácil alternar entre os dois; na verdade, nós poderíamos quebrar isso em outro supplier se quiséssemos que isso fosse o padrão para tudo que fosse false ou null.
public class DebugOptionsWrapper implements Supplier<DebugOptions> {
private final Supplier<DebugOptions> delegate;
public DebugOptionsWrapper(Supplier<DebugOptions> delegate) {
this.delegate = delegate;
}
@Override
public DebugOptions get() {
DebugOptions options = delegate.get();
if(options == null) {
return new DebugOptions() { ... } // Empty implementation
} else {
return options;
}
}
}
Isso poderia ser empacotado para evitar que qualquer NullPointerException seja lançada se o serviço não estiver presente:
private final Supplier<DebugOptions> options = new DebugOptionsWrapper(OSGiSupplier.supply(DebugOptions.class, getClass()));
A mudança para Java 8 facilita esses tipos de melhorias, e deve ser parte de Eclipse. A pergunta é: para onde se deve ir depois disso? Alguns pacotes compartilhados fariam sentido, mas isso pertence a org.eclipse.core.runtime ou org.eclipse.equinox.util. Será que ainda faz sentido adicionar isso ao Eclipse?
***
Alex Blewitt faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: http://alblue.bandlem.com/2015/09/osgi-services-in-java-8.html



