提示:此文不单单是概念的综合、也包含了我个人对Spring的理解,建议大家有自己理解的时候也使用newBing来确认自己的理解是否正确
其清晰程度跟高级开发工程师掌握的知识点应该是差不多了
如何使用new bing来帮助自己理解一些比较抽象的知识和概念?
三种思想和框架概念
1. IOC: 控制反转,强调的是原来在程序中创建Bean的权利反转给第三方
2. DI: 依赖注入,强调的Bean之间的关系,这种关系第三方负责去设置
3. AOP: 面向切面编程,功能的横向抽取,主要实现方式就是Proxy
4.框架: 是基于基础技术之上,从众多业务中抽取出的通用解决方案
以往的项目之中我们可能存在大量的包与类的嵌套、这种耦合度极高。一旦某个类名等需要发生更改
整个项目多处地方都要一并修改,IOC帮我们避免了这种高耦合的情况。把项目拆分成一个个独立的零件
某个零件坏了,我们可以单独对其做出调整。调整的同时又不影响其他零件。
对象实例化在容器当中是通过无参构造器创建的
上面使用BeanFactory完成了loC思想的实现,下面去实现以下DI依赖注入:
1. 定义UserDao接口及其UserDaolmpl实现类;
2. 修改UserServicelmpl代码,添加一个setUserDao(UserDao userDao)用于接收注入的对象;
3. 修改beans.xml配置文件,在UserDaolmpl的中嵌入配置注入;
4. 修改测试代码,获得UserService时,setUserService方法执行了注入操作。
ApplicationContext 与 BeaFactroy 的唯一区别
其中引用的 applicationContext.xml的配置也与上一节一模一样
1. BeanFactory是Spring的早期接口,称为Spring的Bean工厂,ApplicationContext是后期更高级接口,称之为Spring 容器;
2. ApplicationContext在BeanFactory基础上对功能进行了扩展,例如: 监听功能、国际化功能等。BeanFactory的API更偏向底层,ApplicationContext的API大多数是对这些底层API的封装;
3. Bean创建的主要逻辑和功能都被封装在BeanFactory中,ApplicationContext不仅继承了BeanFactory,而目ApplicationContext内部还维护着BeanFactory的引用,所以,ApplicationContext与BeanFactory既有继承关系,又有融合关系。
4. Bean的初始化时机不同,原始BeanFactory是在首次调用getBean时才进行Bean的创建,而ApplicationContext则是配置文件加载,容器一创建就将Bean都实例化并初始化好。
①: id
即使不输入也同样会在容器中创建对象,而它的名称会被置换为 class
中的内容
②: 若 id 和 name 都配置了、优先使用 id 、不过在底层 name 中的别名其实也是指向 id的
没有设置 id 的情况下,就使用 name , 再就是 class。
③: 默认情况下,单纯的Spring环境Bean的作用范围有两个: Singleton和Prototype
singleton: 单例,默认值,Spring容器创建的时候,就会进行Bean的实例化,并存储到容器内部的单例池中每次getBean时都是从单例池中获取相同的Bean实例;
prototype: 原型,Spring容器初始化时不会创建Bean实例,当调用getBean时才会实例化Bean,每次getBean都会创建一个新的Bean实例。
④: 当lazy-init设置为true时为延迟加载,也就是当Spring容器创建的时候,不会立即创建Bean实例,等待用到时在创建Bean实例并存储到单例池中去,后续在使用该Bean直接从单例池获取即可,本质上该Bean还是单例的
<bean id="userDao" class="com,gangan,dao.impl.UserDaoImpl" lazy-init="true"/>
⑤: Bean在被实例化后,可以执行指定的初始化方法完成一些初始化的操作,Bean在销毁之前也可以执行指定的销毁方法完成一些操作,初始化方法名称和销毁方法名称通过。
为什么容器和实例没有了、销毁方法还不触发? 你需要在代码中手动关闭,让spring知道现在马上销毁。
⑥: 你还可以使用实例实现 InitializingBean 接口,实现里面的方法,来达到时机早于init-method 配置的目的
⑦: Spring的实例化方式主要如下两种:
构造方式实例化: 底层通过构造方法对Bean进行实例化
constructor-arg并不是只传入构造器的、即便你只是一个普通方法,它一样会传入参数
工厂方式实例化: 底层通过调用自定义的工厂方法对Bean进行实例化
Ⅰ: 静态工厂方法实例化Bean(可以直接调用类名.方法创建实例)
好处在于、我可以在创建实体类型时做一些其他的事情。
Ⅱ: 实例工厂方法实例化Bean(要先创建类对象、再调用方法)
Ⅲ: 实现FactoryBean规范延迟实例化Bean (当你真正使用到的时候才会调用、下次再用就直接从缓存中拿)
⑧: Bean的依赖注入有两种方式:
用构造Bean的时候记得在接口实现类里面弄一个构造器,constructor-arg 里面的内容时直接对构造器当中的参数进行赋值的
⑨: Bean的依赖注入配置
Ⅰ: 普通数据类型,例如: String、int、boolean等,通过value
属性指定
Ⅱ: 引用数据类型,例如: UserDaolmpl、DataSource等,通过ref
属性指定
Ⅲ: 集合数据类型,例如: List、Map、Properties等
也就只是加个集合类型标签、然后看看是 key value 还是 直接value
如果被注入的属性类型是Bean引用的话,那么可以在标签中使用 autowire 属性去配置自动注入方式,属性值有两个:
Ⅳ: byName: 通过属性名自动装配,即去匹配 setXxx 与id="xxx”(name=“xxx”)是否一致
Ⅴ: byType: 通过Bean的类型从容器中匹配,匹配出多个相同Bean类型时,报错
<bean id = "userService" class="com.gangan.service.impl.UserServiceImpl" autowire="byType">
⑧: Spring的其他配置标签
默认标签: 就是不用额外导入其他命名空间约束的标签,例如< bean >标签
自定义标签: 就是需要额外引入其他命名空间约束,并通过前缀引用的标签,例如< context:property-placeholder/ >标签
命名空间约束下的默认标签:
![在这里插入图片描述](https://img-blog.csdnimg.cn/8ba02d238b6246529362845514f4c152.png =5=600x)
Ⅰ: < beans >标签。出了经常用的作为跟标签。还可以嵌套在根标签内,使用profile属性切换开发环境
可以使用以下两种方式指定被激活的环境:
使用命令行动态参数,虚拟机参数位置加载-Dspring.profiles.active=test
使用代码的方式设置环境变量 System.setProperty(“springprofiles.active”,“test”)
Ⅱ: < import >标签,用于导入其他配置文件,项目变大后,就会导致一个配置文件内容过多,可以将一个配置文件根据业务某块进行拆分,拆分后,最终通过< import >标签导入到一个主配置文件中,项目加载主配置文件就连同< import >导入的文件一并加载了
当你为每个模块写了xml配置文件后、可以这样导入来调用
Ⅲ: < alias >标签是为某个Bean添加别名,与在< bean >标签上使用name属性添加别名的方式一样,我们为UserServicelmpl指定四个别名: aaa、bbb、xxx、yyy、
Ⅳ: Spring的自定义标签需要引入外部的命名空间,并为外部的命名空间指定前缀,使用<前缀:标签>形式的标签,称之为自定义标签,自定义标签的解析流程也是 Spring x ml扩展点方式之一
以上在xml中配置的Bean都是自己定义的,例如: UserDaolmpl,UserServicelmpl。但是,在实际开发中有些功能类并不是我们自己定义的,而是使用的第三方iar包中的,那么,这些Bean要想让Spring进行管理,也需要对其进行配置
①: 配置 Druid 数据源交给Spring管理
导入 Druid 坐标、mysql驱动、之后在主配置xml中配置数据源
②: 配置 Connection 交给Spring管理(基本用不到)
Connection 的产生是通过DriverManager的静态方法etConnection获取的,所以我们要用静态工厂方式配置
③: 配置日期对象交给Spring管理
产生一个指定日期格式的对象,原始代码如下:
可以看成是实例工厂方式,使用Spring配置方式产生Date实例
③: 配置MyBatis的SqlSessionFactory交由Spring管理
导入Mybatis
Spring容器在进行初始化时,会将xml配置的< bean >的信息封装成一个BeanDefinition对象,所有的BeanDefinition存储到一个名为beanDefinitionMap的Map集合中去,Spring框架在对该Map进行遍历,使用反射创建Bean实例对象,创建好的Bean对象存储在一个名为singletonObiects的Map集合中,当调用getBean方法时则最终从该Map集合中取出Bean实例对象返回。
①: 加载xml配置文件,解析获取配置中的每个< bean >的信息,封装成一个个的BeanDefinition对象
②: 将BeanDefinition存储在一个名为beanDefinitionMap的Map
③: ApplicationContext底层遍历beanDefinitionMap,创建Bean实例对象
④: 创建好的Bean实例对象,被存储到一个名为singletonObjects的Map
⑤: 当执行applicationContext.getBean(beanName)时,从singletonObjects去匹配Bean实例返回
Spring的后处理器是Spring对外开发的重要扩展点,允许我们介入到Bean的整个实例化流程中来,以达到动态注册BeanDefinition,动态修改BeanDefinition,以及动态修改Bean的作用。Spring主要有两种后处理器
①: BeanFactoryPostProcessor: Bean工厂后处理器,在BeanDefinitionMap填充完毕,Bean实例化之前执行
BeanFactoryPostProcessor是一个接口规范,实现了该接口的类只要交由Spring容器管理的话,那么Spring就会回调该接口的方法,用于对BeanDefinition注册和修改的功能。
②: BeanPostProcessor: Bean后处理器,一般在Bean实例化之后,填充到单例池singletonObjects之前执行。
Spring 提供了一个BeanFactoryPostProcessor的子接口BeanDefinitionRegistryPostProcessor专门用于注册BeanDefinition操作
我们之前说过xml配置的< bean >信息也会封装成 BeanDfinition对象,然后全部存储到BeanDfinitionMap的Map集合中,Spring就会对这个Map集合遍历 使用反射来创建Bean的实例对象,再存储到singletonObiects的Map集合中,使用getBean就可以调用了
这里使用BeanPostProcessor的操作就相当于你不使用 xml 中 < Bean >对象、手动在代码中注册一个 Bean封装成Beandefinition,这样放进Map集合中、Spring也能够自动转化为Bean实例存储起来
Bean被实例化后,到最终缓存到名为singletonObjects单例池之前,中间会经过Bean的初始化过程,例如: 属性的填充、初始方法nit的执行等,其中有一个对外进行扩展的点BeanPostProessor,我们称为Bean后处理。跟上面的Bean工厂后处理器相似,它也是一个接口,实现了该接口并被容器管理的BeanPostProcessor,会在流程节点上被Spring自动调用。
这两个方法的执行顺序为:
类的实例化 → postProcessBeforeInitialization → 属性设置之后 → init初始化方法执行 → postProcessAfterInitialization
Spring Bean的生命周期是从 Bean 实例化之后,即通过反射创建出对象之后,到Bean成为一个完整对象,最终存储到单例池中,这个过程被称为Spring Bean的生命周期。Spring Bean的生命周期大体上分为三个阶段:
①: Bean的实例化阶段: Spring框架会取出BeanDefinition的信息进行判断当前Bean的范围是否是singleton的是否不是延迟加载的,是否不是FactoryBean等,最终将一个普通的singleton的Bean通过反射进行实例化
②: Bean的初始化阶段
: Bean创建之后还仅仅是个"半成品",还需要对Bean实例的属性进行填充、执行一些Aware接口方法、执行BeanPostProcessor方法、执行InitializinaBean接口的初始化方法、执行自定义初始化init方法等。该阶段是Spring最具技术含量和复杂度的阶段,Aop增强功能,后面要学习的Spring的注解功能等.spring高频面试题Bean的循环引用问题都是在这个阶段体现的:
③: Bean的完成阶段: 经过初始化阶段,Bean就成为了一个完整的Spring Bean,被存储到单例池singletonObjects中去了,即完成了Spring Bean的整个生命周期。
Spring Bean的初始化过程涉及如下几个过程:
①: Bean实例的属性填充(指Bean中填充属性property)
②: Aware接口属性注入
③: BeanPostProcessor的before()方法回调
④: InitializingBean接口的初始化方法回调
⑤: 自定义初始化方法init回调
⑥: BeanPostProcessor的after()方法回调
细说Bean实例属性填充
Spring再进行属性注入时、会分为如下几种情况:
①: 注入普通属性,String、int或存储基本类型的集合时,直接通过set方法的反射设置进去
②: 注入单向对象引用属性时,从容器中getBean获取后通过set方法反射设置进去,如果容器中没有,则先创建被注入对象Bean实例
UserServiceImpl 注入了 UserDao 但是后者并没有注入前者称之为 单向对象引用属性
、你也不需要在一Bean的配置顺序!
但是你的顺序会决定哪个Bean类对象先创建
③: (完成整个生命周期)后,在进行注入操作
注入双向对象引用属性时,就比较复杂了,涉及了循环引用(循环依赖)
问题,下面会详细阐述解决方案
我们都知道在没有填充属性进去的时候,实例化的Bean只是个半成品。是没办法放到单例池的,当Spring判断容器中是否存在另一个类的时候会先去单例池找。那它肯定找不到(也有可能有,具体看情况),因为双向引用两个类永远都是半成品。所以,我们只需要把半成品放到另外一个集合里面存着,当我们进行判断的时候不要去单例池找。去那个集合里面找就完事了。虽然说是是半成品,但是也可以先引用过来创建其中一个对象。我们可以之后再往这个半成品Bean里面去填充属性
我把我的理解问问new bing的看法。大家也可以参考这种方式
Bean实例属性填充
Spring提供了三级缓存
存储完整Bean实例和半成品Bean实例
,用于解决循环引用问题
在存入三级缓存之前,是将Bean对象包装成ObjectFactory类型在存入三级缓存
假设两个Bean A 与 B 相互引用、则顺序如下:
三级缓存中的查找顺序为:先从最终存储单例Bean成品的单例池中寻找,没有就到早期Bean单例池,找里面的半成品对象,这些半成品Bean是已经被其他对象引用过了的。找不到再到单例Bean的工厂池查找。里面是半成品Bean,但是没有被引用过。使用的时候通过工厂创建Bean
①: 实例化A,属性填充时到三级缓存中查找,还没实例化B所以找不到。此时Bean A是半成品所以放在"三级缓存"
②: 实例化B属性填充时在"三级缓存"中找到半成品Bean A,创建好之后Bean B就放入"一级缓存"了。此时Bean A会从"三级缓存"放到"二级缓存"
③: 当Bean B的属性填充完成后,它会自动触发对Bean A的属性填充。此时Bean A的属性填充完成后,它就会从二级缓存中移除,并放入一级缓存中,成为一个成品Bean。
Aware接口是一种框架辅助属性注入的一种思想,其他框架中也可以看到类似的接口。框架具备高度封装性,我们接触到的一般都是业务代码,一个底层功能API不能轻易的获取到,但是这不意味着永远用不到这些对象,如果用到了就可以使用框架提供的类似Aware的接口,让框架给我们注入该对象。
xml整合第三方框架有两种整合方案:
①: 不需要自定义名空间,不需要使用Spring的配置文件配置第三方框架本身内容,例如: MyBatis
②: 需要引入第三方框架命名空间,需要使用Spring的配置文件配置第三方框架本身内容,例如: Dubbo。
我们在需要的交给IOC管理的类上方使用@Component注释、如: @Component(“userDao”);
之后还需要再xml文件中配置如下:这个配置其实也是通过Bean后方法创建的
<context:component-scan base-package="com.gangan"/>
①: **分层注解:**由于JavaEE开发是分层的,为了每层Bean标识的注解语义化更加明确,@Component又衍生出如下三个注解:
不属于这三层的类我们可以使用@Component交给Spring去维护
只需要你在类当中填写属性的时候在上方加入注释、替代掉set方法
在以往使用数据类型注入的时候,如果你的数据类型有多个Bean,都会报错
@Autowired会根据类型注入、如果有多个同类型的Bean,则会根据名字进行二次比较
如果你想要直接比较名字、那你就显示使用@Autowierd注释、之后再使用@Qualifier(“名字”)注释可以直接匹配名字!
@Resource是Java底层的注释、不过Spring也会去解析。
当你使用这个注释指定名称参数时、它会根据数据类型给你找Bean注入、如果有指定名称就根据名称注入
@Resource(name = "名字");
以往在开发配置Bean的时候我们都是在xml的配置文件当中去配置Bean的
我们可以创建一个类来替代配置文件 + 标注@Component(Spring也会来维护这个类)
在类上方@Configuration
标注用来替代配置文件与标注
**替代xml文件当中扫描注解组件使用 @ComponentScan("包名")
,如果存在多个包名则写为 @ComponentScan({"包1","包2","包3"})
**
@Component:告诉Spring过来扫描这个类同级包和子包
@Configuration: 表示这个类或者这些类是替代XML的核心配置类
@PropertiesSource: 其中properties文件是防止与数据库连接的信息(URL、用户名、密码、JDBC驱动类名)
扩展: @Primary注解用于标注相同类型的Bean优先被使用权,@Primary 是Spring3.0引入的,与@Component和@Bean一起使用,标注该Bean的优先级更高,则在通过类型获取Bean或通过@Autowired根据类型进行注入时会选用优先级更高的
我们知道在@Autowried类注入的时候,有多个类型相同的Bean的时候。会去比较名字相同的注入,如果此时你不喜欢名字相同的先注入
你就在你想要先注入的类上方 @Primary
,这样就可以提高它的优先级。优先被注入
如何激活环境呢?
不论使用xml配置的方式或者注解的方式,底层代码使用到的方法和方式都是一样的
Spring与MyBatis注解方式整合有个重要的技术点就是@mport,第三方框架与Spring整合xml方式很多是凭借自定义标签完成的,而第三方框架与Spring整合注解方式很多是靠@Import注解完成的。
在主配置类(替代主xml配置文件)中去导入其他的内容
@Import可以导入如下三种类:
①: 普通的配置类
②: 实现ImportSelector接口的类
③: 实现ImportBeanDefinitionRegistrar接口的类
这是一个很简单的模拟AOP概念的一个demo代码
Object result = method.invoke(bean, args); // bean:指定要调用方法的对象;args:指定调用方法时传递的参数。
连接点是有机会被增强、真正被调用了且增强了的才能叫作切入点
切面: 增强方法和未增强方法的组合代码就是切面了,或者你可以理解成是代理类创建实例的代码。
我们之前写的AOP demo耦合度太高,因此我们需要使用配置文件的方式去解决两个问题:
①: 配置哪些包、哪些类、哪些方法需要被增强
②: 配置目标方法要被哪些通知方法所增强,在目标方法执行之前还是之后执行增强配置方式的设计、配置文件(注解)的解析工作,Spring已经帮我们封装好了
任意参数就是:有参
和无参
的方法都会被增强
配置时使用的标签:
最终通知有点try - catch - finally的那个意思
Spring的AOP也提供了注解方式配置,使用相应的注解替代之前的xml配置,xml配置AOP时,我们主要配置了三部分:目标类被Spring容器管理、通知类被Spring管理、通知与切点的织入 (切面),如下:
事务是开发中必不可少的东西,使用JDBC开发时,我们使用connnection对事务进行控制,使用MyBatis时,我们使用SalSession对事务进行控制,缺点显而易见,当我们切换数据库访问技术时,事务控制的方式总会变化Spring 就将这些技术基础上,提供了统一的控制事务的接口。Spring的事务分为: 编程式事务控制
和 声明式事务控制
事务就是SQL当中使用Begin、操作、commit、事务回滚管理数据库的一系列相关操作