浅谈spring框架之依赖注入(DI)机制

今天我们来看一下spring的第二大特性,依赖注入,英文名称“Dependency Injecttion”,简称DI。在上一篇博客中我们讨论了IOC的作用就是降低程序之间的耦合性。简单说依赖注入就是给系统中的某个变量赋值。
那问题来了,那这种依赖关系由谁来管理那?毫无疑问,肯定是交给我们的spring容器了,我们只需在配置文件中说明即可。这种依赖关系的维护就称之为依赖的注入。
接下来我们 创建一个maven项目,导入spring的jar包。完成spring的基本开发环境。
jar包如下:

 <!--spring依赖-->
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>
    </dependencies>

这里说明一下,为什么一定要用maven来导入jar包那。看下图:
在这里插入图片描述
打开"External Libraries",发现下面有好多子包,其中我们发现有我们需要的sprign-context这个jar包。这就对了。这就是maven的强大之处。在今天的案例当中,我们只能用到spring-context这个jar包。如果我们要做一个完整的项目,上面的这些jar包是最基本的spring环境,如果我们手动的去导包,那多麻烦,还要面临版本兼容的问题。而利用maven来管理jar包的话,他会把我们与spring开发相关的jar都导进来。这就是他的好处。不会存在版本的兼容问题。推荐大家以后使用maven来管理jar包。
还是转入正题,看看spring的如何注入数据的?

spring注入的数据类型有哪些?
1. 基本数据类型和String
2. 其他bean类型(在配置文件中或注解中配置过的bean)
3. 复杂类型/集合类型
spring的注入方式有哪些?
1. 使用构造器来注入
使用的标签:constructor-arg
标签出现的位置:bean标签的内部
标签中的属性:
	type:用于指定要注入的数据类型,该数据类型也是构造函数中某个或某些参数的数据类型
	index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值,索引的位置是从0开始
	name:用于指定给构造函数中指定名称的参数赋值
	==============以上三个用于指定给构造函数中的那个参数赋值==================
	value:用于指定给构造函数和String类型的数据注入
	ref:用于指定其他bean类型的数据,他指的就是在spring的IOC核心容器中出现过的bean对象。
优点:
	在获取bean对象时,注入的数据是必须的操作,否则对象无法创建。
缺点:
	改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据也必须提供

代码如下:
备注:这里展示的是部分代码:只展示spring的配置文件和要交给spring容器管理的类。

import java.util.Date;
public class EntityServiceImpl  {
    private Integer age;     //基本类型的包装类
    private String name;   //String类型
    private Date birthday;  //其他类型
    
    public EntityServiceImpl(Integer age, String name, Date birthday){
        this.age = age;
        this.name = name;
        this.birthday = birthday;
    }
	//测试方法
    public void print(){
        System.out.println("name: "+name+"  age: "+age+"   birthday:  "+birthday);
    }
}

配置文件:

    <bean id="EntityService1" class="jdbc.service.EntityServiceImpl">
        <constructor-arg type="java.lang.Integer" index="0" name="age" value="19"></constructor-arg>
        <constructor-arg name="name" value="zyy"></constructor-arg>
        
        <!-- 这个now就是bean对象的其他类型,要想使用,就必须现在spring中配置他(或者也叫实例化)-->
        <constructor-arg name="birthday" ref="now"></constructor-arg>
    </bean>

其实,我们可以看出,type和index就可以唯一确定一个构造函数中参数的位置,所以,在没有必要写name属性了。一般情况下使用name属性来进行注入。
很明显,这种方式是有前提条件的。就是我们要在类EntityServiceImpl 中有一个指定参数的构造函数。此例中的构造函数有三个参数,所以,在使用构造器进行注入的时候必须也是三个。否则就会报错。这就是我们总结出来的缺点,有时候,有些数据在实例化的时候我们并不用,但是没办法,即使不用,也要注入,这样,才能保证不会出错。

2. 使用set方法来注入
使用的标签:property
出现的位置:bean标签的内部
标签的属性:
	name: 用于指定注入时所调用的set方法名称(其实就是属性名)
	value:用于提供提供基本数据类型和String类型的数据
	ref:用于指定其他bean 类型的数据,他指的就是在spring的IOC容器中出现过的bean对象
优点:
	创建对象时没有明确的限制,可以直接使用默认的构造函数
缺点:
	如果某个成员必须有值,则获取对象时有可能set方法没有执行

代码如下:

import java.util.Date;
public class EntityServiceImpl  {

    private Integer age;
    private String name;
    private Date birthday;
    
    //默认构造函数必须存在(如果自己创建了构造器,默认的构造器将不会存在)
    public EntityServiceImpl(){}
    
    //有参构造器(使用set方法注入时,可以去掉有参构造器)
    public EntityServiceImpl(Integer age, String name, Date birthday){
        this.age = age;
        this.name = name;
        this.birthday = birthday;
    }
    
    public void setAge(Integer age) {
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    //测试方法
    public void print(){
        System.out.println("name: "+name+"  age: "+age+"   birthday:  "+birthday);
    }
}

spring 配置文件如下:

 
<bean id="EntityService2" class="jdbc.service.EntityServiceImpl">
        <property name="age" value="22"></property>
        <property name="name" value="yyl"></property>
        
        <!--  要使用now,必须现在spring的IOC容器中注入这个bean类型 -->
        <property name="birthday" ref="now"></property>
    </bean>

小结一下:set方式来注入数据。第一:我们必须给类中的成员变量提供标准的set方法,第二,类中默认的构造方法必须有。

3. 使用注解注入(请看下篇)

spring框架的注解配置应该是现在比较流行的配置方式,我们一定要掌握。其好处多多。什么简化配置,方便使用,提高开发效率等等一系列的好看、好听、中用的词汇都适合用来修饰spring使用注解带来的好处。但是,有人就会问,既然注解这么多好处,我们为什么还要学基于XML注入的配置。原因很简单,不入虎穴,焉得虎子。就是这个道理,你不了解spring的过去,怎么可能掌握未来。好了,接下来我们看一下spring的注解如何初始化bean对象和注入数据的。
在看注解之前,我们先在复习一下曾经的XML配置(XML曾经也牛掰过):

<bean id="userService" class="com.tff.demo.service.impl.UserServiceImpl"
scope="" destroy-method="" init-method="" >
         <property name="" ref=""| value=""></property>
</bean>
bean: 标签就是用于实例化对象的
id  : 用来表示一个一个唯一的bean对象
class:用来表示这个对象的全路径
scope:用来表示这个bean对象的作用范围
destroy-method:表示bean对象的销毁方法
init-method:用来表示对象的初始化方法
property:用来给这个类的属性注入数据

我们再来看一下了Spring注解是如何完成的。

spring的注解基本上可以分为四类
  • 用于创建对象的
    他们的作用就和XML文件中的bean标签实现的功能一样
    @Component
    作用: 用于把当前对象存入spring容器当中
    @Controller
    @Service
    @Repository
    属性(这四个注解的属性一致):
    value:用于指定bean的id,相当于我们用XML配置时的id属性,可以忽略不写, 当我们不写时,他的默认值是当前的类名(类名的首字母小写)。
    小结:以上三个注解的作用与属性和Component是一样的,只是spring框架为我们提供明确的三层使用的注解,使三层模式更清晰
  • 用于注入数据的
    他们的作用就和在XML配置文件中的bean标签中的property标签的作用是一样的
    @Autowired:
    作用:自动化装配,自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型相匹配时,就可以注入成功。如果没有,就会注入失败。
    如果IOC容器中有多个类型匹配时:解决办法如下:
    • 1.使用Qualifier注解
      作用:在按照类型注入的基础上再按照名称注入,它在给类成员注入时不能单独使用,必须和Autowired注解联合使用,但是在给方法注入时可以单独使用。
      属性:value:用于指定注入的bean的id。
    • 2.Resource注解:
      作用:直接按照bean的id注入,他可以独立使用
      属性:
      name :用于指定bean的id。

备注:以上三个注入都只能注入bean类型的数据,而基本数据类型和String类型都无法使用上述注入方式。另外,集合类的注入只能通过XML来实现。

  • 用于改变作用范围的
    他们的作用和在bean标签中的scope属性是一样的
    @Scope:
    属性值:
    singleton: 单例对象,默认的就是单例对象
    prototype: 多例对象
  • 和生命周期有关的
    他们的作用就和在bean标签中使用init-method和destory-method作用是一样的。
    @PostConstruct
    @PreDestory
    代码如下:
    dao层接口和实现类:
package com.tff.demo.dao;
public interface EntityDao {
    void saveEntity();
    void insertEntity();
    void updateEntity();
    void deleteEntity();
}
package com.tff.demo.dao.impl;
import com.tff.demo.dao.EntityDao;
import org.springframework.stereotype.Repository;
//@Repository(value="dao")
@Repository
public class EntityDaoImpl implements EntityDao {
    public void saveEntity() {
        System.out.println("dao层的添加方法");
    }
    
    public void insertEntity() {
        System.out.println("dao层的插入方法");
    }
    
    public void updateEntity() {
        System.out.println("dao层的修改方法");
    }
    
