因此,要真正了解 HK2 的工作原理,您应该熟悉它ServiceLocator。它类似于SpringApplicationContext
,它是 DI 框架的主要容器。
在独立应用程序中,您可以简单地通过执行以下操作来引导 DI 容器
ServiceLocatorFactory locatorFactory = ServiceLocatorFactory.getInstance();
ServiceLocator serviceLocator = locatorFactory.create("TestLocator");
ServiceLocatorUtilities.bind(serviceLocator, new EntityManagerProvider());
现在你的EntityManagerProvider
已注册到容器中。您可以查找EntityManager
简单地通过做
EntityManager em = serviceLocator.getService(EntityManager.class);
现在为了能够利用容器的注入,服务需要由容器来管理。例如说你有这个
public class BackgroundTask implements Callable<String> {
@Inject
EntityManager em;
@Override
public String call() throws Exception {
...
}
你实际上是这样做的。问题是,BackgroundTask
不受容器管理。因此,即使在独立的引导程序中(如上面的三行代码),实例化任务
BackgroundTask task = new BackgroundTask();
就注入而言,什么都不做,因为任务类不是由容器管理的,and你正在自己创造它。如果您希望对其进行管理,可以通过几种方法将其注册到容器中。您已经发现了一个(使用AbstractBinder
)并将活页夹注册到ServiceLocator
。然后,您不必自己实例化该类,而只需请求它,就像EntityManager
上面的例子。
或者您可以简单地显式注入任务,即
BackgroundTask task = new BackgroundTask();
serviceLocator.inject(task);
这样做的目的是让定位器查找EntityManager
并将其注入到您的任务中。
那么这一切如何与泽西岛相适应呢? Jersey(部分)在运行时处理服务查找和资源注入。这就是它在您的 Jersey 应用程序中起作用的原因。当。。。的时候EntityManager
如果需要,它会查找服务并将其注入到资源实例中。
因此,下一个问题是,如果任务在 Jersey 应用程序范围之外运行,如何注入任务?在大多数情况下,以上内容几乎就是其要点。泽西岛有自己的ServiceLocator
,并且尝试获取对它的引用并不容易。我们可以给泽西岛our ServiceLocator
,但泽西岛最终仍然创造了它own定位器并将填充它our定位器。所以最终仍然会有两个定位器。您可以在下面的重构代码中看到我的意思的示例,其中它检查了ServiceLocatorFeature
.
但如果您确实想提供ServiceLocator
对于 Jersey,您可以将其传递给 Grizzly 服务器工厂方法
server = GrizzlyHttpServerFactory.createHttpServer(
URI.create(BASE_URI),
config,
serviceLocator
);
现在您仍然可以在泽西岛之外使用您的定位器。老实说,在这种情况下,你可以not完全涉及 Jersey 并保留您自己的定位器,然后注册EntityManagerProvider
与泽西岛和你的ServiceLocator
。除了额外的代码行之外,我认为它没有太大区别。从功能上来说,我没有看到任何变化。
要了解有关 HK2 的更多信息,我强烈建议彻底阅读用户指南。您将了解大量有关 Jersey 幕后情况的信息,并了解可合并到 Jersey 应用程序中的功能。
以下是测试的完整重构。我其实并没有改变太多。我所做的任何改变都在上面讨论过。
public class DependencyInjectionTest {
private final ServiceLocatorFactory locatorFactory
= ServiceLocatorFactory.getInstance();
private ServiceLocator serviceLocator;
private final static String BASE_URI = "http://localhost:8888/";
private final static String OK = "OK";
private HttpServer server;
private ExecutorService backgroundService;
public class EntityManagerProvider extends AbstractBinder
implements Factory<EntityManager> {
private final EntityManagerFactory emf;
public EntityManagerProvider() {
emf = Persistence.createEntityManagerFactory("derbypu");
}
@Override
protected void configure() {
bindFactory(this).to(EntityManager.class);
System.out.println("EntityManager binding done");
}
@Override
public EntityManager provide() {
EntityManager em = emf.createEntityManager();
System.out.println("New EntityManager created");
return em;
}
@Override
public void dispose(EntityManager em) {
em.close();
}
}
public class BackgroundTask implements Callable<String> {
@Inject
EntityManager em;
@Override
public String call() throws Exception {
System.out.println("Background task started");
Assert.assertNotNull(em); // will throw exception
System.out.println("EntityManager is not null");
return OK;
}
}
public class ServiceLocatorFeature implements Feature {
@Override
public boolean configure(FeatureContext context) {
ServiceLocator jerseyLocator
= org.glassfish.jersey.ServiceLocatorProvider
.getServiceLocator(context);
System.out.println("ServiceLocators are the same: "
+ (jerseyLocator == serviceLocator));
return true;
}
}
@Path("/test")
public static class JerseyResource {
@Inject
EntityManager em;
@GET
@Produces(MediaType.TEXT_PLAIN)
public Response doGet() {
System.out.println("GET request received");
Assert.assertNotNull(em);
System.out.println("EntityManager is not null");
return Response.ok()
.entity(OK)
.build();
}
}
@Before
public void setUp() {
serviceLocator = locatorFactory.create("TestLocator");
ServiceLocatorUtilities.bind(serviceLocator, new EntityManagerProvider());
System.out.println("Setting up");
ResourceConfig config = new ResourceConfig();
config.register(new ServiceLocatorFeature());
//config.register(new EntityManagerProvider());
config.register(JerseyResource.class);
// can't find a better way to register the resource
//config.registerInstances(JerseyResource.class);
server = GrizzlyHttpServerFactory.createHttpServer(
URI.create(BASE_URI),
config, serviceLocator
);
backgroundService = Executors.newSingleThreadScheduledExecutor();
}
@After
public void tearDown() {
System.out.println("Shutting down");
server.shutdownNow();
backgroundService.shutdownNow();
}
@Test
public void testScheduledBackgroundTask() throws Exception {
Assert.assertTrue(server.isStarted());
BackgroundTask task = new BackgroundTask();
serviceLocator.inject(task);
Future<String> f = backgroundService.submit(task);
System.out.println("Background task submitted");
try {
Assert.assertEquals(OK, f.get()); // forces Exception
} catch (ExecutionException | InterruptedException ex) {
System.out.println("Caught exception " + ex.getMessage());
ex.printStackTrace();
Assert.fail();
}
}
@Test
public void testBackgroundTask() throws Exception {
Assert.assertTrue(server.isStarted());
BackgroundTask task = new BackgroundTask();
serviceLocator.inject(task);
System.out.println("Background task instantiated");
Assert.assertEquals(OK, task.call());
}
@Test
public void testResource() {
Assert.assertTrue(server.isStarted());
Client client = ClientBuilder.newClient();
WebTarget target = client.target(BASE_URI);
Response r = target.path("test")
.request()
.get();
Assert.assertEquals(200, r.getStatus());
Assert.assertEquals(OK, r.readEntity(String.class));
}
}
我可能提到的另一件事是你应该只需要一个EntityManagerFactory
对于应用程序。创建成本很高,并且每次创建一个EntityManager
需要不是一个好主意。查看一种解决方案here.