Spring 依赖注入的方式

该篇博客笔者准备对SpringIoC容器的Bean配置及其依赖注入的几种方式进行详细介绍,该篇博客主要阐述XML形式配置,还有一种注解方式会在之后的博客再进行详细阐述

该篇博客主要阐述

1、Spring容器配置Bean方式
2、依赖注入的三种方式

一、Spring容器配置Bean方式

  • Spring使用一个或多个XML文件作为配置文件方式
  • Spring采用基于Java注解的配置方式(Spring3.0及以上版本)

该篇博客主要阐述第一种使用XML文件配置下的几种依赖注入方式

1、前往Spring容器的XML之旅咯^_^

XML格式的容器信息管理方式是Spring提供的最为强大、支持最为全面的方式。所有使用XML文件进行配置信息加载的Spring IoC容器,包括BeanFactory和ApplicationContext的所有XML相应实现,都使用统一的XML格式,从Spring2.0之后,Spring继续保持向前兼容的前提下,引入了基于XML Schema的文档声明


以下是基于XSD的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"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="foo" class="x.y.Foo">
        <meta key="cacheName" value="foo"/>
        <property name="name" value="Rick"/>
    </bean>

</beans>

配置声明框架可在spring-framework-4.3.10.RELEASE-dist/spring-framework-4.3.10.RELEASE/docs/spring-framework-reference/html/xsd-configuration.html中找到

这里写图片描述

所有注册到容器的业务对象,在Spring中称之为Bean。所以每个对象在XML中的映射也自然而然地对应一个叫< bean>的元素。所以既然容器最终可以管理所有业务对象,那么在XML中把这些叫做< bean>的元素组织起来,就叫做< beans>

2、< beans>了解一下

< beans>是XML配置文件中最顶层的元素。它下面可以包含

- 0个或1个<description>
- 多个<bean>、<import>、<alias>

这里写图片描述

< description>、< import>、< alias>

这三个元素不是必须的,但也会用到

  • < description>:指定一些描述性信息
  • < import>:通常情况下,可以根据模块功能或层次关系,将配置信息分门别类地放到多个XML文件中,在想加载主要配置文件,并将主要配置文件所依赖的配置文件同时加载时,可以在主要配置文件中通过< import>元素对其所依赖的配置文件进行引入:< import resource=”b.xml”>
  • < alias>:通过< alias>为某些名称较长的< bean>起别名(name或alias的名称都可以使用)

    <alias name="dataSourceForMasterDatabase" alias="MasterDatabase"/>
    

二、依赖注入的三种方式

  • 构造方法注入
  • setter方法注入
  • 接口注入

1、构造方法注入

通过构造方法注入方式为当前业务对象注入其所依赖的对象,需要使用< constructor-arg>元素来告诉Spring额外信息。如果不配置< constructor-arg>元素,那么Spring将使用默认的构造方法。

构造方法注入——传值

Student.java

package com.linjie.a;

/**
 * @author LinJie
 * 构造器注入——传值
 */
public class Student {
    private String name;
    private int age;
    private int num;
    private double score;

    public Student(String name, int age, int num, double score) {
        super();
        this.name = name;
        this.age = age;
        this.num = num;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student [name=" + name + ", num=" + num + ", age=" + age + ",score=" + score + "]";
    }
}

测试类

package com.linjie.a;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author LinJie
 * 测试
 */
public class SpringTest1 {
    @Test
    public void SpringTest() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student = (Student) context.getBean("student");
        System.out.println(student);
    }
}

applicationContext.xml

<?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">
    <bean id="student" class="com.linjie.a.Student">
        <constructor-arg name="name" index="0" value="LinJie"></constructor-arg>
        <constructor-arg name="age">
            <value>20</value>
        </constructor-arg>
        <constructor-arg name="num" value="001"></constructor-arg>
        <constructor-arg type="double" value="99.5"></constructor-arg>  
    </bean>
</beans>
解析applicationContext,xml各个元素属性
  • id:每个注册到容器的对象都需要一个唯一标志,除了id可以唯一标识,还可以使用name属性来指定< bean>的别名(alias)
  • class:每个注册到容器的对象都需要通过< bean>元素的class属性来指定其类型,要全类名
  • index:根据属性参数的索引位置定位属性(根据其构造方法参数位置)
  • name:根据属性参数名称定位属性
  • type:根据属性参数类型定位属性:注意非基本类型参数的话比如String,需要type=”java.lang.string”
  • value:为主体对象赋值

结果

这里写图片描述

构造方法注入——为当前业务对象注入其所依赖的对象

这里还是以Service层与Dao层为例

IUserDao

package com.linjie;

/**
 * @author LinJie
 * Dao接口
 */
public interface IUserDao {
    //查询用户年龄
    public void FindUserAge();
}

UserDaoImpl

package com.linjie;

/**
 * @author LinJie
 * Dao层实现类
 */
public class UserDaoImpl implements IUserDao {
    @Override
    public void FindUserAge() {
        System.out.println("UserDaoImpl_Dao层被调用");
    }
}

IUserService

package com.linjie;

/**
 * @author LinJie
 * service接口
 */
public interface IUserService {
    //登录
    public void login();
}

UserServiceImpl

package com.linjie;

public class UserServiceImpl implements IUserService {
    private IUserDao userDao;

    //Spring框架自动创建UserDaoImpl对象后,通过构造方法给对象赋值
    public UserServiceImpl(IUserDao userDao) {
        super();
        this.userDao = userDao;
    }

    public IUserDao getUserDao() {
        return userDao;
    }

    @Override
    public void login() {
        System.out.println("UserServiceImpl_Service层被调用");
        userDao.FindUserAge();
    }
}

测试类

package com.linjie;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTestaa {
    @Test
    public void Test() {
        //读取applicationContext.xml文件
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        //获取bean节点,通过bean的id来访问
        IUserService userService = (IUserService) context.getBean("userService");
        userService.login();
    }
}

applicationContext.xml

<?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">
    <bean id="userDao" class="com.linjie.UserDaoImpl"></bean>

    <bean id="userService" class="com.linjie.UserServiceImpl">
        <constructor-arg ref="userDao"></constructor-arg>
    </bean>
</beans>
ref:引用容器中其他的对象实例

结果

这里写图片描述


2、setter方法注入

与构造方法注入可以使用注入配置相对应,Spring为setter方法注入提供了< property>元素


< property>有一个name属性,用来指定该< property>将会注入的对象所对应的实例变量名称。之后通过value或者ref属性或者内嵌的其他元素来指定具体的依赖对象引用或值

setter方法注入——传值

Student.java

package com.linjie.aaa;

/**
 * @author LinJie
 * Student类
 */
public class Student {
    private String name;
    private int age;

    /**
     * @param name the name to set
     */
    public void setName(String name) {
        this.name = name;
    }
    /**
     * @param age the age to set
     */
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + "]";
    }   
}

测试类

package com.linjie.aaa;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringTestaaa {
    @Test
    public void Test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student = (Student) context.getBean("student");
        System.out.println(student);
    }
}

applicationContext.xml

<?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">
    <bean id="student" class="com.linjie.aaa.Student">  
        <property name="name" value="LinJie"></property>
        <property name="age" value="20"></property>     
    </bean>
</beans>

结果

这里写图片描述

setter方法注入——为当前业务对象注入其所依赖的对象

其实这个例子在之前的博客中已经提及,利用Service层与Dao来示例

IUserDao

package com.linjie;

/**
 * @author LinJie
 * Dao接口
 */
public interface IUserDao {
    //查询用户年龄
    public void FindUserAge();
}

UserServiceImpl

package com.linjie;

/**
 * @author LinJie
 * Dao层实现类
 */
public class UserDaoImpl implements IUserDao {
    @Override
    public void FindUserAge() {
        System.out.println("UserDaoImpl_Dao层被调用");
    }
}

IUserService

package com.linjie;

/**
 * @author LinJie
 * service接口
 */
public interface IUserService {
    //登录
    public void login();
}

UserServiceImpl

package com.linjie;

public class UserServiceImpl implements IUserService {
    private IUserDao userDao;//与配置文件中property name="userDao"的name对应(其实name=""是与setUserDao的名称相同)

    //Spring框架自动创建UserDaoImpl对象后,通过setUserDao方法给userDao赋值
    public void setUserDao(IUserDao userDao) {
        this.userDao = userDao;
    }

    public IUserDao getUserDao() {
        return userDao;
    }

    @Override
    public void login() {
        System.out.println("UserServiceImpl_Service层被调用");
        userDao.FindUserAge();
    }
}

测试类

package com.linjie;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTest {
    @Test
    public void Test() {
        //读取applicationContext.xml文件
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

        //获取bean节点,通过bean的id来访问
        IUserService userService = (IUserService) ac.getBean("userService");
        userService.login();
    }
}

applicationContext.xml

<?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">

    <!-- 配置要创建的对象信息 -->
    <!-- id/name:用于识别不同的bean,唯一标识一个节点,标识对象的名字 -->
    <!-- bean:每个bean相当于要创建的对象(利用反射机制) -->
    <!-- class:创建哪个类的对象,要全类名 -->

    <bean id="userDao" class="com.linjie.UserDaoImpl"></bean>

    <!-- DI:依赖注入
        双方都必须是bean,在创建service的时候,主动将dao的依赖对象交给service -->

        <bean id="userService" class="com.linjie.UserServiceImpl">

        <!-- setter方法注入
        name:要与类中的userDao一致(即创建的userDao变量),然后调用userDao的setUserDao方法
        ref:Spring容器中定义的bean对象的名字 -->

        <property name="userDao" ref="userDao"></property>
        </bean>

</beans>

当然setter方法注入还可以和构造方法注入同时使用

MockBusinessObject.java

package com.linjie.aaa;

/**
 * @author LinJie
 * @description:setter方法注入与构造方法注入同时使用
 */
public class MockBusinessObject {
    private String dependency1;
    private String dependency2;

    public MockBusinessObject(String dependency1) {
        super();
        this.dependency1=dependency1;
    }

    /**
     * @param dependency2 the dependency2 to set
     */
    public void setDependency2(String dependency2) {
        this.dependency2 = dependency2;
    }

    @Override
    public String toString() {
        return "MockBusinessObject [dependency1=" + dependency1 + ", dependency2=" + dependency2  + "]";
    }
}

测试类

package com.linjie.aaa;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringTestaaa {
    @Test
    public void Test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        MockBusinessObject mockBo = (MockBusinessObject) context.getBean("mockBo");
        System.out.println(mockBo);
    }
}

applicationContext.java

<?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">
    <bean id="mockBo" class="com.linjie.aaa.MockBusinessObject">  
        <constructor-arg name="dependency1" value="11111"></constructor-arg>
        <property name="dependency2" value="22222"></property>      
    </bean>
</beans>

这里写图片描述


3、接口注入

关于接口注入因为它强制被注入对象实现不必要的接口,带有侵入性。所以基本已经放弃,所以这里就不多阐述了。


三种注入方式优缺点

1、构造方法注入

优点:对象在构造完成之后,即已经进入就绪状态,可以马上使用

缺点:当依赖对象比较多的时候,构造方法的参数列表会比较长。而通过反射构造对象的时候,对相同类型的参数处理会比较困难。并且在Java中,构造方法无法被继承,无法设置默认值。对于非必须的依赖处理,可能需要引入多个构造方法,而参数数量的变动可能造成维护不方便

2、setter方法注入

优点:因为方法可以命名,所以setter方法注入在描述性上要比构造方法注入要好些,另外,setter方法可以被继承,允许设置默认值,而且有良好的IDE支持

缺点:对象无法构造完成后马上进入就绪状态

3、接口注入

因为它强制被注入对象实现不必要的接口,带有侵入性。所以基本已经放弃使用

建议:采用以setter注入为主,构造注入为辅的注入策略。对于依赖关系无需变化的注入,尽量采用构造注入;而其他的依赖关系的注入,则考虑采用setter注入。

参考

《Spring揭秘》

《Spring IN ACTION》

猜你喜欢

转载自blog.csdn.net/w_linux/article/details/80037885