    public void deleteEntity() {
        System.out.println("dao层的删除方法");
    }
    
}

Service 层接口和实现类

package com.tff.demo.service;
public interface EntityService {
    void saveEntity();
    void insertEntity();
    void updateEntity();
    void deleteEntity();
}
package com.tff.demo.service.impl;
import com.tff.demo.dao.EntityDao;
import com.tff.demo.service.EntityService;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
@Service(value="service")
@Service
@Scope("singleton")
public class EntityServiceImpl implements EntityService {

   /* @Autowired
    @Qualifier(value = "bean的id也就是实例化对象注解的属性的value值")*/
   @Resource(name = "dao")
    private EntityDao entityDao;

    public void saveEntity() {
       entityDao.saveEntity();
    }

    public void insertEntity() {
     entityDao.insertEntity();
    }

    public void updateEntity() {
       entityDao.updateEntity();
    }

    public void deleteEntity() {
        entityDao.deleteEntity();
    }

    @PostConstruct
    public void init(){
        System.out.println("模拟初始化方法");
    }
    
    @PreDestroy
    public void destory(){
        System.out.println("模拟销毁方法");
    }
    
}

测试类:

package com.tff.demo.test;
import com.tff.demo.service.EntityService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestMethod {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        EntityService entityService = ac.getBean("entityServiceImpl",EntityService.class);
        entityService.saveEntity();
        
    }

}

还有最后一步:
就是大家可以看到,我们main函数中依然加载spring的配置文件,你们难道不好奇这个配置文件中究竟有什么东东吗?
答案是肯定有的。我先把代码贴出来再解释:

<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx">
    <!--
        告知spring在创建容器时要扫描的包,配置所需要的标签不是在bean的约束中
        ,而是一个名称为context名称空间和约束中
    -->
    <context:component-scan base-package="com.tff.demo"></context:component-scan>
</beans>

我相信上面的注释已经说的很秦楚了。虽然我们已经在要交给spring容器帮我们实例化的类上面加了相应的注解,但是,spring并不知道在那个包下面去扫描这些类。所以,只欠我们告诉spring你应该去哪里找就OK了。
还有一个问题需要说明一下,就是前面提到的,如果IOC容器中有多个类型与bean对象匹配时,我们有两种解决方案:
第一种:两个注解联合使用(@Autowired+@Qualifier),按照先类型在id的匹配方式诸如数据。
第二种:一个注解解决问题(@Resource),按照指定的id进行注入

但是,问题来了,IOC容器中多个类型bean对象匹配,那怎么样才能使这样的情况发生那?那就是多个类实现了同一个接口或者多个类集成了同一个抽象类。这样,就导致一个接口或者一个抽象类有好多个子类。我们在注入的时候都喜欢用多态的形式来给对象赋值:用图片说吧!
在这里插入图片描述
现在我们直接交给spring来实例化。我们可以看到前面的类型是接口类型,后面是实现类类型。我们知道spring在实例化bean对象的时候,id属性是不允许重负的。他帮我们实例化的是等号右边的,也就是帮我们实例化实现类的(这个就不用解释了吧!,抽象类和接口都不能被实例化,java基础知识),而我们的类型是接口,spring实例化的是实现类。一个接口有很多个实现类。很明显,就出现了IOC容器中多个类型与bean对象匹配。哎呀,不知道你们能不能理解了。我尽力了(作者当时也是废了好大的劲)。
这时候,就是上面的两者解决办法
第一种:
@AutoWried
@Qualifier(value=“如果这个对象交给spring实例化的时候没有没有给id属性赋值,那这里默认就是这个类名(首字母小写)反之,就是id属性的值”)
备注:当然,如果你没有用XML形式实例化,用的是注解形式的话,那就是value的属性值。
第二种:
@Resource(name=“id|value”)
这里的id和value同上面Qualifier注解里面value的值。

复杂类型的注入/集合类型的注入

这个复杂类型就包括我们常用的List,Array,Set,Properties,Map类型的。注入的方式还是通过set方式来注入的。那就意味着我们必须为类中的每个成员提供必要的标准的set方法和默认构造函数。
下面我们上代码:

import java.util.*;
public class EntityService2 {

    private String [] arrStr;
    private List<Object> list;
    private Set<Object> set;
    private Map<String,Object> map;
    private Properties pros;

    public void setArrStr(String[] arrStr) {
        this.arrStr = arrStr;
    }

    public void setList(List<Object> list) {
        this.list = list;
    }

    public void setSet(Set<Object> set) {
        this.set = set;
    }

    public void setMap(Map<String, Object> map) {
        this.map = map;
    }

    public void setPros(Properties pros) {
        this.pros = pros;
    }
	//测试方法
    public void print(){
        System.out.println("array: "+Arrays.toString(arrStr));
        System.out.println(list);
        System.out.println(map);
        System.out.println(pros);
        System.out.println(set);
    }
}

