2016-02-03 35 views
5

我有一個正在運行的Jersey應用程序使用Hibernate作爲JPA實現,並使用Guice將所有服務綁定在一起。基於服務器主機與澤西島的Hibernate持久性上下文

我的用例在於有一個應用程序實例提供多個本地化,可在不同主機下使用。簡單的例子是英文版和法文版application.comapplication.fr。根據哪個主機被觸發,我需要切換應用程序以使用不同的數據庫。

目前,我只有一個singleton SessionFactory配置,它被所有的數據訪問對象使用,只能訪問一個數據庫。

我試圖想出最簡單的方法來傳遞關於國家上下文的所有信息從資源(我可以從請求上下文中獲取它)到DAO,它需要選擇其中的一個多個SessionFactory s。

我可以在每個服務方法中傳遞一個參數,但這看起來很乏味。我想使用一個註冊表,它將有一個由Jersey過濾器設置的當前國家/地區參數的ThreadLocal實例,但是線程本地人會使用Executors等等。

有沒有什麼優雅的方法可以實現這一點?

+0

您使用的是什麼澤西版本? –

+0

我們正在使用Jersey 2.19,不時更新未成年人 – vvondra

回答

2

我不是一個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,它允許更復雜的創建可注射對象。在您的工廠中,您可以使用語法bindFactory(YourFactory.class).to(YourContract.class)

這就是說,你可以實現你的用例如下所示。

  1. 創建英語SessionFactory

    public class EnglishSessionFactoryFactory implements Factory<SessionFactory> { 
        @Override 
        public SessionFactory provide() { 
         ... 
        } 
        @Override 
        public void dispose(SessionFactory t) {} 
    } 
    
  2. 一個Factory爲法國創建FactorySessionFactory

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

    注意上述兩個SessionFactory旨意在單身範圍和被綁定名稱。

  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。爲此,您可以使用ResourceConfigregister(...)方法。 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 s。儘管Factory有一個dispose()方法,但澤西並不可靠地調用它。你可能想看看ApplicationEventListener。您可以將SessionFactory注入其中,並在關閉事件中關閉它們。

+0

感謝您的時間,現在看這個! – vvondra

+0

太棒了!尤其是指出'proxy(true).proxyForSameScope(false)' – vvondra

+0

@wondra另請參閱http://stackoverflow.com/q/35994965/2587435。有了這個,你不需要爲主sessionfactory使用額外的名稱。你可以注入它沒有名字。剛剛學到了新東西:-) –