@Configuration proxyBeanMethods

Last Modified: 2023/01/18

前言

本文讲述 @Configuration(proxyBeanMethods = false)@Configuration(proxyBeanMethods = true) 的区别。

结论

先上结论:proxyBeanMethods 决定了被标记为 @Bean 的方法在被调用时是作为普通方法调用还是作为代理方法调用(spring 使用 cglib 代理方法调用)。proxyBeanMethods 为 true,作为代理方法调用,为 false 作为普通方法调用。具体区别如下:

  • @Bean 标记的方法作为代理方法被调用时,会尝试从 spring 容器中获取 bean,如果容器中没有该 bean,才会真正的调用该方法(返回的对象被放入容器中管理,因此会执行 bean 生命周期相关方法),否则直接从容器中返回 bean。
  • @Bean 标记的方法作为普通方法被调用时,直接调用方法本身,方法返回的对象也不会被放入容器中管理;

实战

Configuration 类中引用的相关类

public class Car {
  @PostConstruct
  public void init(){
      System.out.println("init car ... " + this);
  }
}
public class User {
  private final Car car;
  private final String name;
  
  public User(String name, Car car) {
      this.name = name;
      this.car = car;
  }
  
  @PostConstruct
  public void init() {
      System.out.printf("init user %s ...\n", this.name);
      System.out.printf("%s car is: %s\n", this.name, this.car);
  }
}

Configuration 类

@Configuration(proxyBeanMethods = true)
public class MyConfig {
  @Bean
  public User user1() {
    // car() 方法第1次调用
    return new User("user1", car());
  }
  
  @Bean
  public User user2() {
    // car() 方法第2次调用
    return new User("user2", car());
  }
  
  @Bean
  public Car car() {
      System.out.println("car method called ...");
      return new Car();
  }
}

测试 proxyBeanMethods 为 true 的情况

@Test
public void test() {
  AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
  context.register(MyConfig.class);
  context.refresh();
}

运行上面的代码,控制台输出如下:

car method called ...
init car ... com.gaols.prac.pracboot.hello.Car@17f62e33
init user user1 ...
user1 car is: com.gaols.prac.pracboot.hello.Car@17f62e33
init user user2 ...
user2 car is: com.gaols.prac.pracboot.hello.Car@17f62e33

在 MyConfig 中 user1 bean 和 user2 bean 中都调用了 car() 方法,但是输出内容中只出现一次 “car method called ...”,这是由于 car() 方法被代理,代理后的方法可以用伪代码表示如下:

public Car car() {
  Car car = beanFactory.getBean("car");
  if (car == null) {
    car = myConfig.car();
    car.init();
    // 调用其他 bean 相关的生命周期方法
    // 将 bean 保存到下来,下次请求直接从 registry 中获取
    saveBeanToRegistry(car)
  } else {
    return car;
  }
}

另外从上面的输出可以看出 user1 和 user2 中的 car 对象都是同一个对象 Car@17f62e33,这也从侧面印证了 MyConfig 中的 car() 方法实际上只被调用一次。

测试 proxyBeanMethods 为 false 的情况

只需要将 MyConfig 上面的 proxyBeanMethods 改为 false,其他代码无需改动。

@Configuration(proxyBeanMethods = false)
public class MyConfig {
  // 省略
}

再次运行测试代码,控制台输出如下:

car method called ...
init user user1 ...
user1 car is: com.gaols.prac.pracboot.hello.Car@48974e45
car method called ...
init user user2 ...
user2 car is: com.gaols.prac.pracboot.hello.Car@6a84a97d
car method called ...
init car ... com.gaols.prac.pracboot.hello.Car@6c130c45

控制台输出了三次 “car method called ...”,说明 car() 方法被调用三次,前两次输出是因为创建 user bean 的时候,直接调用 car() 方法产生的输出,最后一次是因为创建 car bean 而产生的输出。

另外我们注意到共创建了三个 car 对象,从他们后面的 hash 值可以看出他们是不同的 car 对象。但是 “init car ...” 只输出了一次,这是因为最后一个 car 对象是 spring 容器创建的 car bean,因此会调用 bean 相关的生命周期方法,而前两次只是普通的方法调用,创建的 car 对象只是普通的 java 对象,不会执行 init 方法。

有问题吗?点此反馈!

温馨提示:反馈需要登录