spring配置文件

        <!--   给数组注入   -->
        <property name="arrStr">
            <array>
                <value>zyy</value>
                <value>gxr</value>
                <value>wwh</value>
            </array>
        </property>
        
        <!-- 给list集合注入数据  -->
        <property name="list">
            <array>
                <value>张三</value>
                <value>李四</value>
                <value>王五</value>
            </array>
        </property>
        
        <!--  给set集合注入  -->
        <property name="set">
            <set>
                <value>lisi</value>
                <value>duanting</value>
                <value>zhaiyuting</value>
                <value>lisiguang</value>
            </set>
        </property>
        
        <!--    map集合注入数据   -->
        <property name="map">
            <map>
                <entry key="name1" value="yyl"></entry>
                <entry key="name2" value="sty"></entry>
                <entry key="name3" value="zjs"></entry>
            </map>
        </property>
		<!-- 给Properties注入数据-->
        <property name="pros">
            <props>
                <prop key="driver">com.jabc.mysql.Driver</prop>
                <prop key="url">http://localhost:8080/loveyou</prop>
                <prop key="username">gxr</prop>
                <prop key="password">1314520</prop>
            </props>
        </property>
        
    </bean>

小结:我们可以看出,给什么类型注入数据,就用什么标签,数组就是Array标签,list就是list标签,Set就是set标签等等。
不过我们还发现,这五类当中,基本可以划分为两大类:
第一类:线性结构:Array、List、Set
线性结构是我们用以上三个任意一个标签都可以注入成功
第二类:键值对 : Properties、Map
键值对时,我们就采用提供标签对来进行对数据的注入。

最后,我同意给出测试类的代码和运行结果:

import jdbc.service.EntityService2;
import jdbc.service.EntityServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestMethod {
    //private static  EntityService entityService = new EntityServiceImpl();
    public static void main(String[] args) {
        //加载配置文件
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        
        //使用构造器注入
        EntityServiceImpl entityService = (EntityServiceImpl) ac.getBean("EntityService1");
        entityService.print();

        //使用set方式来注入
        EntityServiceImpl entityService2 = (EntityServiceImpl) ac.getBean("EntityService2");
        entityService2.print();
        
        //注入复杂类型的数据
       EntityService2 entity =  ac.getBean("EntityService3",EntityService2.class);
        entity.print();
    }
}

代码运行结果:
在这里插入图片描述
说明:这里需要说明一个问题,就是什么样的数据需要注入。一句话,就是经常变换的数据不适合注入。我们需要注入的数据基本上都是不变的。比如数据库的配置文件等。
总结一下:
这一篇文章主要和大家分享了spring的依赖注入,以及在依赖过程中遇到的问题。都给大家一一作了详细的解释。希望读者能从宏观上有一个大的认识。就是spring的总体配置方式有两种,一种是基于XML文件来实现的;另一种就是基于注解类型的。两者没有优劣之分,只是开发人员更喜欢用注解来实现。 另外,集合类型的依赖注入也只能通过XML来搞定。希望这篇文章对大家有用。作者会继续努力改进的。
今天的分享就到这里了……欢迎给为批评指正。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring框架可以通过依赖注入来管理对象之间的关系。泛型依赖注入Spring 4版本引入的一个新特性,它允许我们使用泛型类型作为依赖注入的目标类型。 在Spring中,我们可以使用`@Autowired`注解来实现依赖注入。在Spring 4之前的版本中,我们只能使用具体的类型来指定注入的目标类型。但是在Spring 4中,我们可以使用泛型类型来指定注入的目标类型。 泛型依赖注入的好处之一是可以减少重复的代码。例如,我们可以定义一个通用的泛型接口或抽象类,然后在具体的类中使用泛型类型来指定依赖注入的目标类型。这样,我们可以减少重复的配置代码,并提高代码的可维护性和灵活性。 另一个好处是增加了类型安全性。使用泛型类型来指定依赖注入的目标类型可以在编译时检查类型是否匹配,避免在运行时出现类型转换错误或异常。 下面是一个示例代码,演示如何在Spring 4中使用泛型依赖注入: ```java public interface GenericDao<T> { // ... } @Component public class UserDao implements GenericDao<User> { // ... } @Service public class UserService { @Autowired private GenericDao<User> userDao; // ... } ``` 在上面的示例中,我们定义了一个泛型接口`GenericDao`,并在具体的实现类`UserDao`中使用了具体的类型`User`来指定泛型类型。然后,我们在`UserService`中使用泛型类型`User`来注入`UserDao`。 通过使用泛型依赖注入,我们可以更方便地管理依赖关系,并且减少了重复的配置代码。这是Spring 4版本引入的一个有用的特性,可以在开发中提高效率和代码质量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值