spring · 2023-11-06 0

Spring的@Configuration的full和lite模式

一、maven

<properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>5.2.2.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.2.RELEASE</version>
    </dependency>
</dependencies>

二、full 和 lite 模式

官方管这两种模式分别叫:full @Configuration 和 lite @Bean mode

最初的Spring只支持 xml 方式配置 Bean,从 Spring 3.0 起支持了一种更优的方式:基于Java类的配置方式

@Configuration 标注的类等同于一个 xml 文件,@Bean 标注的方法等同于 xml 文件里的一个 <bean/> 标签

full 模式和 lite 模式均是针对于 Spring 配置类而言的,和 xml 配置文件无关。值得注意的是:判断是 full 模式 和 lite 模式的前提是,首先你得是个容器组件。

lite 模式的 @Bean 方法不能声明 Bean 之间的依赖关系。因此,这样的 @Bean 方法不应该调用其他 @Bean 方法。

三、lite 模式

官方定义为:在没有标注 @Configuration 的类里面有 @Bean 方法就称为 lite 模式的配置。

透过源码,可以看到,类上没有被标注 @Configuration,如下配置均认为是 lite 模式的配置类:

  1. 类上标注有 @Component 注解
  2. 类上标注有 @ComponentScan 注解
  3. 类上标注有 @Import 注解
  4. 类上标注有 @ImportResource 注解
  5. 若类上没有任何注解,但类内存在 @Bean 方法

在Spring 5.2之后新增了一种配置也算作 lite 模式:

  1. 标注有 @Configuration(proxyBeanMethods = false),注意:此值默认是 true,需要显示改为 false 才算是 lite 模式

自 Spring5.2(对应Spring Boot 2.2.0)开始,内置的几乎所有的 @Configuration 配置类都被修改为了 @Configuration(proxyBeanMethods = false),以此来降低启动时间

优点:

  • 运行时不再需要给对应类生成CGLIB子类,提高了运行性能,降低了启动时间
  • 可以该配置类当作一个普通类使用喽:也就是说@Bean方法 可以是private、可以是final

缺点:

  • 不能声明@Bean之间的依赖,也就是说不能通过方法调用来依赖其它 Bean
  • (其实这个缺点还好,很容易用其它方式“弥补”,比如:把依赖Bean放进方法入参里即可)

1.示例代码

User.java

public class User {

    private String name;

    private Integer age;

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + ''' +
                ", age=" + age +
                '}';
    }
}

LiteConfig.java

@Component
// @Configuration(proxyBeanMethods = false) // 这样也是Lite模式
public class LiteConfig {

    @Bean
    public User user() {
        User user = new User("zhangsan1", 21);
        return user;
    }

    @Bean
    private final User user2() {
        User user = new User("zhangsan2", 22);

        // 模拟依赖于user实例  看看是否是同一实例
        System.out.println(System.identityHashCode(user()));
        System.out.println(System.identityHashCode(user()));

        return user;
    }

    public static class InnerConfig {

        @Bean
        // private final User userInner() { // 只在lite模式下才好使
        public User userInner() {
            User user = new User("zhangsan3", 23);
            return user;
        }
    }
}

AppLiteConfig.java

@ComponentScan("org.example.lite")
@Configuration
public class AppLiteConfig {

}

2.测试类

public class Main {

    public static void main(String[] args) {
        lite();
    }

    private static void lite() {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppLiteConfig.class);

        // 配置类情况
        System.out.println(context.getBean(AppLiteConfig.class).getClass());
        System.out.println(context.getBean(LiteConfig.class).getClass());
        System.out.println(context.getBean(LiteConfig.InnerConfig.class).getClass());
        System.out.println();

        String[] beanNames = context.getBeanNamesForType(User.class);
        for (String beanName : beanNames) {
            User user = context.getBean(beanName, User.class);
            System.out.println("beanName:" + beanName);
            System.out.println(user.getClass());
            System.out.println(user);
            System.out.println("------------------------");
        }
    }
}

3.结果

195615004
1935972447
class org.example.config.AppLiteConfig$$EnhancerBySpringCGLIB$$f0a9ba51
class org.example.lite.LiteConfig
class org.example.lite.LiteConfig$InnerConfig

beanName:userInner
class org.example.bean.User
User{name='zhangsan3', age=23}
------------------------
beanName:user
class org.example.bean.User
User{name='zhangsan1', age=21}
------------------------
beanName:user2
class org.example.bean.User
User{name='zhangsan2', age=22}
------------------------

小结:

  • 该模式下,配置类本身不会被 CGLIB 增强
  • 该模式下,对于内部类是没有限制的
  • 该模式下,配置类内部不能通过方法调用来处理依赖,否则每次生成的都是一个新实例而并非 IoC 容器内的单例
  • 该模式下,配置类就是一普通类嘛,所以 @Bean 方法可以使用 private/final 等进行修饰

四、full 模式

@Bean 方法都会在标注有 @Configuration 的类中声明,以确保总是使用 “full 模式”

  • 标注有 @Configuration 或者 @Configuration(proxyBeanMethods = true) 的类被称为 full 模式的配置类

优点:

  • 可以支持通过常规 Java 调用相同类的 @Bean 方法而保证是容器内的 Bean,这有效规避了在“lite 模式”下操作时难以跟踪的细微错误

缺点:

  • 运行时会给该类生成一个 CGLIB 子类放进容器,有一定的性能、时间开销
  • 正因为被代理了,所以 @Bean 方法 不可以是 private、不可以是 final

1.示例代码

User.java

public class User {

    private String name;

    private Integer age;

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + ''' +
                ", age=" + age +
                '}';
    }
}

