SpringBoot系列: 理解 Spring 的依赖注入(二)

==============================
Spring 容器中 Bean 的名称
==============================
声明 bean 有两个方式, 一个是 @Bean, 另一个是 @Component 和它的子类 (包括 @Service/@Controller/@Repository/@Configuration), Spring 容器中 bean 名生成规则分两大类, 分别是:

一. @Component 和它的子注解是用来注解 Class 的. 这些注解标注 Bean 的命名规则是:
1. 如果 @Component 指定了参数值的话, 参数值就是 bean 名称.
2. 如果 @Component 未指定参数值的话, bean 名就要看被注解的类名, 如果类名开头是两个或两个大写字母, bean 名同类名完全一致; 如果开头只有一个大写字母, bean 名是类名首字母小写版.

二. @Bean 注解往往用来标注一个函数, @Bean 注解标注 Bean 的命名规则是:
1. 如果 @Bean 指定了 name 参数, 以参数为准.
2. 未指定 name 参数的情况下, 以方法名作为 bean 的名称.

==============================
Bean 作用域
==============================
参考: https://www.jianshu.com/p/502c40cc1c41
Bean 常用的作用域有下面 5 类,
@Scope("singleton")
@Scope("prototype")
@Scope("request")
@Scope("session")
@Scope("global-session")

1. @Scope("singleton") 标注的 bean, 表示在 Spring 容器中只创建一个 bean 实例, 一般情况下在应用程序启动的时候, 就已经完成 bean 实例化了. 在程序运行过程中, 每次调用这个 bean 将使用同一个实例. 这也是 Spring 默认的 scope.
2. @Scope("prototype") 标注的 bean, 每次获取这个 bean 都创建一个新的实例.
3. @Scope("request"), 仅适用于 Web 项目, 给每个 http request 新建一个 Bean 实例.
4. @Scope("session"), 仅适用于 Web 项目, 给每个 http session 新建一个 Bean 实例.
5. @Scope("global-session"), 仅适用于 portal Web 项目, 给每个 global http session 新建一个 Bean 实例.

singleton bean 默认都是在容器创建后即初始化, 基本上等同于启动一个 JVM 后即创建 bean 实例, 如果应用很关注启动时间, 可以在声明 bean 的时候再加一个 @Lazy 注解, bean 实例化将延迟到第一次获取 bean 对象时. 一般情况下, 强烈建议所有的 Singleton bean 不要启用延迟加载, 这样就能在程序启动的时候发现问题.

==============================
不同 scope bean 的线程安全性问题
==============================
Spring 默认的 scope 时 singleton, 因为不需要频繁的对象创建和内存回收, 性能最好, 但需要注意线程安全问题.
prototype 类型的 bean, 每次注入的都是一个全新对象, 显然没有线程安全问题.
request 和 session 对象, 除非我们在同一个 request 或 session 中使用了多线程, 需要注意一下线程安全问题, 在绝大多数情形下, 这两个 scope 类型的 bean 是线程安全的, 同时因为 bean 是有状态的, 方便了 web 应用开发.

对于 singleton 类型的 bean, 如何做到线程安全呢? 一般有两个方法:
1. bean 类中压根不定义成员变量, 这样就杜绝了线程不安全的隐患.
2. 如果 bean 类必须有成员变量, 一定要将成员变量加到 ThreadLocal 中.
或者, 干脆将 singleton scope 改成 prototype.

==============================
Bean 对象的初始化钩子
==============================
设想如下场景:
1. 我们需要注入一个 bean 对象, 并要对该 bean 对象做一些特别设置, 而这个 bean 对象类的代码又不归我们管.
2. 对于 Boss 这个对象, 注入很多种 bean 对象 (比如 car/office 等), 如何在一处代码中集中为 car/office 对象做一些特别设置?

显然这些场景, 无法通过 bean 类的构造子实现, Spring 项目中, 我们可以使用下面两种方式实现:
1. Spring 提供的 @Bean 注解有 initMethod 和 destroyMethod 属性.
2. JSR-250 定义的标准有 @PostConstruct 以及 @PreDestroy 注解, 这两个注解一般和 @Component 搭配使用.

==============================
@Autowired Bean 注入
==============================
@Autowired 可以对类成员变量、方法和构造函数加注解, 完成自动装配任务.

1. 在类成员变量上加标注, 可以省去 setter 方法.

public class Boss {
    @Autowired
    private Car car;
}

2. 在类的方法上加标注, Spring 自动完成"所有 bean"实参装配.

public class Boss {
    private Car car;
    private Office office;

    @Autowired
    public void Init(Car car, Office office) {
        this.car = car;
        this.office = office ;
    }
}

3. 在类的构造函数上加标注, Spring 自动完成"所有 bean"实参装配.

public class Boss {
    private Car car;
    private Office office;

    @Autowired
    public Boss(Car car, Office office) {
        this.car = car;
        this.office = office ;
    }
}

4. @Autowired 注解标在集合上或数组上的含义: 是用来获取同一个接口的多个实现.
摘自 https://www.chkui.com/article/spring/spring_core_auto_inject_of_annotation

interface A {}

@Component
class implA1 implements A{}

@Component
class implA2 implements A{}

class MyClass {
    @Autowired
    private A[] a;
    @Autowired
    private Set<A> set;
    @Autowired
    private Map<String, A> map;
}

使用 Map 时,key 必须声明为 String,在运行时会 key 是注入 Bean 的 name/id.

==============================
JSR-250/JSR-330 的 Bean 注入
==============================
JSR-250 标准的注入注解是 @Resource.
JSR-330 标准的注入注解是 @Inject.
Spring 专有的注入注解是 @Autowired.

@Inject 注解和 @Autowired 几乎一样, 其功能比 @Autowired 稍弱一些 (比如没有 required 属性), 所以不推荐使用.
在实际项目中, 经常使用到的是 @Autowired 和 @Resource.
@Autowired 默认是 byType 注入的, 而 @Resource 本身有 name 和 type 属性, 默认是按照 byName 注入的.

@Autowired 和 @Inject 注入 bean 对象的执行路径是:
1.Match by Type : 优先 byType 注入
2.Restricts by Qualifier : 依照 @Qualifier 指定的 name 来注入, 但如果没有注入成功, 直接报错.
3.Match by Name : 最后按照默认的 name 注入.

@Resource 注入 bean 对象的执行路径是:
1. 如果同时指定了 name 和 type, 则在容器中找 name 和 type 都匹配的 bean 进行装配, 找不到则抛出异常.
2. 如果指定了 name, 则在容器中查找名称 (id) 匹配的 bean 进行装配, 找不到则抛出异常.
3. 如果指定了 type, 则在容器中找到类型匹配的唯一 bean 进行装配, 找不到或者找到多个, 都会抛出异常.
4. 如果既没有指定 name,又没有指定 type 时, 将按照下面执行路径:
4.1 按照默认的名字注入.
4.2 按照类型注入.
4.3 依照 @Qualifier 的修饰来注入.

参考:
http://javainsimpleway.com/autowired-resource-and-inject-2/
http://einverne.github.io/post/2017/08/autowired-vs-resource-vs-inject.html

==============================
@Primary 解决多个子类无法注入问题
==============================
有时候在我们的项目中, 同一个接口可能会有多个实现 (或子类), 在使用 @Autowired 注入 bean 实例时, 会报错. 原因很简单, @Autowired 默认是按照 type 注入的, 现在有多个子类, Spring IoC 容器不知道该如何注入了.

解决方式式在声明 Bean 的时候, 可以再多加一个 @Primary 注解. @Primary 既可和 @Component 搭配使用, 也可以和 @Bean 搭配使用.

@Component
@Primary
public class BenzCar implements ICar {
    public void run() {
        System.out.println("Benz run");
    }

    @Override
    public String toString() {
        return "Brand: Benz, price:1000";
    }
}

@Component
public class VWCar implements ICar {
    public void run() {
        System.out.println("VW run");
    }

     @Override
    public String toString() {
        return "Brand: Volkwargon, price:1000";
    }
}

@Component
public class Boss {
     @Autowired
    private ICar car;
}

@Configuration
@ComponentScan("javaTestMaven.demo2")
public class SomeConfig {
}

public class App {
     public static void main(String[] args) {
        App app=new App();
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(SomeConfig.class);
        context.refresh();
        Boss boss = context.getBean(Boss.class);
        System.out.println(boss);
        context.close();
    }
}

==============================
@Qualifier 的使用
==============================
上个例子中, 使用了 @Primary 注解来解决多个子类无法注入的情况, 其实 Spring 还提供其他办法, @Qualifier 注解就是其中之一.
一旦加上了 @Qualifier 之后, @Autowired 在 byType 注入失败后, 将按照 @Qualifier 设定的参数来注入. @Qualifier 可以和 @Autowired/ @Resource/ @Inject 一起使用, 但多数情况下是和 @Autowired 一起使用.

