目录
BeanFactory和ApplicationContext的区别
@Autowired @Qualifier @Resource总结
Spring创建了单例对象,如果多线程并发对属性赋值,造成相互覆盖的情况,如何处理。
@Component, @Repository, @Service的区别
注意:本文参考
初始化Spring Bean:Bean初始化有哪些方式?_码农架构-CSDN博客_springbean初始化方式
@Component, @Repository, @Service的区别_fansili的博客-CSDN博客_@service和@compent区别
谈谈自己对于 Spring IoC 的了解
IoC(Inverse of Control:控制反转) 是一种设计思想,而不是一个具体的技术实现。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。不过, IoC 并非 Spring 特有,在其他语言中也有应用。
什么是IOC
IoC - Inversion of Control, 控制反转。
控制反转就是把创建和管理 bean 的过程转移给了第三方。而这个第三方,就是 Spring IoC Container,对于 IoC 来说,最重要的就是容器。
容器负责创建、配置和管理 bean,也就是它管理着 bean 的生命,控制着 bean 的依赖注入。
通俗点讲,因为项目中每次创建对象是很麻烦的,所以我们使用 Spring IoC 容器来管理这些对象,需要的时候你就直接用,不用管它是怎么来的、什么时候要销毁,只管用就好了。
举个例子,就好像父母没时间管孩子,就把小朋友交给托管所,就安心的去上班而不用管孩子了。托儿所,就是第三方容器,负责管理小朋友的吃喝玩乐;父母,相当于程序员,只管接送孩子,不用管他们吃喝。
等下,bean
又是什么?
Bean 其实就是包装了的 Object,无论是控制反转还是依赖注入,它们的主语都是 object,而 bean 就是由第三方包装好了的 object。(想一下别人送礼物给你的时候都是要包装一下的,自己造的就免了。
Bean 是 Spring 的主角,有种说法叫 Spring 就是面向 bean 的编程(Bean Oriented Programming, BOP)。
深入理解 IoC
这里用经典 class Rectangle
来举例:
两个变量:长和宽
自动生成 set()
方法和 toString()
方法
注意 :一定要生成 set()
方法,因为 Spring IoC 就是通过这个 set()
方法注入的;toString()
方法是为了我们方便打印查看。
public class Rectangle {
private int width;
private int length;
public Rectangle() {
System.out.println("Hello World!");
}
public void setWidth(int widTth) {
this.width = widTth;
}
public void setLength(int length) {
this.length = length;
}
@Override
public String toString() {
return "Rectangle{" +
"width=" + width +
", length=" + length +
'}';
}
}
然后在 test
文件中手动用 set()
方法给变量赋值。
嗯,其实这个就是「解藕」的过程!
public class MyTest {
@Test
public void myTest() {
Rectangle rect = new Rectangle();
rect.setLength(2);
rect.setWidth(3);
System.out.println(rect);
}
}
其实这就是 IoC 给属性赋值的实现方法,我们把「创建对象的过程」转移给了 set()
方法,而不是靠自己去 new
,就不是自己创建的了。
这里我所说的“自己创建”,指的是直接在对象内部来 new
,是程序主动创建对象的正向的过程;这里使用 set()
方法,是别人(test)给我的;而 IoC 是用它的容器来创建、管理这些对象的,其实也是用的这个 set()
方法,不信,你把这个这个方法去掉或者改个名字试试?
几个关键问题:
何为控制,控制的是什么?
答:是 bean 的创建、管理的权利,控制 bean 的整个生命周期。
何为反转,反转了什么?
答:把这个权利交给了 Spring 容器,而不是自己去控制,就是反转。由之前的自己主动创建对象,变成现在被动接收别人给我们的对象的过程,这就是反转。
举个生活中的例子,主动投资和被动投资。
自己炒股、选股票的人就是主动投资,主动权掌握在自己的手中;而买基金的人就是被动投资,把主动权交给了基金经理,除非你把这个基金卖了,否则具体选哪些投资产品都是基金经理决定的。
依赖注入
回到文档中,第二句话它说:IoC is also known as DI
.
我们来谈谈 dependency injection
- 依赖注入。
何为依赖,依赖什么?
程序运行需要依赖外部的资源,提供程序内对象的所需要的数据、资源。
何为注入,注入什么?
配置文件把资源从外部注入到内部,容器加载了外部的文件、对象、数据,然后把这些资源注入给程序内的对象,维护了程序内外对象之间的依赖关系。
所以说,控制反转是通过依赖注入实现的。但是你品,你细品,它们是有差别的,像是「从不同角度描述的同一件事」
:
IoC 是设计思想,DI 是具体的实现方式;
IoC 是理论,DI 是实践;
从而实现对象之间的解藕。
当然,IoC 也可以通过其他的方式来实现,而 DI 只是 Spring 的选择。
IoC 和 DI 也并非 Spring 框架提出来的,Spring 只是应用了这个设计思想和理念到自己的框架里去。
为何要用IoC
那么为什么要用 IoC 这种思想呢?换句话说,IoC 能给我们带来什么好处?
答:解藕。
它把对象之间的依赖关系转成用配置文件来管理,由 Spring IoC Container 来管理。
在项目中,底层的实现都是由很多个对象组成的,对象之间彼此合作实现项目的业务逻辑。但是,很多很多对象紧密结合在一起,一旦有一方出问题了,必然会对其他对象有所影响,所以才有了解藕的这种设计思想。
如上图所示,本来 ABCD 是互相关联在一起的,当加入第三方容器的管理之后,每个对象都和第三方法的 IoC 容器关联,彼此之间不再直接联系在一起了,没有了耦合关系,全部对象都交由容器来控制,降低了这些对象的亲密度,就叫“解藕”。
IoC 容器
既然说容器是 IoC 最重要的部分,那么 Spring 如何设计容器的呢?
使用 ApplicationContext
,它是 BeanFactory
的子类,更好的补充并实现了 BeanFactory
的。
BeanFactory
简单粗暴,可以理解为 HashMap:
Key - bean name
Value - bean object
但它一般只有 get, put 两个功能,所以称之为“低级容器”。
而 ApplicationContext
多了很多功能,因为它继承了多个接口,可称之为“高级容器”。在下文的搭建项目中,我们会使用它。
ApplicationContext
的里面有两个具体的实现子类,用来读取配置配件的:
ClassPathXmlApplicationContext
- 从 class path 中加载配置文件,更常用一些;
FileSystemXmlApplicationContext
- 从本地文件中加载配置文件,不是很常用,如果再到 Linux 环境中,还要改路径,不是很方便。
当我们点开 ClassPathXmlApplicationContext
时,发现它并不是直接继承 ApplicationContext
的,它有很多层的依赖关系,每层的子类都是对父类的补充实现。
而再往上找,发现最上层的 class 回到了 BeanFactory
,所以它非常重要。
要注意,Spring 中还有个 FactoryBean
,两者并没有特别的关系,只是名字比较接近,所以不要弄混了顺序。
为了好理解 IoC,我们先来回顾一下不用 IoC 时写代码的过程。
BeanFactory和ApplicationContext的区别
BeanFactory:
是Spring里面最低层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能;
ApplicationContext:
应用上下文,继承BeanFactory接口,它是Spring的一各更高级的容器,提供了更多的有用的功能;
1) 国际化(MessageSource)
2) 访问资源,如URL和文件(ResourceLoader)
3) 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
4) 消息发送、响应机制(ApplicationEventPublisher)
5) AOP(拦截器)
两者装载bean的区别
BeanFactory:
BeanFactory在启动的时候不会去实例化Bean,从容器中拿Bean的时候才会去实例化;
ApplicationContext:
ApplicationContext在启动的时候就把所有的Bean全部实例化了。它还可以为Bean配置lazy-init=true来让Bean延迟实例化;
我们该用BeanFactory还是ApplicationContent
延迟实例化的优点:(BeanFactory)
应用启动的时候占用资源很少;对资源要求较高的应用,比较有优势;
不延迟实例化的优点: (ApplicationContext)
1. 所有的Bean在启动的时候都加载,系统运行的速度快;
2. 在启动的时候所有的Bean都加载了,我们就能在系统启动的时候,尽早的发现系统中的配置问题
3. 建议web应用,在启动的时候就把所有的Bean都加载了。(把费时的操作放到系统启动中完成)
BeanFactory 与 FactoryBean的区别?
BeanFactory 是 IoC 底层容器 ,FactoryBean 是 创建 Bean 的一种方式,帮助实现复杂的初始化逻辑
依赖注入的方式
依赖注入只是把bean添加到IOC容器的一种方式。
从依赖注入的方式来说整体可以分为两大类来处理,一种是手动方式,一种是自动方式。
手动方式:
1 XML 资源配置元信息(比较常见)
2 Java 注解配置元信息 (比较常见)
3 API 配置元信息(不太常用)
自动方式:
1 Autowiring
依赖注入的方式有上面的两种,但是也可按注入的类型来区分:
1 Setter注入
2 构造器注入
3 接口注入
4 方法注入
5 静态工厂
6 实例工厂
自动装配
聊到依赖注入那么首先需要先聊聊 Autowiring Modes自动绑定模式
Spring的官方文档中对Autowiring Modes解释是:
Spring 容器可以自动装配协作 bean 之间的关系。通过检查 ApplicationContext 的内容,您可以让 Spring 自动为您的 bean 解析协作者(其他 bean)
同时也提出了4种自动装配模式
no:(默认)无自动装配。Bean 引用必须由ref
元素定义。对于大型部署,建议不要更改默认设置,因为明确指定协作者可以提供更好的控制和清晰度。在某种程度上,它记录了系统的结构。
byName:按属性名称自动查找。Spring 寻找与需要自动装配的属性同名的 bean。例如,如果一个 bean 定义被设置为按名称自动装配,并且包含一个master
属性(即,它具有setMaster(..)
方法),那么 Spring 将查找一个名为master
的 bean 定义并使用它来设置属性。
byType:如果容器中恰好存在一个该属性类型的 bean,则使该属性自动装配。如果存在多个错误,则会引发致命异常,这表明您可能不对该 bean 使用byType
自动装配。如果没有匹配的 bean,则什么也不会发生(未设置该属性)。
constructor:类似于byType
,但适用于构造函数参数。如果容器中不存在构造函数参数类型的一个 bean,则将引发致命错误。
虽然官方文档提出了Autowiring自动绑定方式,但是在我们的真实的业务场景中,相对来说是用的比较少的,因为它有一定的局限性,而且Spring官方文档中也列出了其中的不足点。
自动装配的局限性和缺点(官方文档链接)
property
和constructor-arg
设置中的显式依赖项始终会覆盖自动装配。您不能自动连接简单属性,例如基元,Strings
和Classes
(以及此类简单属性的数组)。此限制是设计使然 PS:针对这种情况可以通过另外的一种方式@value等进行转化来处理这个场景。
自动装配不如显式接线精确。尽管如前所述,Spring 还是小心避免在可能产生意外结果的模棱两可的情况下进行猜测。SpringManagement 的对象之间的关系不再明确记录。
容器内的多个 bean 定义可能与要自动装配的 setter 方法或构造函数参数指定的类型匹配。对于数组,集合或Map
实例,这不一定是问题。但是,对于需要单个值的依赖项,不会任意解决此歧义。如果没有唯一的 bean 定义可用,则引发异常。
说完这么多文档的基础知识,那么接下来就是开始demo测试环节,来加深理解一下上面的说的那么多到底是个啥。
Setter
先从注入的类型先分析怎么样的一种方式叫Setter
方式注入
/构建一个测试Service
public class SetterServiceInjection {
public void testMethod(String param) {
System.out.println(param);
}
}
public class SetterServiceInjectionTest {
private SetterServiceInjection setterServiceInjection;
// Setter方式注入
public void setSetterServiceInjection(SetterServiceInjection setterServiceInjection) {
this.setterServiceInjection = setterServiceInjection;
}
public void testMethod(){
setterServiceInjection.testMethod("Setter方式注入");
}
// 测试启动demo
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("class