FullConfig.java

@Configuration
public class FullConfig {

    @Bean
    public User user() {
        User user = new User("zhangsan1", 21);
        return user;
    }

    @Bean
    protected User user2() {
        User user = new User("zhangsan2", 22);

        // 模拟依赖于user实例  看看是否是同一实例
        System.out.println(System.identityHashCode(user()));
        System.out.println(System.identityHashCode(user()));

        return user;
    }

    public static class InnerConfig {

        @Bean
        // private final User userInner() { // 只在lite模式下才好使
        public User userInner() {
            User user = new User("zhangsan3", 23);
            return user;
        }
    }
}

AppFullConfig.java

@ComponentScan("org.example.full")
@Configuration
public class AppFullConfig {

}

2.测试类

public class Main {

    public static void main(String[] args) {
        full();
    }

    private static void full() {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppFullConfig.class);

        // 配置类情况
        System.out.println(context.getBean(AppFullConfig.class).getClass());
        System.out.println(context.getBean(FullConfig.class).getClass());
        System.out.println(context.getBean(FullConfig.InnerConfig.class).getClass());
        System.out.println();

        String[] beanNames = context.getBeanNamesForType(User.class);
        for (String beanName : beanNames) {
            User user = context.getBean(beanName, User.class);
            System.out.println("beanName:" + beanName);
            System.out.println(user.getClass());
            System.out.println(user);
            System.out.println("------------------------");
        }
    }
}

3.结果

1447499999
1447499999
class org.example.config.AppFullConfig$$EnhancerBySpringCGLIB$$df598e0e
class org.example.full.FullConfig$$EnhancerBySpringCGLIB$$6020851c
class org.example.full.FullConfig$InnerConfig

beanName:userInner
class org.example.bean.User
User{name='zhangsan3', age=23}
------------------------
beanName:user
class org.example.bean.User
User{name='zhangsan1', age=21}
------------------------
beanName:user2
class org.example.bean.User
User{name='zhangsan2', age=22}
------------------------

小结:

  • 该模式下,配置类会被 CGLIB 增强(生成代理对象),放进 IoC 容器内的是代理
  • 该模式下,对于内部类是没有限制的:可以是 full 模式或者 lite 模式
  • 该模式下,配置类内部可以通过方法调用来处理依赖,并且能够保证是同一个实例,都指向 IoC 内的那个单例
  • 该模式下,@Bean 方法不能被 private/final 等进行修饰(因为方法需要被复写,所以不能私有和final),否则启动报错