IOC的依赖注入原理

Rod Johnson使用配置文件来管理Java实例,并给这种方式起名叫IOC

IOC需要通过xml解析技术获取类的全路径,通过工厂类创建对象,达到依赖对象注入的目的

bean对象之间的依赖关系在运行期决定,即容器动态的将依赖的对象注入到组件Bean中。

依赖注入详解:

依赖注入:指的是应用程序依赖于IOC容器,由IOC容器提供(注入)对象需要的外部资源,这个外部资源包括:对象、资源、常量数据等

IOC是设计思想,DI是实现方式

IOC的三种配置方式

IOC配置支持三种方式:主流的方式是注解+Java配置,参考SpringDemo

  • XML配置

    • 优点:可以使用于任何场景,结构清晰,通俗易懂

    • 缺点:配置繁琐,不易维护,扩展性差

  • Java配置

    • 优点:适用于任何场景,配置方便,纯Java代码方式,扩展性高,十分灵活

    • 缺点:由于采用Java类的方式,声明不明显,如果大量配置,可读性差

  • 注解配置

    • 优点:开发便捷,通俗易懂,方便维护

    • 缺点:具有局限性,对第三方资源无法添加注解,只能采用XML或JavaConfig的方式配置

依赖注入的三种方式

  • set注入:Ioc容器使用成员变量的set方法注入被依赖的对象的属性

    1. 在XML配置方式中,property都是setter方式注入

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
          <!-- services -->
          <bean id="userService" class="tech.pdai.springframework.service.UserServiceImpl">
              <property name="userDao" ref="userDao"/>
              <!-- additional collaborators and configuration for this bean go here -->
          </bean>
          <!-- more bean definitions for services go here -->
      </beans>
      

      执行逻辑为:

      • xml解析后,通过new UserServiceImpl()创建对象

      • 调用setUserDao函数注入userDao的值

      因此对应的service类应该是:

      public class UserServiceImpl {
      
          /**
           * user dao impl.
           */
          private UserDaoImpl userDao;
      
          /**
           * init.
           */
          public UserServiceImpl() {
          }
      
          /**
           * find user list.
           *
           * @return user list
           */
          public List<User> findUserList() {
              return this.userDao.findUserList();
          }
      
          /**
           * set dao.
           *
           * @param userDao user dao
           */
          public void setUserDao(UserDaoImpl userDao) {
              this.userDao = userDao;
          }
      }
      
    2. 在java配置或注解配置方式中

      public class UserServiceImpl {
      
          /**
           * user dao impl.
           */
          private UserDaoImpl userDao;
      
          /**
           * find user list.
           *
           * @return user list
           */
          public List<User> findUserList() {
              return this.userDao.findUserList();
          }
      
          /**
           * set dao.
           *
           * @param userDao user dao
           */
          @Autowired
          public void setUserDao(UserDaoImpl userDao) {
              this.userDao = userDao;
          }
      }
      

    set注入的优缺点:

    优点:符合单一职责的设计原则,每个Set只针对一个对象

    缺点:不能注入不可变对象(final修饰的对象),注入的对象可被修改

  • 构造器注入:Ioc容器使用构造器注入被依赖对象的属性

    1. XML配置方式中,<constructor-arg>是通过构造函数参数注入

      <constructor-arg> 标签属性:

      Type:使用类型指定构造方法中参数的类型

      index:使用构造方法中参数索引值来进行注入

      name:使用构造方法中参数名称来注入

      value:要注入的值(基本数据类型和String类型)

      ref:要注入的值(引用在IOC容器中其他的bean对象)

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
          <!-- services -->
          <bean id="userService" class="tech.pdai.springframework.service.UserServiceImpl">
              <constructor-arg name="userDao" ref="userDao"/>
              <!-- additional collaborators and configuration for this bean go here -->
          </bean>
          <!-- more bean definitions for services go here -->
      </beans>
      

      本质上是:new UserServiceImpl(userDao)创建对象
      所以对应的service类:

      public class UserServiceImpl {
      
          /**
           * user dao impl.
           */
          private final UserDaoImpl userDao;
      
          /**
           * init.
           * @param userDaoImpl user dao impl
           */
          public UserServiceImpl(UserDaoImpl userDaoImpl) {
              this.userDao = userDaoImpl;
          }
      
          /**
           * find user list.
           *
           * @return user list
           */
          public List<User> findUserList() {
              return this.userDao.findUserList();
          }
      
      }
      
    2. 在Java配置或注解配置方式中

       @Service
      public class UserServiceImpl {
      
          /**
           * user dao impl.
           */
          private final UserDaoImpl userDao;
      
          /**
           * init.
           * @param userDaoImpl user dao impl
           */
          @Autowired // 这里@Autowired也可以省略
          public UserServiceImpl(final UserDaoImpl userDaoImpl) {
              this.userDao = userDaoImpl;
          }
      
          /**
           * find user list.
           *
           * @return user list
           */
          public List<User> findUserList() {
              return this.userDao.findUserList();
          }
      
      }
      

    优缺点:

    优点:创建时必须要指定构造方法中的全部参数,bean才能被创建,保证了对象创建出来之后,成员变量一定都有值

    缺点:必须要指定全部参数,否则无法创建,使用该方式改变了对象的创建过程

  • 注解(接口)注入:调用者实现特定的接口,并实现该接口中特定的方法,Ioc容器通过注解检测调用这个特定的方法,完成依赖注入,也叫字段注入

    以@Autowired为例,演示注解注入

    @Service
    public class UserServiceImpl {
    
        /**
         * user dao impl.
         */
        @Autowired
        private UserDaoImpl userDao;
    
        /**
         * find user list.
         *
         * @return user list
         */
        public List<User> findUserList() {
            return userDao.findUserList();
        }
    
    }
    

    @Autowired注解的属性:

    constructor:通过构造方法进行自动注入,Spring会匹配与构造方法参数类型一致的Bean进行注入,如果有一个多参数的构造方法,一个只有一个参数的构造方法,在容器中会查找到多个匹配多参数构造方法的bean,那么Spring会优先将Bean注入到多参数的构造方法中

    byName:被注入的bean的id名必须与set方法后半截匹配,并且id名称的第一个单词首字母必须小写,这一点与手动set注入有些区别

    byType:查找所有的set方法,将符合参数类型的bean注入

    字段注入的优缺点:

    优点:实现简单,容易理解

    缺点:无法注入一个不可变对象(final修饰的对象);只能适应于IOC容器;容易违背单一设计原则

    违背单一设计原则需要进行解释说明:

    注入越简单,滥用性越大,很容易在一个类中同时注入多个类,导致循环依赖问题

    public class A {
        @Autowired
        private B b;
    }
    
    public class B {
        @Autowired
        private A a;
    }
    

    注意:
    如果使用构造器注入,在spring项目启动的时候,就会抛出:BeanCurrentlyInCreationException:Requested bean is currently in creation: Is there an unresolvable circular reference?从而提醒你避免循环依赖
    如果是field注入的话,启动的时候不会报错,在使用那个bean的时候才会报错。


在这些注入方式中,推荐使用构造器注入方式,这是在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.有该类型的参数→传入;2.无该类型的参数→报错

完全初始化的状态:向构造器传参之前,要确保注入的内容不为空,那么肯定要调用依赖组件的构造方法完成实例化。而在java类加载实例化的过程中,构造方法是最后一步,所以返回来的都是初始化之后的状态

@Autowired和@Resource以及@Inject等注解注入的区别

@Autowired

@Autowired是Spring自带的注解,通过AutowiredAnnotationBeanPostProcessor类实现的依赖注入,默认根据类型(byType)进行自动装配

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    boolean required() default true;
}

@Autowired可以作用在CONSTRUCTOR、METHOD、PARAMETER、FIELD、ANNOTATION_TYPE

CONSTRUCTOR:作用在构造函数

METHOD:作用在方法

PARAMETER:作用在方法参数

FIELD:作用在字段、枚举常量

ANNOTATION_TYPE:作用在注解

使用注解:

private HelloDao helloDao;
@Autowired
public HelloServiceImpl(HelloDao helloDao) {
 this.helloDao = helloDao;
}
private HelloDao helloDao;
public HelloDao getHelloDao() {
 return helloDao;
}
@Autowired
public void setHelloDao(HelloDao helloDao) {
 this.helloDao = helloDao;
}
private HelloDao helloDao;

