Использование guice для передачи параметра времени выполнения в конструктор - PullRequest
4 голосов
/ 16 ноября 2011

Если у меня есть следующий класс:

public class ObjectDAOMongoDBImpl<T> extends GenericDAOMongoDBImpl<T, ObjectId> implements ObjectDAO<T> {
    public ObjectDAOMongoDBImpl(Class<T> entityClass, Mongo mongo, Morphia morphia, String dbName) {
        super(entityClass, mongo, morphia, dbName);
    }
}

Где, entityClass предоставляется во время выполнения - как я могу использовать guice для привязки указанного типа к интерфейсу?

public class RunnerModule extends AbstractModule {      
    @Override
    protected void configure() {
        bind(GenericDAO.class).to(ObjectDAOMongoDBImpl.class);
    }
}

public class Runner<T, V> {
    GenericDAO<T, V> dao;

    @Inject
    public Runner(GenericDAO<T, V> dao) {
        this.dao = dao;
    }

    public static void main(String[] args) {
        Injector injector = Guice.createInjector(new RunnerModule());
        injector.getInstance(Runner.class);
    }
}

Можно определить mongo, morphia и dbName как литералы для RunnerModule (есть ли более чистый способ?), Но у меня нет способа узнать, что entityClass до времени выполнения.

Ответы [ 4 ]

5 голосов
/ 03 декабря 2011

Это невозможно сделать с Guice идиоматически, и это не является его основным фокусом.

jfpoilpret сказал все, что можно сказать, ноЯ хотел бы подойти к проблеме с другой стороны, где у вас есть возможность (возможно) решить вашу проблему, потеряв безопасность типов.

Итак, в вашем коде вы просите Guice получить экземпляр вашегоRunner<T, V> класс, подобный этому

injector.getInstance(Runner.class);

, но это не может быть решено Guice, потому что Runner<T, V> имеет зависимость от GenericDAO<T, V>, но вы не связали точную реализацию для него.Итак, как сказал jfpoilpret, вы должны связать некоторые конкретные реализации для него в вашем модуле.

Я предполагаю, что вы хотите определить точную реализацию GenericDAO<T, V>, которую вы передаетеваш Runner<T, V> основан на некоторых входных данных, тип данных которых неизвестен во время компиляции.Теперь давайте предположим, что у вас есть две реализации.

bind(new TypeLiteral<GenericDAO<String, ObjectID>>(){}).to(StringDAO.class);
bind(new TypeLiteral<GenericDAO<Double, ObjectID>>(){}).to(IntegerDAO.class);

На основе различных типов входов вы можете сделать это

Injector injector = Guice.createInjector(new RunnerModule());

// possible input which you get from *somewhere* dynamically
Object object = 1.0;

TypeLiteral<?> matchedTypeLiteral = null;
for (Key<?> key : injector.getAllBindings().keySet()) {
  TypeLiteral<?> typeLiteral = key.getTypeLiteral();
  Type type = typeLiteral.getType();
  if (type instanceof ParameterizedType) {
    ParameterizedType parameterizedType = (ParameterizedType) type;
      if (parameterizedType.getRawType() == GenericDAO.class) {
        List<Type> actualTypeArguments =    Arrays.asList(parameterizedType.getActualTypeArguments());
        if (actualTypeArguments.get(0) == object.getClass())
          matchedTypeLiteral = typeLiteral;
    }
  }
};

Runner<?, ?> runner = new Runner<>((GenericDAO<?, ?>) injector.getInstance(Key.get(matchedTypeLiteral)));
System.out.println(runner.dao.getClass()); // IntegerDAO.class

Если Object object = "string";, то other реализация будет найдена.Это, конечно, довольно уродливо и может быть улучшено проверкой подклассов и прочего, но я думаю, вы поняли идею.Суть в том, что вы не можете обойти это.

Если вам удастся это сделать ( обойти это ), пожалуйста, напишите мне по электронной почте, потому что я хотел бы узнать об этом!Я столкнулся с той же проблемой, с которой вы столкнулись не так давно.Я написал простой кодек BSON , в котором я хотел загрузить конкретные реализации универсального интерфейса, основанные на типе произвольного ввода.Это хорошо работало с отображениями Java-to-BSON, но я не мог сделать это каким-либо другим способом, поэтому я выбрал более простое решение.

3 голосов
/ 16 ноября 2011

Как вы написали, entityClass может быть только Object.class (== Class<Object>) и ничего больше.

Следовательно, прежде всего ваш ObjectDAOMongoDBImpl должен быть универсальным:

public class ObjectDAOMongoDBImpl<T> 
    extends GenericDAOMongoDBImpl<T, ObjectId> ...

Эта часть проблемы связана с Java, а не с Guice.

Теперь для части Guice необходимо определить привязку, включая общие типы, т. Е. С помощью Guice TypeLiteral:

bind(new TypeLiteral<GenericDAO<T, V>>(){}).to(...);

где T и V должны быть известны в вышеприведенном коде (там не может быть просто общих параметров).

Глядя на этот вопрос может также дать вам более подробную информацию о вашей ситуации.

2 голосов
/ 06 января 2013

Этот вопрос немного устарел, но недавно я столкнулся с подобной проблемой, и мне удалось решить ее довольно элегантно, добавив крошечный дополнительный слой, factory .

Рассмотрим следующее хранилище :

public interface Repository<T extends Model<T>> {
  void save(T t);
  T load(long key);
}

class SomeDbRepositoryImpl<T extends Model<T>> implements Repository<T> {
  private final SomeDbConnection db;
  private final Class<T> type;
  RepositoryImpl(final Class<T> type, final SomeDbConnection db) {
    this.db = db;
    this.type = type;
  }
  ...
}

