【问题标题】:How do I get the binding target in a guice provider?如何在 guice 提供程序中获取绑定目标?
【发布时间】:2023-03-04 11:47:01
【问题描述】:

有没有办法让提供者(或其他方式)中注入某些东西的类?这是用于记录的——当我的SqlDatabase 做某事时,我希望它以正在使用的类的名称出现在日志中。我能想到的最好的方法是获取堆栈跟踪并向后追溯以找出它的使用位置,但我真的更愿意在注入时这样做。

提出问题的另一种方式是:我需要找到注入站点——找到 @Inject 注释的确切类——以创建注入类的实例。

【问题讨论】:

  • 如果您的目标是 Java 9,您可以使用新的堆栈遍历 API:javaworld.com/article/3188289/core-java/…
  • @TomG - 我发现了一种更特定于 guice 的方法,但是那个 API 真的很酷。他们花了足够长的时间来添加它:-)。

标签: java dependency-injection guice


【解决方案1】:

我可以想到两种选择:

  1. 在声明绑定时使用@Provides 方法
  2. 使用Guice AOP,以便记录实际的方法执行情况

这是两种方法的玩具示例:

package stackoverflowscrapbook;

import java.util.logging.Logger;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.Test;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Provides;
import com.google.inject.matcher.Matchers;

public class TestLogInjectedClassName {

    interface SqlDatabase {
        void doSomething();
    }

    static class SqlDatabaseImpl implements SqlDatabase {

        public void doSomething() {
            System.out.println("foo");
        }

    }

    static class AlternativeImpl implements SqlDatabase {

        public void doSomething() {
            System.out.println("bar");
        }

    }

    class Module extends AbstractModule {

        @Provides
        SqlDatabase database(SqlDatabaseImpl sqlDatabase) {
            Logger.getLogger(TestLogInjectedClassName.Module.class.getName())
                    .info("Providing " + sqlDatabase.getClass());
            return sqlDatabase;
        }

        @Override
        protected void configure() {
            //TODO: other bindings
        }
    }

    class AlternativeModule extends AbstractModule {

        @Provides
        SqlDatabase database(AlternativeImpl sqlDatabase) {
            Logger.getLogger(TestLogInjectedClassName.Module.class.getName())
                    .info("Providing " + sqlDatabase.getClass());
            return sqlDatabase;
        }

        @Override
        protected void configure() {
            //TODO: other bindings
        }
    }

    /**
     * Just log
     */
    class LoggingInterceptor implements MethodInterceptor {

        public Object invoke(MethodInvocation methodInvocation) throws Throwable {
            Logger.getLogger(getClass().getName()).info(methodInvocation.getStaticPart().toString());
            return methodInvocation.proceed();
        }

    }

    /**
     * Binds an interceptor on any method of any class implementing SqlDatabase
     */
    class AopModule extends AbstractModule {

        @Override
        protected void configure() {
            bindInterceptor(Matchers.subclassesOf(SqlDatabase.class), Matchers.any(), new LoggingInterceptor());

        }
    }

    @Test
    public void test() {
        Injector injector = Guice.createInjector(new Module());
        injector.getInstance(SqlDatabase.class);

        Injector anotheInjector = Guice.createInjector(new AlternativeModule());
        anotheInjector.getInstance(SqlDatabase.class);

    }

    @Test
    public void testAop() {
        Injector aopInjector = Guice.createInjector(new AopModule(), new Module());
        aopInjector.getInstance(SqlDatabase.class).doSomething();

        Injector alterntiveAopInjector = Guice.createInjector(new AopModule(), new AlternativeModule());
        alterntiveAopInjector.getInstance(SqlDatabase.class).doSomething();
    }

}

当我运行 test() 时,这是我的控制台

Dec 10, 2017 9:12:15 AM stackoverflowscrapbook.TestLogInjectedClassName$Module database
INFO: Providing class stackoverflowscrapbook.TestLogInjectedClassName$SqlDatabaseImpl
Dec 10, 2017 9:12:15 AM stackoverflowscrapbook.TestLogInjectedClassName$AlternativeModule database
INFO: Providing class stackoverflowscrapbook.TestLogInjectedClassName$AlternativeImpl

当我运行testAop() 时,这是我的控制台。请注意,在这种情况下,我们同时记录注入方法的执行。您可以通过删除@Provides 方法中的日志来选择仅记录执行。另请注意,只有当 Guice 创建您的 SqlDatabase 实例并且其实现中的方法不是最终的时,才有可能。 (我没有格式化下面的日志代码,因为预览中的行被截断了)

