Spring基础专题——第二章(注入Injection)

前言:去年到现在一直没有很好的时间完成这个spring基础+源码的博客目标,去年一年比较懒吧,所以今年我希望我的知识可以分享给正在奋斗中的互联网开发人员,以及未来想往架构师上走的道友们我们一起进步,从一个互联网职场小白到一个沪漂湿人,一路让我知道分享是一件多么重要的事情,总之不对的地方,多多指出,我们一起徜徉代码的海洋!

 

我这里做每个章节去说的前提,不是一定很标准的套用一些官方名词,目的是为了让大家可以理解更加清楚,如果形容的不恰当,可以留言出来,万分感激

1、什么是注入

定义:通过Spring工厂以及配置文件,为所创建的对象的成员变量赋值

1.1、为什么需要注入

通过编码的方式,为成员变量赋值,存在耦合,举个例子:

public void Test1(){
        ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
        Person person = (Person) ctx.getBean("person");

        //代码中为成员变量赋值,存在耦合
        person.setId(1);
        person.setName("doctchen");

        System.out.println("person = " + person);
    }

1.2、如何进行注入

  • 类的成员变量提供get set方法
  • 配置Spring的配置文件
<bean id="person" class="com.doctchen.spring5.entity.Person">
        <property name="id">
            <value>2</value>
        </property>
        <property name="name">
            <value>zhangsan</value>
        </property>
</bean>

注入的好处:解耦合

2、Spring注入原理分析(简易版)

Spring通过底层调⽤对象属性对应的set⽅法,完成成员变量的赋值,这种⽅式我们也称之为set注⼊

2.1、Set注入详解

针对于不同类型的成员变量,在<property>标签,需要嵌套其他标签,这句话留个影响,后面我们做案例!

<property>
    xxxxxxxx
</property>

看个大概的图文表示:

图中很清楚的表示Set注入可以有两种类型的注入:1、基本的JDK内置类型       2、用户可以自定义类型

所以我们一定要注意,Set注入前提,你这些属性一定是要有Set方法,不然配置文件applicationContext.xml会报错property标签的name属性找不到

我们来看第一种

1、基本的JDK内置类型

  • private String name;
<bean id="customer" class="com.doctchen.spring5.entity.Customer">
        <!--    对应private String name    -->
        <property name="name">
            <value>doctchen</value>
        </property>
</bean>
  • private int age
<bean id="customer" class="com.doctchen.spring5.entity.Customer">
        <!--    对应private int age    -->
        <property name="age">
            <value>111</value>
        </property>
</bean>
  • private String[] emails
 <bean id="customer" class="com.doctchen.spring5.entity.Customer">
        <!--    对应private String[] emails    -->
        <property name="emails">
            <list>
                <value>zhangsan@qq.com</value>
                <value>lisi@qq.com</value>
                <value>wangwu@qq.com</value>
            </list>
        </property>
</bean>
  • private Set<String> tels

注意这里强调下,因为Set集合是无序的,所以后期你打印结果的值,有可能是无序的,你发现你打印的结果是按照顺序打印的,说明巧了,不一定是按照配置文件写入value的顺序打印

还有就是我在Set中定义的泛型是String,所以你的值可以写value,但是如果是private Set tels,泛型就是Object类型了;这时候就可以不止写value了,还可以加其他的类型(JDK基本+自定义),这里先提出一个类型:

<ref bean=""/> ------>自定义类型

<bean id="customer" class="com.doctchen.spring5.entity.Customer">
        <!--    对应private Set<String> tels    -->
        <property name="tels">
            <set>
                <value>set1@qq.com</value>
                <value>set2@qq.com</value>
                <value>set3@qq.com</value>
                <value>set3@qq.com</value>
                <value>set3@qq.com</value>
            </set>
        </property>
</bean>
  • private List<String> address
 <bean id="customer" class="com.doctchen.spring5.entity.Customer">
        <!--    对应private List<String> address    -->
        <property name="address">
            <list>
                <value>list1@qq.com</value>
                <value>list2@qq.com</value>
                <value>list3@qq.com</value>
            </list>
        </property>
</bean>
  • private Map<String,String> qqs

Map我这里要提两句,因为Map对象我们知道是由一个一个Entry对象组成,所以要有个Entry标签,其次在key中,因为我们设定泛型是两个String,key这个标签里不知道你key值的类型,所以要有个<value>标签,当然如果你key泛型为Object,自然value标签就换成<ref bean=""/>;而value值做为key的对应关系,spring为了简化代码,就直接给了<value>标签,不会是

<value><value>sss</value></value>这种类型的,同理Map的value中,也是泛型Object,你也可以套用其他的如自定义<ref bean=""/>,都可以动手试试!!

<bean id="customer" class="com.doctchen.spring5.entity.Customer">
        <property name="qqs">
            <map>
               <entry>
                   <key><value>doctchen</value></key>
                   <value>zhangsan@qq.com</value>
               </entry>
            </map>
        </property>
</bean>
  • private Properties properties;

Properties类型 特殊的Map key=String,value=String  

<bean id="customer" class="com.doctchen.spring5.entity.Customer">
        <property name="p">
            <props>
                <prop key="zhangsan">doctchen</prop>
                <prop key="lisi">doctchen</prop>
                <prop key="wangwu">doctchen</prop>
            </props>
        </property>
</bean>
  • 对于复杂的JDK类型,比如Date

需要程序员自定义类型转换器,处理,这个后面章节体现

上面的代码:

public class Customer implements Serializable {

    private String name;
    private int age;
    private String[] emails;
    private Set<String> tels;
    private List<String> address;
    private Map<String, String> qqs;
    private Properties p;

    @Override
    public String toString() {
        return "Customer{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", emails=" + Arrays.toString(emails) +
                ", tels=" + tels +
                ", address=" + address +
                ", qqs=" + qqs +
                ", p=" + p +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Map<String, String> getQqs() {
        return qqs;
    }

    public void setQqs(Map<String, String> qqs) {
        this.qqs = qqs;
    }

    public String[] getEmails() {
        return emails;
    }

    public void setEmails(String[] emails) {
        this.emails = emails;
    }

    public Set<String> getTels() {
        return tels;
    }

    public void setTels(Set<String> tels) {
        this.tels = tels;
    }

    public List<String> getAddress() {
        return address;
    }

    public void setAddress(List<String> address) {
        this.address = address;
    }

    public Properties getP() {
        return p;
    }

    public void setP(Properties p) {
        this.p = p;
    }
}
    @Test
    public void Test2(){
        ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
        Customer customer = (Customer) ctx.getBean("customer");
        System.out.println("customer = " + customer);
    }

打印结果:

customer = Customer{name='doctchen', age=111, emails=[zhangsan@qq.com, lisi@qq.com, wangwu@qq.com], tels=[set1@qq.com, set2@qq.com, set3@qq.com], address=[list1@qq.com, list2@qq.com, list3@qq.com], qqs={doctchen=zhangsan@qq.com}, p={zhangsan=doctchen, lisi=doctchen, wangwu=doctchen}}

 2、用户可以自定义类型

需求,我们要往UserServiceImpl类中注入UserDao

/**
 * 就把UserServiceImpl作为一个普通的类
 * 这个类里就一个UserDao这个属性
 * 然后剩下get,set方法
 */
public class UserServiceImpl implements UserService {

    private UserDao userDao;

    public UserDao getUserDao() {
        return userDao;
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}

既然要Set注入属性userDao,就一定要先有这个UserServiceImpl对象,也就是先有这个bean

<bean id="userServiceImpl" class="com.doctchen.spring5.service.UserServiceImpl">
        <property name="userDao">
            
        </property>
</bean>

而需要注入userDao的话,又得需要一个userDao对象,所以我们之间在属性property里面之间做一个bean对象

<bean id="userServiceImpl" class="com.doctchen.spring5.service.UserServiceImpl">
        <property name="userDao">
            <bean id="userDao" class="com.doctchen.spring5.dao.UserDaoImpl"/>
        </property>
</bean>

但是假如我又来了一个OrderServiceImpl,里面也需要一个userDao对象,是不是又这样写?

<bean id="userServiceImpl" class="com.doctchen.spring5.service.UserServiceImpl">
        <property name="userDao">
            <bean id="userDao" class="com.doctchen.spring5.dao.UserDaoImpl"/>
        </property>
</bean>

<bean id="orderServiceImpl" class="com.doctchen.spring5.service.OrderServiceImpl">
        <property name="userDao">
            <bean id="userDao" class="com.doctchen.spring5.dao.UserDaoImpl"/>
        </property>
</bean>

如果userDao在很多业务中都要用到,这个重复代码是不是很多,于是Spring有一种简化方式,引出我们的<ref bean=""/>

<bean id="userServiceImpl" class="com.doctchen.spring5.service.UserServiceImpl">
        <!-- name代表private UserDao userDao 这属性值-->
        <property name="userDao">
            <!-- bean的名字代表下面的<bean id="userDao"> 这个对象的id-->
            <ref bean="userDao"></ref>
        </property>
    </bean>

<bean id="userDao" class="com.doctchen.spring5.dao.UserDaoImpl">
</bean>

测试代码:

    //打印 com.doctchen.spring5.dao.UserDaoImpl@63440df3
    @Test
    public void Test3(){
        ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
        UserServiceImpl userServiceImpl = (UserServiceImpl) ctx.getBean("userServiceImpl");
        System.out.println(userServiceImpl.getUserDao());
    }

ok读者可以下去测试下!!

2.2、Set注入简化写法

