Java Guice DI 错误:UnsatisfiedDependencyException:SystemInjecteeImpl 处没有可用于注入的对象

我有一个使用 Jersey 2.x 的简单 REST API 项目.我尝试使用 Google Guice 注入我的依赖项,但它似乎不起作用.我收到此错误:

I have a simple REST API project using Jersey 2.x. I tried using Google Guice to inject my dependencies, but it doesn't seem to work. I get this error:

org.glassfish.hk2.api.UnsatisfiedDependencyException: SystemInjecteeImpl 没有可用于注入的对象(requiredType=AccountService,parent=AccountsResource,qualifiers={},position=0,optional=false,self=false,unqualified=空,1658198405)

org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at SystemInjecteeImpl(requiredType=AccountService,parent=AccountsResource,qualifiers={},position=0,optional=false,self=false,unqualified=null,1658198405)

我有这个简单的资源类

@Path("/accounts")
@Produces(MediaType.APPLICATION_JSON)

public class AccountsResource {

    private final AccountService accountService;

    @Inject
    public AccountsResource(AccountService accountService) {
        this.accountService = accountService;
    }

  @GET
  @Path("test")
  public String test() {
    return this.accountService.test();
  }

我想将此服务注入到我的资源类中

I want to inject this service into my resource class

public class AccountService {

    public AccountService() {}

    public String test() {
        return "test";
    }
}

所以,按照 Guice 的指南,我创建了这个模块类

So, following Guice's guide, I created this module class

import com.google.inject.*;

public class AccountsResourceModule extends AbstractModule  {

@Override
protected void configure() {
    bind(AccountService.class);
}
}

最后,我在 main 方法中添加了注入器

Finally, I added the injector in my main method

public class TradingServer implements Runnable {
private static final int PORT = 8181;

public static void main(String[] args) {
    Injector injector = Guice.createInjector(new AccountsResourceModule());
    AccountsResource accountsResource = injector.getInstance(AccountsResource.class);
    new TradingServer().run();
}

public void run() {
    Server server = new Server(PORT);
    ServletContextHandler contextHandler = new ServletContextHandler(server, "/");
    ResourceConfig packageConfig = new ResourceConfig().packages("ca.ulaval.glo4002.trading");
    ServletContainer container = new ServletContainer(packageConfig);
    ServletHolder servletHolder = new ServletHolder(container);

    contextHandler.addServlet(servletHolder, "/*");

    try {
        server.start();
        server.join();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        server.destroy();
    }
}

}

当我调用我的服务器时,我收到了上面提到的错误.似乎依赖注入不起作用.请帮忙

When I call my server, I get the error mentioned above. It seems like the dependency injection didn't work. Please help

推荐答案

所以 Jersey 对 Guice 一无所知.它已经使用了自己的 DI 框架 HK2.您可以做几件事.您可以将 Guice 与 HK2 绑定在一起,以便 HK2 可以找到绑定在 Guice 中的服务,或者另一种方法是将您的资源类绑定到 Guice 中,然后将这些资源的实例注册到 Jersey.

So Jersey knows nothing about Guice. It already uses it's own DI framework, HK2. There are a couple things you can do. You can either tie Guice together with HK2 so that HK2 can find services that are bound inside Guice, or another way is to just bind your resource classes inside Guice and and register instances of those resources with Jersey.

要将 Guice 与 HK2 绑定,您需要使用 Guice HK2 Bridge.首先需要添加如下依赖

To tie Guice with HK2, you need to use the Guice HK2 Bridge. First you need to add the following dependency

<dependency>
    <groupId>org.glassfish.hk2</groupId>
    <artifactId>guice-bridge</artifactId>
    <version>${hk2.version}</version>
</dependency>

要获得 hk2.version,请查看您的 Jersey 依赖项(您可以运行 mvn dependency:tree 并查看 HK2 Jersey 的哪个版本).您要确保使用的是完全相同的版本.

To get the hk2.version look at your Jersey dependencies (you can run mvn dependency:tree and see what version of HK2 Jersey is pulling in). You want to make sure you are using the exact same version.

接下来您需要做的是以编程方式链接这两个系统.一种方法是在 Feature 中.

Next thing you need to do is to programmatically link the two systems. One way to do this is inside a Feature.

public class GuiceFeature implements Feature {

