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容器
- 过程分析
- 子流程入口在AbstractRefreshableApplicationContext#refreshBeanFactory方法中
- 依次调用多个类的loadBeanDefinitions方法–>AbstractXmlApplicationContext --> AbstractBeanDefinitionReader --> XmlBeanDefinitionReader 一直执行到XmlBeanDefinitionReader的doLoadBeanDefinitions方法
- 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 已经
初始化完成并缓存了起来
- 对于被修饰为lazy-init的bean Spring 容器初始化阶段不会进⾏ init 并且依赖注⼊,当第⼀次
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容器中。
- Spring容器初始化ClassA通过构造器功能初始化对象后提前暴漏到Spring容器
- ClassA调用setClassB方法,Spring首先尝试从容器中获取ClassB,此时ClassB不存在Spring容器中
- Spring容器初始化ClassB,同时也会将ClassB提前暴漏到Spring容器中
- ClassB调用setClassA方法,Spring从容器中获取ClassA,因为第一步中已经提前暴漏了ClassA,因此可以获取ClassA实例
- ClassA通过Spring容器获取到ClassB,完成对象初始化操作
- 这样Class A和ClassB都完成了对象初始化操作,解决了循环依赖问题