Тогда, предположим, у меня есть служба, которой нужен экземпляр Repository<User>. Моей первой попыткой было сделать так, чтобы Guice передал экземпляр Repository<User> в конструктор, а затем я как-нибудь связал его. Проблема в том, что я действительно не хочу добавлять привязки репозитория и провайдеров для каждой модели. Если бы я это сделал, код выглядел бы так:

// Won't work.
class MyService {
  private final Repository<User> userRepository;
  @Inject MyService(final Repository<User> userRepository) {
    this.userRepository = userRepository;
  }
  ...
}

Я закончил тем, что создал класс RepositoryFactory, который сам по себе не является универсальным, но содержит универсальный метод.

public interface RepositoryFactory {
  <T extends Model<T>> Repository<T> getRepository(Class<T> type);
}

class SomeDbRepositoryFactoryImpl implements RepositoryFactory {
  private final SomeDbConnection db;
  @Inject SomeDbRepositoryFactoryImpl(final SomeDbConnection db) {
    this.db = db;
  @Override <T extends Model<T>> Repository<T> getRepository(Class<T> type) {
    return new SomeDbRepositoryImpl(type, db);
  } 
}

Итак, это абсолютно безопасно для типов, и мне не нужно добавлять привязку для каждого модуля. Служба, использующая хранилище, будет выглядеть так:

class MyService {
  private final Repository<User> userRepository;
  @Inject MyService(final RepositoryFactory f) {
    this.userRepository = f.getRepository(User.class);
  }
  ...
}

Вы также можете сохранить экземпляр RepositoryFactory вместо того, чтобы уже получать экземпляр Repository.

Надеюсь, это кому-нибудь пригодится.

1 голос
/ 04 декабря 2011

Помимо того, что сказал Коханьи, вы можете рефлексивно загружать DAO или классы сущностей по имени, а затем связывать только конкретные типы, запрошенные в аргументах командной строки:

package com.example;

public class App
{
    public static void main(final String[] args)
    {
        final Injector appleInjector = Guice.createInjector(new DynamicDaoModule(getClass("com.example.AppleDao")));
        appleInjector.getInstance(Runner.class);

        final Injector orangeInjector = Guice.createInjector(new DynamicDaoModule( getClass("com.example.OrangeDao")));
        orangeInjector.getInstance(Runner.class);

        // final Injector commandLineInjector = Guice.createInjector(new DynamicDaoModule(getClass(args[0])));
        // commandLineInjector.getInstance(Runner.class);
    }

    private static Class getClass(final String className)
    {
        try
        {
            return Class.forName(className);
        }
        catch (final ClassNotFoundException e)
        {
            throw new RuntimeException(e);
        }
    }
}

class DynamicDaoModule extends AbstractModule
{
    private final Class<? extends GenericDao<? extends Entity>> daoClass;

    public DynamicDaoModule(final Class<? extends GenericDao<? extends Entity>> daoClass)
    {
        this.daoClass = daoClass;
    }

    @Override
    protected void configure()
    {
        // bind GenericDao<? extends Entity> to daoClass
        final TypeLiteral<GenericDao<? extends Entity>> daoOfEntity = (TypeLiteral) TypeLiteral.get(Types.newParameterizedType(GenericDao.class, Types.subtypeOf(Entity.class)));
        bind(daoOfEntity).to(daoClass);
    }
}

interface Entity
{
}

class Apple implements Entity
{
}

class Orange implements Entity
{
}

class Runner
{
    @Inject
    public Runner(final GenericDao<? extends Entity> dao)
    {
        System.out.println("This runner has an " + dao);
    }
}

class GenericDao<T extends Entity>
{
    private final Class<? extends Entity> entityClass;

    protected GenericDao(final Class<? extends Entity> entityClass)
    {
        this.entityClass = entityClass;
    }

    @Override
    public String toString()
    {
        return String.format("%s constructed with entityClass %s", getClass().getSimpleName(), entityClass.getSimpleName());
    }
}

class AppleDao extends GenericDao<Apple>
{
    @Inject
    public AppleDao()
    {
        super(Apple.class);
    }
}

class OrangeDao extends GenericDao<Orange>
{
    @Inject
    public OrangeDao()
    {
        super(Orange.class);
    }
}

И вывод будет

This runner has an AppleDao constructed with entityClass Apple
This runner has an OrangeDao constructed with entityClass Orange

Я изменил пример, чтобы классы сущностей реализовали интерфейс, в случае если они совместно используют некоторые функции, которые были бы полезны для Runner или GenericDao. Если на самом деле у вас нет такого интерфейса, метод также работает с классами сущностей, такими как String и Double, если вы удаляете верхние границы extends Entity (например, GenericDao<T>).

Я также удалил параметр <T> в Runner, поскольку он не давал никаких преимуществ из-за стирания типа. Если бы вы были подклассом Runner<T>, то вы можете попросить Guice предоставить AppleRunner extends Runner<Apple> или OrangeRunner extends Runner<Orange>. Но параметр type ничего не дает, если сам Runner является единственным конкретным классом, который будет предоставлять Guice.

РЕДАКТИРОВАТЬ Ой, я оставил инъекции класса. Теперь они удалены. Конечно, если у вас есть конкретный подкласс GenericDao для каждой сущности, то, возможно, вам не нужно вводить сами классы сущностей.

Полагаю, мне не ясно, можете ли вы заранее предоставить конкретные подклассы GenericDao для всех типов сущностей или нет. Если нет, и вы используете только класс GenericDao для каждого отдельного вида класса сущностей, то вам нужно внедрить конкретные классы сущностей, а не конкретные классы DAO.

...