Spring-IOC概述
# IoC是什么
Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
我们来深入分析一下:
- 谁控制谁,控制什么?
传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
- 为何是反转,哪些方面反转了?
有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
- 用图例说明一下?
传统程序设计下,都是主动去创建相关对象然后再组合起来:
当有了IoC/DI的容器后,在客户端类中不再主动去创建这些对象了,如图
# IoC能做什么
IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。
传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。
IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。
# IoC和DI是什么关系
控制反转是通过依赖注入实现的,其实它们是同一个概念的不同角度描述。通俗来说就是IoC是设计思想,DI是实现方式。
DI—Dependency Injection,即依赖注入:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
我们来深入分析一下:
- 谁依赖于谁?
当然是应用程序依赖于IoC容器;
- 为什么需要依赖?
应用程序需要IoC容器来提供对象需要的外部资源;
- 谁注入谁?
很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
- 注入了什么?
就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。
- IoC和DI有什么关系呢?
其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。通俗来说就是IoC是设计思想,DI是实现方式。
# IoC和DI使用问题小结
这里总结下实际开发中会遇到的一些问题:
# 为什么推荐构造器注入方式?
先来看看Spring在文档里怎么说:
The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state.
简单的翻译一下:这个构造器注入的方式能够保证注入的组件不可变,并且确保需要的依赖不为空。此外,构造器注入的依赖总是能够在返回客户端(组件)代码的时候保证完全初始化的状态。
下面来简单的解释一下:
- 依赖不可变:其实说的就是final关键字。
- 依赖不为空(省去了我们对其检查):当要实例化UserServiceImpl的时候,由于自己实现了有参数的构造函数,所以不会调用默认构造函数,那么就需要Spring容器传入所需要的参数,所以就两种情况:1、有该类型的参数->传入,OK 。2:无该类型的参数->报错。
- 完全初始化的状态:这个可以跟上面的依赖不为空结合起来,向构造器传参之前,要确保注入的内容不为空,那么肯定要调用依赖组件的构造方法完成实例化。而在Java类加载实例化的过程中,构造方法是最后一步(之前如果有父类先初始化父类,然后自己的成员变量,最后才是构造方法),所以返回来的都是初始化之后的状态。
所以通常是这样的
@Service
public class UserServiceImpl {
/**
* user dao impl.
*/
private final UserDaoImpl userDao;
/**
* init.
* @param userDaoImpl user dao impl
*/
public UserServiceImpl(final UserDaoImpl userDaoImpl) {
this.userDao = userDaoImpl;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
如果使用setter注入,缺点显而易见,对于IOC容器以外的环境,除了使用反射来提供它需要的依赖之外,无法复用该实现类。而且将一直是个潜在的隐患,因为你不调用将一直无法发现NPE的存在。
// 这里只是模拟一下,正常来说我们只会暴露接口给客户端,不会暴露实现。
UserServiceImpl userService = new UserServiceImpl();
userService.findUserList(); // -> NullPointerException, 潜在的隐患
2
3
循环依赖的问题:使用field注入可能会导致循环依赖,即A里面注入B,B里面又注入A:
public class A {
@Autowired
private B b;
}
public class B {
@Autowired
private A a;
}
2
3
4
5
6
7
8
9
如果使用构造器注入,在spring项目启动的时候,就会抛出:BeanCurrentlyInCreationException:Requested bean is currently in creation: Is there an unresolvable circular reference?从而提醒你避免循环依赖,如果是field注入的话,启动的时候不会报错,在使用那个bean的时候才会报错。
# Autowired、Resource、Inject有何区别?
@Autowired和@Resource以及@Inject等注解注入有何区别? 这时平时在开发中,或者常见的面试题。
# @Autowired
- Autowired注解源码
在Spring 2.5 引入了 @Autowired 注解
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
2
3
4
5
6
从Autowired注解源码上看,可以使用在下面这些地方:
@Target(ElementType.CONSTRUCTOR) #构造函数
@Target(ElementType.METHOD) #方法
@Target(ElementType.PARAMETER) #方法参数
@Target(ElementType.FIELD) #字段、枚举的常量
@Target(ElementType.ANNOTATION_TYPE) #注解
2
3
4
5
还有一个value属性,默认是true。
- 简单总结:
1、@Autowired是Spring自带的注解,通过AutowiredAnnotationBeanPostProcessor 类实现的依赖注入
2、@Autowired可以作用在CONSTRUCTOR、METHOD、PARAMETER、FIELD、ANNOTATION_TYPE
3、@Autowired默认是根据类型(byType )进行自动装配的
4、如果有多个类型一样的Bean候选者,需要指定按照名称(byName )进行装配,则需要配合@Qualifier。
指定名称后,如果Spring IOC容器中没有对应的组件bean抛出NoSuchBeanDefinitionException。也可以将@Autowired中required配置为false,如果配置为false之后,当没有找到相应bean的时候,系统不会抛异常
- 简单使用代码:
在字段属性上。
@Autowired
private HelloDao helloDao;
2
或者
private HelloDao helloDao;
public HelloDao getHelloDao() {
return helloDao;
}
@Autowired
public void setHelloDao(HelloDao helloDao) {
this.helloDao = helloDao;
}
2
3
4
5
6
7
8
或者
private HelloDao helloDao;
//@Autowired
public HelloServiceImpl(@Autowired HelloDao helloDao) {
this.helloDao = helloDao;
}
// 构造器注入也可不写@Autowired,也可以注入成功。
2
3
4
5
6
将@Autowired写在被注入的成员变量上,setter或者构造器上,就不用再xml文件中配置了。
如果有多个类型一样的Bean候选者,则默认根据设定的属性名称进行获取。如 HelloDao 在Spring中有 helloWorldDao 和 helloDao 两个Bean候选者。
@Autowired
private HelloDao helloDao;
2
首先根据类型获取,发现多个HelloDao,然后根据helloDao进行获取,如果要获取限定的其中一个候选者,结合@Qualifier进行注入。
@Autowired
@Qualifier("helloWorldDao")
private HelloDao helloDao;
2
3
注入名称为helloWorldDao 的Bean组件。@Qualifier("XXX") 中的 XX是 Bean 的名称,所以 @Autowired 和 @Qualifier 结合使用时,自动注入的策略就从 byType 转变成 byName 了。
多个类型一样的Bean候选者,也可以@Primary进行使用,设置首选的组件,也就是默认优先使用哪一个。
注意:使用@Qualifier 时候,如何设置的指定名称的Bean不存在,则会抛出异常,如果防止抛出异常,可以使用:
@Qualifier("xxxxyyyy")
@Autowired(required = false)
private HelloDao helloDao;
2
3
在SpringBoot中也可以使用@Bean+@Autowired进行组件注入,将@Autowired加到参数上,其实也可以省略。
@Bean
public Person getPerson(@Autowired Car car){
return new Person();
}
// @Autowired 其实也可以省略
2
3
4
5
# @Resource
- Resource注解源码
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource {
String name() default "";
// 其他省略
}
2
3
4
5
6
从Resource注解源码上看,可以使用在下面这些地方:
@Target(ElementType.TYPE) #接口、类、枚举、注解
@Target(ElementType.FIELD) #字段、枚举的常量
@Target(ElementType.METHOD) #方法
2
3
name 指定注入指定名称的组件。
- 简单总结:
1、@Resource是JSR250规范的实现,在javax.annotation包下
2、@Resource可以作用TYPE、FIELD、METHOD上
3、@Resource是默认根据属性名称进行自动装配的,如果有多个类型一样的Bean候选者,则可以通过name进行指定进行注入
- 简单使用代码:
@Component
public class SuperMan {
@Resource
private Car car;
}
2
3
4
5
按照属性名称 car 注入容器中的组件。如果容器中BMW还有BYD两种类型组件。指定加入BMW。如下代码:
@Component
public class SuperMan {
@Resource(name = "BMW")
private Car car;
}
2
3
4
5
name 的作用类似 @Qualifier
# @Inject
- Inject注解源码
@Target({ METHOD, CONSTRUCTOR, FIELD })
@Retention(RUNTIME)
@Documented
public @interface Inject {}
2
3
4
从Inject注解源码上看,可以使用在下面这些地方:
@Target(ElementType.CONSTRUCTOR) #构造函数
@Target(ElementType.METHOD) #方法
@Target(ElementType.FIELD) #字段、枚举的常量
2
3
- 简单总结:
1、@Inject是JSR330 (Dependency Injection for Java)中的规范,需要导入javax.inject.Inject jar包 ,才能实现注入
2、@Inject可以作用CONSTRUCTOR、METHOD、FIELD上
3、@Inject是根据类型进行自动装配的,如果需要按名称进行装配,则需要配合@Named;
- 简单使用代码:
指定加入BMW组件。
@Inject
@Named("BMW")
private Car car;
2
3
@Named 的作用类似 @Qualifier!
# 总结
1、@Autowired是Spring自带的,@Resource是JSR250规范实现的,@Inject是JSR330规范实现的
2、@Autowired、@Inject用法基本一样,不同的是@Inject没有required属性
3、@Autowired、@Inject是默认按照类型匹配的,@Resource是按照名称匹配的
4、@Autowired如果需要按照名称匹配需要和@Qualifier一起使用,@Inject和@Named一起使用,@Resource则通过name进行指定
# 参考文章
Inversion of Control Containers and the Dependency Injection pattern在新窗口打开 (opens new window)
https://www.iteye.com/blog/jinnianshilongnian-1413846
https://blog.csdn.net/qq_35634181/article/details/104276056
https://www.cnblogs.com/diandianquanquan/p/11518365.html