Проблема разрешения типа бина CDI, когда класс бина реализует параметризованные интерфейсы, которые также расширяют некоторый параметризованный интерфейс
Поскольку мне не хватает словарного запаса, чтобы объяснить проблему, я показываю ее на примере, который воспроизводит ошибку и помогает найти причину:
public interface BaseType<P> {}
public interface DerivedType<T> extends BaseType<T> {}
public interface SomeType1 {}
public interface SomeType2 {}
@Dependent
public static class BeanClass1 implements DerivedType<SomeType1> {}
@Dependent
public static class BeanClass2 implements DerivedType<SomeType2> {}
@ApplicationScoped
public static class Test implements Serializable {
@Inject
private BaseType<SomeType1> field; // if not commented throws following exception during deployment: org.jboss.weld.exceptions.DeploymentException: WELD-001409 Ambiguous dependencies for type [BaseType<SomeType1>] with qualifiers [@Default] at injection point [[field] @Inject private Test.field]. Possible dependencies [[Managed Bean [class BeanClass2] with qualifiers [@Any @Default], Managed Bean [class BeanClass1] with qualifiers [@Any @Default]]]
@Inject
private Instance<BaseType<SomeType1>> instance;
@Inject
private BeanManager bm;
@PostConstruct
private void postConstruct() {
// This block outputs two bean classes and it should be only one:
// beanClass: BeanClass1@11be5bab
// beanClass: BeanClass2@764a72e9
{
Iterator<BaseType<SomeType1>> iterator = instance.iterator();
while (iterator.hasNext()) {
System.out.println("beanClass: " + iterator.next().toString());
}
}
// And this block outputs:
//
// bean: Managed Bean [class BeanClass1] with qualifiers [@Any @Default]
// beanTypes:
// - class BeanClass1
// - DerivedType<SomeType1>
// - class java.lang.Object
// - BaseType<T>
//
// bean: Managed Bean [class BeanClass2] with qualifiers [@Any @Default]
// beanTypes:
// - DerivedType<SomeType2>
// - class java.lang.Object
// - class BeanClass2
// - BaseType<T>
{
Set<Bean<?>> beans = bm.getBeans(new ParameterizedTypeImpl(BaseType.class, new Type[] { SomeType1.class }, null));
for (Bean<?> bean : beans) {
System.out.println("\nbean: " + bean+"\nbeanTypes: ");
for (Type beanType : bean.getTypes()) {
System.out.println(" - " + beanType);
}
}
bm.resolve(beans); // if not commeted throws an AmbiguousResolutionException
}
}
}
Второй блок показывает набор типов bean-компонентов классов bean-компонентов. BeanClass1
а также BeanClass2
, в соответствии с Weld
, Там мы обнаружили, что набор типов bean-компонентов содержит тип BaseType<T>
вместо BaseType<SomeType1>
или же BaseType<SomeType2>
,
Итак, тип бобов BeanClass2
соответствующий стороннему интерфейсу BaseType<P>
тот Weld
помнит, имеет переменную типа T
вместо фактического параметра типа SomeType2
, Следовательно, и в соответствии с последним пунктом этой спецификации, BeanClass2
ошибочно считается присваиваемым BaseType<SomeType1>
,
Это желаемое поведение или ошибка? Есть ли обходной путь? Это было исправлено в новом Weld
версия.
Тест был выполнен на JBoss AS 7.1.1, в котором используется артефакт maven org.jboss.as:jboss-as-weld:7.1.1
РЕДАКТИРОВАТЬ: я думаю, что причина этой проблемы не стирание типа, как предполагает первый ответ (он был удален), а ошибка в Weld
, Вся информация, необходимая для генерации типов компонентов, доступна во время выполнения. Ошибка, я думаю, когда Weld
генерирует типы bean-компонентов класса bean-компонентов посредством отражения. Разрешение переменной типа должно быть рекурсивным, и, по-видимому, это не так.
Я уверен, что информация, необходимая для генерации косвенных интерфейсов типов bean-компонентов, доступна во время выполнения, потому что я создал и протестировал метод, который выполняет это с использованием библиотеки - я сделал несколько лет назад для Java-сериализатора с эффективным использованием памяти - который, к счастью, имеет функцию это именно то, что нам нужно: создать / получить Type
экземпляр для каждого предка Java-класса, рекурсивное разрешение переменных типа. Я попытался поместить сюда вовлеченные методы, но у меня были проблемы с форматированием при вставке кода; и тот факт, что библиотека длинная и плохо документированная, дала мне повод сдаться.
Чтобы показать, по крайней мере, суть того, как разрешить переменные типа косвенных интерфейсов, я написал следующий код. Он работает только для определенного класса, но может быть обобщен с некоторыми усилиями:
public static Set<Type> getActualInterfacesOfBeanClass1() {
Set<Type> actualInterfaces = new HashSet<>();
ParameterizedType directInterface = (ParameterizedType) BeanClass1.class.getGenericInterfaces()[0]; // = DerivedType<SomeType1> ; assumes only one interface is extended
actualInterfaces.add(directInterface);
// because ParameterizedType doesn't have a method like Class.getGenericInterfaces(), we have to do it by hand
Type indirectInterface; // = BaseType<SomeType1>
{
Class<?> directInterfaceRaw = (Class<?>) directInterface.getRawType(); // = DerivedType<T>
Map<String,Type> resolutionMap = new HashMap<>(); // = { T -> SomeType1 }
for( int i=0; i<directInterfaceRaw.getTypeParameters().length;++i) {
resolutionMap.put(directInterfaceRaw.getTypeParameters()[i].getName(),directInterface.getActualTypeArguments()[i]);
}
ParameterizedType indirectInterfaceUnresolved = (ParameterizedType) directInterfaceRaw.getGenericInterfaces()[0]; // = BaseType<T> ; assumes only one interface is extended
ArrayList<Type> resolvedParameters = new ArrayList<>(); // = { SomeType1 }
for(Type param : indirectInterfaceUnresolved.getActualTypeArguments()) {
Type resolvedParam;
if( param instanceof TypeVariable) {
resolvedParam = resolutionMap.get(((TypeVariable<?>)param).getName());
} else {
resolvedParam = param;
}
resolvedParameters.add(resolvedParam);
}
indirectInterface = new ParameterizedTypeImpl(indirectInterfaceUnresolved.getRawType(), resolvedParameters.toArray(new Type[resolvedParameters.size()]),indirectInterfaceUnresolved.getOwnerType());
}
actualInterfaces.add(indirectInterface);
return actualInterfaces;
}
Я надеюсь, что это не позволяет поверить, что стирание типа является причиной этой проблемы.
2 ответа
Наименее навязчивый способ обойти эту ошибку, я обнаружил, для каждого параметризованного типа бина класса бина C
это должно иметь форму T<A>
но Weld
неправильно изменяет фактический параметр типа A
с переменной типа создайте новый пустой интерфейс, который расширяет T<A>
и добавить этот пилотный интерфейс в список реализованных интерфейсов C
и к любому другому классу бобов, который косвенно распространяется T<A>
,
В частности, для кода выше, тип бина BaseType<SomeType1>
класса бобов BeanClass1
неправильно изменен Weld
в BaseType<T>
, Итак, пилотный интерфейс, необходимый для решения проблемы, будет:
public interface PilotInterface1 extends BaseType<SomeType1> {}
И класс бобов с примененным обходным решением будет:
@Dependent
public static class BeanClass1 implements DerivedType<SomeType1>, PilotInterface1 {}
Я предлагаю объединить все пилотные интерфейсы в отдельный пакет или файл, чтобы легко удалить их, когда ошибка будет исправлена.
Я думаю, что это ограничение.
Если я правильно понял, ваша цель - заставить CDI работать с дженериками, вводя и / или производя ваши дженерики.
Я сделал то же самое некоторое время назад и обнаружил, что это ограничение Java. Поскольку Java реализует обобщенные типы с использованием стирания типов, CDI не может должным образом справляться с обобщенным внедрением.
В конце концов, вы обнаружили, что у вас есть BaseType<T>
, но из-за этого ограничения CDI может вводить только с конкретными типами, такими как BaseType<SomeType1>
,
Проверьте мой случай, который, я думаю, немного проще, но тот же принцип:
Интерфейс
public interface Persistable implements Serializable {
...
}
Не работает
@Named
@RequestScoped
public class MyBean<T extends Persistable> {
@Inject
private T model;
}
Работает
@Named
@RequestScoped
public class MyBean<T extends Persistable> {
@Inject
private Persistable model;
}
Также проверьте этот пост ведущим спецификацией WELD/CDI.
Итак, нет проблем с параметризованными bean-компонентами, проблема заключается в том, что, в конце концов, универсальный тип, такой как ваш BaseType<T>
,
Если бы вы могли дать им конкретный тип, это должно работать как предложено в посте, который я упомянул. Не идеально, но...
Я надеюсь, что это помогает.