Что такое прокси в 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
поделиться