zoukankan      html  css  js  c++  java
  • SpringBoot系列: 理解 Spring 的依赖注入(二)

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

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

    @Component 和它的子注解虽然可以加在接口上, 但不推荐这样, 应该直接加在具体的实现类上, 实际项目中, Service 层往往有 Interface 和 impl, 应该在 impl 上加 @Service 注解. 

    二. @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 的线程安全性问题
    ==============================
    Struts2 每次请求过来都会实例化一个对象来响应, 所以没有线程安全问题, 而 Spring MVC 是基于视图函数做拦截的, 每次web请求过来后, Spring 都是使用 controller的指定方法去响应,  而不是新实例化一个controller.  Spring 这样做的好处是: 避免频繁对象实例化和GC, 效率会比Struts2要高; 但也不是没有缺点的, 会有线程不安全的潜在危险.  潜在的风险点是, 如果我们的Controller/Service/Repository类中包含实例变量, 就很可能会引起线程不安全. 避免线程不安全的方法有两个: 第一, Controller/Service/Repository类中不要加实例变量, 这是推荐做法. 第二, 默认情况下Spring IOC管理的bean, 其生命周期是  singleton,   我们可以修改 Controller/Service/Repository 的声明周期为  request. 

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

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

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

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


    ==============================
    @Autowired Bean 注入
    ==============================
    @Autowired 可以对类成员变量、方法和构造函数加注解, Spring会自动扫描所有打了@Autowired注解的变量/方法/构造子, 完成自动装配任务. 换句话讲, 所有打了@Autowired注解的方法/构造子, Spring会在合适的时机自动调用它们, 一般情况下不需要我们再手工调用. 

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

    public class Boss {
        @Autowired
        private Car car;
    }

    2. 在类的方法上加标注, Spring 自动完成"所有 bean"实参装配, 该方法会被Spring在合适的时机自动调用. 

    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

  • 相关阅读:
    colock
    ToggleButton 和 Switch
    radioButon的使用
    kotlin中val和var的区别
    textEdit
    c++ 网络编程基础
    网格布局 GridLayout
    数组、指针和引用
    Hello Word
    Win7-U盘安装出现"We were unable to copy your files. "
  • 原文地址:https://www.cnblogs.com/harrychinese/p/spring_ioc2.html
Copyright © 2011-2022 走看看