    @Override
    public boolean configure(FeatureContext context) {
        // This is the way in Jersey 2.26+ to get the ServiceLocator.
        // In earlier versions, use
        // ServiceLocatorProvider.getServiceLocator(context);
        ServiceLocator locator = InjectionManagerProvider.getInjectionManager(context)
                .getInstance(ServiceLocator.class);

        Injector injector = Guice.createInjector(new AccountResourceModule());
        GuiceBridge.getGuiceBridge().initializeGuiceBridge(locator);
        GuiceIntoHK2Bridge guiceBridge = locator.getService(GuiceIntoHK2Bridge.class);
        guiceBridge.bridgeGuiceInjector(injector);
        return true;
    }
}

然后只需向 Jersey 注册该功能.

Then just register the feature with Jersey.

ResourceConfig packageConfig = new ResourceConfig()
        .packages("ca.ulaval.glo4002.trading")
        .register(GuiceFeature.class);

就是这样.正如我测试过的那样,它应该工作.

And that's it. It should work, as I have tested.

通过上述配置,Jersey 将创建资源类的实例(@Path 注释类).我们需要桥接的原因是 Jersey 与 HK2 紧密耦合,所以当我们注入资源类时,在创建实例时,Jersey 会调用 HK2 来尝试查找资源的所有依赖项.

With the above configuration, Jersey will be creating instances of your resource classes (@Path annotated classes). The reason we need the bridge is that Jersey is tightly coupled with HK2, so when we inject our resources classes, when creating the instance, Jersey will call HK2 to try to find all the dependencies for the resource.

不过,在这种情况下,我们不会依赖 Jersey 来创建资源实例.我们将资源绑定到 Guice 并让 Guice 在我们请求时创建实例.我们将使用该实例向 Jersey 注册.

In this case though, we will not rely on Jersey to create the instance of the resource. We will bind the resource to Guice and let Guice create the instance when we request it. We will use that instance to register with Jersey.

先绑定资源

public class AccountResourceModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(AccountService.class);
        bind(AccountResource.class);
    }
}

还要确保资源类中的 @Inject 注释是 com.google.inject.Inject.

Also make sure that the @Inject annotation in the resource class is com.google.inject.Inject.

获取资源实例并注册

Injector injector = Guice.createInjector(new AccountResourceModule());
AccountResource accountResource = injector.getInstance(AccountResource.class);

ResourceConfig config = new ResourceConfig()
        .register(accountResource);

您可能必须想出一种更简洁的方法来执行此操作,因为您不希望对您拥有的每个资源都执行此操作.但这就是你需要做的事情的要点.

You probably have to figure out a cleaner way to do this as you don't want to have to do this for every resource you have. But this is the gist if what you need to do.

所以这里有一个清理第二个解决方案的快速实现.我们可以做的是递归扫描一个包以获取所有 @Path 注释的类,然后将它们绑定到 Guice 并注册到 Jersey.

So here's a quick implementation to clean up the second solution. What we can do is scan a package recursively to get all the @Path annotated classes and then bind them in Guice and register them with Jersey.

从这篇 SO 帖子,我们可以使用 Reflections 库轻松获取所有类.只需添加以下依赖项

From this SO post, we can use the Reflections library to easily get all the classes. Just add the following dependency

<dependency>
    <groupId>org.reflections</groupId>
    <artifactId>reflections</artifactId>
    <version>0.9.11</version>
</dependency>

然后做一个小助手类

import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.Path;
import org.reflections.Reflections;

public class ResourceClassHelper {

    private static Set<Class<?>> resourceClasses;

    public static Set<Class<?>> getResourceClasses() {
        if (resourceClasses != null) {
            return resourceClasses;
        }

        // the package to scan for @Path classes "com.example"
        Reflections reflections = new Reflections("com.example");

        resourceClasses = reflections.getTypesAnnotatedWith(Path.class);
        resourceClasses = Collections.unmodifiableSet(resourceClasses);
        return resourceClasses;
    }
}

然后在你的 Guice 模块中

Then in your Guice module

public class AccountResourceModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(AccountService.class);

        ResourceClassHelper.getResourceClasses().forEach(this::bind);
    }
}

还有你的资源注册

Injector injector = Guice.createInjector(new AccountResourceModule());

ResourceConfig config = new ResourceConfig();
ResourceClassHelper.getResourceClasses()
            .forEach(cls -> config.register(injector.getInstance(cls)));

相关文章