目录
Spring 通过
@Configuration
和@Bean
注解来管理应用中的所有对象(即 bean)。这两个注解的组合使得 Spring 配置类不仅可以定义 bean,还能确保 bean 之间的依赖关系得到正确的注入和管理。接下来我们将结合这两个注解的用法,进行详细的分析。
@Bean
注解详解
@Bean
注解介绍
@Bean
是一个方法级别的注解,用于声明一个 bean。每个带有 @Bean
注解的方法都将返回一个 Spring 容器管理的对象。通常,@Bean
被放置在 @Configuration
注解的类中,但也可以单独使用。
在 Spring 中,@Bean
注解所标注的方法会告诉 Spring 该方法返回的对象应该作为 Spring 容器中的一个 bean,并将其注册到 Spring 容器中。
使用 @Bean
创建 Bean 的基本示例:
public class AppConfig {
// 通过 @Bean 注解定义了一个名为 myService 的 bean
@Bean
public MyService myService() {
return new MyService();
}
}
public class MyService {
public void query() {
System.out.println("Service query method called!");
}
}
-
在上述代码中,
myService()
方法返回一个MyService
对象,Spring 会将这个对象注册为一个 bean,且默认的 bean 名称是myService
(即方法名的小写形式)。 -
@Bean
的方法就像是一个工厂方法,负责生成一个 bean 实例。
使用 @Bean
注解的方法可以接受参数
@Bean
注解标注的方法不仅可以返回一个 bean,还可以接收其他 bean 作为参数,这样可以实现 bean 之间的依赖注入。
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyService();
}
@Bean
public UserService userService() {
// userService 方法依赖 myService bean,因此直接调用 myService() 方法
return new UserService(myService());
}
}
public class UserService {
private MyService myService;
// 构造方法注入
public UserService(MyService myService) {
this.myService = myService;
System.out.println("UserService created with MyService: " + myService);
}
}
public class MyService {
public void query() {
System.out.println("Service query method called!");
}
}
userService()
方法依赖 myService()
方法返回的 MyService
对象,Spring
会自动将 myService()
返回的对象注入到 UserService
构造函数中。
@Configuration
注解详解
在 Spring 中,@Configuration
注解用于标记一个类为配置类,这意味着该类的目的主要是用来定义和管理 Spring 容器中的 bean。配置类通常与 @Bean
注解一起使用,@Bean
用来声明容器中的单个 bean。
示例:
@Configuration
public class AppConfig {
// 定义一个 Bean
@Bean
public MyService myService() {
return new MyService();
}
@Bean
public UserService userService() {
return new UserService(myService());
}
}
@Configuration
表示 AppConfig
类是一个配置类,Spring 会处理这个类中的 bean 定义。配置类中的方法返回的对象会自动注册为 Spring 的 bean。
@Configuration
的特殊之处
与普通类不同,使用了 @Configuration
的类在 Spring 中有一些特殊的处理。Spring 会将这个类增强为 CGLIB 代理类。代理类的主要目的是保证方法调用之间的依赖注入是通过 Spring 容器进行的,而不是简单的直接调用。这就避免了手动实例化的问题。
CGLIB 代理有什么作用?
使用 CGLIB 代理技术能够确保配置类中的方法调用从 Spring 容器中获取已有的 bean,而不是重新实例化它们。简而言之,@Configuration
修饰的类使得配置类变成了一个代理对象,能够通过代理类进行方法调用的拦截。
使用和不使用@Configuration有什么区别?
当你在一个类上使用 @Configuration
注解时,Spring 会对该类进行特别处理:
-
CGLIB 代理增强:
-
Spring 会将配置类增强为一个 CGLIB 动态代理类。通过这个代理,Spring 确保在配置类内部调用
@Bean
注解的方法时,从容器中获取的是同一个实例,而不是重新实例化对象。
-
-
内部方法调用的依赖注入:
-
在配置类内部,通过
@Bean
注解的方法互相调用时,Spring 会确保这些方法返回的 bean 来自容器,而不是手动创建新的对象。这意味着,即使你在同一个配置类内调用@Bean
方法,Spring 也能保证它们是通过容器注入的,而不是简单地调用方法。
-
示例:
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyService();
}
@Bean
public UserService userService() {
// 依赖 myService bean,这里会调用 myService() 方法
return new UserService(myService());
}
}
public class MyService {
// 服务类
}
public class UserService {
private final MyService myService;
// 构造器注入 MyService
public UserService(MyService myService) {
this.myService = myService;
System.out.println("UserService created with MyService: " + myService);
}
}
@Test
public void test() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class); // 获取 userService bean
context.close();
}
此时的输出为:
UserService created with MyService: MyService@内存地址x
在这个例子中,userService
方法调用了 myService()
方法,Spring 会确保 myService
返回的是容器中的 MyService
实例,而不是重新创建一个新的 MyService
对象。
如果不使用 @Configuration
注解,Spring 就不会将该类视为一个配置类,也不会增强它为 CGLIB 代理类。这会导致一些行为的不同:
-
方法调用直接执行:
-
如果没有
@Configuration
,配置类就只是一个普通的 Java 类,Spring 不会拦截方法调用。因此,当你在配置类内部调用@Bean
注解的方法时,这些方法不会通过 Spring 容器获取实例,而是会直接执行方法,返回新的实例。
-
-
可能导致多例问题:
-
如果没有
@Configuration
,即使你调用@Bean
注解的方法,它们也会直接返回新创建的实例,而不是从 Spring 容器中获取已创建的实例。这会导致每次调用都创建新的对象,可能会导致不必要的性能开销,或者无法实现单例模式。
-
直接把@COnfiguration注解去除,输出变成:
UserService created with MyService: MyService@内存地址x
UserService created with MyService: MyService@内存地址y
由此可以看到,在这个示例中:
-
使用
@Configuration
时:-
userService()
方法依赖myService()
,通过 Spring 容器获取MyService
实例,并保证是同一个对象。
-
-
没有
@Configuration
时:-
userService()
方法每次调用myService()
时都会创建一个新的MyService
实例,而不是从容器中获取它。这导致了每次调用都创建了新的MyService
对象,可能会导致意外的结果。
-
关于这个问题的总结:
-
有
@Configuration
的情况下:-
配置类会通过 CGLIB 代理增强,保证内部
@Bean
方法之间的依赖关系是通过容器管理的,避免了多例对象的问题。
-
-
没有
@Configuration
的情况下:-
配置类只是一个普通的类,Spring 不会对它进行代理,导致
@Bean
注解的方法每次都会执行,返回新创建的对象,无法保证内部方法调用的依赖注入。
-
使用 @Configuration
可以确保 Spring 容器正确地管理 Bean 的生命周期和依赖关系,避免了不必要的重复创建对象,同时也保证了依赖注入的一致性。
@Configuration
和 @Bean
的总结
@Configuration
如何工作?
当我们使用 @Configuration
注解时,Spring 会在容器启动时通过 ConfigurationClassPostProcessor
对这个类进行解析,注册其中的 @Bean
定义的 bean。Spring 还会将该类转变为 CGLIB 代理类,以保证内部方法调用之间的依赖注入。
@Bean
如何工作?
Spring 会执行 @Bean
注解标记的方法来创建对应的 bean。如果方法之间有依赖关系,Spring 会自动处理这些依赖,确保每个 bean 都从容器中获取。
@Configuration
和 @Bean
有什么关系?
-
@Configuration
注解标记的类是一个配置类,表示这个类主要用于定义 Spring 容器中的 bean。 -
@Bean
注解标记的方法则用于定义一个具体的 bean。
在一个 @Configuration
注解的类中,我们通常会看到多个 @Bean
注解的方法。通过这些方法,Spring 容器可以实例化并管理应用所需要的所有 bean。
@Bean
注解的作用
-
定义 bean:
@Bean
表明当前方法返回的对象是 Spring 管理的 bean。每次调用该方法时,Spring 会为我们返回该对象实例。 -
依赖注入:通过在
userService()
方法中调用myService()
方法,我们可以在UserService
中自动注入MyService
,从而实现了 bean 的依赖关系。
@Configuration
注解的作用:
-
用来标记一个类为配置类,该类包含多个
@Bean
定义。 -
会被 Spring 转换为 CGLIB 代理类,确保方法调用之间的依赖注入。
@Bean
方法的每个返回值都会成为容器中的一个 bean。@Bean
注解通常用于什么地方?
-
定义那些没有默认构造函数的类实例。
-
自定义初始化方法和销毁方法。
-
通过
@Bean
方法返回的 bean 可以直接访问和使用容器中的其他 bean。