@Autowired 的标注对象是成员变量、方法、构造函数.
@Qualifier 的标注对象是成员变量、方法入参、构造函数入参.
上面表述看上去是好像一样, 其实是有差别的, 重点是两个注解修饰的主体是不同的, 以注解方法为例, @Qualifier 修饰的是单个实参, 而 @Autowired 修饰的是方法体. 如果该方法有多个 bean 类的形式参数, 每一个参数都可使用 @Qualifier 修饰.

下面是一个 @Qualifier 用在构造参数实参的示例.

public class Boss {
    private Car car;
    private Office office;

    @Autowired
    public Boss(Car car,  @Qualifier("office") Office office){
        this.car = car;
        this.office = office ;
    }
}

==============================
@Conditional 实现条件注入
==============================
@Primary 的缺点: 通过类型方式将接口和某个具体实现绑定死了, 好处:我们可以随时切换 Primary 类, 达到切换具体实现.
@Qualifier 的缺点: 通过名称方式将接口和具体实现的名称绑定死了, 好处:我们可以随时调整名称, 达到切换具体实现.
但总体来讲 @Primary 和 @Qualifier 都有点 hard code 味道, 有什么更好的方案呢? 答案就是 @Conditional, 使用起来稍微复杂些, Spring Boot 中大量使用了 @Conditional 注解, 比如多 profile 环境.

首先我们需要实现 Spring 的 Condition 接口, 然后在 Config 类中使用 @Conditional + @Bean 组合声明 bean 类型. 加上 @Conditional 之后, 将在 bean 实例化时按实际的条件 evaluate 来确定使用哪个子类.

public class LinuxCondition implements Condition {
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        boolean result=context.getEnvironment().getProperty("os.name").contains("Linux");
        return result ;
    }
}

public class WindowsCondition implements Condition {
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        boolean result=context.getEnvironment().getProperty("os.name").contains("Windows");
        return result ;
    }
}

@Configuration
@ComponentScan("javaTestMaven.demo2")
public class SomeConfig {
    @Bean
    @Conditional(LinuxCondition.class)
    public ICar getBenzVWCar() {
        return new VWCar();
    }

    @Bean
    @Conditional(WindowsCondition.class)
    public ICar getBenzCar() {
        return new BenzCar();
    }
}

public class App {
     public static void main(String[] args) {
        App app=new App();
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(SomeConfig.class);
        context.refresh();
        ICar car = context.getBean(ICar.class);
        System.out.println(car);
        context.close();
    }
}

==============================
Spring 的其他注解
==============================
一. @Autowired(required=false)
@Autowired() 如果不指定 required 属性, 相当于 required 属性为 true, 意思是被注入的对象不能为 null, 如果设定 required=false, 被注入的对象可以为 null.
因为 @Autowired(required=false) 容许注入 null, 会给程序带来潜在的问题, 所以仅仅在开发和测试阶段使用, 比如被依赖的类还没被声明成 bean.

二. @Required
依赖注入主要是两种方式, 通过构造子注入或通过 Setter 函数注入, 一般地我们会将必需的依赖项通过构造子注入,对于非必需的依赖项通过 Setter 函数注入.
Spring 提供了 @Required 注解, 可以将某个 Setter 注入上升到必需级别.
@Required 用来注解 Setter 方法, 只适用于基于 XML 配置的 setter 注入方式, 效果和 @Autowired(required=true) 一样, 推荐使用后者.

三. @DependsOn
直接依赖关系推荐使用构造子和 Setter 函数设置, 但对于没有直接依赖的对象或依赖关系不明显, 可以使用 @DependsOn.

四. @Order
@Order 一般用在控制多个子类的 bean 对象实例化的顺序, @Order() 注解的参数值越小, 实例化越早.
在下面例子中, lst 中的第一个元素是 implA2 对象, 第二个元素是 implA1 对象.

interface A {}

@Component
@Order(2)
class implA1 implements A{}

@Component
@Order(1)
class implA2 implements A{}

class MyClass {
    @Autowired
    private List<A> lst;
}

===============================
参考
===============================
https://www.chkui.com/article/spring/spring_core_auto_inject_of_annotation
https://www.ibm.com/developerworks/cn/java/j-lo-spring25-ioc/
https://www.ibm.com/developerworks/cn/java/j-lo-spring25-mvc/
https://www.baeldung.com/spring-autowire

原文地址:https://www.cnblogs.com/harrychinese/p/spring_ioc2.html

时间: 2024-10-27 18:44:00

SpringBoot系列: 理解 Spring 的依赖注入(二)的相关文章

二、Spring的依赖注入

