How do I make a HK2 ServiceLocator use the Singlet

2019-02-15 18:13发布

问题:

We are using using ExtrasUtilities.bridgeServiceLocator() to inject existing Singleton application services created in one ServiceLocator into Jersey RESTful Web Services by bridging the app ServiceLocator into the Jersey ServiceLocator.

However the Singletons that exist in the 'outer' locator aren't being used - each of the services is being created once again when injected into the Jersey services. It seems the Singleton is only visible within the scope of a ServiceLocator, even if it's bridged.

Is this the intended behaviour? And if so is there any way to change this behaviour and have a true Singleton across bridged ServiceLocators?

I have extracted the issue out into a set of test classes that illustrate the point below:

public class BridgedServiceTest
{
  private ServiceLocator _outerServiceLocator;
  private ServiceLocator _innerServiceLocator;

  @Test
  public void testBridgedInnerServiceOK() throws Exception
  {
    ServiceLocatorFactory serviceLocatorFactory = ServiceLocatorFactory.getInstance();

    _outerServiceLocator = serviceLocatorFactory.create("Outer");
    ServiceLocatorUtilities.addClasses(_outerServiceLocator, SingletonServiceImpl.class);

    _innerServiceLocator = serviceLocatorFactory.create("Inner");
    ExtrasUtilities.bridgeServiceLocator(_innerServiceLocator, _outerServiceLocator);

    final Client client1 = new Client();
    _outerServiceLocator.inject(client1);
    assertThat(SingletonServiceImpl.instanceCount.get(), Matchers.is(1));
    client1.test();

    final Client client2 = new Client();
    _innerServiceLocator.inject(client2);
    // next line fails as instance count is 2
    assertThat(SingletonServiceImpl.instanceCount.get(), Matchers.is(1));
    client2.test();
  }

  @After
  public void tearDown() throws Exception
  {
    _innerServiceLocator.shutdown();
    _outerServiceLocator.shutdown();
  }
}

@Contract
public interface SingletonService
{
  void fulfil();
}

@Service
public class SingletonServiceImpl implements SingletonService
{
  public static AtomicInteger instanceCount = new AtomicInteger();

  @PostConstruct
  public void postConstruct()
  {
    instanceCount.incrementAndGet();
  }

  @Override
  public void fulfil()
  {
    System.out.println("Contract Fulfilled.");
  }
}

public class Client
{
  @Inject
  private SingletonService _singletonService;

  public void test()
  {
    _singletonService.fulfil();
  }

  public SingletonService getSingletonService()
  {
    return _singletonService;
  }
}

回答1:

There was a bug in the ServiceLocator bridge which has been fixed. The fix will be in hk2 2.5.0-b07 or later!



回答2:

In the meantime (and in case it is indeed designed behaviour) I have achieved a workaround by implementing my own InjectionResolver. It's not a complete solution as it only works for injection (i.e. a call to ServiceLocator.getService() will not bridge) but it does what I need for now.

Here is the class if anyone needs it. Instead of calling ExtrasUtilities.bridgeServiceLocator(_innerServiceLocator, _outerServiceLocator); you call BridgingInjectionResolver.bridgeInjection(_innerServiceLocator, _outerServiceLocator);

@Singleton
@Rank(1)
public class BridgingInjectionResolver implements InjectionResolver<Inject>
{
  @Inject
  private ServiceLocator _localServiceLocator;

  private ServiceLocator _remoteServiceLocator;

  public BridgingInjectionResolver()
  {
  }

  /**
   * This method will bridge injection of all non-local services from the from ServiceLocator into the into
   * ServiceLocator.  The two ServiceLocators involved must not have a parent/child relationship
   *
   * @param into The non-null ServiceLocator that will be able to inject services from the from ServiceLocator
   * @param from The non-null ServiceLocator that will provide services for injection to the into ServiceLocator
   */
  public static void bridgeInjection(ServiceLocator into, ServiceLocator from)
  {
    checkParentage(into, from);
    checkParentage(from, into);
    ServiceLocatorUtilities.addClasses(into, BridgingInjectionResolver.class);
    into.getService(BridgingInjectionResolver.class).setRemoteServiceLocator(from);
  }

  private static void checkParentage(ServiceLocator a, ServiceLocator b)
  {
    ServiceLocator originalA = a;

    while (a != null) {
      if (a.getLocatorId() == b.getLocatorId()) {
        throw new IllegalStateException("Locator " + originalA + " is a child of or is the same as locator " + b);
      }

      a = a.getParent();
    }
  }

  @Override
  public Object resolve(Injectee injectee, ServiceHandle<?> root)
  {
    ActiveDescriptor<?> ad = _localServiceLocator.getInjecteeDescriptor(injectee);
    if (ad != null) {
      return _localServiceLocator.getService(ad, root, injectee);
    }
    ad = _remoteServiceLocator.getInjecteeDescriptor(injectee);
    if ((ad != null) && (ad.getDescriptorVisibility() == DescriptorVisibility.LOCAL)) {
      ad = null;
    }
    if (ad == null) {
      if (injectee.isOptional()) {
        return null;
      }

      throw new MultiException(new UnsatisfiedDependencyException(injectee));
    }
    return _remoteServiceLocator.getService(ad, root, injectee);
  }

  @Override
  public boolean isConstructorParameterIndicator()
  {
    return false;
  }

  @Override
  public boolean isMethodParameterIndicator()
  {
    return false;
  }

  private void setRemoteServiceLocator(ServiceLocator remoteServiceLocator)
  {
    _remoteServiceLocator = remoteServiceLocator;
  }

}


标签: java hk2