Spring中的IOC详解
文章目录
-
- IOC
-
- IOC容器的工作原理
- Bean的生命周期
- Bean的自动装配
-
- @Autowired
- @Resource
- @Inject
- 使用Spring底层组件
IOC
Spring的核心之一是IOC,IOC全称为Inversion of Control
,中文译为控制反转,是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。IOC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection
,依赖注入)来实现的。
所谓IOC,对于Spring框架来说,就是由Spring来负责对象的创建、配置和管理,所以可将IOC理解为一个大容器。IOC通过将对象创建和管理的控制权从应用代码转移到Spring容器中,实现了松耦合设计。IOC使用依赖注入(DI)来管理组成一个应用程序的组件,这些对象被称为Spring Beans。
管理Bean的创建、配置和生命周期,Spring提供了两个主要的IOC容器:BeanFactory
和ApplicationContext
。IOC容器管理的对象,通常使用注解,如@Component
、@Service
、@Autowired
,或XML配置声明Bean。IOC容器的工作流程:
- 读取配置,通过XML文件、注解或Java配置类读取Bean定义和依赖关系。
- 创建和配置Bean,容器根据配置实例化Bean,并注入它们的依赖。
- 管理Bean生命周期,容器负责调用Bean的初始化和销毁方法,管理其整个生命周期。
通过Spring的IOC容器,开发者可以更加专注于业务逻辑,而无需关心对象的创建和管理,从而提高了代码的可维护性和可扩展性。
IOC容器的工作原理
IOC容器是Spring框架的核心,它负责管理应用程序中对象的生命周期和依赖关系。
-
想要管理Bean,首先需要将Bean加载进来。IOC容器首先需要加载应用程序的配置元数据,这些配置可以通过XML文件、Java注解或者Java配置类等方式定义。加载完配置之后,容器会使用相应的解析器(如Dom4j解析XML配置文件),将配置信息转换为容器可以理解的数据结构,通常是
BeanDefinition
对象。BeanDefinition
包含了类的名称、依赖关系、初始化方法、销毁方法等元数据信息。<!-- applicationContext.xml --> <bean id="userRepository" class="com.example.UserRepository"><!-- 定义UserRepository的依赖或配置 --> </bean><bean id="userService" class="com.example.UserService"><property name="userRepository" ref="userRepository" /> </bean>// 加载和解析XML配置 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
-
一旦容器解析了Bean的配置信息,它会根据这些信息使用Java的反射机制来创建Bean的实例。通常情况下,Spring会调用Bean的默认构造方法来实例化对象。
// 获取UserService Bean UserService userService = (UserService) context.getBean("userService");
-
对象实例化完成,容器会根据配置文件或者注解中定义的依赖关系,将其他Bean的实例或者值注入到当前Bean中。依赖注入可以通过构造函数注入、Setter方法注入或者字段注入来完成。
public class UserService {private UserRepository userRepository;// Setter方法注入public void setUserRepository(UserRepository userRepository) {this.userRepository = userRepository;}// 其他方法 }
-
在依赖注入完成后,如果配置了初始化方法,例如使用
init-method
指定的方法、实现InitializingBean
接口的方法或者使用@PostConstruct
注解标记的方法),容器会调用这些方法来执行一些初始化的操作,例如加载资源、建立连接等。<!-- applicationContext.xml --> <bean id="userRepository" class="com.example.UserRepository" init-method="init" destroy-method="destroy"><!-- 定义UserRepository的依赖或配置 --> </bean>// UserRepository.java public class UserRepository {// 初始化方法public void init() {System.out.println("UserRepository 初始化方法被调用");}// 销毁方法public void destroy() {System.out.println("UserRepository 销毁方法被调用");} }
Bean的生命周期
Spring中的Bean是指由Spring容器管理的对象实例。在Spring框架中,Bean是应用程序的核心组件,它们由Spring容器创建、组装和管理,以帮助开发者实现松耦合、可测试和可维护的代码。
Spring Bean的生命周期包含从创建到销毁的一系列过程。即Bean的 实例化->初始化->使用->销毁
的过程。Spring中的Bean可以根据其作用域的不同可分为,单例Bean、原型Bean,不同作用域的Bean生命周期也不同。
特征
单例Bean
原型Bean
创建
容器启动时创建一次。
每次请求时创建新实例。
作用域管理
由Spring容器管理。
每次请求时由Spring容器管理新实例。
线程安全性
单例Bean在多线程环境下共享。
原型Bean本身不具备线程安全性。
适用性
适用于无状态Bean、缓存对象、共享资源等。
Spring中的默认作用域。
适用于有状态Bean、需要频繁重新初始化的对象等。
在每次请求时需要新实例。
销毁管理
由Spring容器自动管理。
- @PreDestroy 方法(如果存在)。
- DisposableBean.destroy() 方法(如果实现)。
- 自定义销毁方法(如果在Bean定义中指定)。
没有自动的Spring管理销毁过程。
- 需要由客户端手动管理销毁。
- 可以通过实现DisposableBean接口或自定义方法手动释放资源。
单实例Bean生命周期:
- 实例化:在容器启动时创建该Bean的唯一实例。
- 初始化:
- 初始化前置处理:调用所有注册的
BeanPostProcessor
的postProcessBeforeInitialization
方法,可以在初始化之前对Bean进行修改。 - 初始化:按照顺序执行以下方法,如果Bean实现了
InitializingBean
接口,则调用其afterPropertiesSet
方法;如果在Bean定义中指定了init-method
,则调用这个方法;如果Bean中有用@PostConstruct
注解标记的方法,则调用该方法。 - 初始化后处理:调用所有注册的
BeanPostProcessor
的postProcessAfterInitialization
方法,可以在初始化之后对Bean进行修改。
- 初始化前置处理:调用所有注册的
- 使用:当Bean初始化之后,Bean处于就绪状态,可以被应用程序中的其他组件使用。
- 销毁:
- 销毁前处理:在销毁之前,Spring容器会依次调用注册的所有
BeanPostProcessor
的postProcessBeforeDestruction
方法。如果Bean类中有用@PreDestroy
注解标记的方法,Spring容器会在销毁之前调用该方法。 - 销毁:如果在Bean的定义中通过配置
destroy-method
属性指定了销毁方法,Spring容器会调用这个方法来执行特定的清理操作。
- 销毁前处理:在销毁之前,Spring容器会依次调用注册的所有
单例Bean和多实例Bean的生命周期主要区别在于实例化和销毁的管理方式,单例Bean在容器启动时创建一个实例,并由容器负责管理其生命周期的完整过程。而多实例Bean在每次请求时创建新的实例,并且销毁过程需要开发者手动管理。
@Configuration
public class AppConfig {@Bean(initMethod = "init", destroyMethod = "destroy")@Scope("singleton")public SingletonBean singletonBean() {return new SingletonBean();}@Bean(initMethod = "init", destroyMethod = "destroy")@Scope("prototype")public PrototypeBean prototypeBean() {return new PrototypeBean();}public static class SingletonBean implements InitializingBean, DisposableBean {public SingletonBean() {System.out.println("SingletonBean 实例化");}@PostConstructpublic void postConstruct() {System.out.println("SingletonBean @PostConstruct 方法调用");}@Overridepublic void afterPropertiesSet() {System.out.println("SingletonBean afterPropertiesSet 方法调用");}public void init() {System.out.println("SingletonBean 自定义初始化方法调用");}@PreDestroypublic void preDestroy() {System.out.println("SingletonBean @PreDestroy 方法调用");}@Overridepublic void destroy() {System.out.println("SingletonBean destroy 方法调用");}public void customDestroy() {System.out.println("SingletonBean 自定义销毁方法调用");}}public static class PrototypeBean implements InitializingBean, DisposableBean {public PrototypeBean() {System.out.println("PrototypeBean 实例化");}@PostConstructpublic void postConstruct() {System.out.println("PrototypeBean @PostConstruct 方法调用");}@Overridepublic void afterPropertiesSet() {System.out.println("PrototypeBean afterPropertiesSet 方法调用");}public void init() {System.out.println("PrototypeBean 自定义初始化方法调用");}@PreDestroypublic void preDestroy() {System.out.println("PrototypeBean @PreDestroy 方法调用");}@Overridepublic void destroy() {System.out.println("PrototypeBean destroy 方法调用");}public void customDestroy() {System.out.println("PrototypeBean 自定义销毁方法调用");}}public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);SingletonBean singletonBean1 = context.getBean(SingletonBean.class);SingletonBean singletonBean2 = context.getBean(SingletonBean.class);System.out.println("singletonBean1 == singletonBean2 : " + (singletonBean1 == singletonBean2));PrototypeBean prototypeBean1 = context.getBean(PrototypeBean.class);PrototypeBean prototypeBean2 = context.getBean(PrototypeBean.class);System.out.println("prototypeBean1 == prototypeBean2 : " + (prototypeBean1 == prototypeBean2));context.close();// 手动销毁 Prototype BeanprototypeBean1.destroy();prototypeBean2.destroy();}
}
举个例子,来更好的理解Bean的生命周期:
-
首先,在Spring的配置文件(如XML配置)或者使用注解方式,我们定义
UserService
类作为一个Bean,并配置它的初始化方法、销毁方法以及其他属性。// UserService.java public class UserService implements InitializingBean, DisposableBean, BeanNameAware {private String message;// 初始化方法public void init() {System.out.println("UserService 初始化方法被调用");}// 销毁方法public void destroy() {System.out.println("UserService 销毁方法被调用");}// Setter 方法public void setMessage(String message) {this.message = message;}// Getter 方法public String getMessage() {return message;}// 实现 InitializingBean 接口的方法@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("UserService InitializingBean 的 afterPropertiesSet 方法被调用");}// 实现 DisposableBean 接口的方法@Overridepublic void destroy() throws Exception {System.out.println("UserService DisposableBean 的 destroy 方法被调用");}// 实现 BeanNameAware 接口的方法@Overridepublic void setBeanName(String name) {System.out.println("UserService BeanNameAware 的 setBeanName 方法被调用,Bean的名称为:" + name);} }
-
在Spring的配置文件中,我们将UserService类定义为一个Bean,并配置初始化方法、销毁方法以及其他属性。
<!-- applicationContext.xml --> <bean id="userService" class="com.example.UserService" init-method="init" destroy-method="destroy"><property name="message" value="Hello, Spring!" /> </bean>
-
当应用程序启动并且Spring容器加载配置时,将会执行以下步骤来管理
UserService
Bean的生命周期:- 实例化:Spring容器根据配置文件或者注解,实例化
UserService
类的一个对象实例。 - 依赖注入:将配置的属性(如
message
)注入到UserService
实例中。 - 初始化:调用
init-method
指定的初始化方法或者InitializingBean
接口的afterPropertiesSet()
方法,例如执行init()
方法。在初始化过程中,还可以调用BeanNameAware
接口的方法,获取和设置Bean的名称。 - 使用:
UserService
Bean可以被应用程序的其他组件使用,执行其业务逻辑,如打印消息。 - 销毁:当应用程序关闭时,Spring容器会调用
destroy-method
指定的销毁方法或者DisposableBean
接口的destroy()
方法,例如执行destroy()
方法。
- 实例化:Spring容器根据配置文件或者注解,实例化
Bean的自动装配
Bean的自动装配是Spring框架提供的一种便捷的方式,用于自动解析和设置Bean之间的依赖关系,而无需显式配置每一个依赖关系的方式。Spring支持以下几种自动装配的方式:
-
根据类型自动装配:Spring会自动将一个属性与同一上下文中具有兼容类型的Bean进行匹配。如果容器中存在多个符合类型的Bean,则会抛出异常。
public interface UserRepository {// 接口定义 }@Component public class UserRepositoryImpl1 implements UserRepository {// 实现1 }@Component public class UserRepositoryImpl2 implements UserRepository {// 实现2 }// 示例:根据类型自动装配 @Autowired private UserRepository userRepository;
-
根据名称自动装配:Spring会自动将一个属性与容器中相同名称的Bean进行匹配,要求Bean的名称必须与属性名称完全一致。
public interface UserRepository {// 接口定义 }@Component("userRepository1") public class UserRepositoryImpl1 implements UserRepository {// 实现1 }@Component("userRepository2") public class UserRepositoryImpl2 implements UserRepository {// 实现2 }// 示例:根据名称自动装配 @Autowired private UserRepository userRepository;
-
构造函数自动装配:Spring会自动通过构造函数来注入依赖,从而避免了使用
@Autowired
注解的繁琐。Spring会查找与构造函数参数类型相匹配的Bean,并自动进行注入。// 示例:构造函数自动装配 @Autowired public UserService(UserRepository userRepository) {this.userRepository = userRepository; }
-
自动装配标识符:可以使用
@Autowired
注解结合@Qualifier
注解来指定具体的Bean名称,来解决多个相同类型Bean的自动装配歧义问题。// 示例:结合@Qualifier注解指定Bean名称 @Autowired @Qualifier("userRepository") private UserRepository userRepository;
-
自动装配和主候选Bean:可以使用
@Primary
注解来标识一个主要的Bean候选者,当存在多个匹配的Bean时,Spring会优先选择标有@Primary
注解的Bean进行注入。// 示例:使用@Primary注解标识主候选Bean @Component @Primary public class PrimaryUserRepository implements UserRepository {// 实现代码 }
在Spring中用于实现自动装配的注解有三个,它们都能自动注入依赖,但在一些细节上有所区别。
自动装配
来源
装配方式
支持 @Primary
支持的属性
@Autowired
Spring 框架原生
根据类型装配
是
required
(boolean),指定是否必须注入,默认为true
。
@Resource
JSR-250 (Java EE 标准)
根据名称装配,按名称找不到时根据类型
否
name
(String),指定要装配的 Bean 名称,默认为属性名称。
@Inject
JSR-330 (Java EE 标准)
根据类型装配
是
无
在日常开发中,都是使用SpringBoot进行开发,一般使用@Autowired
注解就够了,适合大多数Spring应用场景。
@Autowired
@Autowired
是Spring框架中用于自动装配Bean的主要方式之一。它可以根据类型来自动注入依赖关系。
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {/*** Declares whether the annotated dependency is required.* <p>Defaults to {@code true}.*/boolean required() default true;}
在使用@Autowired
时,Spring会尝试将一个属性与容器中具有兼容类型的Bean进行匹配。
@Component
public class UserService {private UserRepository userRepository;@Autowiredpublic void setUserRepository(UserRepository userRepository) {this.userRepository = userRepository;}
}
如果存在多个同类型的Bean,可以结合@Primary
注解,指定优先级最高的Bean进行注入。
@Component
public class UserRepositoryImpl1 implements UserRepository {// implementation
}@Component
@Primary
public class UserRepositoryImpl2 implements UserRepository {// implementation
}@Component
public class UserService {private UserRepository userRepository;@Autowiredpublic void setUserRepository(UserRepository userRepository) {this.userRepository = userRepository;}
}
除了使用@Primary
还可以使用@Qualifier
注解来指定具体的Bean名称,来解决多个相同类型Bean的自动装配歧义问题。
@Component("userRepository1")
public class UserRepositoryImpl1 implements UserRepository {// 实现1
}@Component("userRepository2")
public class UserRepositoryImpl2 implements UserRepository {// 实现2
}// 示例:结合@Qualifier注解指定Bean名称
@Autowired
@Qualifier("userRepository2")
private UserRepository userRepository;
@Autowired
可以使用required
属性控制是否要求依赖关系存在,默认为true
,表示必须存在兼容的Bean,设为false
可以允许null
值注入。
@Component
public class UserService {private UserRepository userRepository;@Autowired(required = false)public void setUserRepository(UserRepository userRepository) {this.userRepository = userRepository;}
}
@Autowired
可以放在构造器、参数、方法、属性上。
-
构造器注入:可以在构造器上使用
@Autowired
来完成构造器注入,Spring会自动根据类型进行注入。@Component public class UserService {private final UserRepository userRepository;@Autowiredpublic UserService(UserRepository userRepository) {this.userRepository = userRepository;} }
-
属性注入:可以直接在属性上使用
@Autowired
注解来进行依赖注入。@Component public class UserService {@Autowiredprivate UserRepository userRepository; }
-
方法注入:可以在方法上使用
@Autowired
注解,Spring会在初始化Bean时调用这些方法完成依赖注入。@Component public class UserService {private UserRepository userRepository;@Autowiredpublic void setUserRepository(UserRepository userRepository) {this.userRepository = userRepository;} }
-
参数注入:可以在方法参数上使用
@Autowired
注解,Spring会根据参数类型自动注入对应的Bean。@Component public class UserService {private UserRepository userRepository;@Autowiredpublic void setUserRepository(UserRepository userRepository) {this.userRepository = userRepository;}public void processUserData(@Autowired User user) {} }
@Autowired
的实现原理,是通过@Autowired
后置处理器实现的。在@Autowired
注解文档注释上面,可以看到与之息息相关的一个类AutowiredAnnotationBeanPostProcessor
,即@Autowired
后置处理器。看到该类实现了MergedBeanDefinitionPostProcessor
接口,在postProcessMergedBeanDefinition
方法上打一个断点,就可以看到@Autowired
的调用栈。
/** @see AutowiredAnnotationBeanPostProcessor*/
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired{}
@Autowired
注解调用栈:
AbstractApplicationContext.refresh(容器初始化)---> registerBeanPostProcessors (注册AutowiredAnnotationBeanPostProcessor) ---> finishBeanFactoryInitialization---> AbstractAutowireCapableBeanFactory.doCreateBean---> AbstractAutowireCapableBeanFactory.applyMergedBeanDefinitionPostProcessors---> MergedBeanDefinitionPostProcessor.postProcessMergedBeanDefinition---> AutowiredAnnotationBeanPostProcessor.findAutowiringMetadata
核心调用:
postProcessMergedBeanDefinition--->findAutowiringMetadata--->buildAutowiringMetadata@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {// 调用 findAutowiringMetadataInjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);metadata.checkConfigMembers(beanDefinition);
}private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {// Fall back to class name as cache key, for backwards compatibility with custom callers.String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());// Quick check on the concurrent map first, with minimal locking.InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);if (InjectionMetadata.needsRefresh(metadata, clazz)) {synchronized (this.injectionMetadataCache) {metadata = this.injectionMetadataCache.get(cacheKey);if (InjectionMetadata.needsRefresh(metadata, clazz)) {if (metadata != null) {metadata.clear(pvs);}// 调用buildAutowiringMetadatametadata = buildAutowiringMetadata(clazz);this.injectionMetadataCache.put(cacheKey, metadata);}}}return metadata;
}private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {LinkedList<InjectionMetadata.InjectedElement> elements = new LinkedList<>();Class<?> targetClass = clazz;//需要处理的目标类do {final LinkedList<InjectionMetadata.InjectedElement> currElements = new LinkedList<>();// 通过反射获取该类所有的字段,并遍历每一个字段,并通过方法findAutowiredAnnotation遍历每一个字段的所用注解,// 如果用autowired修饰了,则返回auotowired相关属性ReflectionUtils.doWithLocalFields(targetClass, field -> {AnnotationAttributes ann = findAutowiredAnnotation(field);if (ann != null) {//校验autowired注解是否用在了static方法上if (Modifier.isStatic(field.getModifiers())) {if (logger.isWarnEnabled()) {logger.warn("Autowired annotation is not supported on static fields: " + field);}return;}//判断是否指定了requiredboolean required = determineRequiredStatus(ann);currElements.add(new AutowiredFieldElement(field, required));}});// 和上面一样的逻辑,但是是通过反射处理类的methodReflectionUtils.doWithLocalMethods(targetClass, method -> {Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {return;}AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod);if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {if (Modifier.isStatic(method.getModifiers())) {if (logger.isWarnEnabled()) {logger.warn("Autowired annotation is not supported on static methods: " + method);}return;}if (method.getParameterCount() == 0) {if (logger.isWarnEnabled()) {logger.warn("Autowired annotation should only be used on methods with parameters: " +method);}}boolean required = determineRequiredStatus(ann);PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);currElements.add(new AutowiredMethodElement(method, required, pd));}});// 用@Autowired修饰的注解可能不止一个,因此都加在currElements这个容器里面,一起处理 elements.addAll(0, currElements);targetClass = targetClass.getSuperclass();}while (targetClass != null && targetClass != Object.class);return new InjectionMetadata(clazz, elements);
}
通过上面的源码,可以看到Spring在运行时通过反射查找@Autowired
注解,并自动注入相关字段。Spring框架利用反射遍历目标类及其超类的所有字段和方法,查找并收集所有使用了@Autowired
注解的元素。对于每个字段和方法,首先通过反射获取注解信息,如果字段或方法被@Autowired
注解修饰且符合条件(如非静态),则将其封装成对应的注入元素(AutowiredFieldElement
或AutowiredMethodElement
)并添加到当前元素列表中。最后,这些注入元素会被封装到InjectionMetadata
对象中,并用于实际的依赖注入过程,从而实现Spring的自动注入功能。
@Resource
@Resource
注解来自JSR-250
,JDK自带,主要用于通过名称注入依赖。它的行为类似于@Autowired
,但它更倾向于按名称进行注入。默认情况下,@Resource
注解按名称进行注入。如果找不到同名的Bean,再按类型进行匹配。它不支持@Primary
,如果存在多个同类型的Bean且未指定name
属性,会抛出异常。
@Component
public class UserService {@Resource(name = "userRepositoryImpl1")private UserRepository userRepository;
}
假设我们有一个旧项目,其中大量使用了JDK标准的@Resource
注解进行依赖注入,而我们现在想要将项目迁移到Spring,同时保持现有的依赖注入逻辑不变。在这种情况下,我们可以继续使用@Resource
注解进行依赖注入。
@Inject
@Inject
注解来自JSR-330
,需要导入javax.inject
包。它的行为与@Autowired
类似,但没有任何属性。
<dependency><groupId>javax.inject</groupId><artifactId>javax.inject</artifactId><version>1</version>
</dependency>
@Inject
注解按类型进行注入,可以结合@Primary
注解,指定优先级最高的Bean进行注入。
@Component
public class UserService {@Inject@Named("userRepositoryImpl1")private UserRepository userRepository;
}// Define multiple implementations
@Component
@Named("userRepositoryImpl1")
public class UserRepositoryImpl1 implements UserRepository {// implementation details
}@Component
@Named("userRepositoryImpl2")
public class UserRepositoryImpl2 implements UserRepository {// implementation details
}
也可以结合@Named
注解,显式指定要注入的Bean名称,解决多个同类型Bean的注入问题。
@Component("userRepository1")
public class UserRepositoryImpl1 implements UserRepository {// 实现1
}@Primary
@Component("userRepository2")
public class UserRepositoryImpl2 implements UserRepository {// 实现2
}@Component
public class UserService {@Inject@Named("userRepository1")private UserRepository userRepository;
}
假设我们有一个项目,需要在不同的环境中运行。在本地开发时,我们使用Spring,但在生产环境中,我们使用 Java EE 容器,这些容器使用 CDI(Contexts and Dependency Injection)作为依赖注入框架。为了在不同的环境中都能够使用相同的代码进行依赖注入,我们可以使用JSR-330
标准的@Inject
注解。这种方式使得代码能够在Spring和Java EE环境中都能正常运行。
CDI(Contexts and Dependency Injection,上下文与依赖注入)是 Java EE 标准的一部分,定义了一种类型安全的依赖注入机制,主要用于管理 Java EE 应用程序中的生命周期和依赖关系。CDI 提供了一种统一的、标准的依赖注入方式,使得开发者可以更容易地管理对象的创建、销毁以及对象之间的依赖关系。
使用Spring底层组件
为了在Spring框架的基础上实现更加细粒度的控制或定制化需求,可以使用Spring底层组件。
Aware
接口是一组特定于Spring容器的接口,允许beans感知和与Spring容器进行交互。通过实现Aware
接口的子接口,来使用Spring的底层的组件。Aware
接口类似于回调方法的形式在Spring加载的时候将我们自定以的组件加载。
/*** A marker superinterface indicating that a bean is eligible to be notified by the* Spring container of a particular framework object through a callback-style method.* The actual method signature is determined by individual subinterfaces but should* typically consist of just one void-returning method that accepts a single argument.*/
public interface Aware {}
常用的Aware
接口:
-
ApplicationContextAware
,允许Bean访问ApplicationContext
,从而可以访问容器中的其他Bean或执行更高级的容器操作。@Component public class MyBean implements ApplicationContextAware {private ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) {this.applicationContext = applicationContext;}public void someMethod() {// 使用 ApplicationContext 获取其他 BeanAnotherBean anotherBean = applicationContext.getBean(AnotherBean.class);// 执行更高级的容器操作,如发布事件等applicationContext.publishEvent(new CustomEvent(this, "Some message"));} }
-
BeanFactoryAware
允许Bean访问配置它的Bean工厂。@Component public class MyBean implements BeanFactoryAware {private BeanFactory beanFactory;@Overridepublic void setBeanFactory(BeanFactory beanFactory) {this.beanFactory = beanFactory;}public void someMethod() {// 使用 BeanFactory 获取其他 BeanAnotherBean anotherBean = beanFactory.getBean(AnotherBean.class);// 可以进一步操作 BeanFactory,如获取 Bean 的定义信息等String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();} }
BeanPostProcessor
也是常用的底层组件。它是Bean
的后置处理器,在初始化前后进行处理工作。需要在Bean实例化后和初始化前后执行自定义的处理逻辑,如AOP切面的实现、自定义注解处理等。调用顺序为:
创建对象 --> postProcessBeforeInitialization --> 初始化 --> postProcessAfterInitialization --> 销毁public class MainTest {public static void main(String[] args) {// 获取Spring IOC容器AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(DemoConfiguration.class);System.out.println("容器初始化完成...");annotationConfigApplicationContext.close();System.out.println("容器销毁了...");}
}@Configuration
class DemoConfiguration implements BeanPostProcessor {@Bean(initMethod = "init", destroyMethod = "destroy")public DemoEntity getDemoEntity(){return new DemoEntity();}@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {System.out.println("调用了 postProcessBeforeInitialization");return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {System.out.println("调用了 postProcessAfterInitialization");return bean;}
}@Component
class DemoEntity {public DemoEntity(){System.out.println("调用了构造器...");}public void destroy(){System.out.println("调用了销毁方法...");}public void init() {System.out.println("调用了初始化方法...");}
}
通过打断点可以看到,在创建Bean的时候会调用AbstractAutowireCapableBeanFactory
类的doCreateBean
方法,这也是创建Bean的核心方法。
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {// 创建 Bean 实例Object beanInstance = createBeanInstance(mbd, beanName, args);// 提前暴露已经创建的 Bean 实例,用于解决循环依赖问题Object exposedObject = beanInstance;try {// 给 Bean 实例应用属性填充,包括依赖注入populateBean(beanName, mbd, instanceWrapper);// 初始化 Bean,执行各种初始化方法exposedObject = initializeBean(beanName, exposedObject, mbd);}catch (Exception ex) {throw new BeanCreationException(beanName, "Initialization of bean failed", ex);}// 注册销毁回调,用于在 Bean 销毁时执行清理操作registerDisposableBeanIfNecessary(beanName, exposedObject, mbd);return exposedObject;
}
doCreateBean
方法中核心方法为populateBean
方法,其调用栈大致如下:
populateBean(){applyBeanPostProcessorsBeforeInitialization() --> invokeInitMethods()--> applyBeanPostProcessorsAfterInitialization()
}
在初始化之前调用populateBean()
方法给Bean进行属性赋值,之后再调用applyBeanPostProcessorsBeforeInitialization
方法。
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)throws BeansException {Object result = existingBean;for (BeanPostProcessor processor : getBeanPostProcessors()) {Object current = processor.postProcessBeforeInitialization(result, beanName);if (current == null) {return result;}result = current;}return result;
}
该方法作用是,遍历容器中所有的BeanPostProcessor
挨个执行postProcessBeforeInitialization
方法,一旦返回null
,将不会执行后面Bean的postProcessBeforeInitialization
方法。之后在调用invokeInitMethods
方法,进行Bean的初始化,最后在执行applyBeanPostProcessorsAfterInitialization
方法,执行一些初始化之后的工作。
相关文章:
Spring中的IOC详解
文章目录 IOC IOC容器的工作原理Bean的生命周期Bean的自动装配 AutowiredResourceInject 使用Spring底层组件 IOC Spring的核心之一是IOC,IOC全称为Inversion of Control,中文译为控制反转,是面向对象编程中的一种设计原则,可…...
深挖vue3基本原理之七 —— 功能模块的深度技术解析
Vue 3 四个核心功能模块的深度技术解析 一、Effect 调度系统:同步/异步任务队列 实现原理 // runtime-core/src/scheduler.ts const queue: (EffectJob | null)[] [] let isFlushing false const resolvedPromise Promise.resolve()function queueJob(job: Ef…...
数据结构 day 07
数据结构 day07 7. 树7.3. 层次遍历代码实现 8. 查询算法8.1. 顺序查找 seqSearch代码实现 8.2. 二分法查找 binarySearch代码实现 8.2. 分块查找 blockSearch代码实现 8.3. 哈希表 hash 9. 排序算法9.1. 冒泡排序 bubSort代码实现 9.2. 选择排序 selSort代码实现 9.3. 插入排序…...
《代码随想录》刷题笔记——回溯篇【java实现】
文章目录 组合组合总和 III电话号码的字母组合组合总和组合总和II思路代码实现 分割回文串※思路字符串分割回文串判断效率优化※ 复原 IP 地址优化版本 子集子集 II使用usedArr辅助去重不使用usedArr辅助去重 递增子序列※全排列全排列 II重新安排行程题意代码 N 皇后解数独直…...
React:初识React
React是什么? React是由Meta公司研发,也就是Facebook的公司(马克扎克伯格这个见人)研发的构建Web和原生交互界面的库 不仅可以写网页,还可以写苹果和安卓上面的app React的优势: React也是前端里最流行的…...
全面理解-c++中的内存布局
在 C 中,程序的内存布局指的是程序运行时,代码和数据在内存中的组织和分布方式。一般来说,C 程序的内存可以划分为以下几个主要区域: 1. 代码段(Text Segment,也称为 .text 段) 存储内容&…...
百度沈抖:传统云计算不再是主角,智能计算呼唤新一代“操作系统”
Create 2024 百度AI开发者大会 4月16日,Create 2024 百度AI开发者大会在深圳召开。期间,百度集团执行副总裁、百度智能云事业群总裁沈抖正式发布新一代智能计算操作系统——万源,通过对AI原生时代的智能计算平台进行抽象与封装设计ÿ…...
【银河麒麟高级服务器操作系统】服务器卡死后恢复系统日志丢失-分析及处理全过程
了解更多银河麒麟操作系统全新产品,请点击访问 麒麟软件产品专区:https://product.kylinos.cn 开发者专区:https://developer.kylinos.cn 文档中心:https://document.kylinos.cn 服务器环境以及配置 【机型】 处理器ÿ…...
VSCode Error Lens插件介绍(代码静态检查与提示工具)(vscode插件)
文章目录 VSCode Error Lens 插件介绍**功能概述****开发背景****使用方法****适用场景** VSCode Error Lens 插件介绍 功能概述 Error Lens 是一款增强 VS Code 错误提示的扩展工具,通过 内联显示错误和警告信息,直接定位代码问题,提升开发…...
ffmpeg configure 研究1-命令行参数的分析
author: hjjdebug date: 2025年 02月 14日 星期五 17:16:12 CST description: ffmpeg configure 研究1 ./configure 命令行参数的分析 文章目录 1 configure 对命令行参数的分析,在4019行1.1 函数名称: is_in1.2. 函数名称: enable1.3. 函数名称: set_all 2 执行退出判断的关键…...
如何调整 Nginx工作进程数以提升性能
🏡作者主页:点击! Nginx-从零开始的服务器之旅专栏:点击! 🐧Linux高级管理防护和群集专栏:点击! ⏰️创作时间:2025年2月15日14点20分 Nginx 的工作进程数࿰…...
分布式 NewSQL 数据库(TiDB)
TiDB 是一个分布式 NewSQL 数据库。它支持水平弹性扩展、ACID 事务、标准 SQL、MySQL 语法和 MySQL 协议,具有数据强一致的高可用特性,是一个不仅适合 OLTP 场景还适合 OLAP 场景的混合数据库。 TiDB是 PingCAP公司自主设计、研发的开源分布式关系型数据…...
try learning-git-branching
文章目录 mergerebase分离 HEAD相对引用利用父节点branch -f 撤销变更cherry-pick交互式 rebase只取一个提交记录提交的技巧rebase 在上一次提交上amendcherry-pick 在上一次提交上 amend tag多分支 rebase两个parent节点纠缠不清的分支偏离的提交历史锁定的Main推送主分支合并…...
【kafka系列】Kafka事务的实现原理
目录 1. 事务核心组件 1.1 幂等性生产者(Idempotent Producer) 1.2 事务协调器(TransactionCoordinator) 1.3 事务日志(Transaction Log) 2. 事务执行流程 2.1 事务初始化 2.2 发送消息 2.3 事务提…...
数据结构6
一、哈希散列--通讯录查找 #include "hash.h" #include <stdio.h> #include <stdlib.h> #include <string.h>//int *a[10];int hash_function(char key) {if (key > a && key < z){return key - a;}else if (key > A && …...
Flutter 的 Widget Key 提议大调整?深入聊一聊 Key 的作用
Flutter 的 Widget Key 提议大调整?深入聊一聊 Key 的作用 在 Flutter 里,Key 对象存在的目的主要是区分和维持 Widget 的状态,它是控件在渲染树里的「复用」标识之一,这一点在之前的《深入 Flutter 和 Compose 在 UI 渲染刷新时…...
src和href区别
src和href区别 (1)请求资源类型不同(2)作用结果不同(3)解析方式不同 (1)请求资源类型不同 href 用来建立文档和元素之间的链接(是引用),常用的有a、linksrc 在请求src资源时候会将指向的资源下载并且应用到文档中(引入),常用的有script、iframe、image。 (2)作用结果不同 hr…...
STM32之SG90舵机控制
目录 前言: 一、硬件准备与接线 1.1 硬件清单 1.2 接线 二、 SG90舵机简介 1.1 外观 1.2 基本参数 1.3 引脚说明 1.4 控制原理 1.5 特点 1.6 常见问题 三、 单片机简介 四、 程序设计 4.1 定时器配置 4.2 角度控制函数 4.3 主函数调用 五、 总结 …...
尚硅谷课程【笔记】——大数据之Hadoop【一】
课程视频链接:尚硅谷Hadoop3.x教程 一、大数据概论 1)大数据概念 大数据(Big Data):指无法再一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合,是需要新处理模式才能具有更强的决策力、洞察发…...
QEMU 搭建 Ubuntu x86 虚拟机
1. 安装 QEMU 在 Ubuntu 系统中,可以通过以下命令安装 QEMU: sudo apt-get update sudo apt-get install qemu-system-x86_64 qemu-kvm libvirt-daemon libvirt-clients bridge-utils virt-manager2. 创建虚拟硬盘镜像 qemu-img create -f raw ubuntu…...
mac 意外退出移动硬盘后再次插入移动硬盘不显示怎么办
第一步:sudo ps aux | grep fsck 打开mac控制台输入如下指令,我们看到会出现两个进程,看进程是root的这个 sudo ps aux|grep fsck 第二步:杀死进程 在第一步基础上我们知道不显示u盘的进程是:62319,我们…...
Acwing-基础算法课笔记之基础算法(双指针)
Acwing-基础算法课笔记之基础算法(双指针) 一、双指针算法概念二、关于双指针的一个问题三、模板 一、双指针算法概念 双指针(又称尺取法)是一个常用的优化技巧,用来解决序列的区间问题。 两个指针i,j&am…...
PCIE基础学习
PCIE PIO模式: 一个CPU传输一个32bit给PCIE(IP)。CPU直接与PCIE做数据传输。 DMA模式: CPU通过PCIE bridge 与多个PCIE设备连接,CPU发送命令给桥,桥控制PCIE与memory直接数据连接。 tlp报文 读报文 …...
架构——Nginx功能、职责、原理、配置示例、应用场景
以下是关于 Nginx 的功能、职责、原理、配置示例、应用场景及其高性能原因的详细说明: 一、Nginx 的核心功能 1. 静态资源服务 功能:直接返回静态文件(如 HTML、CSS、JS、图片、视频等)。配置示例:server {listen 80…...
【教程】比亚迪车机接入AI大模型语音助手
转载请注明出处:小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你,欢迎[点赞、收藏、关注]哦~ 更新说明: v1.1.0.2 1、新增长按音量键触发,不再需要迪加 (需设置modelisten)。 2、新增kimi、豆包、ChatGPT等多个GPT接口。 3…...
ios中常见的设计原则和设计模式
七大设计原则 1:开闭原则 对扩展开放,对修改关闭,在设计模块的时候,使模块在不被修改的前提下可以扩展功能 2:依赖倒置原则 实现尽量依赖抽象,不依赖具体实现 (1)高层模块不应该依赖底层模…...
WSL Ubuntu 安装 CUDA 教程
WSL Ubuntu 安装 CUDA 教程 1. 概述2. 准备工作3. 删除旧的 GPG 密钥4. 安装 CUDA Toolkit4.1 使用 WSL-Ubuntu 包安装(推荐) 5. 设置环境变量6. 注意事项7. 参考链接8. 总结 1. 概述 随着 WSL 2 的推出,Windows 用户现在可以在 Windows 子系…...
案例-02.部门管理-查询
一.查询部门-需求 二.查询部门-思路 API接口文档 三.代码实现 1.controller层:负责与前端进行交互,接收前端所发来的请求 注:Slf4j用于记录日志使用,可以省略private static Logger log LoggerFactory.getLogger(DeptControlle…...
【ARM】解决ArmDS Fast Models 中部分内核无法上电的问题
1、 文档目标 解决ArmDS Fast Models 中部分内核无法上电的问题。 2、 问题场景 在调用ArmDS的Fast Models中的Cortex-A55的模型,只有Core 0是上电状态,而Core 1处于掉电状态,如图2-1所示: 图2-1 3、软硬件环境 1)…...
docker 基础命令使用(ubuntu)
docker 状态查询 docker ps docker ps -adocker --version docker info docker --help docker run --help docker ps --help ...docker 操作镜像命令 docker imagesdocker rmi 镜像id/镜像名docker 操作容器命令 docker ps docker ps -adocker run 命令 # 端口映射 -p 参数…...
WEB安全--SQL注入--二次注入
一、原理: 二次注入的关键在于攻击者的输入并不立即执行,而是经过某些存储或处理后,在后续某个步骤中再触发注入攻击 二、示例: 2.1、sqli-labs-master/less-24: admin# 第一次在网页注册账号和密码时没有漏洞&#x…...
c++中什么时候应该使用final关键字?
在C中,final关键字是自C11标准引入的重要特性,主要用于类继承和虚函数重写机制的约束。下面从技术原理、使用场景和最佳实践三个维度进行系统分析,并给出工业级代码示例。 目录 一、技术原理深度解析 二、关键使用场景分析 1. 类级别的fi…...
DeepSeek学术秘籍:如何让DeepSeek辅助论证?
随着人工智能技术的飞速发展,AIGC技术在学术领域的应用逐渐引起了广泛关注。其中最近大火的DeepSeek作为一款基于大语言模型的应用,其出现标志着学术论文写作中研究方法的一次重大变革。 辅助论证 在学术论文写作中,借助DeepSeek优化辅助论证…...
Atlassian工具集:Jira与Confluence集成优势、使用技巧、更新功能等
本文由Atlassian全球白金合作伙伴-龙智翻译整理,深入探讨了Jira和Confluence最受欢迎的集成功能与技巧,期待为您新一年的团队协作开个好头。 此前,来自K15t 的Customer Advocate Matt Reiner 和Atlassian副产品经理David Olive在一场学习会议…...
传输层协议TCP ( 下 )
文章目录 前言序号与确认序号超时重传RTOJacobson算法内核中超时时间的计算 滑动窗口滑动窗口延迟应答流量控制 拥塞控制慢启动拥塞避免快重传快速恢复 保活机制参考资料 前言 TCP(Transmission Control Protocol,传输控制协议)是互联网最重要…...
【Deepseek 零门槛指南】DeepSeek 教程和常见问题解答 | 大白技术控
粉丝朋友们大家好,我是极客学长。最近一直在玩 DeepSeek,积累了一点经验,用它提高写作的效率挺好用的。 在使用DeepSeek的过程中,也遇到了如下几个问题(相信很多小伙伴也遇到了): DeepSeek 官网卡顿,突然出…...
ELK组成及实现原理
ELK是由三个主要组件组成的日志处理和搜索平台,分别是: Elasticsearch:Elasticsearch 是一个基于Lucene构建的开源搜索引擎,提供强大的搜索、分析功能。它负责存储和索引所有数据,并提供实时搜索能力。数据可以通过HTT…...
迅为RK3568开发板篇OpenHarmony实操HDF驱动配置LED-LED测试
将编译好的镜像全部进行烧写,镜像在源码根目录 out/rk3568/packages/phone/images/目录下。 烧写完成之后,在调试串口查看打印日志,如下图所示: 然后打开 hdc 工具,运行测试程序,输入“led_test 1”&…...
【C++】IO流
目录 一、C语言的输入与输出二、流是什么三、CIO流3.1 C标准IO流3.2 C文件IO流3.2.1 二进制读写3.2.2 文本读写 四、stringstream的简单介绍结尾 一、C语言的输入与输出 C语言中我们用到的最频繁的输入输出方式就是scanf ()与printf()。 scanf(): 从标准输入设备(键盘)读取数据…...
前端知识速记--css篇:CSS3中的常见动画及实现方式
前端知识速记–css篇:CSS3中的常见动画及实现方式 常见的CSS3动画 1. 过渡 (Transitions) 过渡是一种非常简单的动画效果,允许你在元素的状态变更时平滑过渡到新状态。 语法格式: transition: property duration timing-function delay;…...
一个根据输入内容过滤下拉选的组件
1.element的select自定义过滤不是很灵,使用了input和dropdown 组件 <template><div class"autocomplete-wrapper"><!-- 使用 el-input 组件 --><el-inputv-model"inputValue"input"handleInput"placeholder&q…...
Java中的分布式(概念说明)
1. 分布式的基本概念 1.1 什么是分布式系统? 分布式系统(Distributed System):由多台服务器(或节点)协同工作,对外提供一个整体服务。不同节点之间通过网络通信来协同处理请求或共享数据&…...
国产编辑器EverEdit - 上下翻滚不迷路(历史编辑位置、历史光标位置回溯功能)
1 光标位置跳转 1.1 应用场景 某些场景下,用户从当前编辑位置跳转到别的位置查阅信息,如果要快速跳转回之前编辑位置,则可以使用光标跳转相关功能。 1.2 使用方法 1.2.1 上一个编辑位置 跳转到上一个编辑位置,即文本修改过的位…...
css简介
一.css-网页的美容师 css也是一种标记语言,主要用于设置HTML页面中的文本内容(字体大小对齐方式),图片外形(宽高 边框样式 边距等)以及版面的布局和外观显示样式。 二.css语法规范 css规则由两个主要的部分构成:选择器以及一条…...
GoC题解(21) 725.画迷宫(下册第4课)
题目描述 真观察下面迷宫图。发现它是一个边长逐渐变长的15边回旋图,边长依次为10、20、30....。 参考答案 int main(){int len0;for(int i1;i<15;i){ len10;pen.fd(len).rt(90); }return 0; } 解题思路 使用一个变量来记录每次循环时应该画多长的边&#…...
DDD该怎么去落地实现(3)通用的仓库和工厂
通用的仓库和工厂 我有一个梦,就是希望DDD能够成为今后软件研发的主流,越来越多研发团队都转型DDD,采用DDD的设计思想和方法,设计开发软件系统。这个梦想在不久的将来是有可能达成的,因为DDD是软件复杂性的解决之道&a…...
sql sqlserver的特殊函数COALESCE和PIVOT的用法分析
一、COALESCE是一个返回参数中第一个非NULL值的函数, 列如:COALESCE(a,b,c,d,e);可以按照顺序取abcde,中的第一个非空数据,abcde可以是表达式 用case when 加ISNULL也可以实现,但是写法复杂了…...
FPGA简介|结构、组成和应用
Field Programmable Gate Arrays(FPGA,现场可编程逻辑门阵列),是在PAL、GAL、CPLD等可编程器件的基础上进一步发展的产物, 是作为专用集成电路(ASIC)领域中的一种半定制电路而出现的,…...
Vue2/Vue3生命周期对比
Vue2的生命周期钩子 beforeCreate 在实例初始化之后,数据观测(data)和事件配置之前调用。此时无法访问 data、methods 等。 created 在实例创建完成后调用。此时可以访问 data、methods,但 DOM 还未生成。 beforeMount 在挂载…...
Spring Boot 携手 DeepSeek:开启智能交互新时代
前言 在当今数字化浪潮汹涌澎湃的时代,人工智能技术正以前所未有的速度改变着我们的生活和工作方式。大语言模型作为人工智能领域的一颗璀璨明星,凭借其强大的自然语言处理能力,为各个行业带来了新的发展机遇。DeepSeek 作为一款性能卓越的大语言模型,以其高效、准确的文本…...