Что такое прокси в Spring?

Как мы знаем, Spring использует прокси для добавления функциональности (например, @Transactional и @Scheduled). Есть два варианта - использование динамического прокси JDK (класс должен реализовывать непустые интерфейсы) или создание дочернего класса с использованием генератора кода CGLIB. Я всегда думал, что proxyMode позволяет мне выбирать между динамическим прокси JDK и CGLIB.

Но мне удалось создать пример, который показывает, что мое предположение неверно:

Случай 1:

Синглтон:

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

    public void foo() {
        System.out.println(myBeanB.getCounter());
    }

    public MyBeanB getMyBeanB() {
        return myBeanB;
    }
}

Прототип:

@Service
@Scope(value = "prototype")
public class MyBeanB {
    private static final AtomicLong COUNTER = new AtomicLong(0);

    private Long index;

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

    @Transactional // just to force Spring to create a proxy
    public long getCounter() {
        return index;
    }
}

Основной:

MyBeanA beanA = context.getBean(MyBeanA.class);
beanA.foo();
beanA.foo();
MyBeanB myBeanB = beanA.getMyBeanB();
System.out.println("counter: " + myBeanB.getCounter() + ", class=" + myBeanB.getClass());

Выход:

constructor invocation:0
0
0
counter: 0, class=class test.pack.MyBeanB$EnhancerBySpringCGLIB$2f3d648e

Здесь мы видим две вещи:

  1. MyBeanB был создан только один раз .
  2. Чтобы добавить функциональность @Transactional для MyBeanB, Spring использовал CGLIB.

Случай 2:

Позвольте мне исправить определение MyBeanB:

@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBeanB {

В этом случае вывод будет:

constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class test.pack.MyBeanB$EnhancerBySpringCGLIB$b06d71f2

Здесь мы можем видеть две вещи:

  1. MyBeanB был создан 3 раз.
  2. Чтобы добавить функциональность @Transactional для MyBeanB, Spring использовал CGLIB.

Не могли бы вы объяснить, что происходит? Как работает режим прокси?

P.S.

Я прочитал документацию:

/**
 * Specifies whether a component should be configured as a scoped proxy
 * and if so, whether the proxy should be interface-based or subclass-based.
 * <p>Defaults to {@link ScopedProxyMode#DEFAULT}, which typically indicates
 * that no scoped proxy should be created unless a different default
 * has been configured at the component-scan instruction level.
 * <p>Analogous to {@code <aop:scoped-proxy/>} support in Spring XML.
 * @see ScopedProxyMode
 */

, но мне это не ясно.

Обновление

Случай 3:

Я исследовал еще один случай, в котором я извлек интерфейс из MyBeanB:

public interface MyBeanBInterface {
    long getCounter();
}



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


@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.INTERFACES)
public class MyBeanB implements MyBeanBInterface {

и в этом случае вывод:

constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class com.sun.proxy.$Proxy92

Здесь мы можем видеть две вещи:

  1. MyBeanB был создан 3 раз.
  2. Чтобы добавить функциональность @Transactional для MyBeanB, Spring использовал динамический прокси JDK.
  3. [+1135]
19
задан gstackoverflow 9 October 2019 в 13:33
поделиться

1 ответ

Прокси, сгенерированный для @Transactional поведение, служит другой цели, чем ограниченные по объему прокси.

Эти @Transactional прокси является тем, который обертывает определенный боб для добавления поведения управления сеансами. Все вызовы метода выполнят управление транзакциями прежде и после делегирования к фактическому бобу.

при иллюстрировании его это было бы похоже

main -> getCounter -> (cglib-proxy -> MyBeanB)

В наших целях, можно по существу проигнорировать его поведение (удалите @Transactional, и необходимо видеть, что то же поведение, кроме Вас не будет иметь прокси cglib).

@Scope прокси ведет себя по-другому. Состояния документации:

[...] необходимо ввести объект прокси, который представляет тот же открытый интерфейс как ограниченный по объему объект , но это может также получить реальный целевой объект от соответствующего объема (такого как Запрос HTTP) и вызовы метода делегата на реальный объект.

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

при иллюстрировании его это было бы похоже

main -> getCounter -> (cglib-scoped-proxy -> context/bean-factory -> new MyBeanB)

, Так как MyBeanB опытный боб, контекст будет всегда возвращать новый экземпляр.

В целях этого ответа, предположите, что Вы получили MyBeanB непосредственно с [1 144]

MyBeanB beanB = context.getBean(MyBeanB.class);

, который является по существу, что Spring делает для удовлетворения @Autowired инжекционная цель.

<час>

В Вашем первом примере,

@Service
@Scope(value = "prototype")
public class MyBeanB { 

Вы объявляете опытный боб определение (через аннотации). @Scope имеет proxyMode элемент, который

Определяет, должен ли компонент быть настроен как ограниченный по объему прокси и если так, должен ли прокси быть основан на интерфейсе или основан на подклассе.

Значения по умолчанию к [1 117], который обычно указывает, что никакой ограниченный по объему прокси не должен быть создан , если другое значение по умолчанию не было настроено на уровне инструкции сканирования компонента.

, Таким образом, Spring не создает ограниченный по объему прокси для получающегося боба. Вы получаете тот боб с [1 150]

MyBeanB beanB = context.getBean(MyBeanB.class);

, у Вас теперь есть ссылка на новое MyBeanB объект, созданный Spring. Это похоже на любой другой объект Java, вызовы метода перейдут непосредственно к экземпляру, на который ссылаются.

, Если бы Вы использовали getBean(MyBeanB.class) снова, Spring возвратила бы новый экземпляр, так как бобовое определение для опытный боб . Вы не делаете этого, таким образом, все Ваши вызовы метода переходят к тому же объекту.

<час>

В Вашем втором примере,

@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBeanB {

Вы объявляете ограниченный по объему прокси, который реализован через cglib. При запросе боба этого типа от Spring с [1 154]

MyBeanB beanB = context.getBean(MyBeanB.class);

Spring знает, что MyBeanB ограниченный по объему прокси и поэтому возвращает объект прокси, который удовлетворяет API [1 121] (т.е. реализует все его открытые методы), который внутренне знает, как получить фактический боб типа MyBeanB для каждого вызова метода.

Попытка, работающая

System.out.println("singleton?: " + (context.getBean(MyBeanB.class) == context.getBean(MyBeanB.class)));

, Это возвратится true вывод подсказок тому, что Spring возвращает одноэлементный объект прокси (не опытный боб).

На вызове метода, в реализации прокси, Spring будет использовать специальное предложение getBean версия, которая знает, как различать определение прокси и фактическое MyBeanB бобовое определение. Это возвратит новое MyBeanB экземпляр (так как это - прототип), и Spring делегирует вызов метода к нему посредством отражения (классик Method.invoke).

<час>

Вашим третьим примером является по существу то же как Ваша секунда.

9
ответ дан 30 November 2019 в 05:17
поделиться
Другие вопросы по тегам:

Похожие вопросы: