Spring IoC
对Spring IoC的理解
IoC(Inversion of Control:控制反转) 是一种设计思想,而不是一个具体的技术实现。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。不过, IoC 并非 Spring 特有,在其他语言中也有应用。
为什么叫控制反转?
- 控制 :指的是对象创建(实例化、管理)的权力
- 反转 :控制权交给外部环境(Spring 框架、IoC 容器)
将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。
在实际项目中一个 Service 类可能依赖了很多其他的类,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。
在 Spring 中, IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。
Spring 时代我们一般通过 XML 文件来配置 Bean,后来开发人员觉得 XML 文件来配置不太好,于是 SpringBoot 注解配置就慢慢开始流行起来。
相关阅读:
IoC(Inverse of Control:控制反转)是一种设计思想 或者说是某种模式。这个设计思想就是 将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。 IoC 在其他语言中也有应用,并非 Spring 特有。IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。
IoC 最常见以及最合理的实现方式叫做依赖注入(Dependency Injection,简称 DI)。
什么是 Spring Bean?
简单来说,Bean 代指的就是那些被 IoC 容器所管理的对象。
我们需要告诉 IoC 容器帮助我们管理哪些对象,这个是通过配置元数据来定义的。配置元数据可以是** XML 文件**、注解或者 Java 配置类。
<!-- Constructor-arg with 'value' attribute -->
<bean id="..." class="...">
<constructor-arg value="..."/>
</bean>
下图简单地展示了 IoC 容器如何使用配置元数据来管理对象。
org.springframework.beans和 org.springframework.context 这两个包是 IoC 实现的基础
将一个类声明为 Bean 的注解有哪些?
@Component:通用的注解,可标注任意类为Spring组件。如果一个 Bean 不知道属于哪个层,可以使用@Component注解标注。@Repository: 对应持久层即 Dao 层,主要用于数据库相关操作。@Service: 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。@Controller: 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。
@Component 和 @Bean 的区别是什么?
@Component注解作用于类,而@Bean注解作用于方法。@Component通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中(我们可以使用@ComponentScan注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean告诉了 Spring 这是某个类的实例,当我需要用它的时候还给我。@Bean注解比@Component注解的自定义性更强,而且很多地方我们只能通过@Bean注解来注册 bean。比如当我们引用第三方库中的类需要装配到Spring容器时,则只能通过@Bean来实现。
@Bean注解使用示例:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
上面的代码相当于下面的 xml 配置
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
下面这个例子是通过 @Component 无法实现的。
@Bean
public OneService getService(status) {
case (status) {
when 1:
return new serviceImpl1();
when 2:
return new serviceImpl2();
when 3:
return new serviceImpl3();
}
}
注入 Bean 的注解有哪些?
Spring 内置的 @Autowired 以及 JDK 内置的 @Resource 和 @Inject 都可以用于注入 Bean。
| Annotaion | Package | Source |
|---|---|---|
@Autowired | org.springframework.bean.factory | Spring 2.5+ |
@Resource | javax.annotation | Java JSR-250 |
@Inject | javax.inject | Java JSR-330 |
@Autowired 和@Resource使用的比较多一些。
# @Autowired 和 @Resource 的区别是什么?
Autowired 属于 Spring 内置的注解,默认的注入方式为byType(根据类型进行匹配),也就是说会优先根据接口类型去匹配并注入 Bean (接口的实现类)。
这会有什么问题呢? 当一个接口存在多个实现类的话,byType这种方式就无法正确注入对象了,因为这个时候 Spring 会同时找到多个满足条件的选择,默认情况下它自己不知道选择哪一个。
这种情况下,注入方式会变为 byName(根据名称进行匹配),这个名称通常就是类名(首字母小写)。就比如说下面代码中的 smsService 就是我这里所说的名称,这样应该比较好理解了吧。
// smsService 就是我们上面所说的名称
@Autowired
private SmsService smsService;
举个例子,SmsService 接口有两个实现类: SmsServiceImpl1和 SmsServiceImpl2,且它们都已经被 Spring 容器所管理。
// 报错,byName 和 byType 都无法匹配到 bean
@Autowired
private SmsService smsService;
// 正确注入 SmsServiceImpl1 对象对应的 bean
@Autowired
private SmsService smsServiceImpl1;
// 正确注入 SmsServiceImpl1 对象对应的 bean
// smsServiceImpl1 就是我们上面所说的名称
@Autowired
@Qualifier(value = "smsServiceImpl1")
private SmsService smsService;
我们还是建议通过 @Qualifier 注解来显式指定名称而不是依赖变量的名称。
@Resource属于 JDK 提供的注解,默认注入方式为 byName。如果无法通过名称匹配到对应的 Bean 的话,注入方式会变为byType。
@Resource 有两个比较重要且日常开发常用的属性:name(名称)、type(类型)。
public @interface Resource {
String name() default "";
Class<?> type() default Object.class;
}
如果仅指定 name 属性则注入方式为byName,如果仅指定type属性则注入方式为byType,如果同时指定name 和type属性(不建议这么做)则注入方式为byType+byName。
// 报错,byName 和 byType 都无法匹配到 bean
@Resource
private SmsService smsService;
// 正确注入 SmsServiceImpl1 对象对应的 bean
@Resource
private SmsService smsServiceImpl1;
// 正确注入 SmsServiceImpl1 对象对应的 bean(比较推荐这种方式)
@Resource(name = "smsServiceImpl1")
private SmsService smsService;
简单总结一下:
@Autowired是 Spring 提供的注解,@Resource是 JDK 提供的注解。Autowired默认的注入方式为byType(根据类型进行匹配),@Resource默认注入方式为byName(根据名称进行匹配)。- 当一个接口存在多个实现类的情况下,
@Autowired和@Resource都需要通过名称才能正确匹配到对应的 Bean。Autowired可以通过@Qualifier注解来显式指定名称,@Resource可以通过name属性来显式指定名称。
# Bean 的作用域有哪些?
Spring 中 Bean 的作用域通常有下面几种:
- singleton : IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。
- prototype : 每次获取都会创建一个新的 bean 实例。也就是说,连续
getBean()两次,得到的是不同的 Bean 实例。 - request (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。
- session (仅 Web 应用可用) : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。
- application/global-session (仅 Web 应用可用): 每个 Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效。
- websocket (仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean。
如何配置 bean 的作用域呢?
xml 方式:
<bean id="..." class="..." scope="singleton"></bean>
注解方式:
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person personPrototype() {
return new Person();
}
# 单例 Bean 的线程安全问题了解吗?
大部分时候我们并没有在项目中使用多线程,所以很少有人会关注这个问题。单例 Bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候是存在资源竞争的。
常见的有两种解决办法:
- 在 Bean 中尽量避免定义可变的成员变量。
- 在类中定义一个
ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中(推荐的一种方式)。
不过,大部分 Bean 实际都是无状态(没有实例变量)的(比如 Dao、Service),这种情况下, Bean 是线程安全的。
ThreadLocal是一个线程本地变量,用于在多线程环境下保证每个线程都可以独立地访问和修改自己的变量副本,而不会影响其他线程的变量值。ThreadLocal通常作为成员变量使用,每个线程访问该变量时都会得到一个独立的副本。在实现中,ThreadLocal使用一个ThreadLocalMap来保存每个线程对应的变量副本,ThreadLocalMap的key是ThreadLocal对象,value是对应线程的变量副本。每个线程访问ThreadLocal变量时,会根据当前线程获取对应的变量副本,然后对副本进行操作,从而避免了多线程并发访问变量时的竞争问题。
以下是chatGPT的答案: 单例 Bean 是指在 Spring 容器中只存在一个实例的 Bean。线程安全问题指的是在多线程环境下,对同一个对象的操作可能会导致不正确的结果或者异常。单例 Bean 的线程安全问题主要有两个方面:
- 状态共享问题:单例 Bean 的状态是共享的,如果多个线程同时访问修改状态可能会导致竞态条件,需要保证线程安全。
- 初始化问题:如果单例 Bean 的初始化过程中存在线程不安全的操作,可能会导致多个线程同时初始化,也需要保证线程安全。
针对第一个问题,可以采用 synchronized 等方式进行同步,或者使用无状态的 Bean 来避免状态共享。
针对第二个问题,可以采用懒加载等方式进行延迟初始化,或者使用线程安全的单例模式实现(如双重检查锁定)来保证线程安全。
懒加载(Lazy Loading)是指在需要的时候才加载数据或创建对象,而不是一开始就加载数据或创建对象。在 Spring 中,懒加载可以应用于 Bean 的创建过程中。默认情况下,Spring 会在应用启动时创建所有的单例 Bean,但是如果将 Bean 设置为懒加载,那么该 Bean 只有在第一次被使用时才会被创建。这样可以提高应用启动的速度和内存的使用效率,因为只有必要的 Bean 才会被创建和加载。
# Bean 的生命周期了解么?
下面的内容整理自:yemengying.com/2016/07/14/… ,除了这篇文章,再推荐一篇很不错的文章 :www.cnblogs.com/zrtqsk/p/37… 。
- 实例化Bean对象
- Bean 容器找到配置文件中 Spring Bean 的定义。
- Bean 容器利用 Java Reflection API 创建一个 Bean 的实例。
- 设置对象属性
- 如果涉及到一些属性值 利用
set()方法设置一些属性值。
- 如果涉及到一些属性值 利用
- 检查Aware相关接口并设置相关依赖
- 如果 Bean 实现了
BeanNameAware接口,调用setBeanName()方法,传入 Bean 的名字。 - 如果 Bean 实现了
BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。 - 如果 Bean 实现了
BeanFactoryAware接口,调用setBeanFactory()方法,传入BeanFactory对象的实例。 - 与上面的类似,如果实现了其他
*.Aware接口,就调用相应的方法。
- 如果 Bean 实现了
这三个Aware其实也就是让我们的bean可以知道自己名字,自己的ClassLoader是哪个,加载自己的BeanFactory是哪个,结合我们上面的代码我们知道其实设置这三个Aware是在bean的生命周期内设置的。
-
BeanPostProcessor前置处理
- 如果有和加载这个 Bean 的 Spring 容器相关的
BeanPostProcessor对象,执行postProcessBeforeInitialization()方法
- 如果有和加载这个 Bean 的 Spring 容器相关的
-
检查是否是InitializingBean以决定是否调用afterPropertiesSet()方法
- 如果 Bean 实现了
InitializingBean接口,执行afterPropertiesSet()方法。
- 如果 Bean 实现了
-
检车是否配置有自定义的init-method
- 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
-
BeanPostProcessor后处理
- 如果有和加载这个 Bean 的 Spring 容器相关的
BeanPostProcessor对象,执行postProcessAfterInitialization()方法
- 如果有和加载这个 Bean 的 Spring 容器相关的
-
注册必要的Destruction相关回调接口
-
运行中
-
是否实现DisposableBean接口
- 当要销毁 Bean 的时候,如果 Bean 实现了
DisposableBean接口,执行destroy()方法。
- 当要销毁 Bean 的时候,如果 Bean 实现了
-
是否配置有自定义的destroy方法
- 当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。
图示:
与之比较类似的中文版本:
Bean的完整生命周期经历了各种方法调用,这些方法可以划分为以下几类:
1、Bean自身的方法 : 这个包括了Bean本身调用的方法和通过配置文件中的init-method和destroy-method指定的方法
2、Bean级生命周期接口方法 : 这个包括了BeanNameAware、BeanFactoryAware、InitializingBean和DiposableBean这些接口的方法
3、容器级生命周期接口方法 : 这个包括了InstantiationAwareBeanPostProcessor 和 BeanPostProcessor 这两个接口实现,一般称它们的实现类为“后处理器”。
4、工厂后处理器接口方法 : 这个包括了AspectJWeavingEnabler, ConfigurationClassPostProcessor, CustomAutowireConfigurer等等非常有用的工厂后处理器接口的方法。工厂后处理器也是容器级的。在应用上下文装配配置文件之后立即调用。
afterPropertiesSet()方法一般用来干什么?
afterPropertiesSet() 方法是 Spring 框架中的 InitializingBean 接口中定义的方法,用于在 bean 的所有属性设置完成后执行一些初始化操作。一般来说,我们可以在这个方法中进行一些需要在 bean 属性设置完成之后进行的操作,比如校验、初始化等。当然,如果你在 bean 的配置文件中使用了 init-method 属性,那么 afterPropertiesSet() 方法的作用可以被这个属性所代替。
init-method与afterPropertiesSet()方法的区别?
init-method 和 afterPropertiesSet() 方法都是在 Spring Bean 初始化时调用的方法,用于执行一些初始化操作。它们的主要区别在于:
init-method 是在 Spring 配置文件中通过 init-method 属性指定的方法名称,而 afterPropertiesSet() 是 InitializingBean 接口中定义的方法,Spring 在初始化 Bean 后会自动调用它。
init-method 可以在任何 Bean 中定义,而 afterPropertiesSet() 只能在实现了 InitializingBean 接口的 Bean 中定义。
afterPropertiesSet() 方法的执行顺序要比 init-method 方法更早,因为 Spring 在执行 init-method 方法之前会先执行 afterPropertiesSet() 方法。
总的来说,init-method 和 afterPropertiesSet() 都可以用来执行初始化操作,具体使用哪个取决于个人习惯和项目需求。但是需要注意的是,如果 Bean 实现了 InitializingBean 接口并且同时在配置文件中指定了 init-method,那么 init-method 方法会最后被执行。
postProcessBeforeInitialization()和afterProcessBeforeInitialization()
postProcessBeforeInitialization()和postProcessAfterInitialization()都是Spring框架中的BeanPostProcessor接口中的方法,用于在bean实例化、依赖注入和初始化的前后做一些额外的处理工作。
postProcessBeforeInitialization()方法在bean的初始化之前被调用,允许我们在bean被初始化之前修改它的属性值。这个方法可以用来实现自定义的属性解析器,可以在bean初始化之前修改bean的属性,例如替换属性的值、根据属性值生成其他属性等。
postProcessAfterInitialization()方法在bean的初始化之后被调用,允许我们在bean初始化之后再次修改它的属性值或者做其他处理。这个方法可以用来实现自定义的初始化后处理器,比如对bean进行监控、对bean的属性进行验证等。
总的来说,postProcessBeforeInitialization()和postProcessAfterInitialization()方法提供了在bean实例化、依赖注入和初始化的前后做一些额外的处理工作的机会,可以用来实现一些自定义的逻辑和处理,增强Spring框架的功能。
什么是DispoasbleBean
DisposableBean 是 Spring Framework 提供的一个接口,用于定义 Bean 销毁时执行的回调方法。其定义如下:
public interface DisposableBean {
void destroy() throws Exception;
}
当一个 Bean 实现了 DisposableBean 接口时,在该 Bean 被销毁时,Spring 容器会自动调用其 destroy() 方法进行销毁操作。需要注意的是,该方法抛出的异常将被 Spring 捕获并记录到日志中,但并不会影响其他 Bean 的销毁。
与之对应的是 init-method,用于在 Bean 实例化并完成属性注入后执行自定义的初始化操作;DisposableBean 的作用是在 Bean 销毁前执行自定义的销毁操作。使用哪种方式取决于具体需求,但需要确保销毁操作可以正确地释放资源以避免内存泄漏等问题。