簡體   English   中英

在獨立應用程序中使用Jersey的依賴項注入

[英]Using Jersey's Dependency Injection in a Standalone application

我在這里有一個界面

interface Idemo{
  public int getDemo(int i);
}

這是一種實現

class DemoImpl implements Idemo{
  @Override
  public int getDemo(int i){
    return i+10;
  }
}

並且有一類依賴於Idemo

class Sample{
  @Inject
  Idemo demo;

  public int getSample(int i){
    return demo.getDemo(i);
  }
}

現在說我要測試Sample類

public class SampleTest extends JerseyTest {
  @Inject
  Sample s; 

  @Override
  protected Application configure() {
    AbstractBinder binder = new AbstractBinder() {
      @Override
      protected void configure() {
        bind(Demo.class).to(Idemo.class);
        bind(Sample.class).to(Sample.class); //**doesn't work**
      }
    };
    ResourceConfig config = new ResourceConfig(Sample.class);
    config.register(binder);
    return config;
  }
  @Test
  public void test_getSample() {
    assertEquals(15, s.getSample(5)); //null pointer exception
  }
}

這里沒有創建Sample實例,並且s保持為空。我想這是因為當執行到達指定綁定的行時,已經創建了該測試類。但是我不確定。使用Spring Autowired而不是jersey CDI相同的作品

如果Sample是資源/控制器類,則測試框架可以創建它的實例而無需注入它,但是是否可以使用Jersey DI測試其他任何非Web類呢?

它與Spring配合使用的原因是測試類由Spring容器通過使用@RunWith(SpringJUnit4ClassRunner.class) 運行程序會將所有托管對象注入測試對象。 JerseyTest不能通過這種方式進行管理。

如果需要,您可以創建自己的運行程序,但是您需要稍微了解一下HK2(Jersey的DI框架)的工作方式。 看一下文檔 一切都圍繞ServiceLocator 在獨立環境中,您可能會看到類似的內容來引導DI容器

ServiceLocatorFactory factory = ServiceLocatorFactory.getInstance();
ServiceLocator locator = factory.create(null);
ServiceLocatorUtilities.bind(locator, new MyBinder());

然后獲得服務,做

Service service = locator.getService(Service.class);

在測試類的情況下,我們不需要獲得對服務對象的任何訪問權限,只需使用ServiceLocator注入測試對象即可:

locator.inject(test);

上面的test是在自定義運行程序中傳遞給我們的測試類實例。 這是自定義運行器的示例實現

import java.lang.annotation.*;
import org.glassfish.hk2.api.*;
import org.glassfish.hk2.utilities.*;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.*;

public class Hk2ClassRunner extends BlockJUnit4ClassRunner {

    private final ServiceLocatorFactory factory = ServiceLocatorFactory.getInstance();
    private Class<? extends Binder>[] binderClasses;

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public static @interface Binders {

        public Class<? extends Binder>[] value();
    }

    public Hk2ClassRunner(Class<?> cls) throws InitializationError {
        super(cls);
        Binders bindersAnno = cls.getClass().getAnnotation(Binders.class);
        if (bindersAnno == null) {
            binderClasses = new Class[0];
        }
    }

    @Override
    public Statement methodInvoker(FrameworkMethod method, final Object test) {
        final Statement statement = super.methodInvoker(method, test);
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                ServiceLocator locator = factory.create(null);
                for (Class<? extends Binder> c : binderClasses) {
                    try {
                        ServiceLocatorUtilities.bind(locator, c.newInstance());
                    } catch (InstantiationException | IllegalAccessException ex) {
                        throw new RuntimeException(ex);
                    }
                }
                locator.inject(test);
                statement.evaluate();
                locator.shutdown();
            }
        };
    }
}

methodInvoker程序中,每個測試方法都會調用methodInvoker ,因此我們正在為每個調用的測試方法創建一組新的對象。

這是一個完整的測試案例

@Binders({ServiceBinder.class})
@RunWith(Hk2ClassRunner.class)
public class InjectTest {

    public static class Service {

        @Inject
        private Demo demo;

        public void doSomething() {
            System.out.println("Inside Service.doSomething()");
            demo.doSomething();
        }   
    }

    public static class Demo {
        public void doSomething() {
            System.out.println("Inside Demo.doSomething()");
        }
    }

    public static class ServiceBinder extends AbstractBinder {
        @Override
        protected void configure() {
            bind(Demo.class).to(Demo.class);
            bind(Service.class).to(Service.class);
        }
    }


    @Inject
    private Service service;

    @Test
    public void testInjections() {
        Assert.assertNotNull(service);
        service.doSomething();
    }
}

我面臨着同樣的情況,但是在運行一些集成測試時,它需要具有我的應用程序已經定義的一些單例。

我發現的技巧如下。 您只需要創建一個普通的測試類或使用DropwizardAppRule的獨立類DropwizardAppRule

就我而言,我在編寫集成測試時使用了JUnit

public class MyIntegrationTest{

 //CONFIG_PATH is just a string that reference to your yaml.file
 @ClassRule
    public static final DropwizardAppRule<XXXConfiguration> APP_RULE =
        new DropwizardAppRule<>(XXXApplication.class, CONFIG_PATH);

}

@ClassRule將像在這里所說的那樣啟動您的應用程序。 這意味着您將有權訪問應用程序需要啟動的所有內容和每個對象。 就我而言,我需要使用我的服務訪問一個單例,使用@Inject批注和@Named

public class MyIntegrationTest {

    @ClassRule
    public static final DropwizardAppRule<XXXConfiguration> APP_RULE =
        new DropwizardAppRule<>(XXXAplication.class, CONFIG_PATH);

    @Inject
    @Named("myService")
    private ServiceImpl myService;

}

運行此命令將使服務無效,因為@Inject無法正常工作,因為此時我們沒有任何將Bean放入引用中的內容。 在此方法很方便。

    @Before
    public void setup() {


        ServiceLocator serviceLocator =((ServletContainer)APP_RULE.getEnvironment().getJerseyServletContainer()).getApplicationHandler().getServiceLocator();

        //This line will take the beans from the locator and inject them in their 
        //reference, so each @Inject reference will be populated.
        serviceLocator.inject(this);

    }

這樣可以避免在應用程序現有目錄之外創建其他綁定程序和配置。

可以在此處找到對DropwizardAppRule創建的ServiceLocator引用

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM