前言:去年到现在一直没有很好的时间完成这个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注⼊
画了一张图,可以看看理解下
好了,本章就先到这,下一章节我们说反转控制+依赖注入,敬请期待!!!
喜欢的点赞三连吧 哈哈哈