Spring的依赖注入 1.理解依赖注入 (1)A对象需要调用B对象的方法,这种情形被称为依赖注入,即A对象依赖B对象:依赖注入(DI)也被成为控制反转(IoC): (2)依赖注入的两种方式: 1)设值注入:IoC容器通过使用成员变量的setter方法来注入被依赖对象: 2)构造注入:IoC容器通过使用构造器来注入被依赖的对象: 2.设置注入 (1)Bean与Bean之间的依赖关系由Spring管理,Spring采用setter方法为目标Bean注入所需要的值,这种注入方式被称为设值注入: (2

Spring IoC 依赖注入(二)源码分析

目录 Spring IoC 依赖注入(二)源码分析 1. 依赖注入口 - populateBean 1.1 doCreateBean 1.2 populateBean 2. 手动注入 2.1 相关的类说明 2.2 applyPropertyValues 2.3 BeanDefinitionValueResolver 2.4 依赖检查 2. 自动注入 2.1 那些字段会自动注入 2.2 名称注入 2.3 类型注入 Spring IoC 依赖注入(二)源码分析 本章主要分析 Spring IoC 依

(spring-第3回)spring的依赖注入-属性、构造函数、工厂方法等的注入

Spring要把xml配置中bean的属性实例化为具体的bean,"依赖注入"是关卡.所谓的"依赖注入",就是把应用程序对bean的属性依赖都注入到spring容器中,由spring容器实例化bean然后交给程序员.spring的依赖注入有属性注入.构造函数注入.工厂方法注入等多种方式,下面用几个简单的栗子来一一道来. 一.首先是属性注入: 代码001 1 <?xml version="1.0" encoding="UTF-8&q

Spring BeanFactory 依赖注入

Spring BeanFactory 依赖注入 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) 一.autowire 五种注入方式测试 (1) 环境准备 public class Company { private Department department; private List<Employee> employees; public Company() { } public Company(Departmen

第二十七天 春之细雨润物于无形 —Spring的依赖注入

6月11日,晴."夏条绿已密,朱萼缀明鲜.炎炎日正午,灼灼火俱燃." IT人习惯把具体的事物加工成的形状一致的类,正是这样的一致,加上合适的规范,才能彰显对象筋道的牙感和bean清香的味道.Spring比谁都清楚OO的奥妙,让组件之间的依赖关系由容器在运行时期决定,称作依赖注入(Dependency Injection). 下面用一通俗的例子,一探依赖注入奥妙. 设计模式中的一个原则:针对接口编程,不要针对实现编程. 一.设计两个接口: (1)奶制品接口-MilkProductInte

Spring的依赖注入(DI)三种方式

Spring依赖注入(DI)的三种方式,分别为: 1.  接口注入 2.  Setter方法注入 3.  构造方法注入 下面介绍一下这三种依赖注入在Spring中是怎么样实现的. 首先我们需要以下几个类: 接口 Logic.java 接口实现类 LogicImpl.java 一个处理类 LoginAction.java 还有一个测试类 TestMain.java Logic.java如下: package com.spring.test.di; public interface Logic {

关于Spring IOC (依赖注入)你需要知道的一切

[版权申明]未经博主同意,不允许转载!(请尊重原创,博主保留追究权) http://blog.csdn.net/javazejian/article/details/54561302 出自[zejian的博客] <Spring入门经典>这本书无论对于初学者或者有经验的工程师还是很值一看的,最近花了点时间回顾了Spring的内容,在此顺带记录一下,本篇主要与spring IOC相关 ,这篇博文适合初学者也适合spring有过开发经验的工程师,前者可用于全面了解Spring IOC的知识点,后者且

Spring.NET依赖注入框架学习--入门

Spring.NET依赖注入框架学习--入门 在学些Spring.net框架之前,有必要先脑补一点知识,比如什么是依赖注入?IOC又是什么?控制反转又是什么意思?它们与Spring.net又有什么关系 带着问题,我们一起来看看下面内容(适合刚刚学习或者对依赖注入还太懂的小神看---大神直接飘过) 对以上几个问题都滚瓜烂熟的直接跳下一篇 这里我找到一篇我认为比较好的博文   原地址:http://www.cnblogs.com/jhli/p/6019895.html ---感谢博主分享 1. Io

Spring.NET依赖注入框架学习--概述

Spring.NET依赖注入框架学习--Spring.NET简介 概述 Spring.NET是一个应用程序框架,其目的是协助开发人员创建企业级的.NET应用程序.它提供了很多方面的功能,比如依赖注入.面向方面编程(AOP).数据访问抽象及ASP.NET扩展等等.Spring.NET以Java版的Spring框架为基础,将Spring.Java的核心概念与思想移植到了.NET平台上. 企业级应用一般由多个物理层组成,每个物理层也经常划分为若干功能层.不同层次之间需要相互协作,例如,业务服务层一般需