Почему я не испытываю никаких исключений, когда смотрю bean-компонент, обернутый динамическим прокси-сервером JDK по классам (вместо интерфейса)? - PullRequest
2 голосов
/ 03 октября 2019

Давайте рассмотрим следующий компонент:

@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.INTERFACES)
public class MyBeanB implements MyBeanBInterface {
    private static final AtomicLong COUNTER = new AtomicLong(0);

    private Long index;

    public MyBeanB() {
        index = COUNTER.getAndIncrement();
        System.out.println("constructor invocation:" + index);
    }

    @Transactional 
    @Override
    public long getCounter() {
        return index;
    }
}

и рассмотрим 2 различных использования:

ИСПОЛЬЗОВАНИЕ 1:

@Service
public class MyBeanA {
    @Autowired
    private MyBeanB myBeanB;   
    ....
}

В этом случае приложение не может быть запущенои печатает:

***************************
APPLICATION FAILED TO START
***************************

Description:

The bean 'myBeanB' could not be injected as a 'my.pack.MyBeanB' because it is a JDK dynamic proxy that implements:
    my.pack.MyBeanBInterface


Action:

Consider injecting the bean as one of its interfaces or forcing the use of CGLib-based proxies by setting proxyTargetClass=true on @EnableAsync and/or @EnableCaching.

Я ожидал увидеть это, потому что я попросил Spring создать динамический прокси JDK для bean MyBeanB, и этот прокси не является подтипом MyBeanB. Мы можем легко исправить это так:

@Service
public class MyBeanA {
    @Autowired
    private MyBeanBInterface myBeanB;   
    ....
}

ИСПОЛЬЗОВАНИЕ 2:

MyBeanB beanB = context.getBean(MyBeanB.class);
System.out.println(beanB.getCounter());

Удивительно для меня, но это работает без каких-либо исключений времени выполнения, но я ожидал увидеть NoSuchBeanDefinitionException в этом случае, потому чтоПриложение int case 1 не может запуститься

Спасибо за комментарии от парня - я проверил класс beanB, и он my.pack.MyBeanB$$EnhancerBySpringCGLIB$$b1346261, поэтому Spring использовал CGLIB для создания прокси, но это противоречит определению компонента (* 1024)*) и выглядит как ошибка. )

Не могли бы вы объяснить, почему это работает для случая 2, не работает для случая 1?

1 Ответ

4 голосов
/ 04 октября 2019

Как я объяснил вам в моих комментариях к другому вопросу , Spring AOP может использовать оба прокси-сервера CGLIB и JDK в зависимости от ситуации. По умолчанию используются прокси-серверы JDK для классов, реализующих интерфейсы, но вы также можете использовать CGLIB для них. Для классов, не реализующих интерфейсы, остается только CGLIB, поскольку прокси-серверы JDK могут создавать только динамические прокси-серверы на основе интерфейсов.

Итак, рассматривая ваш случай 1, вы явно говорите, что хотите использовать прокси-интерфейсы интерфейса, то есть прокси-серверы JDK:

@Scope(value = "prototype", proxyMode = ScopedProxyMode.INTERFACES)

Но MyBeanA не реализует никаких интерфейсов. Следовательно, вы получаете сообщение об ошибке, которое вы видите в этом случае.

В случае 2, однако, вы используете ApplicationContext.getBean(..) для создания прокси. Здесь вы полагаетесь на Spring, чтобы определить, какой тип прокси выбрать, вы не пытаетесь принудительно применить что-либо. Таким образом, проксирование через CGLIB завершается успешно.

Здесь никаких сюрпризов.

Если вы хотите избежать сообщения об ошибке в случае 1, возможно, вам следует использовать ScopedProxyMode.TARGET_CLASS.


Обновление: Извините, меня раздражали ваши похожие и неописуемые имена классов MyBeanA и MyBeanB. В следующий раз имело бы смысл использовать более описательные имена, подобные чистому коду, в идеале те, которые описывают роли классов в вашем сценарии, например MyService, MyInterface, MyScopedBean.

В любом случаеЯ прочитал ваш вопрос и сообщение об ошибке снова. В сообщении об ошибке говорится, что согласно вашей аннотации создается прокси на основе интерфейса, но вы пытаетесь внедрить его в тип класса. Вы можете исправить это, объявив его так:

@Autowired
private MyBeanBInterface myBeanB;

В случае / использовании 2 вы снова явно объявляете класс, а не тип интерфейса для вашего компонента. Как я уже сказал, Spring пытается удовлетворить ваши требования единственным возможным способом, то есть путем создания прокси CGLIB для класса. Вы можете исправить это, объявив тип интерфейса, и вы получите ожидаемый JDK-прокси:

MyBeanBInterface myBeanBInterface = appContext.getBean(MyBeanBInterface.class);
System.out.println(myBeanBInterface.getCounter());
System.out.println(myBeanBInterface.getClass());

Обновление 2: Что-то, что, я думаю, вы все еще не понимаете в соответствии с вашимкомментарии это базовый факт ООП: если у вас есть

  • класс Base и класс Sub extends Base или
  • интерфейс Base и класс Sub implements Base

Вы можете объявить Base b = new Sub(), но, конечно, не Sub s = new Base(), потому что Sub также является Base, но не каждый Base является Sub. Например, если у вас также есть OtherSub extends Base, при попытке назначить объект Base переменной Sub это может быть экземпляр OtherSub. Вот почему это даже компилируется без использования Sub s = (Sub) myBaseObject.

Пока все хорошо. Теперь посмотрите на ваш код еще раз:

При использовании 1 у вас есть @Autowired private MyBeanB myBeanB;, но вы настроили MyBeanB для создания прокси JDK, то есть нового прокси-класса с родительским классом Proxy напрямуюреализация MyBeanBInterface будет создана. Т.е. у вас есть два разных класса, оба напрямую реализующие один и тот же интерфейс. Эти классы несовместимы друг с другом по причине, которую я объяснил выше. Что касается интерфейса, у нас есть иерархия классов

MyBeanBInterface
  MyBeanB
  MyBeanB_JDKProxy

Таким образом, вы не можете внедрить MyBeanB_JDKProxy в поле MyBeanB, поскольку прокси-объект не является экземпляром MyBeanB. Ты не понимаешь? Проблема сидит перед компьютером, там нет таинственной ошибки Spring. Вы настроили его на сбой.

Вот почему я сказал вам изменить код на @Autowired private MyBeanBInterface myBeanB;, потому что тогда, конечно, он работает, потому что прокси реализует интерфейс, и все в порядке. Я также сказал вам, что в качестве альтернативы вы можете оставить @Autowired private MyBeanB myBeanB;, если вы используете proxyMode = ScopedProxyMode.TARGET_CLASS для декларации области действия.

При использовании 2 проблема та же: вы говорите getBean(ClassB.class)то есть вы явно указываете Spring создать прокси для этого класса. Но для класса нельзя создать JDK-прокси, только CGLIB-прокси, что делает Spring. Опять же, я дал вам решение, указав вместо этого использовать getBean(MyBeanBInterface.class). Тогда вы получите ожидаемый JDK-прокси.

Spring достаточно умен для обоих

  • заставляет прокси-сервер JDK при использовании 1 находить служебный бин области действия MyClassB и вызывает для него вызовы методов делегата (примечание: делегирование, а не наследование!) И
  • расширяет прокси-сервер CGLIB MyClassB (примечание: наследование здесь, делегирование не требуется).
...