一、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 模式的配置类:
- 类上标注有 @Component 注解
- 类上标注有 @ComponentScan 注解
- 类上标注有 @Import 注解
- 类上标注有 @ImportResource 注解
- 若类上没有任何注解,但类内存在 @Bean 方法
在Spring 5.2之后新增了一种配置也算作 lite 模式:
- 标注有 @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),否则启动报错