簡體   English   中英

如何在guice提供程序中獲取綁定目標?

[英]How do I get the binding target in a guice provider?

有沒有一種方法可以將要在提供程序中注入某些東西的類(或其他方法)獲得? 這用於記錄-當我的SqlDatabase做某事時,我希望它以使用該類的類的名稱顯示在日志中。 我能想到的最好的方法是獲取堆棧跟蹤,並通過它進行追溯以找出使用它的位置,但是我真的希望在注入時進行。

提出問題的另一種方式是:我需要找到注入位置-找到@Inject批注的確切類-創建注入類的實例。

所以我想出了一個...事情...似乎可行:

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. 聲明綁定時使用@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年10月10日上午9:14:01 AM stackoverflowscrapbook.TestLogInjectedorcept $ LogInfoging: void stackoverflowscrapbook.TestLogInjectedClassName $ SqlDatabaseImpl.doSomething()foo 2017年10月10日上午9:14:01 stackoverflowscrapbook.TestLogInjectedClassName $ AlternativeModule數據庫信息:提供了類stackoverflowscrapbook.TestLogInjectedClassName $ AlternativeImpl $$ Enhancer8yBef $ 9:10,10:9 11:00 AM stackoverflowscrapbook.TestLogInjectedClassName $ LoggingInterceptor調用INFO:public void stackoverflowscrapbook.TestLogInjectedClassName $ AlternativeImpl.doSomething()bar

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM