Spring IOC

Spring

一、Spring核心思想

1、Ioc

1.1 什么是Ioc

Ioc(Inversion of Control):控制反转或反转控制(控制:指的是对象创建、即实例化和管理的权力;反转:控制权交给外部环境,即Spring框架、Ioc容器),它是一个技术思想,而不是技术实现。该思想不是Spring独有的,是在其开发之前就存在的理论思想。

1.2 基本思想
  • Ioc主要目的是在Java开发中对象的创建和管理。
  • 传统开发方式:比如类A依赖与类B,往往会在类A中new一个B的对象
  • Ioc思想下的开发方式:不用自己new对象,而是由Ioc容器(Spring框架)去帮助实例化对象,并且管理它,需要哪个对象,到Ioc容器获取即可
  • Ioc的利弊
    • 弊:丧失了创建、管理对象的权利
    • 利:不用考虑对象的创建、管理等一系列事情

在这里插入图片描述

1.3 Ioc解决的问题
  • Ioc解决对象之间的耦合问题
    在这里插入图片描述
1.4 Ioc和DI的区别
  • DI(Dependancy Injection):依赖注入
    在这里插入图片描述

2、AOP

2.1 什么是AOP
  • AOP:(Aspect Oriented Programming)面向切面编程/面向方面编程
  • AOP是OOP的延续
  • OOP是一种垂直继承体系,通过父类子类继承关系逐渐继承下去。该思想可以解决大多数的代码重复问题,但是有一些情况是处理不了的,比如在同一个类中不同的多个方法中相同位置出现的重复代码。例如:性能监控,日志保存等
  • AOP思想提出横向抽取机制,将横切逻辑代码和业务逻辑代码分析
    在这里插入图片描述
  • AOP解决了在不改变业务逻辑的情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复
2.2 面向切面编程
  • 切:指的是横切逻辑,原有业务逻辑代码不动,只能操作横切逻辑代码,所以面向横切逻辑
  • 面:横切逻辑代码往往要影响很多方法,每个方法都如同一个点,多个点构成面,构成面的概念

二、Servlet修改成Spring

1、问题分析

1.1 耦合问题
  • new关键字将service层的实现类和Dao层的具体实现类耦合在了一起,当需要切换Dao层实现类的时候必须得修改service代码,不符合面向接口开发的最优原则
1.2 数据安全问题
  • 如果service层没有添加事务控制,出现异常可能导致数据错乱,问题很严重,尤其是金融银行业务

2、解决思路

2.1 解决耦合
  • 通过反射,把类的全限定类名配置到xml中
  • 使用工厂通过反射技术生产对象,工厂模式是解耦合非常好的一种方式
2.2 解决数据安全
  • 添加事务管理控制,手动控制JDBC的Connection,这样操作才能针对的是同一个Connection,进而控制的是同一个事务
3、代码开发
  • 通过dom4j解析xml文件,在dao层,service层,servlet层通过set方法,取代new方法解耦。
  • 事务管理中,开启事务,提交事务,回滚事务等增强逻辑,可以通过AOP思想进行开发。通过动态代理工厂的方式解决。

三、Spring IOC应用

1、Spring IOC基础

1.1 BeanFactory与ApplicationContext区别
  • BeanFactory是Spring框架中IOC容器的顶层接口,它只是用来定义一些基础功能,定义一些基础规范。而ApplicationContext是它的一个子接口,具备BeanFactory提供的全部功能。
  • 通常,BeanFactory为SpringIOC的基础容器,ApplicationContext是容器的高级接口,比BeanFactory拥有更多的功能,比如国际化支持和资源访问(xml,java配置类)等
  • Java环境下启动Ioc容器的方式
    • ClassPathXmlApplicationContext:从类的根路径下加载配置文件
    • FileSystemXmlApplicationContext:从磁盘路径上加载配置文件
    • AnnotationConfigApplicationContext:纯注解模式下启动Spring容器
  • Web环境下启动Ioc容器
    • 从XML启动容器:在web中配置监听启动
<!--配置Spring ioc容器的配置⽂件-->
 <context-param>
 <param-name>contextConfigLocation</param-name>
 <param-value>classpath:applicationContext.xml</param-value>
 </context-param>
 <!--使⽤监听器启动Spring的IOC容器-->
 <listener>
 <listenerclass>org.springframework.web.context.ContextLoaderListener</listenerclass>
 </listener>
1.2 纯XML
  • 只通过XML的配置,不需要添加注解的方式实现Ioc容器
  • 实例化Bean的三种方式
    • 方法一:使用无参构造函数
<bean id="userService" class="com.lagou.service.impl.TransferServiceImpl">
</bean>
  • 方法二:使用静态方法创建
<bean id="userService" class="com.lagou.factory.BeanFactory"
 factory-method="getTransferService"></bean>
  • 方法三:使用实例化方法创建
<bean id="beanFactory"
class="com.lagou.factory.instancemethod.BeanFactory"></bean> <bean id="transferService" factory-bean="beanFactory" factorymethod="getTransferService"></bean>
  • 在Spring框架管理Bean对象的创建中,Bean对象默认都是单例(singleton),但是也可以改变作用范围,比如多例(prototype)
    • 单例:
      • 对象出生:当创建容器时,对象就被创建了
      • 对象活着:只要容器在,对象一直活着
      • 对象死亡:当销毁容器时,对象被销毁
    • 多例
      • 对象出生:当使用对象时,创建新的对象实例
      • 对象或者,只要对象在使用中,就一直活着
      • 对象死亡:当对象长时间不用,被java垃圾回收器回收
  • Bean标签属性
    • id属性:用于给bean提供唯一标识,在一个标签内部,标识唯一
    • class属性:用于指定创建Bean对象的全限定名
    • name属性:用于给Bean提供一个或多个别名
    • factory-bean属性:用于指定创建当前bean队形的工厂
    • scope属性:用于指定bean作用范围
    • init-method属性:用于bean对象初始化方法
    • destory-method属性:用于bean对象销毁方法
1.3 xml与注解相结合模式
  • 目前企业开发中,纯xml模式使用已经很少,引入注解功能后,不需要引入额外的jar
  • 该模式spring IOC容器的启动,仍然从加载xml开始
  • 该模式下,第三方的Jar中的bean定义在xml中,例如德鲁伊数据库连接池;自己开发的bean定义使用注解
  • xml中标签与注解的对应(IOC)
XML形式对应的注解形式
标签@Component(“id”) ,注解加在类上,bean的id属性内容直接配置在注解后面,如果不配置,默认定义这个bean的id为类的类名;另外,针对分层代码开发提供了@Component的三种别名@Controller、@Service、@Respository分别用于控制层类、服务层类、dao层类的bean定义,这四个注解的用法完全一样,只是为了更清晰的区分
标签的scope属性@Scope(“prototype”) ,默认为单例,注解加载类上
标签的init-method属性@PostConstruct,注解加在方法上,该方法就是初始化后调用的方法
标签的destory-method属性@PreDestory,注解加在方法上,该方法就是销毁前调用的方法
  • DI依赖注入的注解实现方式
  • @Autowired(推荐使用):其为Spring提供的注解,需要导入包org.springframework.beans.factory.annotation.Autowired
  • @Autowired采用的策略为按照类型注入
public class TransferServiceImpl {
 	@Autowired
	 private AccountDao accountDao; 
 }
  • 如上代码所示,这样装配回去spring容器中找到的类型为AccountDao的类,然后将其注入。这样会产生一个问题,当一个类型由多个bean值的时候,会造成无法选择具体注入哪个的情况
  • @qualifier:告诉spring具体装配哪个对象
public class TransferServiceImpl {
 @Autowired
 @Qualifier(name="jdbcAccountDaoImpl") 
 private AccountDao accountDao; }
