Open GoogleCodeExporter opened 9 years ago
I've been able to use the current TypeListener to provide optional injection
without any core changes (from the context of simulating Plexus on top of
Guice). Basically it involved a utility method that could wrap the provider
from the binder and turn it into a no-op provider. If necessary you can also
defer specific lookups even more by requesting a provider for the injector and
then using that later on to do additional lookups (although this does mean that
you're then sacrificing Guice's early error detection).
Are you able to share your spring-integration code, or maybe reduce it down to
an example of how this feature could be used?
Original comment by mccu...@gmail.com
on 6 Aug 2013 at 2:06
Just to warn you, this is my first stab at trying to get this to work (and I
apologize for the Scala).
This is how it works prior to my changes:
/**
* This helper class helps recreate optional injection for not-required auto-wired annotations.
*/
class OptArg[T] {
@Inject(optional = true)
private val t: T = null.asInstanceOf[T]
def arg(): Option[T] = Option(t)
}
class SecurityModule extends AbstractModule {
def configure() {
/* ... a bunch of bindings ... */
bindListener(Matchers.any(), new TypeListener {
def hear[I](literal: TypeLiteral[I], encounter: TypeEncounter[I]) {
def hearClass(cls: Class[_]) {
for (method <- cls.getDeclaredMethods.filter(_.isAnnotationPresent(classOf[Autowired]))) {
val memberAnno = Option(method.getAnnotation(classOf[Qualifier])).map(q => Names.named(q.value()))
val anno = method.getAnnotation(classOf[Autowired])
val params = method.getParameterTypes
if (params.length == 1) {
val provider = if (anno.required()) memberAnno match {
case Some(a) => encounter.getProvider(Key.get(params(0), a))
case None => encounter.getProvider(params(0))
} else memberAnno match {
case Some(a) => encounter.getProvider(Key.get(Types.newParameterizedType(classOf[OptArg[_]], params(0)), a))
case None => encounter.getProvider(Key.get(Types.newParameterizedType(classOf[OptArg[_]], params(0))))
}
encounter.register(new MembersInjector[I]() {
def injectMembers(instance: I) {
val member = provider.get()
if (anno.required()) {
method.invoke(instance, member.asInstanceOf[Object])
} else {
val optMember = member.asInstanceOf[OptArg[Object]]
optMember.arg().foreach(o => method.invoke(instance, o))
}
}
})
}
}
if (cls.getSuperclass != null) hearClass(cls.getSuperclass)
}
hearClass(literal.getRawType)
}
})
}
}
This, previous, piece of code was working fine (even if ugly) until I tried to
add Spring's @Qualifier annotation which is basically equivalent to Guice's
@Named annotation. So the problems are thus:
1) This doesn't work at all if I 'binder.requireExplicitBindings()' in my
Module (because it needs to try and create an OptArg on the fly to do the
optional injection, but it cannot). (I prefer to require explicit bindings as
often as possible; this is the first time I've found that it doesn't do what I
really want it to do.)
2) You can't automagically generate bindings for OptArg if it is annotated with
an @Named annotation. Which, technically, makes a lot of sense because I
actually want the field 't' to have the specific Named annotation and not the
wrapper class.
Now with my change, the code changes to something like this:
class SecurityModule extends AbstractModule {
def configure() {
binder.requireExplicitBindings()
/* ... a bunch of bindings ... */
bindListener(Matchers.any(), new TypeListener {
def hear[I](literal: TypeLiteral[I], encounter: TypeEncounter[I]) {
def hearClass(cls: Class[_]) {
for (method <- cls.getDeclaredMethods.filter(_.isAnnotationPresent(classOf[Autowired]))) {
val memberAnno = Option(method.getAnnotation(classOf[Qualifier])).map(q => Names.named(q.value()))
val anno = method.getAnnotation(classOf[Autowired])
val params = method.getParameterTypes.asInstanceOf[Array[Class[AnyRef]]]
def getProvider(k: Key[AnyRef]): Provider[AnyRef] = if (anno.required()) encounter.getProvider(k) else encounter.getOptionalProvider(k)
if (params.length == 1) {
val key = memberAnno.map(a => Key.get(params(0), a)).getOrElse(Key.get(params(0)))
val provider = getProvider(key)
encounter.register(new MembersInjector[I]() {
def injectMembers(instance: I) {
Option(provider.get()).foreach(o => method.invoke(instance, o))
}
})
}
}
if (cls.getSuperclass != null) hearClass(cls.getSuperclass)
}
hearClass(literal.getRawType)
}
})
}
}
Now, notice there are some major benefits in the second implementation:
1) I can require explicit bindings, because optional providers do not throw
errors if the provider cannot be resolved.
2) I can optionally inject objects that have bound annotations.
3) The code is cleaner without the need of a helper class nor do I need to cast
to the helper class before pulling out the real value.
I hadn't thought about looking up the injections inside of the MembersInjector
-- I had, apparently incorrectly, assumed that the injector was not yet
'ready'. Is that your recommendation/preference?
Original comment by nate.bau...@gmail.com
on 6 Aug 2013 at 5:40
Hmm, yeah that seems to work just fine. For anyone reading I did this:
val injectorProvider = getProvider(classOf[Injector])
val provider = if (anno.required()) encounter.getProvider(k) else new
Provider[AnyRef]() {
def get(): AnyRef = try { injectorProvider.get().getInstance(k) } catch { case e: Exception => null }
}
Any chance you happen to know which injector I get? For example, if I have a
private module and I save off a provider for the injector, can I use that
injector to get access to non-exposed objects?
Original comment by nate.bau...@gmail.com
on 6 Aug 2013 at 6:41
To answer my own question -- Interestingly enough it gives you the local
injector. In the PrivateModule case, it gives you an injector that can pull
objects out that were not exposed! That's actually very exciting and simplifies
an idea that I've had for a long time! I probably shouldn't be so surprised,
but that sure makes me happy.
Feel free to close this issue request.
Original comment by nate.bau...@gmail.com
on 6 Aug 2013 at 10:16
Glad to hear you found a solution - btw, you might also be interested in the
new ProvisionListener API that's available in the latest code and the recent
4.0-beta release:
http://code.google.com/p/google-guice/issues/detail?id=78#c16
http://code.google.com/p/google-guice/source/browse/core/src/com/google/inject/s
pi/ProvisionListener.java
http://code.google.com/p/google-guice/source/browse/core/src/com/google/inject/B
inder.java#379
this provides additional functionality (and flexibility) over the TypeListener
approach.
Original comment by mccu...@gmail.com
on 8 Aug 2013 at 12:52
Closing issue as requested.
Original comment by mccu...@gmail.com
on 8 Aug 2013 at 12:53
Original issue reported on code.google.com by
nate.bau...@gmail.com
on 5 Aug 2013 at 5:05