Springbean(二)@Component及其派生注解自动注入(2)使用注意和加载问题
一、加载顺序
1、默认扫描顺序
Spring 在 @ComponentScan
或者 @SpringBootApplication
默认的包扫描路径下,会按照类路径扫描所有的 @Component
及其派生注解(如 @Service
, @Repository
, @Controller
)的 Bean,并将其注册到 Spring 容器中。
(1)默认情况下,扫描的顺序是不确定的,因为 Spring 使用反射扫描类路径,而类路径的顺序通常依赖于底层文件系统或者 ClassLoader 的实现。即使这个bean继承了ApplicationContextAware、InitializingBean、或者bean的某个接口使用了@PostConstruct注解,这些Bean的加载顺序是不确定的,见:
SpringBean模块(二)bean初始化(2)和容器初始化顺序的比较--引入ApplicationContextInitializer-CSDN博客
(2)但如果 @ComponentScan
通过 basePackages
指定了多个包路径,Spring 会按声明的顺序扫描这些包。其实也只是包有了顺序,实际上类还是按照类的路径加载,也就是无序的。
2、依赖关系对扫描顺序的影响
上面提到在没有依赖关系的情况下,bean的加载顺序是随机的。
而如果一个 @Component
需要依赖另一个 Bean,Spring 会确保 先初始化被依赖的 Bean,再初始化依赖它的 Bean。
(1)使用@Autowired等自动装配
见SpringBean模块(二)bean初始化(2)和容器初始化顺序的比较--引入ApplicationContextInitializer-CSDN博客
(2)构造函数的依赖
@Component
public class A {
public A(B b) {
System.out.println("A initialized");
}
}
@Component
public class B {
public B() {
System.out.println("B initialized");
}
}
输出:
B initialized
A initialized
3、显示顺序调整问题
(1)@DependsOn
显示指明顺序
@Component
@DependsOn("c")
public class D {
public D() {
System.out.println("D initialized");
}
}
@Component
public class C {
public C() {
System.out.println("C initialized");
}
}
输出:
C initialized
D initialized
(2)@Order影响 List
或 Set
类型的 Bean
如果多个 @Component
实现了同一个接口,并且注入为 List
或 Set
类型,Spring 会根据 @Order
注解的值来决定顺序:
@Component
@Order(2)
public class First implements MyInterface { }
@Component
@Order(1)
public class Second implements MyInterface { }
当它们被注入 List<MyInterface>
时,顺序会是 Second
→ First
。
但**@Order
只影响 List
/Set
注入的顺序,不影响 Bean 的初始化顺序**。
(3)@Primary
和 @Qualifier
影响注入选择
如果多个 @Component
适用于相同类型的注入:
-
@Primary
标注的 Bean 会优先被注入。 -
@Qualifier
可以精确指定要注入哪个 Bean,但不影响初始化顺序。
(4)懒加载 @Lazy
可能会延迟初始化
如果 @Component
标注了 @Lazy
,它的 Bean 只有在被使用时才会初始化,而不是在 Spring 容器启动时初始化:如果该 Bean 没有被使用,它就不会初始化。
@Component
@Lazy
public class LazyBean {
public LazyBean() {
System.out.println("LazyBean initialized");
}
}
二、加载原理对比
1、@configuration和@component
使用@component+@Bean与@configuration+@Bean,加载的区别很大
(1)@component+@Bean
不会被Spring代理,会直接获取一个全新的Bean对象实例所以全局会有多个Bean对象的实例;而
(2)@configuration注解的@Bean
全局只有一个Bean对象。
Spring 容器在启动时,会加载默认的一些PostProcessor,其中就有ConfigurationClassPostProcessor,这个后置处理程序专门处理带有@Configuration注解的类,这个程序会在bean 定义加载完成后,在bean初始化前进行处理。主要处理的过程就是使用cglib动态代理增强类,而且是对其中带有@Bean注解的方法进行处理。
Spring发现方法所请求的Bean已经在容器中,那么就直接返回容器中的Bean。所以全局只有一个SimpleBean对象的实例。
(3)demo
package com.bit.demo.test.bean;
import com.bit.demo.dto.UserDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class CompBean {
@Bean
public UserDTO initUser() {
log.info("CompBean获取user");
UserDTO user = new UserDTO();
user.setUserName("zs");
return user;
}
}
package com.bit.demo.test.bean;
import com.bit.demo.dto.RoleDTO;
import com.bit.demo.dto.UserDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
@Configuration
@Slf4j
public class ConfigBean {
@Bean
public RoleDTO initRole() {
log.info("ConfigBean 获取user");
RoleDTO user = new RoleDTO();
user.setRoleName("school");
return user;
}
}
单测
@Autowired
private CompBean compBean;
@Autowired
private ConfigBean configBean;
@Test
public void testBean(){
compBean.initUser();
configBean.initRole();
}
执行输出
2025-03-28T16:43:08.200+08:00 INFO 21600 --- [ main] com.bit.demo.test.bean.CompBean : CompBean获取user
2025-03-28T16:43:08.201+08:00 INFO 21600 --- [ main] com.bit.demo.test.bean.ConfigBean : ConfigBean 获取user
2025-03-28T16:43:08.587+08:00 INFO 21600 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 52533 (http) with context path '/bitDemo'
2025-03-28T16:43:08.594+08:00 INFO 21600 --- [ main] com.bit.demo.UserSysFlagTest : Started UserSysFlagTest in 2.366 seconds (process running for 2.916)
Java HotSpot(TM) 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
2025-03-28T16:43:09.447+08:00 INFO 21600 --- [ main] com.bit.demo.test.bean.CompBean : CompBean获取user
可见,@Configuration+@Bean只执行一次。