Spring-SpringIOC
Spring-SpringIOC
学习核心
- SpringIOC核心概念
- 名词理解:IOC、DI、DL等
- IOC的作用
参考资料
IOC核心
IOC基础概念
IOC(控制反转:Inverser Of Control):把原来在程序中创建Java对象的控制权限交给Spring管理
简单的说,把原来在程序中创建HelloService对象的控制权限交给Spring管理(HelloService对象控制器被反转到Spring框架内)
IOC 又称为依赖倒置原则(设计模式六大原则之一),它的要点在于:程序要依赖于抽象接口,不要依赖于具体实现。它的作用就是用于降低代码间的耦合度。
SpringIOC容器就像一个工厂一样,当需要创建一个对象的时候,只需要配置好配置文件/注解即可,而不需要关注对象是如何被创建出来的。IOC容器负责创建对象并管理这些对象的生命周期,直到它们被完全销毁。
在实际项目中一个 Service 类如果有几百甚至上千个类作为它的底层,当需要实例化这个 Service,可能要每次都要搞清这个 Service 所有底层类的构造函数,无形中增加开发成本。如果利用 IOC 的话,只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度
IOC的实现方式
- 依赖注入(Dependency Injection,简称 DI):不通过
new()
的方式在类内部创建依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类使用。 - 依赖查找(Dependency Lookup):容器中的受控对象通过容器的 API 来查找自己所依赖的资源和协作对象
IOC要点(理解IOC:什么是控制、什么是反转)
- 谁控制谁,控制什么:
- 传统 Java SE 程序设计,直接通过 new 创建对象,是程序主动去创建依赖对象;
- IOC有专门一个容器来创建这些对象,即由IOC容器来控制对象的创建;由IOC容器控制对象,控制了外部资源获取(不只是对象包括比如文件等)
- 为何是反转,哪些方面反转了:由容器来查找及注入依赖对象,对象只是被动的接受依赖对象,依赖对象的获取被反转了
- 传统应用程序是在对象中主动控制去直接获取依赖对象,也就是正转;
- 反转则是由容器来帮忙创建及注入依赖对象;
IOC是如何来管理对象的?
IOC容器就是具有依赖注入功能的容器。IOC 容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。应用程序无需直接在代码中 new 相关的对象,应用程序由 IOC 容器进行组装。在 Spring 中 BeanFactory 是 IOC 容器的实际代表者。
Spring IOC 容器如何知道哪些是它管理的对象呢?配置文件,Spring IOC 容器通过读取配置文件中的配置元数据,通过元数据对应用中的各个对象进行实例化及装配。Spring 与配置文件完全解耦的,常用的方式有xml配置文件、注解方式、基于Java文件、基于属性文件等方式来配置元数据
IOC容器
1.IOC容器分类
在 Spring 中,有两种 IOC 容器:BeanFactory
和 ApplicationContext
,负责bean的实例化、配置、组装
BeanFactory
:BeanFactory
是 Spring 基础 IoC 容器。BeanFactory
提供了 Spring 容器的配置框架和基本功能ApplicationContext
:ApplicationContext
是具备应用特性的BeanFactory
的子接口。它还扩展了其他一些接口,以支持更丰富的功能,如:国际化、访问资源、事件机制、更方便的支持 AOP、在 web 应用中指定应用层上下文等。
一般实际开发中推荐使用ApplicationContext,其功能较为丰富
2.IOC容器使用(工作步骤)
配置元数据:需要配置一些元数据来告诉 Spring,希望容器如何工作
实例化容器:定位资源文件(例如xml)、读取配置信息(Reource)并转为Spring可识别的数据形式
由 IOC 容器解析配置的元数据(BeanReader读取并解析配置文件,根据 BeanDefinition实例化、配置、组装Bean)
使用容器:由客户端实例化容器,获取需要的 Bean
IOC的加载过程分析
【1】通过BeanDefinitionReader读取指定配置文件生成的bean定义信息
【2】在BeanDefinition和完整的BeanDefinition之间会通过一道关口进行功能增强(通过实现BeanFactoryPostProcessor接口扩展扩展功能,实现多个就会执行多次),执行完成得到完整的BeanDefinition对象
【3】随后执行Bean的实例化操作,即创建对象
案例1:xml方式
【1】配置元数据(此处使用xml方式)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--1.快速入门 ========================================================= -->
<!-- - bean:就是定义一个具体的类 可以是一个对象 id 属性 就是一个合法的标识符 class 是当前类的全名称 -->
<bean id="helloService" class="com.noob.spring.a_quickstart.HelloServiceImpl">
<property name="info" value="hello"></property>
</bean>
<!--1.end快速入门 ====================================================== -->
</beans>
【2】实例化容器并使用
public class HelloServiceTest {
public static void main(String[] args) {
// 实例化容器
ApplicationContext applicationContext =new ClassPathXmlApplicationContext("spring/applicationContext.xml");
// 使用容器
HelloService service =(HelloService) applicationContext.getBean("helloService");
service.sayHello();
}
}
案例2:注解方式
Spring 默认是不启用注解的。如果想使用注解,需要先在 xml 中配置启动注解
<context:annotation-config/>
注解 | 说明 |
---|---|
@Required | 只能用于修饰 bean 属性的 setter 方法 受影响的 bean 属性必须在配置时被填充在 xml 配置文件中,否则容器将抛出 BeanInitializationException |
📌@Autowired | 用于修饰属性、setter 方法、构造方法 |
📌@Qualifier | 搭配@Autowired 使用,指定 bean 名称来锁定真正需要的那个 bean |
📌@Resource | 根据指定的名称来注入 bean |
@PostConstruct 和 @PreDestroy | 用于规定生命周期的方式 |
@Inject | @Inject 和 @Autowired 一样,可以修饰属性、setter 方法、构造方法如果要使用 @Inject 注解,则需要引入外部依赖(javax.inject) |
案例3:Java配置
基于 Java 配置 Spring IoC 容器,实际上是Spring 允许用户定义一个类,在这个类中去管理 IOC 容器的配置
为了让 Spring 识别这个定义类为一个 Spring 配置类,需要用到两个注解:@Configuration
和@Bean
可以将@Configuration
等价于<beans>
标签;将@Bean
等价于<bean>
标签
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
// 等价于xml配置
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
3.循环依赖场景
A 类通过构造器注入需要 B 类的实例,B 类通过构造器注入需要 A 类的实例。Spring IoC 容器会在运行时检测到此循环引用,并抛出 BeanCurrentlyInCreationException
。
解决方案1:是使用 setter 方法注入替代构造器注入
解决方案2:bean A 和 bean B 之间的循环依赖关系,强制其中一个 bean 在完全初始化之前注入另一个 bean(典型的先有鸡还是先有蛋的场景)
Spring 会在容器加载时检测配置问题,例如引用不存在的 bean 或循环依赖。在实际创建 bean 时,Spring 会尽可能晚地设置属性并解析依赖关系。这意味着,如果在创建该对象或其依赖项之一时出现问题,则正确加载的 Spring 容器稍后可以在请求对象时生成异常 — 例如,bean 由于丢失或无效而引发异常。某些配置问题的这种潜在的延迟可见性是默认情况下 ApplicationContext 实现预实例化单例 bean 的原因。以在实际需要之前创建这些 bean 的一些前期时间和内存为代价,会在创建 ApplicationContext 时发现配置问题,而不是稍后。仍然可以覆盖此默认行为,以便单例 bean 延迟初始化,而不是急切地预先实例化
扩展
1.singleton 的 Bean 如何注入 prototype 的 Bean(最佳实践)
Spring 创建的 Bean 默认是单例的,但当 Bean 遇到继承的时候,可能会忽略这一点。
假设有一个 SayService 抽象类,其中维护了一个类型是 ArrayList 的字段 data,用于保存方法处理的中间数据。每次调用 say 方法都会往 data 加入新数据,可以认为 SayService 是有状态,如果 SayService 是单例的话必然会 OOM。
/**
* SayService 是有状态,如果 SayService 是单例的话必然会 OOM
*/
@Slf4j
public abstract class SayService {
List<String> data = new ArrayList<>();
public void say() {
data.add(IntStream.rangeClosed(1, 1000000)
.mapToObj(__ -> "a")
.collect(Collectors.joining("")) + UUID.randomUUID().toString());
log.info("I'm {} size:{}", this, data.size());
}
}
但实际开发的时候,没有过多思考就把 SayHello 和 SayBye 类加上了 @Service 注解,让它们成为了 Bean,也没有考虑到父类是有状态的。
@Service
@Slf4j
public class SayBye extends SayService {
@Override
public void say() {
super.say();
log.info("bye");
}
}
@Service
@Slf4j
public class SayHello extends SayService {
@Override
public void say() {
super.say();
log.info("hello");
}
}
在为类标记上 @Service 注解把类型交由容器管理前,首先评估一下类是否有状态,然后为 Bean 设置合适的 Scope。
调用代码:
@Slf4j
@RestController
@RequestMapping("beansingletonandorder")
public class BeanSingletonAndOrderController {
@Autowired
List<SayService> sayServiceList;
@Autowired
private ApplicationContext applicationContext;
@GetMapping("test")
public void test() {
log.info("====================");
sayServiceList.forEach(SayService::say);
}
}
可能有人认为,为 SayHello 和 SayBye 两个类都标记了 @Scope 注解,设置了 PROTOTYPE 的生命周期就可以解决上面的问题。
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
但实际上还是有问题。因为@RestController 注解 =@Controller 注解 +@ResponseBody 注解,又因为 @Controller 标记了 @Component 元注解,所以 @RestController 注解其实也是一个 Spring Bean。
Bean 默认是单例的,所以单例的 Controller 注入的 Service 也是一次性创建的,即使 Service 本身标识了 prototype 的范围也没用。
修复方式是,让 Service 以代理方式注入。这样虽然 Controller 本身是单例的,但每次都能从代理获取 Service。这样一来,prototype 范围的配置才能真正生效。
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProx)
2.概念补充
✨BeanFactory VS FactoryBean
BeanFactory 和 FactoryBean 是 Spring 框架中的两个关键概念,用于创建和管理 Bean 实例
- BeanFactory 是 Spring 的基本容器(IOC的底层容器),负责创建和管理 Bean 实例的,它的一个重要特性是延迟初始化(即其不是在容器启动的时候就创建所有的Bean,而是在首次使用的时候才会实例化Bean)
- FactoryBean 是一个特殊的接口,允许开发者通过自定义逻辑创建复杂的Bean实例,提供了一种灵活的方式来控制Bean的创建过程,适用于生成动态代理或者需要复杂配置的Bean
FactoryBean
FactoryBean 的典型使用场景
- 复杂对象创建:如果某个 Bean 的创建过程比较复杂,比如需要动态加载配置文件或执行其他逻辑才能实例化对象,FactoryBean 是非常合适的选择
- 代理对象生成:FactoryBean 常用于生成动态代理对象。例如,Spring AOP 使用 FactoryBean 来生成代理对象,使得 AOP 切面能够透明地应用于目标对象
- 条件性 Bean:在某些条件下返回不同的 Bean 实例,例如根据应用的环境配置不同的数据库连接池或者日志框架实现
FactoryBean 的使用实例
(1)实现FactoryBean接口
public class MyFactoryBean implements FactoryBean<MyBean> { @Override public MyBean getObject() throws Exception { // 复杂逻辑创建 MyBean 对象 return new MyBean(); } @Override public Class<?> getObjectType() { return MyBean.class; } @Override public boolean isSingleton() { return true; } }
(2)使用FactoryBean:在Spring容器中定义FactoryBean,Sprin会通过FactoryBean创建Bean实例
<bean id="myBean" class="com.example.MyFactoryBean"/>
✨BeanFactory VS ApplicationContext
BeanFactory 和 ApplicationContext 是Spring框架中两个重要的接口,用于管理Spring Bean对象,但是在功能上有些许不同:
BeanFactory
:BeanFactory
是 Spring 基础 IOC 容器,提供了 Spring 容器的配置框架和基本功能,采用的是懒加载/延时加载方式(即不是在容器启动时加载备案,而是在使用到bean的时候才进行加载,因此性能相对较慢)ApplicationContext
:ApplicationContext
是具备应用特性的BeanFactory
的子接口,可以理解为它是基于BeanFactory扩展而来的,以支持更丰富的功能,如:国际化、访问资源、事件机制、更方便的支持 AOP、在 web 应用中指定应用层上下文等。它采用的是预加载机制,在容器启动时一次性加载所有的bean,因此运行时速度相对较快,但对内存的要求比较高
一般实际开发中推荐使用ApplicationContext,其功能较为丰富
✨ObjectFactory 是什么?
ObjectFactory是 Spring 框架中的一个接口,主要用于延迟获取 Bean 实例。
ObjectFactory提供了一种延迟加载的机制,它通过getObject()
方法返回一个 Bean 的实例。使用ObjectFactory可以避免在容器启动时立即创建所有 Bean,即只有在真正需要使用 Bean 时才会从 Spring 容器中获取该 Bean 实例,有助于优化性能
ObjectFactory 的使用场景
- 懒加载 Bean:当某个 Bean 的创建过程可能耗时较长或依赖的资源较重时,可以通过 ObjectFactory 进行懒加载,避免容器启动时不必要的 Bean 创建。这能有效提升系统的启动速度
- 避免循环依赖:在某些情况下,两个 Bean 可能相互依赖,导致循环依赖问题。通过使用 objectFactory,可以延迟其中一个 Bean 的创建,避免循环依赖
ObjectFactory 使用示例
@Component
public class MyService {
@Autowired
private ObjectFactory<MyBean> myBeanFactory;
public void doSomething() {
// 当需要时,获取 Bean 实例
MyBean myBean = myBeanFactory.getObject();
// 使用 myBean 执行逻辑
}
}