diff --git a/README.md b/README.md index e2ed286..03ce1c4 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,8 @@ bind[A].toProvider[BProvider] bind[A].toProvider[TypeProvider[B]] bind[A[String]].to[B[String]] bind[A].to[B].in[Singleton] + +bindInterceptor[AOPI](methodMatcher = annotatedWith[AOP]) ``` ### Multibinding @@ -190,6 +192,18 @@ If you call `mapBinder.permitDuplicates()` on the binder then you may also injec - `immutable.Map[K, immutable.Set[V]]` - `immutable.Map[K, immutable.Set[Provider[V]]]` +### Interceptor Binding + +bindInterceptor adds scala style interceptor binding + +```java +bindInterceptor(Matchers.any(), Matchers.annotatedWith(classOf[Logging]), new LoggingInterceptor()) +``` + +```scala +bindInterceptor[LoggingInterceptor](methodMatcher = annotatedWith[Logging]) +``` + ## Gotchas In Scala, the words `override` and `with` are reserved and must be escaped to be used. diff --git a/src/main/scala/net/codingwell/scalaguice/ScalaModule.scala b/src/main/scala/net/codingwell/scalaguice/ScalaModule.scala index 411259e..e7abc3a 100644 --- a/src/main/scala/net/codingwell/scalaguice/ScalaModule.scala +++ b/src/main/scala/net/codingwell/scalaguice/ScalaModule.scala @@ -15,10 +15,14 @@ */ package net.codingwell.scalaguice -import com.google.inject.{PrivateModule, PrivateBinder, Binder, Scope, AbstractModule} import binder._ +import com.google.inject.matcher.{Matcher, Matchers} +import com.google.inject.{PrivateModule, PrivateBinder, Binder, Scope, AbstractModule} import java.lang.annotation.Annotation +import java.lang.reflect.{AnnotatedElement, Method} import javax.inject.Provider +import org.aopalliance.intercept.MethodInterceptor + /** * Allows binding via type parameters. Mix into AbstractModule @@ -33,6 +37,8 @@ import javax.inject.Provider * bind(classOf[CreditCardPaymentService]) * bind(new TypeLiteral[Bar[Foo]]{}).to(classOf[FooBarImpl]) * bind(classOf[PaymentService]).to(classOf[CreditCardPaymentService]) + * + * bindInterceptor(Matchers.any(), Matchers.annotatedWith(classOf[AOP]), new AOPI()) * } * } * }}} @@ -44,6 +50,8 @@ import javax.inject.Provider * bind[CreditCardPaymentService] * bind[Bar[Foo]].to[FooBarImpl] * bind[PaymentService].to[CreditCardPaymentService] + * + * bindInterceptor[AOPI](methodMatcher = annotatedWith[AOP]) * } * } * }}} @@ -63,6 +71,17 @@ trait InternalModule[B <: Binder] { val self = myBinder.bind(typeLiteral[T]) } + protected[this] def bindInterceptor[I <: MethodInterceptor : Manifest](classMatcher: Matcher[_ >: Class[_]] = Matchers.any(), methodMatcher: Matcher[_ >: AnnotatedElement]) { + val myBinder = binderAccess + val interceptor = manifest[I].runtimeClass.newInstance.asInstanceOf[MethodInterceptor] + myBinder.requestInjection(interceptor) + myBinder.bindInterceptor(classMatcher, methodMatcher, interceptor) + } + + protected[this] def annotatedWith[A <: Annotation : Manifest]: Matcher[AnnotatedElement] = { + Matchers.annotatedWith(cls[A]) + } + protected[this] def bindScope[T <: Annotation : Manifest](scope: Scope) = binderAccess.bindScope(cls[T], scope) protected[this] def requestStaticInjection[T: Manifest](): Unit = binderAccess.requestStaticInjection(cls[T]) protected[this] def getProvider[T: Manifest] = binderAccess.getProvider(cls[T]) diff --git a/src/test/java/net/codingwell/scalaguice/AOP.java b/src/test/java/net/codingwell/scalaguice/AOP.java new file mode 100644 index 0000000..e1fa97a --- /dev/null +++ b/src/test/java/net/codingwell/scalaguice/AOP.java @@ -0,0 +1,17 @@ +package net.codingwell.scalaguice; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * There is no support for runtime annotation in Scala, so far + * Java interfaces need to be used. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface AOP { + +} diff --git a/src/test/scala/net/codingwell/scalaguice/ClassesForTesting.scala b/src/test/scala/net/codingwell/scalaguice/ClassesForTesting.scala index d45cb8e..e470269 100644 --- a/src/test/scala/net/codingwell/scalaguice/ClassesForTesting.scala +++ b/src/test/scala/net/codingwell/scalaguice/ClassesForTesting.scala @@ -17,6 +17,8 @@ package net.codingwell.scalaguice import com.google.inject.TypeLiteral import javax.inject.{Provider, Inject, Named} +import org.aopalliance.intercept.MethodInterceptor +import org.aopalliance.intercept.MethodInvocation object Outer { trait A @@ -68,3 +70,14 @@ class FooProviderWithJavax extends javax.inject.Provider[Foo] { } case class TwoStrings @Inject()(@Named("first") first: String, @Named("second") second: String) + +trait Say { + def hi(str: String): String +} +class SayHi extends Say { + @AOP + def hi(str: String): String = str +} +class AOPI extends MethodInterceptor { + def invoke(invocation: MethodInvocation): AnyRef = s"""Hi ${invocation.proceed().toString}""" +} diff --git a/src/test/scala/net/codingwell/scalaguice/ScalaModuleSpec.scala b/src/test/scala/net/codingwell/scalaguice/ScalaModuleSpec.scala index 6a48684..acf5b26 100644 --- a/src/test/scala/net/codingwell/scalaguice/ScalaModuleSpec.scala +++ b/src/test/scala/net/codingwell/scalaguice/ScalaModuleSpec.scala @@ -15,9 +15,8 @@ */ package net.codingwell.scalaguice -import org.scalatest.{Matchers, WordSpec} - import com.google.inject._ +import org.scalatest.{Matchers, WordSpec} class ScalaModuleSpec extends WordSpec with Matchers { @@ -78,7 +77,7 @@ class ScalaModuleSpec extends WordSpec with Matchers { } "allow binding with annotation using a type parameter" in { - import name.Named + import com.google.inject.name.Named val module = new AbstractModule with ScalaModule { def configure() = { bind[A].annotatedWith[Named].to[B] @@ -123,6 +122,17 @@ class ScalaModuleSpec extends WordSpec with Matchers { twoStrings.first should be ("first") twoStrings.second should be ("second") } + + "allow binding annotation interceptor" in { + val module = new AbstractModule with ScalaModule { + def configure() = { + bind[Say].to[SayHi] + bindInterceptor[AOPI](methodMatcher = annotatedWith[AOP]) + } + } + val say = Guice.createInjector(module).getInstance(classOf[Say]) + say.hi("Bob") should be ("Hi Bob") + } } }