无法掌握 Guice 方法拦截器的窍门(bindInterceptor 期间出现空指针异常)
Unable to get the hang of Guice Method Interceptor (Null Pointer Exception during bindInterceptor)
我有一个拦截器来限制对任意 API 的请求。我正在尝试编写支持插入 TPS 值的注释,以便可以限制任何方法的速率。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimitMethodAnnotation {
// Permissible transactions per second.
long tps() default Long.MAX_VALUE;
// The identifier for the rate limiter. A distinct token bucket is defined
// per id.
String id();
}
而拦截器的实现如下:-
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.isomorphism.util.TokenBucket;
import org.isomorphism.util.TokenBuckets;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
* Implementation of the rate limiter.
*/
public class RateLimitMethodAnnotationInterceptor implements MethodInterceptor {
private static final ConcurrentHashMap<String, TokenBucket>
TOKEN_BUCKET_MAP = new ConcurrentHashMap<String, TokenBucket>();
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
final RateLimitMethodAnnotation rateLimitMethod =
methodInvocation.getMethod().getAnnotation(RateLimitMethodAnnotation.class);
final String rateLimitId = rateLimitMethod.id();
final long tps = rateLimitMethod.tps();
boolean proceedMethodCall = tryProceed(rateLimitId, tps);
while(!proceedMethodCall) {
Thread.sleep(getDurationTillRefillInMilliSecond(rateLimitId, tps));
proceedMethodCall = tryProceed(rateLimitId, tps);
}
return methodInvocation.proceed();
}
private boolean tryProceed(final String tokenBucketId, final long tps) {
final TokenBucket tokenBucket = TOKEN_BUCKET_MAP.get(tokenBucketId);
if (tokenBucket == null) {
TOKEN_BUCKET_MAP.put(tokenBucketId, buildTokenBucket(tps));
}
return tokenBucket.tryConsume();
}
private long getDurationTillRefillInMilliSecond(final String tokenBucketId, long tps) {
final TokenBucket tokenBucket = TOKEN_BUCKET_MAP.get(tokenBucketId);
if (tokenBucket == null) {
TOKEN_BUCKET_MAP.put(tokenBucketId, buildTokenBucket(tps));
}
return tokenBucket.getDurationUntilNextRefill(TimeUnit.MILLISECONDS);
}
private TokenBucket buildTokenBucket(final long tps) {
return TokenBuckets.builder().withCapacity(tps)
.withFixedIntervalRefillStrategy(1, 1, TimeUnit.SECONDS)
.build();
}
}
现在为了定义绑定,我使用了以下代码:-
import com.google.inject.AbstractModule;
import com.google.inject.matcher.Matchers;
/**
* Configuration for rate limiting.
*/
public class RateLimitConfig extends AbstractModule {
public void configure() {
bindInterceptor(Matchers.any(),
Matchers.annotatedWith(RateLimitMethodAnnotation.class),
new RateLimitMethodAnnotationInterceptor());
}
}
我写了一个非常简单的完整性测试来证明注入配置的工作原理如下:-
import org.junit.Test;
import static org.junit.Assert.assertTrue;
/**
* Rate limit test class.
*/
public class TestRateLimit {
final int TEST_VAL = 1;
@Test
public void testRateLimitInterceptorSanityTest() {
final RateLimitConfig config = new RateLimitConfig();
config.configure();
int retVal = stubMethod();
assertTrue(retVal == TEST_VAL);
}
@RateLimitMethodAnnotation(tps = Long.MAX_VALUE, id="stubMethod")
public int stubMethod() {
return TEST_VAL;
}
}
我得到了 NPE
Running TestRateLimit
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.073 sec <<< FAILURE!
testRateLimitInterceptorSanityTest(TestRateLimit) Time elapsed: 0.013 sec <<< ERROR!
java.lang.NullPointerException
at com.google.inject.AbstractModule.bindInterceptor(AbstractModule.java:167)
at org.isomorphism.annotation.RateLimitConfig.configure(RateLimitConfig.java:12)
at org.isomorphism.annotation.TestRateLimit.testRateLimitInterceptorSanityTest(TestRateLimit.java:17)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
我查看了这里的代码 https://github.com/google/guice/blob/master/core/src/com/google/inject/AbstractModule.java 但没有发现任何有用的东西。我调试了代码,但无法理解数据结构(并且为了完全理解框架,我必须投入数小时,我不想为简单的任务花费时间)。
1. Even for a simple task like this, Guice should not throw an NPE even if 0 methods were annotated with the annotation I have in mind.
2. Is the configure method never supposed to be called directly in code? If so there is no API given in AbstractModule nor documentation how to configure bindInterceptors. Taking the code out of RateLimitConfig did not work (after putting it into the test suite).
谁能帮我解决这个问题?
您没有在测试用例中创建任何注入器:
@Test
public void testRateLimitInterceptorSanityTest() {
final RateLimitConfig config = new RateLimitConfig();
Injector injector = Guice.createInjector(config);
TestRateLimit testInstance = injector.getInstance(TestRateLimit.class);
int retVal = testInstance.stubMethod();
assertTrue(retVal == TEST_VAL);
}
我有一个拦截器来限制对任意 API 的请求。我正在尝试编写支持插入 TPS 值的注释,以便可以限制任何方法的速率。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimitMethodAnnotation {
// Permissible transactions per second.
long tps() default Long.MAX_VALUE;
// The identifier for the rate limiter. A distinct token bucket is defined
// per id.
String id();
}
而拦截器的实现如下:-
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.isomorphism.util.TokenBucket;
import org.isomorphism.util.TokenBuckets;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
* Implementation of the rate limiter.
*/
public class RateLimitMethodAnnotationInterceptor implements MethodInterceptor {
private static final ConcurrentHashMap<String, TokenBucket>
TOKEN_BUCKET_MAP = new ConcurrentHashMap<String, TokenBucket>();
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
final RateLimitMethodAnnotation rateLimitMethod =
methodInvocation.getMethod().getAnnotation(RateLimitMethodAnnotation.class);
final String rateLimitId = rateLimitMethod.id();
final long tps = rateLimitMethod.tps();
boolean proceedMethodCall = tryProceed(rateLimitId, tps);
while(!proceedMethodCall) {
Thread.sleep(getDurationTillRefillInMilliSecond(rateLimitId, tps));
proceedMethodCall = tryProceed(rateLimitId, tps);
}
return methodInvocation.proceed();
}
private boolean tryProceed(final String tokenBucketId, final long tps) {
final TokenBucket tokenBucket = TOKEN_BUCKET_MAP.get(tokenBucketId);
if (tokenBucket == null) {
TOKEN_BUCKET_MAP.put(tokenBucketId, buildTokenBucket(tps));
}
return tokenBucket.tryConsume();
}
private long getDurationTillRefillInMilliSecond(final String tokenBucketId, long tps) {
final TokenBucket tokenBucket = TOKEN_BUCKET_MAP.get(tokenBucketId);
if (tokenBucket == null) {
TOKEN_BUCKET_MAP.put(tokenBucketId, buildTokenBucket(tps));
}
return tokenBucket.getDurationUntilNextRefill(TimeUnit.MILLISECONDS);
}
private TokenBucket buildTokenBucket(final long tps) {
return TokenBuckets.builder().withCapacity(tps)
.withFixedIntervalRefillStrategy(1, 1, TimeUnit.SECONDS)
.build();
}
}
现在为了定义绑定,我使用了以下代码:-
import com.google.inject.AbstractModule;
import com.google.inject.matcher.Matchers;
/**
* Configuration for rate limiting.
*/
public class RateLimitConfig extends AbstractModule {
public void configure() {
bindInterceptor(Matchers.any(),
Matchers.annotatedWith(RateLimitMethodAnnotation.class),
new RateLimitMethodAnnotationInterceptor());
}
}
我写了一个非常简单的完整性测试来证明注入配置的工作原理如下:-
import org.junit.Test;
import static org.junit.Assert.assertTrue;
/**
* Rate limit test class.
*/
public class TestRateLimit {
final int TEST_VAL = 1;
@Test
public void testRateLimitInterceptorSanityTest() {
final RateLimitConfig config = new RateLimitConfig();
config.configure();
int retVal = stubMethod();
assertTrue(retVal == TEST_VAL);
}
@RateLimitMethodAnnotation(tps = Long.MAX_VALUE, id="stubMethod")
public int stubMethod() {
return TEST_VAL;
}
}
我得到了 NPE
Running TestRateLimit
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.073 sec <<< FAILURE!
testRateLimitInterceptorSanityTest(TestRateLimit) Time elapsed: 0.013 sec <<< ERROR!
java.lang.NullPointerException
at com.google.inject.AbstractModule.bindInterceptor(AbstractModule.java:167)
at org.isomorphism.annotation.RateLimitConfig.configure(RateLimitConfig.java:12)
at org.isomorphism.annotation.TestRateLimit.testRateLimitInterceptorSanityTest(TestRateLimit.java:17)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
我查看了这里的代码 https://github.com/google/guice/blob/master/core/src/com/google/inject/AbstractModule.java 但没有发现任何有用的东西。我调试了代码,但无法理解数据结构(并且为了完全理解框架,我必须投入数小时,我不想为简单的任务花费时间)。
1. Even for a simple task like this, Guice should not throw an NPE even if 0 methods were annotated with the annotation I have in mind.
2. Is the configure method never supposed to be called directly in code? If so there is no API given in AbstractModule nor documentation how to configure bindInterceptors. Taking the code out of RateLimitConfig did not work (after putting it into the test suite).
谁能帮我解决这个问题?
您没有在测试用例中创建任何注入器:
@Test
public void testRateLimitInterceptorSanityTest() {
final RateLimitConfig config = new RateLimitConfig();
Injector injector = Guice.createInjector(config);
TestRateLimit testInstance = injector.getInstance(TestRateLimit.class);
int retVal = testInstance.stubMethod();
assertTrue(retVal == TEST_VAL);
}