[英]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
。
我可以想到兩種選擇:
@Provides
方法 這是這兩種方法的一個玩具示例:
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.