  • 基于属性的简化

我们用value属性的方式,替换了原有的value标签,举个例子

<bean id="customer" class="com.doctchen.spring5.entity.Customer">
        <!--    对应private String name    -->
<!--        <property name="name">-->
<!--            <value>doctchen</value>-->
<!--        </property>-->

        <!--  简化后 -->
        <property name="name" value="doctchen"/>
</bean>

注意:value属性,只能简化,8种基本类型+String,也就是value属性只能替换value标签,这里不理解也没关系,只是简化写法的一种方式,但是像List,Set,Map里面的value标签,是简化不了的;

下面这种是用户自定义类型简化

<bean id="userServiceImpl" class="com.doctchen.spring5.service.UserServiceImpl">
<!--        <property name="userDao">-->
<!--            <ref bean="userDao"></ref>-->
<!--        </property>-->

        <!--  用户自定义类型简化 -->
        <property name="userDao" ref="userDao"/>
</bean>
  • 基于p空间命名简化

p的意思就是property标签的缩写,记得你用p标签的时候,xml文件中要有这句,idea会自动给你引入的不需要手敲

这就很明白意思了,上代码你们看看

这里强调下,p命名空间注入Map,我目前是没有做过,但是应该可以,后续我可以贴出来,这里了解下p命名空间就可以了,知道是可以简化代码的一种方式

那么对于p命名在自定义类型呢

<bean id="userService" class="xx.UserServiceImpl">
    <property name="userDAO">
        <ref bean="userDAO"/>
    </property>
</bean>

<!-- 简化后 -->
<bean id="userService" class="xxx.UserServiceImpl" p:userDAO-ref="userDAO"/>

3、构造注入

我们总结下前面的

  • 注入:通过Spring的配置文件,为成员变量赋值
  • Set注入:Spring调用Set方法,通过配置文件,为成员变量赋值
  • 构造注入:Spring调用构造方法,通过配置文件,为成员变量赋值

 

3.1、提供有参构造

public class Customer implements Serializable {

    public Customer(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

3.2、Spring的配置文件

当构造方法重载的时候,注意以下两个特点(重载定义:方法名字相同,而参数类型不同,参数个数,参数顺序不同

1、参数个数不同时,很明显下面代码

public Customer(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Customer(String name, int age, String[] emails) {
        this.name = name;
        this.age = age;
        this.emails = emails;
    }

 如果是前两个参数,Spring就调用第一个构造方法,如果三个参数都有,就调用第二个构造方法

<bean id="customer" class="com.doctchen.spring5.entity.Customer">
        <constructor-arg>
            <value>doctchen</value>
        </constructor-arg>
        <constructor-arg>
            <value>21</value>
        </constructor-arg>
        <constructor-arg>
            <array>
                <value>doc</value>
                <value>sss</value>
                <value>chenx</value>
            </array>
        </constructor-arg>
    </bean>

 这个时候通过控制<constructor-arg>标签的数量进⾏区分

2、构造参数个数相同时,顺序不同

public Customer(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public Customer(int age, String name) {
        this.name = name;
        this.age = age;
    }

配置文件

<bean id="customer" class="com.doctchen.spring5.entity.Customer">
<!--  一个constructor-arg对应构造方法的一个参数 -->
        <constructor-arg>
            <value>doctchen</value>
        </constructor-arg>
        <constructor-arg>
            <value>21</value>
        </constructor-arg>
 </bean>

这个是语法允许的,如果出现这样的情况,想靠两个<constructor-arg>标签来让Spring区别,就有点为难了,因为你的String,int都在8中基本数据+String类型,并且都是<value>标签(上面说过value标签适用8+String),所以Spring分不出来,默认就会按序只赋值给第一个构造方法。

如果想让Spring区分,系统通过在标签引⼊type属性 进⾏类型的区分 <constructor-arg type="">

 <bean id="customer" class="com.doctchen.spring5.entity.Customer">
        <constructor-arg type="int">
            <value>111</value>
        </constructor-arg>
        <constructor-arg type="java.lang.String">
            <value>doctchen</value>
        </constructor-arg>
    </bean>

这样就可以调用到第二个构造方法了!!!

4、注入总结

未来的实战中,应⽤set注⼊还是构造注⼊?
答案:set注⼊更多

  • 构造注⼊麻烦(重载)
  • Spring框架底层 ⼤量应⽤了set注⼊

画了一张图,可以看看理解下

好了,本章就先到这,下一章节我们说反转控制+依赖注入,敬请期待!!!

喜欢的点赞三连吧 哈哈哈

 

 

 

 

 

 

 

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

风清扬逍遥子

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

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

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

打赏作者

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

抵扣说明:

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

余额充值