在独立应用程序中使用Jersey的dependency injection
我这里有一个界面
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保持为null。我想这是因为当执行到达指定绑定的行时,已经创建了这个测试类。但我不确定。使用Spring Autowired而不是jersey CDI同样的作品
如果Sample是一个资源/控制器类,测试框架会创建它的一个实例,而不需要注入它,但是可以使用Jersey DI测试任何其他非Web类吗?
它与Spring一起使用的原因是测试类由Spring容器通过使用@RunWith(SpringJUnit4ClassRunner.class)
。 运行器将所有托管对象注入测试对象。 JerseyTest
不是以这种方式管理的。
如果你愿意,你可以创建自己的跑步者,但你需要了解HK2(泽西岛的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
,因此我们为每个被调用的测试方法创建一组新的对象。
这是一个完整的测试用例
@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
的独立测试类
在我的情况下,我使用JUnit
因为我正在编写一些集成测试。
public class MyIntegrationTest{ //CONFIG_PATH is just a string that reference to your yaml.file @ClassRule public static final DropwizardAppRule APP_RULE = new DropwizardAppRule<>(XXXApplication.class, CONFIG_PATH); }
@ClassRule
将启动您的应用程序,如此处所述。 这意味着您可以访问应用程序需要启动的所有对象。 在我的情况下,我需要使用@Inject
注释和@Named
来访问我的服务的单例
public class MyIntegrationTest { @ClassRule public static final DropwizardAppRule APP_RULE = new DropwizardAppRule<>(XXXAplication.class, CONFIG_PATH); @Inject @Named("myService") private ServiceImpl myService; }
运行此命令将设置为null,因为@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
引用
- 当@Context用于setter / field / constructor注入时,HK2 Factory在Jerseyfilter之前调用
- JavaFX fxml – 如何将Spring DI与嵌套的自定义控件一起使用?
- 通过Spring将字段注入由Hibernate加载的实体
- Spring:构造函数注入具有基于注释的配置的原始值(属性)
- 使用没有Servlet容器的CDI
- 如何解决松散耦合/dependency injection与富域模型之间的冲突?
- 使用注释注入依赖项是否会消除dependency injection(外部配置)的主要好处?
- JSF – 会话范围的托管bean没有在会话反序列化上重新注入依赖项
- 什么是JavaFX中的“将位置和资源属性自动注入控制器”?