1.4 纯注解模式
  • @Configuration:注解,表明当前类是一个配置类
  • @ComponetScan:注解,标注引入对象@ComponentScan({“com.lagou.edu”})
  • PropertySource:引入外部属性配置文件。@PropertySource({“classpath:jdbc.properties”})
  • @Value:对变量赋值,可以直接赋值,也可以使用${}读取资源配置文件中的信息
  • @Bean:将方法返回对象加入SpringIOC容器

2、Spring IOC高级特性

2.1 lazy-init延迟加载
  • ApplicationContext容器默认在服务器启动时将所有的singleton bean提前进行实例化,即实例化作为初始化的一部分。bean标签中的lazy-init默认时false
  • 如果将lazy-init设置为true,则ApplicationContext启动时不会提前实例化,而是第一次向容器通过getBean索取bean时被实例化
  • 如果一个设置立即加载的bean1,引用了延迟加载的bean2,那么bean1在容器启动时被实例化,而bean2由于被bean1引用,所以也被实例化。这种情况也符合延时加载的bean在第一次调用时才被实例化的规则
2.2 FactoryBean和BeanFactory
  • BeanFactory:该接口是容器的顶级接口,定义了容器的一些基础行为,负责生产和管理bean的一个工厂,具体使用它下面的子接口,比如ApplicationContext
  • FactoryBean:Spring中Bean的一种(另一种是普通bean),FactoryBean可以生成某一个类型的Bean实例,即借助它自定义Bean的创建过程
2.3 后置处理器
  • Bean生命周期
    在这里插入图片描述
2.3.1 BeanPostProcessor
  • BeanPostProcessor是针对Bean级别的处理,可以针对某个具体的bean。该接口由两个方法,分别在Bean的初始化方法前和初始化方法后执行,具体初始化方法指在定义bean时,定义了init-method所指定的方法
  • 定义⼀个类实现了BeanPostProcessor,默认是会对整个Spring容器中所有的bean进⾏处理。如果要对
    具体的某个bean处理,可以通过⽅法参数判断,两个类型参数分别为Object和String,第⼀个参数是每
    个bean的实例,第⼆个参数是每个bean的name或者id属性的值。所以我们可以通过第⼆个参数,来判
    断我们将要处理的具体的bean。
2.3.2 BeanFactoryPostProcessor
  • BeanFactory级别的处理,是针对整个Bean工厂进行处理。典型应用:PropertyPlaceholderConfigurer,此接口只有一个方法,参数为ConfigurableListableBeanFactory,该参数类型定义的方法中,存在getBeanDefinition方法。该方法可以找到定义bean的BeanDefinition对象,用以对定义属性进行修改。
  • BeanDefinition:XML中定义的bean标签,Spring解析bean标签成为一个JavaBean,这个JavaBean就是BeanDefinition
  • 调用BeanFactoryPostProcessor方法时,bean还没有实例化,此时bean刚被解析成BeanDefinition对象

3、Spring IOC源码剖析

3.0 阅读源码原则
  • 定焦原则:抓主线
  • 宏观原则:站在上帝视角,关注源码结构和业务流程(淡化具体某行代码的编写细节)
3.1 Spring IOC的容器体系
  • IOC容器是Spring的核心模块,是抽象了对象管理、依赖关系管理的框架解决方案。Spring提供了很多的容器,其中BeanFactory是顶层容器(根容器),不能被实例化,它定义了所有的IOC容器必须遵从的一套原则,具体的容器实现可以增加额外的功能,例如ApplicationContext,其下更具体的实现如ClassPathXmlApplicationContext(包含解析xml等一系列功能),AnnotationConfigApplicationContext(包含注解解析等一系列内容)
  • ApplicationContext:
  • 继承BeanFactory子接口
  • 继承ResourceLoader接口获取各种资源
  • 继承MessageSource接口获取国际化、各种语言等
