您的位置 首页 golang

长文干货!一文搞懂IoC的依赖注入

一、注解驱动IoC

xml驱动 的IoC容器使用的是 ClassPathXmlApplicationContext 读取xml内bean信息

注解驱动 的IoC容器使用的是 AnnotationConfigApplicationContext 读取Java类中的bean信息

1. AnnotationConfigApplicationContext 的注册使用

相比于xml文件作为驱动, 注解驱动需要指明 配置类 一个配置类可以理解为”相当于”一个xml 配置类只需要在类上标注注解 @Configuration

 @Configuration
public class DemoConfiguration {
}
  

在xml中声明bean的方式

 <bean id="person" class="com.huodd.bean.Person"></bean>
  

在配置类中使用的是 @Bean 注解

 @Bean
public Person person() {
    return new Person();
}
  

说明: 向IoC容器注册一个类型为Persion,id为Person的Bean

方法名表示的是bean的id 返回值表示的是注册的bean的类型

@Bean 注解也可以显示的声明bean的id 如 @Bean(“person1”)

2. 注解IoC容器的初始化

 public class AnnotationConfigApplication {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(DemoConfiguration.class);
        Person person = ctx.getBean(Person.class);
        System.out.println(person);
    }
}
  

运行后Person控制台打印结果

 com.huodd.bean.Person@55536d9e
  

3. 组件的注册和扫描

上述初始化时 我们在使用 AnnotationConfigApplicationContext 时传递了参数 Class<?>… componentClasses

翻看 AnnotationConfigApplicationContext 的构造方法可以发现还可以传递参数的参数类型还有 String… basePackages

这里就涉及到 组件的注册和扫描

这里可以思考一个问题, 如果我们要注册的组件特别多, 那进行编写这些 @Bean 的时候代码工作量也会特别多,这时候该如何解决呢?

Spring 给我们提供了几个注解,可以帮助我们快速注册需要的组件, 这些注解被称为 模式注解(stereotype annotations)

@Component

@Component可以说是所有组件注册的根源 在类上标注 @Component 代表该类被注册到IoC容器中作为一个Bean

 @Component
public class Person {
}
  

如果未指定 Bean 的名称 默认规则是 ” 类名称首字母小写 ” 上面的bean名称默认会是 person

如果要自定义bean的名称 可以在 @Component 声明 value 的值即可 如

 @Component("person1")
public class Person {
}
  

在xml中相当于

 <bean id="person1" class="com.huodd.bean.Person"/>
  

@ComponentScan

这个时候 如果我们直接运行启动类 获取 Person 的bean对象,会报错 NoSuchBeanDefinitionException 这是为什么呢?

因为我们只是声明了组件,而后直接启动了IoC容器,这样容器是感知不到有 @Component 存在的,

解决方案1:

我们需要在写配置类时再额外标注一个新的注解 @ComponentScan

目的是告诉IoC容器 我要扫描哪个包下面的带有 @Component 注解的类

 @Configuration
@ComponentScan("com.huodd.bean")
public class DemoComponentScanConfiguration {
}
  

注: 如果不指定扫描路径, 则 默认扫描本类所在包及所有子包 下带有 @Component 的组件

启动类代码如下:

 public class AnnotationConfigApplication {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(DemoComponentScanConfiguration.class);
        Person person = ctx.getBean(Person.class);
        System.out.println(person);
    }
}
  

解决方案2:

这里也可以不写 @ComponentScan 而直接在 AnnotationConfigApplicationContext 方法参数内传入String类型的 包扫描路径 代码如下

 public class AnnotationConfigApplication {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext("com.huodd.bean");
        Person person = ctx.getBean(Person.class);
        System.out.println(person);
    }
}
  

PS: 组件扫描并非是注解驱动IoC所特有的, 其实在xml驱动的IoC模式下 同样可以启用组件扫描, 只需要在xml中声明一个标签即可

 <context:component-scan base-package="com.huodd.bean"/>
  

这里需要注意下: 如需要扫描多个路径,需要写多个标签 也就是 一个标签只能声明一个根包

组件注册的补充

SpringFramework 提供了在进行Web开发三层架构时的扩展注解: 分别为 @Controller @Service @Repository 小伙伴有没有很熟悉?

分别代表 表现层 业务层 持久层 这三个注解的作用与 @Component 完全一样 扒开源码我们可以看到 底层在这三个注解类上又添加了 @Component

 @Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
}
  

这样 我们在进行符合三层架构的开发时 对于相应的如 ServiceImpl等 就可以直接标注 @Service 等注解了

@Configuration

@Configuration 底层也有标注 @Component

 @Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration { ... }

  

由此可以说明,配置类不是向我们所想的那样,只是单纯的做一个配置而已, 它也会被视为 bean,也被注册到IoC容器里面

4. 注解驱动与xml驱动互相引用

4.1 xml引用注解

需开启注解配置 再注册相应配置类

 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="#34;
       xmlns:xsi="#34;
       xmlns:context="#34;
       xsi:schemaLocation="
        
        
        #34;>

    <!-- 开启注解配置 -->
    <context:annotation-config />
    <!-- 注册配置类 -->
    <bean class="com.huodd.config.AnnotationConfigConfiguration"/>
</beans>
  

4.2 注解引用XMl

需在配置类上标注 @ImportResource 并声明配置文件的路径

 @Configuration
@ImportResource("classpath:annotation/demo-beans.xml")
public class ImportXmlAnnotationConfiguration { 
}
  

二、IoC的依赖注入

1.Setter属性注入

创建对象 将属性值set进去 之后返回对象

 @Bean
public Person person() {
    Person person = new Person();
    person.setId(1);
    person.setName("PoXing");
    person.setAge(18);
    return person;
}
  

xml中的setter注入

 <bean id="person" class="com.huodd.bean.Person">
    <property name="id" value="1"/>
    <property name="name" value="PoXing"/>
    <property name="age" value="18"/>
</bean>
  

2. 构造器注入

使用构造器注入,需要在bean本身添加有参构造方法, 如在Person中添加有参构造方法如下

 public Person(Integer id, String name, Integer age) {
        this.id = id;
        this.name = name;
        this.age = age;
}
  

注解驱动 中,我们创建bean的时候注入属性时 就需要同时指定参数值

 @Bean
public Person person() {
    return new Person(1, "PoXing", 18);
}
  

xml驱动 中如下

 <bean id="person" class="com.huodd.bean.Person">
    <!-- 
  index: 表示构造器的参数索引
  value: 表示对应的参数值
 -->
    <constructor-arg index="0" value="1"/>
    <constructor-arg index="1" value="PoXing"/>
    <constructor-arg index="2" value="18"/>
</bean>
  

3. 注解式属性注入

这里先说明一下,为何会有注解式属性值注入. 细心的小伙伴可能会发现 上面我们谈到的 Setter属性注入 构造器注入 好像在只能是在使用 @Bean 注解的时候时候使用, 但是 如果是通过标注 @Component 注解的组件呢(像前面我们的Person类中标注了 @Component 注解),怎么给它设定属性值, 该节主要就是说一下这部分

@Component 下的属性注入

这里我们使用Dog类做为演示(这里我悄悄的添加了 @Component 注解 自己尝试的小伙伴要注意哦 否则会报错的)

 @Component
public class Dog {
    private Integer id;
    private String name;
    private Integer age;

   ... 省略 Getter、Setter
   ... 省略 toString

}
  

这里要实现注解式属性注入,可以直接在要注入的字段上标注 @Value 注解 如

 @Value("1")
private Integer id;

@Value("wangcai")
private String name;

@Value("3")
private Integer age;
  

启动类代码如下

 public class DiApplication {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext("com.huodd.bean");
        Dog dog = ctx.getBean(Dog.class);
        System.out.println(dog);
    }
}
  

控制台打印结果

 Dog{id=1, name='wangcai', age=3}
  

外部配置文件(@PropertySource)

这里主要是解决上面的@Value中注入 我们把属性值直接固定写死了,如果要修改 还要去Java代码中去修改,很不符合开发规范,

SpringFramework为我们扩展了新的注解 @PropertySource 主要用来导入外部配置文件

  1. 这里我们创建一个 dog.properties
 dog.id=1
dog.name=wangcai
dog.age=3
  
  1. 引入配置文件
 @PropertySource("classpath:di/dog.properties")
@ComponentScan("com.huodd.bean")
@Configuration
public class DemoComponentScanConfiguration {
}
  
  1. Dog类中属性注入 这里 @Value 需要配合 占位符 来获取properties配置文件中的内容
 @Value("${dog.id}")
private Integer id;

@Value("${dog.name}")
private String name;

@Value("${dog.age}")
private Integer age;
  
  1. 修改一下启动类
 public class DiApplication {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(DemoComponentScanConfiguration.class);
        Dog dog = ctx.getBean(Dog.class);
        System.out.println(dog);
    }
}
  

控制台打印结果如下

 Dog{id=1, name='wangcai', age=3}
  

此时配置文件的属性已经注入成功

4.自动注入

在xml模式中有 ref 属性 可以将一个bean注入到另外一个bean中, 注解模式中也同样可以

@Autowired

给Dog的bean中注入 Person的Bean (即 给dog指定它的主人)

方法1 → 在属性上标注

 @Component
public class Dog {
    // ......
    @Autowired
    private Person person;
}
  

方法2 → 使用构造器注入方式

 @Component
public class Dog {
    // ......
    private Person person;

    @Autowired
    public Dog(Person person) {
     this.person = person;
    }
}
  

方法3 → 使用setter方法注入

 @Component
public class Dog {
    // ......
    private Person person;
    
    @Autowired
    public void setPerson(Person person) {
        this.person = person;
    }
}
  

JSR250规范下的@Resource

@Resource 也是用来属性注入的注解

