Spring底层之控制反转-IOC使用配置及原理

Spring底层之控制反转-IOC使用配置及原理,第1张

Spring底层之控制反转-IOC使用配置及原理 Spring bean是什么

IoC Container管理的是Spring Bean, 那么Spring Bean是什么呢?

Spring里面的bean就类似是定义的一个组件,而这个组件的作用就是实现某个功能的,这里所定义的bean就相当于给了你一个更为简便的方法来调用这个组件去实现你要完成的功能。

IOC是什么

Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。

问题

谁控制谁,控制了什么

传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。

为什么是反转,反转了什么

有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

使用图例说明

在传统设计中,相关对象都是主动创建然后再组合起来:

IOC容器下,客户端不在需要手动创建这些对象,而是从容器中获取

传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。

IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。

IOC与DI的关系

控制反转IOC是通过依赖注入DI实现的,换言之,IOC是设计思想,DI是实现方式

DI—Dependency Injection,即依赖注入:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

谁依赖于谁?

当然是应用程序依赖于IoC容器;

为什么需要依赖?

应用程序需要IoC容器来提供对象需要的外部资源;

谁注入谁?

很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;

注入了什么?

就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

IoC和DI由什么关系呢?

其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。通俗来说就是IoC是设计思想,DI是实现方式。

IOC配置的三种方式

XML配置

顾名思义,就是将bean的信息配置.xml文件里,通过Spring加载文件为我们创建bean。这种方式出现很多早前的SSM项目中,将第三方类库或者一些配置工具类都以这种方式进行配置,主要原因是由于第三方类不支持Spring注解。

优点: 可以使用于任何场景,结构清晰,通俗易懂缺点: 配置繁琐,不易维护,枯燥无味,扩展性差

Java配置

将类的创建交给我们配置的JavcConfig类来完成,Spring只负责维护和管理,采用纯Java创建方式。其本质上就是把在XML上的配置声明转移到Java配置类中

优点:适用于任何场景,配置方便,因为是纯Java代码,扩展性高,十分灵活缺点:由于是采用Java类的方式,声明不明显,如果大量配置,可读性比较差

注解配置

通过在类上加注解的方式,来声明一个类交给Spring管理,Spring会自动扫描带有@Component,@Controller,@Service,@Repository这四个注解的类,然后帮我们创建并管理,前提是需要先配置Spring的注解扫描器。

优点:开发便捷,通俗易懂,方便维护。缺点:具有局限性,对于一些第三方资源,无法添加注解。只能采用XML或JavaConfig的方式配置

DI依赖注入的三种方式

与上面的区别:
IOC配置是配置bean的方式,而依赖注入实现是这些IOC配置是如何具体执行

相当于 IOC配置是面向客户端的上层接口,依赖注入实现是面向下层的 *** 作
(就比如xml,java注解,java配置三种IOC配置都能使用Setter注入的方式注入)

setter方式 在XML配置方式中:

property都是setter方式注入,比如下面的xml:



    
    
        
        
    
    

本质上包含两步:

    第一步,需要new UserServiceImpl()创建对象, 所以需要默认构造函数第二步,调用setUserDao()函数注入userDao的值, 所以需要setUserDao()函数

public class UserServiceImpl {

    
    private UserDaoImpl userDao;

    
    public UserServiceImpl() {
    }

    
    public List findUserList() {
        return this.userDao.findUserList();
    }

    
    public void setUserDao(UserDaoImpl userDao) {
        this.userDao = userDao;
    }
}
在注解和Java配置方式下:
public class UserServiceImpl {

    
    private UserDaoImpl userDao;

    
    public List findUserList() {
        return this.userDao.findUserList();
    }

    
    @Autowired
    public void setUserDao(UserDaoImpl userDao) {
        this.userDao = userDao;
    }
}
构造函数

通过调用构造函数注入属性

 @Service
public class UserServiceImpl {

    
    private final UserDaoImpl userDao;

    
    @Autowired // 这里@Autowired也可以省略
    public UserServiceImpl(final UserDaoImpl userDaoImpl) {
        this.userDao = userDaoImpl;
    }

    
    public List findUserList() {
        return this.userDao.findUserList();
    }

}
注解注入



以@Autowired(自动注入)注解注入为例,修饰符有三个属性:Constructor,byType,byName。默认按照byType注入。

constructor:通过构造方法进行自动注入,spring会匹配与构造方法参数类型一致的bean进行注入,如果有一个多参数的构造方法,一个只有一个参数的构造方法,在容器中查找到多个匹配多参数构造方法的bean,那么spring会优先将bean注入到多参数的构造方法中。byName:被注入bean的id名必须与set方法后半截匹配,并且id名称的第一个单词首字母必须小写,这一点与手动set注入有点不同。byType:查找所有的set方法,将符合符合参数类型的bean注入。

@Service
public class UserServiceImpl {

    
    @Autowired
    private UserDaoImpl userDao;

    
    public List findUserList() {
        return userDao.findUserList();
    }

}
使用中的问题 为什么推荐使用构造器注入

Spring文档原文:
这个构造器注入的方式能够保证注入的组件不可变,并且确保需要的依赖不为空。此外,构造器注入的依赖总是能够在返回客户端(组件)代码的时候保证完全初始化的状态。


依赖不可变:其实说的就是final关键字。依赖不为空(省去了我们对其检查):当要实例化UserServiceImpl的时候,由于自己实现了有参数的构造函数,所以不会调用默认构造函数,那么就需要Spring容器传入所需要的参数,所以就两种情况:1、有该类型的参数->传入,OK 。2:无该类型的参数->报错。(在对象构建阶段就报错,而不是等到调用的时候去报空指针)完全初始化的状态:这个可以跟上面的依赖不为空结合起来,向构造器传参之前,要确保注入的内容不为空,那么肯定要调用依赖组件的构造方法完成实例化。而在Java类加载实例化的过程中,构造方法是最后一步(之前如果有父类先初始化父类,然后自己的成员变量,最后才是构造方法),所以返回来的依赖都是初始化之后的状态。

 @Service
public class UserServiceImpl {

    
    private final UserDaoImpl userDao;

    
    public UserServiceImpl(final UserDaoImpl userDaoImpl) {
        this.userDao = userDaoImpl;
    }

}


循环依赖的问题:使用字段注入可能会导致循环依赖,即A里面注入B,B里面又注入A:

public class A {
    @Autowired
    private B b;
}

public class B {
    @Autowired
    private A a;
}

Setter潜在的隐患

UserServiceImpl userService = new UserServiceImpl();
userService.findUserList(); // -> NullPointerException, 潜在的隐患

若在IOC容器之外的地方,无法直接复用该实现类,
当你new 一个service时系统不会提醒你这个时候,依赖的dao还是空的,除非到真正调用,否则一直无法发现空指针的问题

@Autowired和@Resource以及@Inject等注解注入有何区别? @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;
}

从Autowired注解源码上看,可以使用在下面这些地方:

@Target(ElementType.CONSTRUCTOR) #构造函数
@Target(ElementType.METHOD) #方法
@Target(ElementType.PARAMETER) #方法参数
@Target(ElementType.FIELD) #字段、枚举的常量
@Target(ElementType.ANNOTATION_TYPE) #注解

简单总结:

    @Autowired是Spring自带的注解,通过AutowiredAnnotationBeanPostProcessor 类实现的依赖注入@Autowired可以作用在CONSTRUCTOR、METHOD、PARAMETER、FIELD、ANNOTATION_TYPE@Autowired默认是根据类型(byType )进行自动装配的如果有多个类型一样的Bean候选者,需要指定按照名称(byName )进行装配,则需要配合@Qualifier。指定名称后,如果Spring IOC容器中没有对应的组件bean抛出NoSuchBeanDefinitionException。也可以将@Autowired中required配置为false,如果配置为false之后,当没有找到相应bean的时候,系统不会抛异常

@Resource
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource {
    String name() default "";
    // 其他省略
}
  

从Resource注解源码上看,可以使用在下面这些地方:


@Target(ElementType.TYPE) #接口、类、枚举、注解
@Target(ElementType.FIELD) #字段、枚举的常量
@Target(ElementType.METHOD) #方法



简单总结:

    @Resource是JSR250规范的实现,在javax.annotation包下@Resource可以作用TYPE、FIELD、METHOD上@Resource是默认根据属性名称进行自动装配的,如果有多个类型一样的Bean候选者,则可以通过name进行指定进行注入

@Inject
@Target({ METHOD, CONSTRUCTOR, FIELD })
@Retention(RUNTIME)
@documented
public @interface Inject {}

从Inject注解源码上看,可以使用在下面这些地方:


@Target(ElementType.CONSTRUCTOR) #构造函数
@Target(ElementType.METHOD) #方法
@Target(ElementType.FIELD) #字段、枚举的常量



简单总结:

    @Inject是JSR330 (Dependency Injection for Java)中的规范,需要导入javax.inject.Inject jar包 ,才能实现注入@Inject可以作用CONSTRUCTOR、METHOD、FIELD上@Inject是根据类型进行自动装配的,如果需要按名称进行装配,则需要配合@Named;


总结
    @Autowired是Spring自带的,@Resource是JSR250规范实现的,@Inject是JSR330规范实现的@Autowired、@Inject用法基本一样,不同的是@Inject没有一个request属性@Autowired、@Inject是默认按照类型匹配的,@Resource是按照名称匹配的@Autowired如果需要按照名称匹配需要和@Qualifier一起使用,@Inject和@Named一起使用,@Resource则通过name进行指定

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5717323.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存