Поведение при вызове в классах @Configuration и @Component - PullRequest
1 голос
/ 06 июля 2019

Мой вопрос касается поведения AOP Spring в случае внутренних вызовов методов.

@Service
class Service {
    @Transactional
    public void method1() {
        method1();
    }

    @Transactional
    public void method2() {}
}

Если мы вызываем method1 () извне, method1 () будет выполняться в режиме транзакции, но, поскольку он вызывает внутренний метод method2 (), код внутри method2 () не будет выполняться в режиме транзакции.

Параллельно для класса Configuration обычно мы должны иметь такое же поведение:

@Configuration
class MyConfiguration{
    @Bean
    public Object1 bean1() {
        return new Object1();
    }

    @Bean
    public Object1 bean2() { 
        Object1 b1 = bean1();
        return new Object2(b1);
    }
}

Обычно, если я хорошо понял, вызов метода bean1 () из bean2 () не должен быть перехвачен прокси-объектом и, следовательно, если мы вызываем bean1 () много раз, мы должны каждый раз получать разные объекты.

Во-первых, не могли бы вы технически объяснить, почему внутренние вызовы не перехватываются прокси-объектом, и, во-вторых, проверить правильность моего понимания второго примера.

1 Ответ

1 голос
/ 07 июля 2019

Regular Spring @Component s

Для объяснения того, как обычные Spring (AOP) прокси или динамические прокси (JDK, CGLIB) в целом работают, см. мой другой ответ с иллюстративнымобразец кода.Сначала прочтите это, и вы поймете, почему самозвонок для этих типов прокси не может быть перехвачен через Spring AOP.

@Configuration классы

Что касается @Configuration классов, они работают по-разному.Во избежание повторного создания бинов Spring, которые уже были созданы, просто потому, что их фабричные методы @Bean вызываются снова как внутри, так и изнутри, Spring создает для них специальные прокси-серверы CGLIB.

Один из моих настроекклассы выглядят так:

package spring.aop;

import org.springframework.context.annotation.*;

@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class ApplicationConfig {
  @Bean(name = "myInterfaceWDM")
  public MyInterfaceWithDefaultMethod myInterfaceWithDefaultMethod() {
    MyClassImplementingInterfaceWithDefaultMethod myBean = new MyClassImplementingInterfaceWithDefaultMethod();
    System.out.println("Creating bean: " + myBean);
    return myBean;
  }

  @Bean(name = "myTestBean")
  public Object myTestBean() {
    System.out.println(this);
    myInterfaceWithDefaultMethod();
    myInterfaceWithDefaultMethod();
    return myInterfaceWithDefaultMethod();
  }
}

Соответствующее приложение выглядит так:

package spring.aop;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext appContext = SpringApplication.run(DemoApplication.class, args);
        MyInterfaceWithDefaultMethod myInterfaceWithDefaultMethod =
          (MyInterfaceWithDefaultMethod) appContext.getBean("myInterfaceWDM");
        System.out.println(appContext.getBean("myTestBean"));
    }
}

Это печатает (отредактировано, чтобы удалить вещи, которые мы не хотим видеть):

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.2.RELEASE)

2019-07-07 08:37:55.750  INFO 22656 --- [           main] spring.aop.DemoApplication               : Starting DemoApplication on (...)
(...)
Creating bean: spring.aop.MyClassImplementingInterfaceWithDefaultMethod@7173ae5b
spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a@72456279

При запуске приложения метод myInterfaceWithDefaultMethod() не вызывается несколько раз, даже если в myTestBean() есть несколько вызовов.Почему?

Вы узнаете больше, если поставите точку останова на один из вызовов myInterfaceWithDefaultMethod() в myTestBean() и позволите отладчику остановиться на этом.Затем вы можете проверить ситуацию, оценив код:

System.out.println(this);

spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a@72456279

Таким образом, класс config действительно является прокси CGLIB.Но какие методы у него есть?

for (Method method: this.getClass().getDeclaredMethods()) {
  System.out.println(method);
}

