Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
21 changes: 20 additions & 1 deletion src/main/scala/net/codingwell/scalaguice/ScalaModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 <code>AbstractModule</code>
Expand All @@ -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())
* }
* }
* }}}
Expand All @@ -44,6 +50,8 @@ import javax.inject.Provider
* bind[CreditCardPaymentService]
* bind[Bar[Foo]].to[FooBarImpl]
* bind[PaymentService].to[CreditCardPaymentService]
*
* bindInterceptor[AOPI](methodMatcher = annotatedWith[AOP])
* }
* }
* }}}
Expand All @@ -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])
Expand Down
17 changes: 17 additions & 0 deletions src/test/java/net/codingwell/scalaguice/AOP.java
Original file line number Diff line number Diff line change
@@ -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 {

}
13 changes: 13 additions & 0 deletions src/test/scala/net/codingwell/scalaguice/ClassesForTesting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}"""
}
16 changes: 13 additions & 3 deletions src/test/scala/net/codingwell/scalaguice/ScalaModuleSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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")
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to see a test that checks that the interceptor intercepts an annotated method.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For bonus points, you're welcome to add a test with a class annotated with AOP. But by no means is that a requirement to merge this!

}

}