3.1.1 Bean生命周期
  • Bean的创建,在未设置延时加载时,在容器初始化时完成
  • 构造函数的调用时机在AbstactApplicationContext类refresh方法的finishBeanFactoryInitialization(bean Factory)
  • IntializingBean中的afterProperiesSet方法的调用时机是AbstractApplicationContext类refresh方法的finishBeanFactoryIntialization(beanFactory)
  • BeanFactoryPostProcessor初始化在AbstractApplicationContext类refresh方法的invoke Bean FactoryPostProcessors(beanFactory)
  • postProcessBeanFactory调用在AbstactApplicationContext类refresh方法的invokeBeanFactoryPostProcessors(beanFactory)
  • BeanPostProcessor 初始化在AbstractApplicationContext类refresh⽅法的
    registerBeanPostProcessors(beanFactory);
  • postProcessBeforeInitialization 调⽤在AbstractApplicationContext类refresh⽅法的
    finishBeanFactoryInitialization(beanFactory);
  • postProcessAfterInitialization 调⽤在AbstractApplicationContext类refresh⽅法的
    finishBeanFactoryInitialization(beanFactory);
  • 根据以上内容整理AbstractApplicationContext类的refresh方法,该方法对于Spring IOC容器初始化相当关键
3.1.2 Spring IOC容器初始化流程
@Override
public void refresh() throws BeansException, IllegalStateException {
 synchronized (this.startupShutdownMonitor) {
 // 第⼀步:刷新前的预处理
 prepareRefresh();
 /*
 第⼆步:
 获取BeanFactory;默认实现是DefaultListableBeanFactory
 加载BeanDefition 并注册到 BeanDefitionRegistry
 */
 ConfigurableListableBeanFactory beanFactory =
obtainFreshBeanFactory();
 // 第三步:BeanFactory的预准备⼯作(BeanFactory进⾏⼀些设置,⽐如context的类加
载器等)
 prepareBeanFactory(beanFactory);
 try {
 // 第四步:BeanFactory准备⼯作完成后进⾏的后置处理⼯作
 postProcessBeanFactory(beanFactory);
 // 第五步:实例化并调⽤实现了BeanFactoryPostProcessor接⼝的Bean
 invokeBeanFactoryPostProcessors(beanFactory);
 // 第六步:注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执registerBeanPostProcessors(beanFactory);
 // 第七步:初始化MessageSource组件(做国际化功能;消息绑定,消息解析);
 initMessageSource();
 // 第⼋步:初始化事件派发器
 initApplicationEventMulticaster();
 // 第九步:⼦类重写这个⽅法,在容器刷新的时候可以⾃定义逻辑
 onRefresh();
 // 第⼗步:注册应⽤的监听器。就是注册实现了ApplicationListener接⼝的监听器
bean
 registerListeners();
 /*
 第⼗⼀步:
 初始化所有剩下的⾮懒加载的单例bean
 初始化创建⾮懒加载⽅式的单例Bean实例(未设置属性)
 填充属性
 初始化⽅法调⽤(⽐如调⽤afterPropertiesSet⽅法、init-method⽅法)
 调⽤BeanPostProcessor(后置处理器)对实例bean进⾏后置处
 */
 finishBeanFactoryInitialization(beanFactory);
 /*
 第⼗⼆步:
 完成context的刷新。主要是调⽤LifecycleProcessor的onRefresh()⽅法,并且发布事
件 (ContextRefreshedEvent)
 */
 finishRefresh();
 }
3.2 BeanFactory创建流程
3.2.1 BeanDefinitionjia加载解析及注册子流程
  • 该子流程涉及如下关键步骤:
  • Resource定位:指对BeanDefinition的资源定位过程。通俗讲就是找到定义javabean信息的XML文件,并将其封装成Resource对象
  • BeanDefinition载入:把用户定义好的javabean表示为IOC容器内容的数据结构,这个容器内部的数据结构就是BeanDifinition。注册BeanDefinition到IOC容器
  • 过程分析
    1. 子流程入口在AbstractRefreshableApplicationContext#refreshBeanFactory方法中
    2. 依次调用多个类的loadBeanDefinitions方法–>AbstractXmlApplicationContext --> AbstractBeanDefinitionReader --> XmlBeanDefinitionReader 一直执行到XmlBeanDefinitionReader的doLoadBeanDefinitions方法
    3. XmlBeanDefinitionReader类的registerBeanDefinitions方法,产生多次重载调用
  • 注册就是把封装的XML中定义的Bean信息封装为BeanDefinition对象之后放入一个Map中,BeanDefinition是以Map的结构组织这些BeanDefinition
3.2.2 Bean创建流程
  • Bean创建子流程入口在AbstractApplicationContext#refresh()方法的finishBeanFactoryInitialization(beanFactory)
  • 进入finishBeanFactoryInitialization
  • 进入DefaultListableBeanFactory类的preinstantiateSingletions方法
  • 进入AbstractBeanFactory类的doGetBean方法
  • 进入AbstractAutowireCapableBeanFactory类
3.3 lazy-init延迟加载机制原理
  • 普通Bean的初始化时在容器启动初始化阶段执行的,而被lazy-init=true修饰的bean则是从容器里第一次进行context.getBean()时进行触发。
  • Spring启动的时候会把所有bean信息(包括XML和注解)解析转化成为Spring能够识别的BeanDefinition并存到Hashmap里供下面初始化使用,然后对每个BeanDefinition进行处理。如果是懒加载的则在容器初始化阶段不处理,其他的则在容器初始化阶段进行初始化并依赖注入
  • 说明
    • 对于被修饰为lazy-init的bean Spring 容器初始化阶段不会进⾏ init 并且依赖注⼊,当第⼀次
      进⾏getBean时候才进⾏初始化并依赖注⼊
    • 对于⾮懒加载的bean,getBean的时候会从缓存⾥头获取,因为容器初始化阶段 Bean 已经
      初始化完成并缓存了起来
3.4 Spring IOC循环依赖问题
3.4.1 概念
  • 循环依赖就是循环引用,就是两个或者两个以上的Bean互相持有对方,最终形成闭环。例如A依赖于B,B依赖于C,C又依赖于A。
  • Spring中循环依赖场景:
    • 构造器的循环依赖(构造器注入)
    • Field属性的循环依赖(set注入)
  • 构造器的循环依赖问题无法解决,只能抛出BeanCurrentlyCreationException异常,在解决属性循环依赖时,spring采用的是提前暴漏对象的方法
3.4.2 循环依赖处理机制
  • 单例bean构造器参数循环依赖(无法解决)
  • prototype原型bean循环依赖(无法解决):对于原型bean的初始化过程中不论是通过构造器参数循环依赖还是通过setXxx方法产生循环依赖,Spring都会直接报错处理。故,Spring不支持原型bean的循环依赖
  • 单例bean通过setXxx或者@Autowired进行循环依赖
  • Spring的循环依赖理论依据基于java的引用传递,当获取对象的引用时,对象的属性可以延后设置,但是构造器必须是在获取引用之前
  • Spring通过setXxx或者@Autowired方法解决循环依赖其实就是通过提前暴漏一个ObjectFactory对象来完成的。简单来说ClassA在调用构造器完成对象初始化之后,在调用ClassA的setClassB方法之前就把ClassA实例化的对象通过ObjectFactory提前暴漏到Spring容器中。
  1. Spring容器初始化ClassA通过构造器功能初始化对象后提前暴漏到Spring容器
  2. ClassA调用setClassB方法,Spring首先尝试从容器中获取ClassB,此时ClassB不存在Spring容器中
  3. Spring容器初始化ClassB,同时也会将ClassB提前暴漏到Spring容器中
  4. ClassB调用setClassA方法,Spring从容器中获取ClassA,因为第一步中已经提前暴漏了ClassA,因此可以获取ClassA实例
  5. ClassA通过Spring容器获取到ClassB,完成对象初始化操作
  6. 这样Class A和ClassB都完成了对象初始化操作,解决了循环依赖问题
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值