Google Guice - how to automatically add binding

|▌冷眼眸甩不掉的悲伤 提交于 2021-01-29 00:03:22

问题


In my project I am using Google Guice for dependency injection. In my Module class which extends Google Guice's AbstactModule I have a MapBinder, where the key is a name of an item and the value is an implementation of my Item interface (e.g. FirstItem).

MapBinder<String, Item> itemMapBinder = MapBinder.newMapBinder(binder(), String.class, Item.class);
itemMapBinder.addBinding("FirstItem").to(FirstItem.class);

Currently every time I add new implementation of Item (e.g. SecondItem) I also have to add a new line to Module, e.g.

itemMapBinder.addBinding("SecondItem").to(SecondItem.class);

I am using this map of items in my ItemFactory.

public class ItemFactoryImpl implements ItemFactory {

    private final Map<String, Item> items;

    @Inject
    public ItemFactoryImpl(Map<String, Item> items) {
        this.items = items;
    }

    @Override
    public Item getItem(String name) {
        if (name == null) throw new IllegalArgumentException("Name cannot be null");
        return items.get(name);
    }
}

I was trying to find a way how to automatically add binding for a new implementation of Item interface. My idea was use a custom annotation which will be added to new implementation of Item interface and modify Module#configure somehow to add binding for all classes with this custom annotation. I finished with something like this:

// cz.milanhlinak.guiceautobinding.item.AutoBindableItem.java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoBindableItem {
}

// cz.milanhlinak.guiceautobinding.item.Item.java
public interface Item {
}

// cz.milanhlinak.guiceautobinding.item.SecondItem.java
@AutoBindableItem
public class SecondItem implements Item {
}

// cz.milanhlinak.guiceautobinding.Module.java
public class Module extends AbstractModule {

    @Override
    protected void configure() {

        MapBinder<String, Item> itemMapBinder = MapBinder.newMapBinder(binder(), String.class, Item.class);
        new Reflections("cz.milanhlinak.guiceautobinding.item")
            .getTypesAnnotatedWith(AutoBindableItem.class)
            .stream()
            .filter(Item.class::isAssignableFrom)
            .forEach(typeAnnotatedWith -> itemMapBinder
                    .addBinding(typeAnnotatedWith.getSimpleName())
                    .to((Class<? extends Item>) typeAnnotatedWith)
            );

        bind(ItemFactory.class).to(ItemFactoryImpl.class).in(Singleton.class);
    }
}

Full code is available in my GitHub repository.

I would like to know if there is a better way how to achieve the automatic binding with Google Guice, because as you can see, I am currently using an additional library - Reflections.

UPDATE

Another option might be using of Google Guava instead of Reflections

public class Module extends AbstractModule {

    @Override
    protected void configure() {

        MapBinder<String, Item> itemMapBinder = MapBinder.newMapBinder(binder(), String.class, Item.class);

        ClassPath classPath;
        try {
            classPath = ClassPath.from(Thread.currentThread().getContextClassLoader());
        } catch (IOException e) {
            throw new RuntimeException("Unable to read class path resources", e);
        }

        ImmutableSet<ClassPath.ClassInfo> topLevelClasses = classPath.getTopLevelClassesRecursive("cz.milanhlinak.guiceautobinding.item");
        topLevelClasses.stream()
                .map(ClassPath.ClassInfo::load)
                .filter(clazz -> clazz.isAnnotationPresent(AutoBindableItem.class) && Item.class.isAssignableFrom(clazz))
                .forEach(clazz -> itemMapBinder
                        .addBinding(clazz.getSimpleName())
                        .to((Class<? extends Item>) clazz));

        bind(ItemFactory.class).to(ItemFactoryImpl.class).in(Singleton.class);
    }
}

回答1:


You're not doing anything wrong.

There is nothing wrong with using Reflections. That tool is excellent and does the job that you want.

Guice has no discovery tool, so there is no "default" way to do what you want in Guice only.

If you want to use something else than Reflections, you can fall back on java.util.ServiceLoader, but ServiceLoader is not really Guice-friendly because it automatically creates instances, but you usually use Guice because you want it to create instances for you instead of letting another framework do that for you.

The alternative of using Guava's Classpath also makes sense, but Reflections is way more powerful because you can also load classes not in your class path.

Bottom line, you've already made the better choice.



来源:https://stackoverflow.com/questions/52915611/google-guice-how-to-automatically-add-binding

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!