2016-02-03 2 views
5

У меня есть приложение для запуска приложений на Java, использующее Hibernate в качестве реализации JPA, и использование Guice для объединения всех сервисов.Контекст сохранения Hibernate на основе хоста сервера с Джерси

В моем случае используется один экземпляр приложения, обслуживающий несколько локализаций, доступных под разными хостами. Простым примером может быть английская версия и французская версия на application.com и application.fr. В зависимости от того, какой хост запущен, мне нужно будет переключить приложение, чтобы использовать другую базу данных.

В настоящее время у меня только один singleton SessionFactory configure, который используется всеми объектами доступа к данным, обеспечивая доступ только к одной базе данных.

Я пытаюсь получить самый простой способ передать информацию о контексте страны, все они из ресурса (где я могу извлечь его из контекста запроса) в DAO, который должен выбрать один из несколько SessionFactory s.

Я могу передать параметр в каждом методе обслуживания, но это кажется очень утомительным. Я подумал о том, чтобы использовать реестр, который будет иметь экземпляр ThreadLocal текущего параметра страны, установленный фильтром Джерси, но потолочные локаторы будут работать с использованием исполнителей и т. Д.

Есть ли какие-либо изящные способы достижения этого?

+0

Какую версию с Джерси вы используете? –

+0

Мы используем Jersey 2.19, обновляя несовершеннолетние время от времени – vvondra

ответ

2

Я не очень люблю пользователя Guice, поэтому в этом ответе используется каркас DI для Джерси, HK2. На базовом уровне конфигурации HK2 не сильно отличается от конфигурации Guice. Например, у Guice есть AbstractModule, где HK2 имеет AbstractBinder. С обоими компонентами вы будете использовать аналогичный синтаксис bind(..).to(..).in(Scope). Одно из отличий заключается в том, что с Guice это bind(Contract).to(Impl), тогда как с HK2 это bind(Impl).to(Contract).

HK2 также имеет Factory с, что позволяет более сложное создание ваших инъекционных объектов. С вашими заводами вы бы использовали синтаксис bindFactory(YourFactory.class).to(YourContract.class).

Это, как говорится, вы можете реализовать свой прецедент с чем-то вроде следующего.

  1. Создать Factory для английского SessionFactory

    public class EnglishSessionFactoryFactory implements Factory<SessionFactory> { 
        @Override 
        public SessionFactory provide() { 
         ... 
        } 
        @Override 
        public void dispose(SessionFactory t) {} 
    } 
    
  2. Создать Factory для французского SessionFactory

    public class FrenchSessionFactoryFactory implements Factory<SessionFactory> { 
        @Override 
        public SessionFactory provide() { 
         ... 
        } 
        @Override 
        public void dispose(SessionFactory t) {}  
    } 
    

    Примечание выше двух SessionFactory s будут переплетены в одноплодной объеме и имя.

  3. Создайте еще один Factory, который будет находиться в области запроса, которая будет использовать контекстную информацию запроса. Эта фабрика будет вводить указанные выше два SessionFactory по имени (используя привязку имени), и из любой информации контекста запроса верните соответствующий 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; 
        } 
    } 
    
  4. Тогда вы можете просто впрыснуть SessionFactory (который мы дадим другое имя) в вашем дао.

    public class IDaoImpl implements IDao { 
    
        private final SessionFactory sessionFactory; 
    
        @Inject 
        public IDaoImpl(@Named("SessionFactory") SessionFactory sessionFactory) { 
         this.sessionFactory = sessionFactory; 
        } 
    } 
    
  5. Чтобы связать все вместе, вы будете использовать 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); 
        } 
    } 
    

    Вот некоторые вещи, чтобы отметить о связующем

    • Два разных языка конкретных SessionFactory s связаны по имени. Который используется для инъекции @Named, как вы можете видеть на шаге 3.
    • Завод по заказу, которому принято решение, также присвоено имя.
    • Вы увидите proxy(true).proxyForSameScope(false). Это необходимо, так как мы предполагаем, что IDao будет одиночным, и поскольку «выбранный» SessionFactory мы находимся в области запроса, мы не можем ввести фактический SessionFactory, так как он изменится с запроса на запрос, поэтому нам нужно для ввода прокси. Если бы IDao был запрошен в области, вместо одиночного, тогда мы могли бы оставить эти две строки. Возможно, было бы лучше просто сделать запрос dao областью, но я просто хотел показать, как это должно быть сделано как одноэлемент.

      См. Также Injecting Request Scoped Objects into Singleton Scoped Object with HK2 and Jersey, для более детальной экспертизы на эту тему.

  6. Тогда вам просто нужно зарегистрировать AbstractBinder с Джерси. Для этого вы можете просто использовать метод 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(), он не надежно называется Джерси. Вы можете посмотреть на ApplicationEventListener. Вы можете ввести в него SessionFactory s и закрыть их при закрытии.

+0

за то время, глядя на это сейчас! – vvondra

+0

отлично! особенно для указания прокси (true) .proxyForSameScope (false) ' – vvondra

+0

@wondra см. также http://stackoverflow.com/q/35994965/2587435. При этом вам не нужно использовать дополнительное имя для основной сессии. Вы можете ввести его без имени. Просто узнал что-то новое :-) –

Смежные вопросы