Вручную зарегистрировать класс в контейнере CDI

У меня есть группа классов, которые создаются путем отражения, поэтому они не управляются контейнером CDI, и никакие инъекции не создаются контекстом. Мой вопрос в том, есть ли способ зарегистрировать эти классы в контексте CDI, чтобы классы управлялись контекстом?

Bellow, как я создаю классы:

String clazz = "org.myorg.thisIsMyClass";
MyClass myClass = Class.forName(clazz).newInstance(); // myClass instance not managed by CDI

Как создать экземпляр myClass, управляемый контейнером CDI?

Ответы

Ответ 1

Если ваши классы были зарегистрированы как bean в контейнере, вы можете использовать программный поиск, чтобы получить их легко.

@Inject
@Any
Instance<Object> myBeans;

public Object getMyBeanFromClassName(String className) throws Exception{    
    Class clazz = Class.forName(className);
    return myBeans.select(clazz).get();  
}

Et voilà.

Ответ 2

Самый простой способ сделать CDI для управления вашим классом - использовать производителя.

public class MyProducers {

  @Produces
  @RequestScoped //Could be any scope here
  @FromReflection //a qualifier to distinguish this Bean of type Object from others. Would be better to have a more specific bean type if all your class come from the same ancestor.
  public Object produceMyClass()
  {
    String clazz = "org.myorg.thisIsMyClass";
    Object myObject = Class.forName(clazz).newInstance();
    return myObject;
  }
}

В другом месте кода вы можете использовать этого производителя следующим образом:

@Inject
@FromReflection
Object myBean;

** Изменить: добавление InjectionPoint использования. **

Теперь вы можете улучшить своего производителя, введя его InjectionPoint в свой список параметров. Затем вы можете использовать метаданные точки впрыска (например, квалификатор) для динамического поиска вашего класса.

Сначала вам нужно добавить поле для хранения имени класса в своем квалификаторе @FromReflection:

@Qualifier
@Target({TYPE, METHOD, PARAMETER, FIELD})
@Retention(RUNTIME)
@Documented
public @interface FromReflection {

    @Nonbinding String value(); // classname will be store here 
}

то вы используете эту информацию в своем продюсере:

public class MyProducers {

  private String extractClassName(InjectionPoint ip) {
    for (Annotation annotation : ip.getQualifiers()) {
        if (annotation.annotationType().equals(FromReflection.class))
            return ((FromReflection) annotation).value();
    }
    throw new IllegalStateException("No @FromReflection on InjectionPoint");
  }

  @Produces
  @FromReflection
  public Object produceMyClass(InjectionPoint ip)
  {
    String clazzNanme = extractClassName(ip);
    Object myObject = Class.forName(clazz).newInstance();
    return myObject;
  }

}

Обратите внимание, что созданный bean должен находиться в области @Dependent, это ограничение при вводе InjectionPoint в параметры производителя. Теперь вы можете ввести свой bean следующим образом:

@Inject
@FromReflection("org.myorg.thisIsMyClass")
Object myBean;

Теперь, если вы хотите решить во время выполнения, какой класс вы хотите построить, вам придется использовать программную функцию поиска CDI, которая позволит вам создать синтетический классификатор. Сначала создайте для вашего определителя литературу AnnotationLiteral, чтобы создать экземпляр нового квалификатора.

public class FromReflectionLiteral extends AnnotationLiteral<FromReflection> implements FromReflection {

    private String value;

    public FromReflectionLiteral(String value) {
        this.value = value;
    }

    @Override
    public String value() {
        return value;
    }
}

Затем вы будете использовать Instance<> bean для запроса окончательного bean.

public class ConsumingBean {

    @Inject
    @Any
    Instance<Object> myBeanInstance;

    public Object getBeanFor(String className) {
     return myBeanInstance.select(new FromReflectionLiteral(className)).get();
    }
    ...
}

Следующим шагом будет использование портативного расширения...

Ответ 3

Следуя комментариям @AdrianMitev, я, наконец, закончил писать этот класс, который возвращает экземпляр управляемого CDI Bean с учетом его имени класса (elName) или типа класса:

public class GetInstance {
    public static Object of(String elName) {
       BeanManager bm = getBeanManager();
       Bean<?> bean = bm.resolve(bm.getBeans(elName));
       return bm.getReference(bean, bean.getBeanClass(), bm.createCreationalContext(bean));
    }

    @SuppressWarnings("unchecked")
    public static <T> T of(Class<T> clazz) {
        BeanManager bm = getBeanManager();
        Bean<?> bean = bm.resolve(bm.getBeans(clazz));
        return (T) bm.getReference(bean, bean.getBeanClass(), bm.createCreationalContext(bean));
    }

    private static BeanManager getBeanManager() {
        try {
            return (BeanManager) new InitialContext().lookup("java:comp/BeanManager");
        } catch (NamingException e) {
            e.printStackTrace();
        }
        return null;
    }
}

Итак, если у вас есть класс вроде этого:

@Named
public class FooClass {
...
}

Вы можете получить управляемый экземпляр CDI, используя:

FooClass fC = GetInstance.of(FooClass.class);

или используя elName

FooClass fC = (FooClass) GetInstance.of("fooClass");

или вы можете выбрать имя для использования:

@Named(value="CustomFooClassName")
public class FooClass {
...
}

И используя:

FooClass fC = (FooClass) GetInstance.of("CustomFooClassName");

Ответ 4

Вы можете сделать CDI осведомленным о вашем экземпляре, не создавая экземпляр bean самостоятельно (как я указал в предыдущем сообщении), но чтобы CDI создавал экземпляр bean. Вот пример кода для этого:

InitialContext initialContext = new InitialContext();
BeanManager bm = (BeanManager) initialContext.lookup("java:comp/BeanManager");

//List all CDI Managed Beans and their EL-accessible name
Set<Bean<?>> beans = bm.getBeans(AbstractBean.class, new AnnotationLiteral<Any>() {});
List<Object> beanInstances = new ArrayList<Object>();

for (Bean bean : beans) {
    CreationalContext cc = bm.createCreationalContext(bean);
    //Instantiates bean if not already in-service (undesirable)
    Object beanInstance = bm.getReference(bean, bean.getBeanClass(), cc);
    beanInstances.add(beanInstance);
}

return beanInstances;

Если вы уверены, что существует только один bean определенного типа, вы можете использовать beans.iterator.next().