我有一个运行中的 Jersey 应用程序,用 Java 编写,使用 Hibernate 作为 JPA 实现,并使用 Guice 将所有服务绑定(bind)在一起。
我的用例是让一个应用程序实例服务于多个本地化,在不同的主机下可用。简单示例是 application.com
和 application.fr
上的英文版和法文版。根据触发的主机,我需要切换应用程序以使用不同的数据库。
目前,我只有一个单例SessionFactory
配置,它被所有数据访问对象使用,只提供对一个数据库的访问。
我正在尝试想出最简单的方法来将有关国家/地区上下文的信息从资源(我可以从请求上下文中获取它)一路传递到 DAO,DAO 需要从多个选项中选择一个SessionFactory
我可以在每个服务方法中传递一个参数,但这看起来很乏味。我想过使用一个注册表,它有一个由 Jersey 过滤器设置的当前国家/地区参数的 ThreadLocal
实例,但是线程局部变量会在使用 Executors 等时中断。
有什么优雅的方法可以做到这一点吗?
最佳答案
我不是 Guice 用户,所以这个答案使用 Jersey 的 DI 框架,HK2 .在基本配置层面,HK2 与 Guice 配置没有太大区别。例如,对于 Guice,您有 AbstractModule
,其中 HK2 有 AbstractBinder
.对于这两个组件,您将使用类似的 bind(..).to(..).in(Scope)
句法。一个区别是 Guice 是 bind(Contract).to(Impl)
,而 HK2 是 bind(Impl).to(Contract)
.
HK2 还有 Factory
s,它允许更复杂地创建可注入(inject)对象。对于您的工厂,您将使用语法 bindFactory(YourFactory.class).to(YourContract.class)
.
话虽这么说,您可以使用类似以下的内容来实现您的用例。
创建
Factory
对于英语SessionFactory
public class EnglishSessionFactoryFactory implements Factory<SessionFactory> { @Override public SessionFactory provide() { ... } @Override public void dispose(SessionFactory t) {} }
创建
Factory
对于法国人SessionFactory
public class FrenchSessionFactoryFactory implements Factory<SessionFactory> { @Override public SessionFactory provide() { ... } @Override public void dispose(SessionFactory t) {} }
注意上面两个
SessionFactory
s 将在单例范围内按名称绑定(bind)。创建另一个
Factory
这将在请求范围内,将使用请求上下文信息。本厂将注入(inject)以上两种SessionFactory
s 按名称(使用名称绑定(bind)),并根据任何请求上下文信息,返回适当的SessionFactory
.下面的例子只是使用了一个查询参数public class SessionFactoryFactory extends AbstractContainerRequestValueFactory<SessionFactory> { @Inject @Named("EnglishSessionFactory") private SessionFactory englishSessionFactory; @Inject @Named("FrenchSessionFactory") private SessionFactory frenchSessionFactory; @Override public SessionFactory provide() { ContainerRequest request = getContainerRequest(); String lang = request.getUriInfo().getQueryParameters().getFirst("lang"); if (lang != null && "fr".equals(lang)) { return frenchSessionFactory; } return englishSessionFactory; } }
然后你可以注入(inject)
SessionFactory
(我们会给它一个不同的名字)到你的 dao 中。public class IDaoImpl implements IDao { private final SessionFactory sessionFactory; @Inject public IDaoImpl(@Named("SessionFactory") SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } }
要将所有内容绑定(bind)在一起,您将使用
AbstractBinder
类似于下面的实现public class PersistenceBinder extends AbstractBinder { @Override protected void configure() { bindFactory(EnglishSessionFactoryFactory.class).to(SessionFactory.class) .named("EnglishSessionFactory").in(Singleton.class); bindFactory(FrenchSessionFactoryFactory.class).to(SessionFactory.class) .named("FrenchSessionFactory").in(Singleton.class); bindFactory(SessionFactoryFactory.class) .proxy(true) .proxyForSameScope(false) .to(SessionFactory.class) .named("SessionFactory") .in(RequestScoped.class); bind(IDaoImpl.class).to(IDao.class).in(Singleton.class); } }
这里有一些关于 Binder 的注意事项
- 两种不同的语言具体
SessionFactory
s 由名称绑定(bind)。用于@Named
注入(inject),如您在第 3 步中所见。 - 做出决定的请求范围工厂也被赋予一个名称。
您会注意到
proxy(true).proxyForSameScope(false)
.这是必需的,因为我们假设IDao
将是一个单例,并且由于“选择”SessionFactory
我们在请求范围内,我们无法注入(inject)实际的SessionFactory
,因为它会随着请求的不同而变化,所以我们需要注入(inject)一个代理。如果IDao
是请求范围的,而不是单例,那么我们可以省略这两行。将 dao 请求设置为范围可能会更好,但我只是想展示它应该如何作为一个单例来完成。另见 Injecting Request Scoped Objects into Singleton Scoped Object with HK2 and Jersey ,以获取有关此主题的更多检查。
- 两种不同的语言具体
然后你只需要注册
AbstractBinder
与 Jersey 。为此,您可以使用register(...)
ResourceConfig
的方法. See also ,如果您需要 web.xml 配置。
就是这样。下面是使用 Jersey Test Framework 的完整测试.您可以像运行任何其他 JUnit 测试一样运行它。 SessionFactory
使用的只是一个虚拟类,而不是实际的 Hibernate SessionFactory
.这只是为了让示例尽可能简短,而只是将其替换为常规的 Hibernate 初始化代码。
import java.util.logging.Logger;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.filter.LoggingFilter;
import org.glassfish.jersey.process.internal.RequestScoped;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.internal.inject.AbstractContainerRequestValueFactory;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import static junit.framework.Assert.assertEquals;
/**
* Stack Overflow https://stackoverflow.com/q/35189278/2587435
*
* Run this like any other JUnit test. There is only one required dependency
*
* <dependency>
* <groupId>org.glassfish.jersey.test-framework.providers</groupId>
* <artifactId>jersey-test-framework-provider-inmemory</artifactId>
* <version>${jersey2.version}</version>
* <scope>test</scope>
* </dependency>
*
* @author Paul Samsotha
*/
public class SessionFactoryContextTest extends JerseyTest {
public static interface SessionFactory {
Session openSession();
}
public static class Session {
private final String language;
public Session(String language) {
this.language = language;
}
public String get() {
return this.language;
}
}
public static class EnglishSessionFactoryFactory implements Factory<SessionFactory> {
@Override
public SessionFactory provide() {
return new SessionFactory() {
@Override
public Session openSession() {
return new Session("English");
}
};
}
@Override
public void dispose(SessionFactory t) {}
}
public static class FrenchSessionFactoryFactory implements Factory<SessionFactory> {
@Override
public SessionFactory provide() {
return new SessionFactory() {
@Override
public Session openSession() {
return new Session("French");
}
};
}
@Override
public void dispose(SessionFactory t) {}
}
public static class SessionFactoryFactory
extends AbstractContainerRequestValueFactory<SessionFactory> {
@Inject
@Named("EnglishSessionFactory")
private SessionFactory englishSessionFactory;
@Inject
@Named("FrenchSessionFactory")
private SessionFactory frenchSessionFactory;
@Override
public SessionFactory provide() {
ContainerRequest request = getContainerRequest();
String lang = request.getUriInfo().getQueryParameters().getFirst("lang");
if (lang != null && "fr".equals(lang)) {
return frenchSessionFactory;
}
return englishSessionFactory;
}
}
public static interface IDao {
public String get();
}
public static class IDaoImpl implements IDao {
private final SessionFactory sessionFactory;
@Inject
public IDaoImpl(@Named("SessionFactory") SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
@Override
public String get() {
return sessionFactory.openSession().get();
}
}
public static class PersistenceBinder extends AbstractBinder {
@Override
protected void configure() {
bindFactory(EnglishSessionFactoryFactory.class).to(SessionFactory.class)
.named("EnglishSessionFactory").in(Singleton.class);
bindFactory(FrenchSessionFactoryFactory.class).to(SessionFactory.class)
.named("FrenchSessionFactory").in(Singleton.class);
bindFactory(SessionFactoryFactory.class)
.proxy(true)
.proxyForSameScope(false)
.to(SessionFactory.class)
.named("SessionFactory")
.in(RequestScoped.class);
bind(IDaoImpl.class).to(IDao.class).in(Singleton.class);
}
}
@Path("test")
public static class TestResource {
private final IDao dao;
@Inject
public TestResource(IDao dao) {
this.dao = dao;
}
@GET
public String get() {
return dao.get();
}
}
private static class Mapper implements ExceptionMapper<Throwable> {
@Override
public Response toResponse(Throwable ex) {
ex.printStackTrace(System.err);
return Response.serverError().build();
}
}
@Override
public ResourceConfig configure() {
return new ResourceConfig(TestResource.class)
.register(new PersistenceBinder())
.register(new Mapper())
.register(new LoggingFilter(Logger.getAnonymousLogger(), true));
}
@Test
public void shouldReturnEnglish() {
final Response response = target("test").queryParam("lang", "en").request().get();
assertEquals(200, response.getStatus());
assertEquals("English", response.readEntity(String.class));
}
@Test
public void shouldReturnFrench() {
final Response response = target("test").queryParam("lang", "fr").request().get();
assertEquals(200, response.getStatus());
assertEquals("French", response.readEntity(String.class));
}
}
您可能还需要考虑的另一件事是关闭 SessionFactory
秒。虽然 Factory
有一个 dispose()
方法,它不能被 Jersey 可靠地调用。您可能想查看 ApplicationEventListener
.您可以注入(inject) SessionFactory
进入它,并在关闭事件中关闭它们。
关于java - 基于带有 Jersey 的服务器主机的 Hibernate 持久性上下文,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35189278/