如何在guice提供程序中获取绑定目标?

时间:2017-12-06 21:08:14

标签: java dependency-injection guice

有没有办法让类在提供者中注入某些东西(或其他方式)?这是用于记录 - 当我的SqlDatabase执行某些操作时,我希望它显示在日志中,其中包含正在使用它的类的名称。我能想到的最好的方法是获得堆栈跟踪并向后查看它以找出它的使用位置,但我真的宁愿在注入时进行。

提出问题的另一种方法是:我需要找到注入网站 - 找到@Inject注释的确切类 - 来创建注入类的实例。

2 个答案:

答案 0 :(得分:0)

所以我想出了一件......似乎有用......

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

答案 1 :(得分:0)

我可以想到两种选择:

  1. 声明绑定时使用@Provides方法
  2. 使用Guice AOP以便记录实际方法执行
  3. 这是两种方法的玩具示例:

    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实例并且其实现中的方法 final时才可以。 (我没有格式化下面的日志代码,因为预览中的行被切断了)

    Dec 10,2017 9:14:01 AM stackoverflowscrapbook.TestLogInjectedClassName $ Module database 信息:提供类stackoverflowscrapbook.TestLogInjectedClassName $ SqlDatabaseImpl $$ EnhancerByGuice $$ d7a0782d 2017年12月10日上午9:14:01 stackoverflowscrapbook.TestLogInjectedClassName $ LoggingInterceptor调用 信息:public void stackoverflowscrapbook.TestLogInjectedClassName $ SqlDatabaseImpl.doSomething() FOO 2017年12月10日上午9:14:01 stackoverflowscrapbook.TestLogInjectedClassName $ AlternativeModule数据库 信息:提供类stackoverflowscrapbook.TestLogInjectedClassName $ AlternativeImpl $$ EnhancerByGuice $$ 3ef8fbf1 2017年12月10日上午9:14:01 stackoverflowscrapbook.TestLogInjectedClassName $ LoggingInterceptor调用 信息:public void stackoverflowscrapbook.TestLogInjectedClassName $ AlternativeImpl.doSomething() 杆