public void show(@Autowired HelloDao helloDao) {
 System.out.println("hellDao");
}
@Autowired
private HelloDao helloDao;
@Inherited
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Autowired
public @interface AuthRest {

}

@Autowired常见问题

  • 由于@Autowired是根据类型(byType)进行自动装配,如果有多个类型一样的Bean,需要指定按照名称(byName)进行装配,则需要配合@Qualifier注解

    @Autowired
    @Qualifier("helloWorldDao")
    private HelloDao helloDao;
    
  • 指定名称后,如果Spring IOC容器中没有对应的组件bean,则会抛出NoSuchBeanDefinitionException。也可以将@Autowired中required配置为false,如果配置为false,则当没有找到相应的bean的时候,系统不会抛出异常

    @Qualifier("helloWorldDao")
    @Autowired(required = false)
    private HelloDao helloDao;
    
  • 将@Autowired写在被注入的成员变量上,setter或者构造器上,就不用再xml文件中配置了

@Resource

@Resource是JSR250规范的实现,是JDK的原生注解,在javax.annotation包下,默认根据名称(byName)进行自动装配

@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource {
    String name() default "";
    String lookup() default "";
    Class<?> type() default java.lang.Object.class;
    enum AuthenticationType {
            CONTAINER,
            APPLICATION
    }
    AuthenticationType authenticationType() default AuthenticationType.CONTAINER;
    boolean shareable() default true;
    String mappedName() default "";
    String description() default "";
}

@Resource可以作用在TYPE、FIELD、METHOD

TYPE:作用在接口、类、枚举、注解上

FIELD:作用在字段、枚举的常量上

METHOD:作用在方法上

使用注解:

@Component
public class SuperMan {
    @Resource
    private Car car;
}
@Resource
public void setCar(Car car) {
 this.car = car;
}
@Resource
public class SuperMan {
    @Resource
    private Car car;
}

@Resource注解默认会通过ByName进行注入,name属性可以指定bean的名称(类似@Qualifier)

@Component
public class SuperMan {
    @Resource(name = "BMW")
    private Car car;
}

@Inject

@Inject是JSR330中的规范,在javax.inject包下,需要导入javax.inject包才能实现注入,默认根据类型(byType)进行自动装配

在这里插入图片描述

@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Inject {
}

@Inject注解可以作用在METHOD、CONSTRUCTOR、FIELD

METHOD:作用在方法上

CONSTRUCTOR:作用在构造器上

FIELD:作用在字段、枚举的常量

使用注解:

@Inject
private Car car;
private Car car;
@Inject
public SuperMan(Car car) {
 this.car = car;
}
@Inject
public void setCar(Car car) {
 this.car = car;
}

@Inject注解是根据类型自动装配的,如果要按照名称来进行装配,则需要配合@Named注解

@Inject
@Named("BMW")
private Car car;

总结

  • @Autowired注解是Spring自带的,@Resource是JSR250规范实现的,是JDK8的原生注解(在JDK11中已经移除了),@Inject是JSR330规范实现的

  • @Autowired和@Inject注解用法是一样的,不同的是@Inject没有required属性

  • @Autowired和@Inject注解是默认按照类型匹配的,@Resource是按照名称匹配的

  • @Autowired如果需要按照名称匹配需要和@Qualifier一起使用,@Inject和@Named一起使用,@Resouce则通过name属性来指定

  • 9
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Carl·杰尼龟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值