2017 年 12 月 10 日上午 9:14:01 stackoverflowscrapbook.TestLogInjectedClassName$Module 数据库 信息:提供类stackoverflowscrapbook.TestLogInjectedClassName$SqlDatabaseImpl$$EnhancerByGuice$$d7a0782d 2017 年 12 月 10 日上午 9:14:01 stackoverflowscrapbook.TestLogInjectedClassName$LoggingInterceptor 调用 信息:公共无效 stackoverflowscrapbook.TestLogInjectedClassName$SqlDatabaseImpl.doSomething() 富 2017 年 12 月 10 日上午 9:14:01 stackoverflowscrapbook.TestLogInjectedClassName$AlternativeModule 数据库 信息:提供类 stackoverflowscrapbook.TestLogInjectedClassName$AlternativeImpl$$EnhancerByGuice$$$3ef8fbf1 2017 年 12 月 10 日上午 9:14:01 stackoverflowscrapbook.TestLogInjectedClassName$LoggingInterceptor 调用 信息:公共无效 stackoverflowscrapbook.TestLogInjectedClassName$AlternativeImpl.doSomething() 酒吧

【讨论】:

  • 谢谢!遗憾的是,这些方法都没有办法在注入完成之前进入上下文,或者根据注入的位置以不同的方式更改/初始化对象。
【解决方案2】:

所以我想出了一个……似乎可行的东西:

import com.google.inject.Binder;
import com.google.inject.Binding;
import com.google.inject.Key;
import com.google.inject.matcher.AbstractMatcher;
import com.google.inject.matcher.Matcher;
import com.google.inject.spi.DependencyAndSource;
import com.google.inject.spi.InjectionPoint;
import com.google.inject.spi.ProvisionListener;
import java.util.List;
import java.util.function.Function;
import javax.inject.Provider;

/**
 * This is a dirty, dirty hack to allow some types to know what they're being injected into. There are other
 * (possibly cleaner) ways to do this, but this has the nice advantage of the injected class not needing
 * to know anything about this.
 */
public class ContextAwareInjection
{
    public static <T> void bindContextAwareProvider(Binder binder, Class<T> type, Function<InjectionPoint, T> provider)
    {
        bindContextAwareProvider(binder, Key.get(type), provider);
    }

    public static <T> void bindContextAwareProvider(Binder binder, Key<T> key, Function<InjectionPoint, T> provider)
    {
        Matcher<Binding<?>> matcher = new AbstractMatcher<Binding<?>>() {
            @Override public boolean matches(Binding<?> binding) { return binding.getKey().equals(key); } };
        ContextAwareImpl<T> impl = new ContextAwareImpl<>(provider, key);
        binder.bind(key).toProvider(impl);
        binder.bindListener(matcher, impl);
    }

    private static class ContextAwareImpl<T> implements ProvisionListener, Provider<T>
    {
        private final Key<T> key;
        private final Function<InjectionPoint, T> provider;
        private final ThreadLocal<T> current = new ThreadLocal<>();

        private ContextAwareImpl(Function<InjectionPoint, T> provider, Key<T> key)
        {
            this.provider = provider;
            this.key = key;
        }

        @Override
        public <T2> void onProvision(ProvisionInvocation<T2> pi)
        {
            if(!pi.getBinding().getKey().equals(key))
                throw new RuntimeException("Unexpected key -- got " + pi.getBinding().getKey() + ", expected " + key);
            try
            {
                List<DependencyAndSource> chain = pi.getDependencyChain();
                if(chain.isEmpty())
                    throw new RuntimeException("This should never be empty");
                DependencyAndSource das = chain.get(chain.size() - 1);
                InjectionPoint ip = das == null || das.getDependency() == null ? null : das.getDependency().getInjectionPoint();
                T value = provider.apply(ip);
                if(value == null)
                    throw new RuntimeException("Context aware providers should never return null");
                current.set(value);
                pi.provision();
            }
            finally
            {
                current.remove();
            }
        }

        @Override
        public T get()
        {
            T value = current.get();
            if(value == null)
                throw new RuntimeException("There is no current value -- this should never happen");
            return value;
        }
    }
}

这可能会对运行时性能产生一些轻微的影响,但对于测试它可以正常工作。更重要的是,它根本不需要对目标类进行任何更改。您可以像往常一样继续使用@Inject

【讨论】:

    猜你喜欢
    • 2016-07-26
    • 2016-08-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多