简体   繁体   中英

How to inject indexed (and class specific) strings using Guice

I've been hacking with Google Guice a bit lately and I came up with an idea to inject a String to a constructor according to the class it is being declared in and other several parameters defined in an annotation. For example: If I define a new qualifier annotation @NamedInjectable to be used by Guice:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Qualifier
public @interface NamedInjectable
{
    String name() default "";

    boolean indexed() default true;
}

Where name is a new name base for the string (default is only the class' name), and indexed states whether or not the name should be incremented each time a new string is being injected.
eg

public MyClass {
    @Inject
    public MyClass(@NamedInjectable(name = "foo", indexed = true) String name) {
        // some code
    }
}

And name param should be given a value such as " I considered using Provider Bindings or AssistedInject but I could get it done. One main reason to failing, is somehow getting the name of the class.

Do you have any other idea?

There's no built-in way to customize a standard Guice binding based on names. If you want to stick to Guice alone, you'll probably need Custom Injections .

In addition to the standard @Inject-driven injections, Guice includes hooks for custom injections. This enables Guice to host other frameworks that have their own injection semantics or annotations. Most developers won't use custom injections directly; but they may see their use in extensions and third-party libraries. Each custom injection requires a type listener, an injection listener, and registration of each.

Guice's documentation example for Custom Injections demonstrates a logger instance customized with the type of the injecting class, which sounds very much like something you want to do—it's no more difficult to read the parameters of the annotation you create from within your TypeListener. However, this doesn't work directly with @Inject annotations or constructors, so you may have trouble if you're trying to make the injection happen entirely behind the scenes.

Another option is much simpler: Just use a factory and pass in the newly-constructed class.

public MyClass {
    private final String name;

    @Inject
    public MyClass(NameInjector nameInjector) {
        this.name = nameInjector.get(this, "foo", true);
    }
}

For ordinary Guice injections, you can't access the name of the class where the something is being injected. If you really need to do that, you would need to use custom injection.

By using a custom TypeListener , you can listen for injection events and know the class that is being injected. On hearing an injection event, you can register a custom MembersInjector that Guice will invoke after it finishes its own injections. This MembersInjector has access to the fully-constructed instance of the class, so it can reflect on fields and inspect annotations. However, it obviously can't inject constructor parameters, since the object has already been created.

In short, there is no way to do custom injection of constructor parameters. But the idea you describe is very possible for field injection!

How to do it

First, you need to register a TypeListener (this code based on the linked Guice wiki page):

public class NamedStringListener implements TypeListener {
    public <T> void hear(TypeLiteral<T> typeLiteral, TypeEncounter<T> typeEncounter) {
        Class<?> clazz = typeLiteral.getRawType();
        while (clazz != null) {
             for (Field field : clazz.getDeclaredFields()) {
             if (field.getType() == String.class &&
                 field.isAnnotationPresent(NamedInjectable.class)) {
                     Annotation annotation = field.getAnnotation(NamedInjectable.class);

                     // How you create and configure this provider is up to you.
                     Provider<String> provider = new MyStringProvider(clazz, annotation);
                     typeEncounter.register(new MyMembersInjector<T>(field, provider));
                 }
             }
         clazz = clazz.getSuperclass();
         }
     }
}

Then, inside MyMembersInjector<T> :

public class MyMembersInjector<T> implements MembersInjector<T> {
    final Field field;
    final Provider<String> provider;

    NamedMembersInjector(Provider<String> provider) {
        this.field = field;
        this.provider = provider;
        this.field.setAccessible(true);
    }

    public void injectMembers(T t) {
         field.set(t, provider.get());
    }
}

I leave the implementation of MyStringProvider up to you.

See the Guice CustomInjections wiki page for more.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM