2016-09-18 7 views
0

У меня есть перехватчик для запроса дроссельной заслонки на любые API. Я пытаюсь написать аннотацию, которая поддерживает подключение значения TPS, так что любой метод может быть ограничен лимитом.Невозможно получить перехват Guice Method Interceptor (исключение Null Pointer во время bindInterceptor)

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$1.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). 

Может ли кто-нибудь помочь мне с этим?

ответ

1

Вы не создавали инжектор в тестовом случае:

@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); 
} 
+1

Ах спасибо. Мне было интересно, является ли метод configure общедоступным. Джавак не жаловался на переопределение, поэтому я пропустил это. –