Spring-依赖注入
来源:互联网 发布:免费舆情采集软件 编辑:程序博客网 时间:2024/05/13 10:30
- 概述
- 属性注入
- 属性注入实例
- 代码演示
- JavaBean关于属性命名的特殊规范
- 属性注入实例
- 构造函数注入
- 按类型匹配入参
- 按索引匹配入参
- 联合使用类型和索引匹配入参
- 通过自身反射类型匹配入参
- 循环依赖问题
- 工厂方法注入
- 选择注入方式的考量
概述
Spring支持两种依赖注入的方式
- 属性注入
- 构造函数注入
此外Spring还支持工厂方法注入。 这篇博文我们将了解到不同注入方式的具体配置方法。
属性注入
属性注入指的是通过setXxx()方法注入Bean的属性值或者依赖对象。
由于属性注入方式具有可选择性和灵活性高的有点,因此属性注入是实际应用中最常用的注入方式。
属性注入实例
属性注入的要求
- 提供一个默认的构造函数
- 为需要注入的属性提供对应的Setter方法
Spring先调用Bean的默认构造函数实例化Bean对象,然后通过反射调用Setter方法注入属性值。
代码演示
POJO对象
package com.xgj.ioc.inject.set;public class Plane { private String brand; private String color; private int speed; /** * 在没有其他显示构造函数的情况下,默认构造函数可省略,有则必须声明 */ public Plane(){ } public Plane(String brand,String color,int speed){ this.brand = brand; this.color = color; this.speed = speed; } public void setBrand(String brand) { this.brand = brand; } public void setColor(String color) { this.color = color; } public void setSpeed(int speed) { this.speed = speed; } public void introduce() { System.out.println("Plane brand:" + brand + ",color:" + color + ",speed," + speed); }}
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"> <bean id="plane" class="com.xgj.ioc.inject.set.Plane"> <property name="brand"> <value>A380</value> </property> <property name="color"> <value>red</value> </property> <property name="speed"> <value>700</value> </property> </bean></beans>
测试类:
package com.xgj.ioc.inject.set;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class SetTest { public static void main(String[] args) { // 加载配置文件,实例化bean ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:com/xgj/ioc/inject/set/plane.xml"); // 从容器中获取bean Plane plane = ctx.getBean("plane",Plane.class); // 调用方法 plane.introduce(); }}
结果:
解读:
上面的代码配置了一个Bean,并为该Bean的3个属性提供了属性值, 具体来说Bean的每一个属性对应一个property标签,name为属性的名称(上述配置较为繁琐,后续介绍p的用法),在Bean实现类中拥有与其对应的Setter方法: 比如 brand对应setBrand()。
有一点需要注意: spring只会检查Bean中是否有对应的Setter方法,至于Bean中是否有对应的属性成员变更则不做要求。
举个例子:
配置文件中<property name="brand">
的属性配置项仅要求Plane中拥有setBrand()方法,但Plane类中不一定要拥有brand成员变量。
比如
private String color;private int speed;// 仅拥有setBrand方法,但是类中没有brand成员变量public void setBrand(String brand) { ...... }
但一般情况下,还是按照约定俗成的方式在Bean中提供同名的属性变量
注意:
默认构造函数是不带参数的构造函数。 Java语言规定,如果类中没有定义任何构造函数,JVM会自动为其生成一个默认的构造函数;反之,如果类中显式的定义了构造函数,JVM则不会为其生成默认的构造函数。如果类中显式的声明了其他构造函数,如果未提供一个默认的构造函数,则属性注入时,会抛出异常。
举例:
我们在Plane中增加一个显式构造函数,去掉默认的构造函数
public Plane(String brand,String color,int speed){ this.brand = brand; this.color = color; this.speed = speed; }
运行测试类,
Failed to instantiate [com.xgj.ioc.inject.set.Plane]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.xgj.ioc.inject.set.Plane.<init>()
JavaBean关于属性命名的特殊规范
Spring配置文件中property元素所指定的属性名和Bean实现类的Setter方法满足Sun JavaBean的属性命名规范: xxx的属性对应setXxx()方法.
一般情况下,Java的属性变量名都以小写字母开头,比如brand,speed等,但也有些特殊情况的存在。考虑到一些特定意义的大写英文缩略字母(比如USA、XML等),Javabean也允许以大写字母开头的属性变量名,不过必须满足 变量的前两个字母要么全部大写,要么全部小写。
比如brand,IDCode、IC等是合法的,而iC、iDCard等是非法的。
比如我们在Plane类中添加属性和setter方法
// 非法的属性变量名,但是Java并不会报错,因为它将iDCard看做普通的变量 private String iDCard; // 改setter方法对应IDCard属性,而非iDCard属性 public void setIDCard(String iDCard) { this.iDCard = iDCard; }
因为xxx的属性对应setXxx()方法. 所以是setIDCard().
bean配置文件
STS中校验都未通过,更加无法运行了。
总结:
以大写字母开头的变量总是显得比较另类,为了规避这种诡异的错误,用户可遵照以下编程的经验:比如QQ、MSN、ID等正常情况下以大写字母出现的专业术语,在Java中一律调整为小写形式,如qq、msn、id等,以保证命名的统一性(变量名称都小写字母开头),减少出错概率。
构造函数注入
构造函数注入是除了属性注入之外另外一种常用的注入方式,构造函数注入保证一些必要的属性在Bean实例化的时候得到设置,确保Bean在实例化之后就可以使用
按类型匹配入参
举个例子,假设任何使用Tank对象都必须提供brand和weight,若使用属性注入 方式,这只能人为在配置时候提供保证而无法再语法级提供保证,这时候构造函数注入就可以很好地满足这一个需求。
使用构造函数的前提是Bean必须提供带参的构造函数。
代码演示:
Pojo Bean实现类
package com.xgj.ioc.inject.construct;public class Tank { private String brand; private double weight; public Tank(String brand, double weight) { super(); this.brand = brand; this.weight = weight; } public void introduce() { System.out.println("Tank information: brand:" + brand + ",weight:" + weight + "KG"); }}
配置文件
<?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="tank" class="com.xgj.ioc.inject.construct.Tank"> <constructor-arg type="java.lang.String"> <value>T72</value> </constructor-arg> <constructor-arg type="double"> <value>15000.00</value> </constructor-arg> </bean></beans>
测试类:
package com.xgj.ioc.inject.construct;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class ConstructInjectTest { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:com/xgj/ioc/inject/construct/tank.xml"); Tank tank = ctx.getBean("tank", Tank.class); tank.introduce(); }}
运行结果:
解读:
在constructor-arg的元素中有个type属性,它为Spring提供了判断配置项和构造函数入参对应关系的信息。
一个疑问
- 配置文件中constructor-arg声明顺序难道不能用于确定构造函数入参的顺序吗?—在只有一个构造函数的情况下当然是可以的,如果类中定义了多个具有相同入参的构造函数,这种顺序标识就失效了。
另外,Spring的配置文件采用和元素标签顺序无关的策略,一定程度上保证了配置信息的确定性,避免一些似是而非的问题。
<constructor-arg type="java.lang.String"> <value>T72</value> </constructor-arg> <constructor-arg type="double"> <value>15000.00</value> </constructor-arg>
这两个参数的位置并不会影响对最终的配置效果产生影响。
按索引匹配入参
众所周知,Java语言通过入参的类型和顺序区分不同的重载方法。
如果Tank类中有两个相同类型的入参,仅仅通过type就无法确定的对应关系了。这是需要通过入参索引的方式进行确定。
POJO对象
package com.xgj.ioc.inject.construct.index;public class Tank { private String brand; private double weight; private double speed; /** * * @param brand * @param weight * @param speed * 第二个参数和第三个参数同为double类型 */ public Tank(String brand, double weight, double speed) { this.brand = brand; this.weight = weight; this.speed = speed; } public void introduce() { System.out.println("Tank information: brand:" + brand + ",weight:" + weight + "KG,speed:" + speed + "km/h"); }}
第二个参数和第三个参数同为double类型,所以Spring无法确定type为double到底对应constructor-arg中的哪个,但是通过显示指定参数的索引能消除这种不确定性。
配置文件
<?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="tank" class="com.xgj.ioc.inject.construct.index.Tank"> <constructor-arg index="0" value="T72" /> <constructor-arg index="1" value="20000" /> <constructor-arg index="2" value="300" /> </bean></beans>
构造函数的一个参数索引为0,第二个为1,以此类推
测试类
package com.xgj.ioc.inject.construct.index;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class ConstructInjectTest { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:com/xgj/ioc/inject/construct/index/tank_index_match.xml"); Tank tank = ctx.getBean("tank", Tank.class); tank.introduce(); }}
运行结果:
联合使用类型和索引匹配入参
有时候需要type和 index联合使用才能确定配置项和构造函数入参的对应关系。
POJO 对象
package com.xgj.ioc.inject.construct.joint;public class Tank { private String brand; private double weight; private double speed; // 载人数量 private int manned; /** * * @param brand * @param weight * @param speed * 第二个参数和第三个参数同为double类型 */ public Tank(String brand, double weight, double speed) { this.brand = brand; this.weight = weight; this.speed = speed; } /** * * @param brand * @param weight * @param manned */ public Tank(String brand, double weight,int manned){ this.brand =brand; this.weight = weight; this.manned = manned; } public void introduce() { System.out.println("Tank information: brand:" + brand + ",weight:" + weight + "KG,speed:" + speed + "km/h"); } public void introduce2() { System.out.println("Tank information: brand:" + brand + ",weight:" + weight + "KG,manned:" + manned + "/person"); }}
配置文件
<?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="tank" class="com.xgj.ioc.inject.construct.joint.Tank"> <constructor-arg type="java.lang.String" index="0" value="T72" /> <constructor-arg type="double" index="1" value="25000" /> <constructor-arg type="int" index="2" value="3" /> </bean></beans>
如果仅仅根据index来配置,Spring无法知道第三个入参配置的类型究竟是int还是double ,因此需要明确指出第三个入参的类型以消除歧义。
事实上,constructor-arg中前两个的type属性可以去掉、
当然了,也可以直接用type来判断。
测试类
package com.xgj.ioc.inject.construct.joint;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class ConstructInjectTest { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:com/xgj/ioc/inject/construct/joint/tank_joint_match.xml"); Tank tank = ctx.getBean("tank", Tank.class); tank.introduce2(); }}
运行结果:
通过自身反射类型匹配入参
如果Bean构造函数入参的类型是可辩别的(非基础数据类型且入参类型各不相同),由于Java反射机制可以获取构造函数的入参类型,即使构造函数注入的配置不提供类型和索引的信息,Spring依然可以正确的完成构造函数的注入工作。
例子:
POJO类-Tank
package com.xgj.ioc.inject.construct.reflect;public class Tank { public void attack() { System.out.println("tank begins to attack"); }}
POJO类-Plane
package com.xgj.ioc.inject.construct.reflect;public class Plane { public void attack(){ System.out.println("plane begins to attack"); }}
配置文件
<?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="plane" class="com.xgj.ioc.inject.construct.reflect.Plane"/> <bean id="tank" class="com.xgj.ioc.inject.construct.reflect.Tank"/> <bean id="commander" class="com.xgj.ioc.inject.construct.reflect.Commander"> <!-- 没有设置type和index属性,通过入参值的类型完成映射匹配 --> <constructor-arg value="XGJ"/> <constructor-arg ref="plane"/> <constructor-arg ref="tank"/> </bean></beans>
解析:
由于plane tank name入参的类型都是可辨别的,所以无需再构造函数注入的配置时指定constructor-arg的type和index,因此可以采用如上的简易配置方式
测试类
package com.xgj.ioc.inject.construct.reflect;public class Commander { private Plane plane; private Tank tank; private String name; public Commander(Plane plane, Tank tank, String name) { super(); this.plane = plane; this.tank = tank; this.name = name; } public void direct(){ System.out.println("Commamder name:" + name); System.out.println("Commnader begins to direct the army"); plane.attack(); tank.attack(); }}
package com.xgj.ioc.inject.construct.reflect;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class ConstructInjectTest { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:com/xgj/ioc/inject/construct/reflect/beans.xml"); Commander commander = ctx.getBean("commander", Commander.class); commander.direct(); }}
运行结果
循环依赖问题
Spring容器能够对构造函数配置的Bean进行实例化有一个前提:Bean构造函数入参所引用的对象必须已经准备就绪。
鉴于这个机制,如果两个Bean都采用构造函数注入,并且都通过构造函数入参引用对方,就会发生类属于线程死锁的的循环依赖问题。
举个例子说明一下(飞行员和飞机):
package com.xgj.ioc.inject.construct.loop;public class Pilot { private String pilotNname; private Plane plane; public Pilot(String pilotNname, Plane plane) { super(); this.pilotNname = pilotNname; this.plane = plane; } public void drivePlane() { plane.fly(); }}
package com.xgj.ioc.inject.construct.loop;public class Plane { private Pilot pilot; private String planeBrand; public Plane(Pilot pilot, String planeBrand) { super(); this.pilot = pilot; this.planeBrand = planeBrand; } public void fly() { System.out.println("Plane :" + planeBrand + " is reday to fly"); }}
<?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="pilot" class="com.xgj.ioc.inject.construct.loop.Pilot"> <constructor-arg ref="plane"/> <constructor-arg value="F35"/> </bean> <bean id="plane" class="com.xgj.ioc.inject.construct.loop.Plane"> <constructor-arg ref="pilot"/> <constructor-arg value="XGJ"/> </bean></beans>
package com.xgj.ioc.inject.construct.loop;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class ConstructInjectTest { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:com/xgj/ioc/inject/construct/loop/beans.xml"); Pilot pilot = ctx.getBean("pilot", Pilot.class); pilot.drivePlane(); }}
运行结果:
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'pilot' defined in class path resource [com/xgj/ioc/inject/construct/loop/beans.xml]: Cannot resolve reference to bean 'plane' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'plane' defined in class path resource [com/xgj/ioc/inject/construct/loop/beans.xml]: Cannot resolve reference to bean 'pilot' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'pilot': Requested bean is currently in creation: Is there an unresolvable circular reference? at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:359).....................org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83) at com.xgj.ioc.inject.construct.loop.ConstructInjectTest.main(ConstructInjectTest.java:10)Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'plane' defined in class path resource [com/xgj/ioc/inject/construct/loop/beans.xml]: Cannot resolve reference to bean 'pilot' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'pilot': Requested bean is currently in creation: Is there an unresolvable circular reference? at ....... ....... ... 29 more
如何解决呢? 构造函数注入修改为 属性注入即可。
如下所示:
只需要修改beans.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="pilot" class="com.xgj.ioc.inject.construct.loopsolve.Pilot"> <property name="pilotNname" value="XGJ"></property> <property name="plane" ref="plane"></property> </bean> <bean id="plane" class="com.xgj.ioc.inject.construct.loopsolve.Plane"> <property name="planeBrand" value="F35"></property> <property name="pilot" ref="pilot"></property> </bean></beans>
运行结果
工厂方法注入
分为非静态工厂方法和静态工厂方法。
对于一个全新开发的应用来说,我们不推荐使用工厂方法的注入方式。因为工厂方法需要额外的类和代码,这些功能和业务并无关系。
选择注入方式的考量
仁者见仁 智者见智,并无定论。在合适的场景下使用合适的注入方式。
- Spring依赖注入:注解注入
- spring 的依赖注入
- Spring依赖注入实践经验
- Spring的依赖注入
- Spring依赖注入
- spring(依赖注入-DI)
- spring依赖注入
- spring依赖注入
- 白话spring依赖注入
- Spring依赖注入方式
- Spring的依赖注入
- Spring依赖注入
- Spring依赖注入方式
- Spring 依赖注入
- spring依赖注入方式
- spring的依赖注入
- Spring依赖注入方式
- Spring依赖注入方式:
- UncaughtException
- 毕业三年的一份工作总结
- 自定义Behavior之Floating控件进阶版
- MySQL InnoDB Cluster搭建
- Java经典算法40例(三十一)
- Spring-依赖注入
- 记录我的编程生涯
- windows下安装maven
- Nginx缓存原理及配置
- 【链表】判断链表是否有环,环的长度,环的入口点
- 做ppt课件直播(课堂直播)使用云课堂直播软件的教程
- 【bzoj2561】最小生成树
- 命令代换
- ACSII码排序