public final java.lang.Object spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a.myTestBean()
public final spring.aop.MyInterfaceWithDefaultMethod spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a.myInterfaceWithDefaultMethod()
public final void spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a.setBeanFactory(org.springframework.beans.factory.BeanFactory) throws org.springframework.beans.BeansException
final spring.aop.MyInterfaceWithDefaultMethod spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a.CGLIB$myInterfaceWithDefaultMethod$1()
public static void spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a.CGLIB$SET_THREAD_CALLBACKS(org.springframework.cglib.proxy.Callback[])
public static void spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a.CGLIB$SET_STATIC_CALLBACKS(org.springframework.cglib.proxy.Callback[])
public static org.springframework.cglib.proxy.MethodProxy spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a.CGLIB$findMethodProxy(org.springframework.cglib.core.Signature)
final void spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a.CGLIB$setBeanFactory$6(org.springframework.beans.factory.BeanFactory) throws org.springframework.beans.BeansException
static void spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a.CGLIB$STATICHOOK4()
private static final void spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a.CGLIB$BIND_CALLBACKS(java.lang.Object)
final java.lang.Object spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a.CGLIB$myTestBean$0()
static void spring.aop.ApplicationConfig$$EnhancerBySpringCGLIB$$8b4ed8a.CGLIB$STATICHOOK3()

Это выглядит довольно грязно, давайте просто напечатаем имена методов:

for (Method method: this.getClass().getDeclaredMethods()) {
  System.out.println(method.name);
}

myTestBean
myInterfaceWithDefaultMethod
setBeanFactory
CGLIB$myInterfaceWithDefaultMethod$1
CGLIB$SET_THREAD_CALLBACKS
CGLIB$SET_STATIC_CALLBACKS
CGLIB$findMethodProxy
CGLIB$setBeanFactory$6
CGLIB$STATICHOOK4
CGLIB$BIND_CALLBACKS
CGLIB$myTestBean$0
CGLIB$STATICHOOK3

Этот прокси реализует какие-либо интерфейсы?

for (Class<?> implementedInterface : this.getClass().getInterfaces()) {
  System.out.println(implementedInterface);
}

interface org.springframework.context.annotation.ConfigurationClassEnhancer$EnhancedConfiguration

Хорошо, интересно.Давайте читать немного Javadoc.На самом деле класс ConfigurationClassEnhancer является областью действия пакета, поэтому мы должны прочитать Javadoc прямо в исходном коде :

Улучшает классы конфигурации, генерируя подкласс CGLIB, который взаимодействует сSpring-контейнер для соблюдения семантики области действия bean-компонента для методов @Bean. Каждый такой метод @Bean будет переопределен в сгенерированном подклассе, делегируя только фактической реализации метода @Bean, если контейнер фактически запрашивает создание нового экземпляра. В противном случае вызов такого метода @Beanслужит ссылкой на контейнер, получая соответствующий бин по имени.

Внутренний интерфейс EnhancedConfiguration на самом деле является общедоступным, но Javadoc все еще находится только в исходном коде 1055 *:

Интерфейс маркера, который должен быть реализован всеми подклассами @Configuration CGLIB.Облегчает идемпотентное поведение для улучшения посредством проверки, чтобы видеть, присваиваются ли ему классы-кандидаты, например, уже улучшены.Также расширяет BeanFactoryAware, поскольку все расширенные классы @Configuration требуют доступа к BeanFactory, которая их создала.

Обратите внимание, что этот интерфейс предназначен только для внутреннего использования в фреймворке, однако должен оставаться открытым, чтобы разрешить доступ к генерируемым подклассам.из других пакетов (т. е. кода пользователя).

Что мы увидим, если перейдем к вызову myInterfaceWithDefaultMethod()?Сгенерированный прокси-метод вызывает метод ConfigurationClassEnhancer.BeanMethodInterceptor.intercept(..), а Javadoc этого метода говорит:

Расширение метода @Bean для проверки предоставленного BeanFactory на наличие этого объекта bean.

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

Надеюсь, это поможет.

...