由此及彼

我在写如何在Spring中优雅的使用单例模式?一文的时候有使用到@Component(“xxx”)注解,总所周知,该注解可以将普通的Java类实例化到Spring容器中,可以替代Spring 4.0 版本之前xml配置,xxx就等同于xml中的bean标签的id(所以需要保证唯一),不知道大家有没有了解过Spring配置类的Full模式和Lite模式?如果您第一次听到这个概念,您不妨花费几分钟往下读一读,或许可以让您少掉几根秀发。

详细介绍

Full模式、Lite模式是针对于Spring的配置“类”而言的,xml配置不能与之相提并论,大家都是知道,@Configuration+@Bean注解可以扫描到方法级别的配置,但是为什么不使用@Configuration注解也可以扫描到配置呢?Spring 5.2(SpringBoot 2.0)之后,在使用@Component的类中@Bean注解声明的方法上,或者只使用@Bean注解声明的方法都被称为是配置的Lite模式,而使用@Configuration声明的类+@Bean声明的方法被称为Full模式。具有以下特点的配置都被称为Lite模式:

  • 类上标注有@Component注解
  • 类上标注有@ComponentScan注解
  • 类上标注有@Import注解
  • 类上标注有@ImportResource注解
  • 若类上没有任何注解,但类内存在@Bean方法
  • 标注有@Configuration(proxyBeanMethods = false)

@Configuration注解中,proxyBeanMethods 默认(不写)是true,自Spring 5.2开始,几乎所有内置的@Configuration配置类都使用了proxyBeanMethods = false,具体原因是:

  1. proxyBeanMethods 为true时,需要使用到CGlib动态代理,CGLib将继承用到了极致,CGLib动态代理是代理类去继承目标类,然后重写其中目标类的方法,反之,如果不使用CGlib动态代理,就不用生成CGLib的子类,从而提高运行速度。
  2. 既然proxyBeanMethods 为true的时候,该类被声明为配置类,反之,proxyBeanMethods 为false的时候,就可以将Lite模式的配置类视为普通类,所以使用@Bean注解的方法,可以当成普通方法,可以使用private、final、static修饰符。

Lite模式的缺点:各个Bean之间不能通过方法互相调用

此时就体现了Full模式的优点:Full模式的配置类在Spring容器中是其本身,保证在运行时单例,在多次使用时,都是一个实例

talk is cheaper,show me your code.
Full模式:

Teacher.java

@Accessors(chain = true)
@Data
public class Teacher {
    private String tName;

    private int tAge;

    public Teacher() {
        System.out.println("teacher create current INSTANCE is : " + this.hashCode());
    }
}

TeaWebConfig.java

@Configuration
public class TeaWebConfig {

    @Bean
    public Teacher teacher() {
        return new Teacher();
    }

    @Bean
    public String equalsTeacher(Teacher teacher) {
        System.out.println("invoke others Bean's INSTANCE is "+teacher.hashCode());
        //使用teacher()模拟不同Bean之间的调用
        System.out.println("invoke Constructor's INSTANCE is "+teacher().hashCode());
        return "万物基于MIUI";
    }
}

Test.java

    public static void main(String[] args) {
        ApplicationContext teaContext = new AnnotationConfigApplicationContext(TeaWebConfig.class);
        Teacher teacher = teaContext.getBean(Teacher.class);
        System.out.println("Test.invoke + : " + teacher.hashCode());
    }
输出
teacher create current INSTANCE is : 6018
invoke others Bean's INSTANCE is 6018
invoke Constructor's INSTANCE is 6018
Test.invoke + : 6018

可以看到,Full模式下,配置类对象在Spring中是单例。

Lite模式

User.java

@Accessors(chain = true)
@Data
public class User {

    private int age;

    private String name;

    private String doHomeWork;

    public User() {
        System.out.println("user create current INSTANCE is : " + this.hashCode());
    }
}

userConfig.class

@Component
public class StuWebConfig {
    @Bean
    public User user() {
        return new User();
    }

    @Bean
    public String name(User user) {
        System.out.println(user.hashCode());
        //使用user()模拟不同Bean之间的调用
        System.out.println(user().hashCode());
        return "万物基于MIUI";
    }
}

如果是在IDEA下,在System.out.println(user().hashCode());这行会有错误提醒:

Method annotated with @Bean is called directly. Use dependency injection instead.

表示:未添加@Configuration注解,导致@Bean之间相互调用出错。

当然,我们会把实验继续下去,实际在本demo中不会因为这个报错而启动失败。

输出
user create current INSTANCE is : 45326
invoke others Bean's INSTANCE is 207959
user create current INSTANCE is : 23423
invoke Constructor Bean's INSTANCE is 207959

Lite模式使用配置类时,多次调用时会创建多个对象,并不是单例模式

所以,为了您的秀发:

建议使用Full模式, @Configuration+@Bean,以避免奇奇怪怪的问题。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