Проблема разрешения типа bean-компонента CDI, когда класс bean-компонента реализует параметризованные интерфейсы, которые также расширяют некоторый параметризованный интерфейс.

Поскольку мне не хватает словарного запаса для объяснения проблемы, я показываю ее на примере, воспроизводящем сбой и помогающем найти причину:

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. Там мы обнаружили, что набор типов компонентов содержит тип 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. Вся информация, необходимая для создания типов bean-компонентов, доступна во время выполнения. Ошибка, я думаю, когда 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;
}

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


person Community    schedule 23.03.2013    source источник


Ответы (2)


Я думаю, это ограничение.

Если я правильно понял, ваша цель — заставить 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>.

Если бы вы могли дать им конкретный тип, это должно работать, как было предложено в сообщении, которое я упомянул. Не идеал, но...

Я надеюсь, что это помогает.

person rbento    schedule 23.03.2013
comment
Если вы знаете, что вы хотите, чтобы bean-компонент был типизирован, вы можете использовать аннотацию @Typed(Class) для очистки. - person LightGuard; 23.03.2013
comment
@LightGuard Я понимаю твою точку зрения. Я попробовал то, что вы предложили, но это тоже не работает. В этом проблема, нам нужно знать конкретный тип. Большое спасибо. - person rbento; 24.03.2013
comment
Понятно. В этом случае, да, вы можете немного пострадать из-за стирания шрифта. - person LightGuard; 24.03.2013
comment
Спасибо за ваше время. Но я думаю, что стирание шрифта тут ни при чем. Вся необходимая информация доступна во время выполнения. Я отредактирую сообщение с вопросом, чтобы объяснить свою точку зрения. - person Readren; 24.03.2013

Наименее навязчивый способ обхода этой ошибки, который я обнаружил, заключается в том, что для каждого параметризованного типа компонента класса компонента C, который должен иметь форму T<A>, но Weld неправильно изменяется фактический параметр типа A с помощью переменной типа, создайте новый пустой интерфейс, который расширяет T<A> и добавляет этот экспериментальный интерфейс в список реализованных интерфейсов C и в любой другой класс компонента, который косвенно расширяет T<A>.

В частности, для приведенного выше кода тип компонента BaseType<SomeType1> класса компонента BeanClass1 неправильно изменен с помощью Weld на BaseType<T>. Таким образом, пилотный интерфейс, необходимый для обхода проблемы, будет следующим:

public interface PilotInterface1 extends BaseType<SomeType1> {}

И класс bean-компонента с примененным обходным путем будет:

@Dependent
public static class BeanClass1 implements DerivedType<SomeType1>, PilotInterface1 {}

Я предлагаю собрать все пилотные интерфейсы в отдельный пакет или файл, чтобы их можно было легко удалить, когда ошибка будет исправлена.

person Community    schedule 08.04.2013