【问题标题】:Using Jersey's Dependency Injection in a Standalone application在独立应用程序中使用 Jersey 的依赖注入
【发布时间】:2015-06-11 19:02:25
【问题描述】:

我这里有一个界面

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 代替球衣 CDI 相同的作品

如果 Sample 是一个资源/控制器类,测试框架会创建它的一个实例而无需注入它,但是否可以使用 Jersey DI 测试任何其他非 Web 类?

【问题讨论】:

    标签: java dependency-injection jersey jersey-2.0 hk2


    【解决方案1】:

    它与 Spring 一起工作的原因是测试类由 Spring 容器使用@RunWith(SpringJUnit4ClassRunner.class) 管理。运行器会将所有托管对象注入到测试对象中。 JerseyTest 不是这样管理的。

    如果您愿意,您可以创建自己的跑步者,但您需要了解一点 HK2(Jersey 的 DI 框架)的工作原理。看看the documentation。一切都围绕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,因此我们正在为每个调用的测试方法创建一组全新的对象。

    这是一个完整的测试用例

    @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();
        }
    }
    

    【讨论】:

      【解决方案2】:

      我遇到了同样的情况,但是在运行一些集成测试的上下文中,需要有一些我的应用程序已经定义的单例。

      我发现的技巧如下。你只需要创建一个使用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 将启动您的应用程序,就像上面所说的 here 一样。那 意味着您将可以访问应用程序需要启动的所有内容和每个对象。就我而言,我需要为我的服务访问单例,我使用 @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的引用可以在here找到

      【讨论】:

        猜你喜欢
        • 2010-10-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-08-02
        • 1970-01-01
        • 2013-04-19
        相关资源
        最近更新 更多