它与 @Autowired 的区别是:

  • @Autowired 是按照 类型 注入
  • @Resource 是按照 属性名 (也就是bean的名称)注入
  • @Resource 注解相当于标注 @Autowired @Qualifier

    @Qualifier 这里简要说明下,为指定bean的名称而存在,如果存在多个相同的bean,而bean的名称不同,我们可以使用 @Autowired 配置 @Qualifier 注解

    如: 下面表示该Dog类注入的主人Bean是名称为 xiaowang的, 而当前容器内可能存在多个 主人bean对象 比如 xiaoli、xiaoming ….

     @Component
    public class Dog {
        // ......
        @Autowired
        @Qualifier("xiaowang")
        private Person person;
    }
      

    下面如果使用 @Resource 可以更方便些 代码如下

     @Component
    public class Dog {
        // ......
        @Resource(name="xiaowang")
        private Person person;
    }
      

    JSR330规范下的@Inject

    @Inject 注解也是 按照类型注入 ,与 @Autowire 的策略一样, 不过如要使用 @Inject 需要额外的导入依赖

     <!-- jsr330 -->
    <dependency>
        <groupId>javax.inject</groupId>
        <artifactId>javax.inject</artifactId>
        <version>1</version>
    </dependency>
      

    后面的使用方法就与SpringFramework 原生的 @Autowire + @Qualifier 相同了

     @Component
    public class Dog {
        
        @Inject // 等同于@Autowired
        @Named("xiaowang") // 等同于@Qualifier
        private Person person;
      

    它与 @Autowired 的区别是:

  • @Autowired 所在的包为 org.springframework.beans.factory.annotation.Autowired 即为 SpringFramework 提供的
  • @Inject 所在的包为 javax.inject.Inject 属于JSR的规范 也就是说如果不使用SpringFramework时可以使用该注解
  • 5. 复杂类型注入

    Array注入

     <property name="names">
        <array>
            <value>PoXing</value>
            <value>LaoWang</value>
        </array>
    </property>
      

    List注入

     <property name="tels">
        <list>
            <value>13000000000</value>
            <value>13000000001</value>
        </list>
    </property>
      

    Set注入-

     <!-- 已经提前声明好的Dog -->
    <bean id="wangcai" class="com.huodd.bean.ext.Dog"/>
    ---
    
    <property name="dogs">
        <set>
            <bean class="com.huodd.bean.Dog"/>
            <ref bean="wangcai"/>
        </set>
    </property>
      

    Map注入

     <property name="homesMap">
        <map>
            <entry key="1" value="main">
             <ref bean="myHome1" />
            </entry>
            <entry key="2" value="other">
                 <ref bean="myHome2" />
            </entry>
        </map>
    </property>
      

    Properties注入

     <property name="props">
        <props>
            <prop key="sex">男</prop>
            <prop key="age">18</prop>
        </props>
    </property>
      

    面试题

    1.@Autowired注入原理是什么?

    1. 先拿属性对应的类型,去IoC容器中找相应的Bean
    2. 如果没有找到 直接抛出 NoUniqueBeanDefinitionException 异常
    3. 如果找到一个 直接返回
    4. 如果找到多个相同类型的bean 再拿属性名去与这多个bean的id进行对比
    5. 如果有多个或者没有 则会抛出 NoUniqueBeanDefinitionException 异常
    6. 如果只有一个 直接返回

    2.依赖注入的方式有哪些,都有什么区别

    注入方式 被注入对象是否可改变 是否依赖IOC框架的API 使用场景 构造器注入 不可变 否 不可变的固定注入 参数注入 不可变 否 注解配置类中 @Bean方法注册 bean 属性注入(注解式属性注入) 不可变 是(只能通过标注注解来侵入式注入) 通常用于不可变的固定注入 setter注入 可变 否 可选属性的注入

    3.自动注入的注解对比

    注解 注入方式 是否支持@Primary 来源 Bean不存在时处理 @Autowired 根据类型注入 是 SpringFramework原生注解 可指定required=false来避免注入失败 @Resource 根据名称注入 是 JSR250规范 容器中不存在指定Bean会抛出异常 @Inject 根据类型注入 是 JSR330规范 ( 需要导jar包 ) 容器中不存在指定Bean会抛出异常

    @Qualifier :如果被标注的成员/方法在根据类型注入时发现有多个相同类型的 Bean ,则会根据该注解声明的 name 寻找特定的 bean

    @Primary :如果有多个相同类型的 Bean 同时注册到 IOC 容器中,使用 “根据类型注入” 的注解时会注入标注 @Primary 注解的 bean 即默认策略

    4.使用依赖注入有什么优缺点

  • 依赖注入作为 IOC 的实现方式之一,目的就是 解耦 ,我们不需要直接去 new 那些依赖的类对象就可以直接从容器中去取来使用, 如果组件存在多级依赖,依赖注入可以将这些依赖的关系简化。
  • 依赖对象的 可配置 :通过 xml 或者注解声明,可以指定和调整组件注入的对象,借助 Java 的多态特性,可以不需要大批量的修改就完成依赖注入的对象替换

  • 我是Baoxing, 开始做一件事情最好的时机,要么是十年前,要么就是现在 。 感谢各位的 点赞 收藏 评论

    文章来源:智云一二三科技

    文章标题:长文干货!一文搞懂IoC的依赖注入

    文章地址:https://www.zhihuclub.com/92352.shtml

    关于作者: 智云科技

    热门文章

    网站地图