Spring依赖注入——DI
Spring 依赖注入(DI Dependency Injection),可以自动帮我们解决类与类之间的各种依赖问题。通常公司中的项目都包含很多的类,而这些类与类之间都有各种依赖关系,比如:组合、聚合、依赖等。通过Spring的DI功能,结合IOC容器, 我们可以把这些复杂的关系交由Spring来管理, 我们在使用时,Spring会自动帮我们把依赖的对象注入进来,大大降低开发难度。
Spring中的依赖注入主要包含两种:通过构造方法中的注入和通过Setter方法注入
Setter方法注入:
1.注册匿名内部bean
匿名内部bean不需要写id,无法让外部访问
示例:
<bean id="aa" class="com.lanou3g.spring.bean.Student" >
<property name="fruit">
<bean class="com.lanou3g.spring.simple.Apple" />
</property>
</bean>
使用入口:
public static void main(String[] args) {
//初始化容器
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ctx.registerShutdownHook();
Student s=ctx.getBean("aa",Student.class);
s.getFruit().eatFruit();
}
2.注入集合类型属性
如果你想传递多个值,spring为我们提供了四中类型的集合,List、Set、Map 和 Properties,这四种 几个对应 和 标签来方便我们注入集合的属性
示例:
@Setter
@Getter
public class Java {
private List<Object> list;
private Set<Object> set;
private Map<String,Object> map;
private Properties properties;
}
XML配置代码
<bean id="aaa" class="com.lanou3g.spring.bean.Java">
<property name="list">
<list>
<value>l章三</value>
<value>l李四</value>
<value>l王五</value>
</list>
</property>
<property name="set">
<set>
<value>s章三</value>
<value>s李四</value>
<value>s王五</value>
</set>
</property>
<property name="map">
<map>
<entry key="章三" value="三"></entry>
<entry key="李四" value="四"></entry>
</map>
</property>
<property name="properties">
<props>
<prop key="one">one</prop>
<prop key="two">two</prop>
</props>
</property>
</bean>
示例入口:
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Java j=ctx.getBean("aaa",Java.class);
System.out.println(j.getList());
System.out.println(j.getSet());
System.out.println(j.getMap());
System.out.println(j.getProperties());
}
3.注入null和空字符串属性值
示例:
@Getter
@Setter
public class Null {
private String name;
private String nnn;
}
xml配置代码
<bean id="ss" class="com.lanou3g.spring.bean.Null">
<property name="name" value="">
</property>
<property name="nnn">
<null></null>
</property>
</bean>
使用入口:
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Null n=ctx.getBean("ss",Null.class);
System.out.println("空字符="+n.getName());
System.out.println("空字符="+n.getNnn());
}
4.注入复合属性值
例如有一个Java类中有一个Null类型的属性,Null类型中又包含了一个name属性,name属性的值需要设置为’章撒’,我们可以通过下面的方式配置
@Setter
@Getter
public class Java {
private List<Object> list;
private Set<Object> set;
private Map<String,Object> map;
private Properties properties;
private Null anull;
}
@Getter
@Setter
public class Null {
private String name;
}
<bean id="ss" class="com.lanou3g.spring.bean.Java">
<property name="anull">
<bean class="com.lanou3g.spring.bean.Null"/>
</property>
<property name="anull.name" value="章撒"></property>
</bean>
5.注入外部properties配置文件
将外部properties文件中的属性注入到bean中,
示例:
@Getter
@Setter
public class Null {
private String url;
private String driver;
private String user;
private String password;
}
<!-- 引入外部的properties文件开始 在External Libraries里面-->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:jdbc.properties"/>
</bean>
<bean id="null" class="com.lanou3g.spring.bean.Null">
<property name="url" value="${jdbc.url}" />
<property name="driver" value="${jdbc.driver}"/>
<property name="user" value="${jdbc.user}" />
<property name="password" value="${jdbc.password}" />
</bean>
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Null n=ctx.getBean(Null.class);
System.out.println(n.getUrl());
System.out.println(n.getDriver());
System.out.println(n.getUser());
System.out.println(n.getPassword());
}
6.通过p命名空间注入属性
要想使用p命名空间,必须要加相应的p的schema
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
通过p命名空间来注入属性
<!--<bean id="yunjie1" class="com.lanou3g.spring.bean.Student">
<property name="sname" value="张三" />
</bean>-->
<!-- 等效于上面的配置 -->
<bean id="yunjie1" class="com.lanou3g.spring.bean.Student" p:sname="张三" />
构造参数注入
package x.y;
public class ThingOne {
private int age;
private String sname;
private int gender;
private ThingTwo thingTwo;
public ThingOne(int age, ThingTwo thingTwo, String sname, int gender) {
this.age = age;
this.thingTwo = thingTwo;
this.sname = sname;
this.gender = gender;
// ...
}
}
public class ThingTwo {
// ...
}
<beans>
<bean id="beanOne" class="x.y.ThingOne">
<!-- 通过此标签注入构造方法中的参数 -->
<constructor-arg name="age" value="18" />
<constructor-arg type="java.lang.String" value="张三" />
<constructor-arg ref="beanTwo"/>
</bean>
<bean id="beanTwo" class="x.y.ThingTwo"/>
</beans>
constructor-arg标签属性
属性名 | 取值 | 含义 | |
name | 字符串 | 构造参数的名称 | |
value | 数值、字符串 | 构造参数的属性值,只适用于简单类型 | |
type | 类的全名 | 构造参数的类型,如果是简单类型,需要指定对应的封装类型 如果构造参数中有多个命中的类型,按照配置的先后顺序注入(可以通过配合index属性改变默认顺序) | |
index | 数值 | 构造参数的位置,从0开始 | |
ref | 其他在Spring容器中配置的bean的name或者id | 将容器中的其他bean作为构造参数注入 |
通过c命名空间注入构造参数
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<!-- 传统方式注入构造参数 -->
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="something@somewhere.com"/>
</bean>
<!-- c命名空间方式注入构造参数 -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>
</beans>
方法注入(method Injection)
通过lookup-method方式覆盖目标bean的指定方法
设想一下,如果我们在ioc容器中有个普通的单例类,它有一个方法中需要依赖其他一个非单例的类。由于单例类在整个ioc容器的生命周期中只会被初始化一次,那它依赖的那个非单例的类也就只有一个实例(因为只有初始单例类时注入一次),这显然不满足我们的需求。
Spring除了构造参数注入和setter注入外,还给我们提供了一种方式叫做方法注入,专门用于解决这种场景的问题,具体用法如下
实例
假设有一个果盘,果盘里放了一些水果,比如苹果,香蕉等,我们希望我们每次在果盘里拿到的都是最新鲜的水果。
java代码:
// 定义一个水果类
public class Fruit {
public Fruit() {
System.out.println("I got Fruit");
}
}
// 苹果
public class Apple extends Fruit {
public Apple() {
System.out.println("I got a fresh apple");
}
}
// 香蕉
public class Banana extends Fruit {
public Banana () {
System.out.println("I got a fresh banana");
}
}
// 水果盘,可以拿到水果
public abstract class FruitPlate{
// 抽象方法获取新鲜水果
public abstract Fruit getFruit();
}
spring配置:
<!-- method lookup inject start -->
<!-- 注意这两个bean是prototype -->
<bean id="apple" class="com.john.spring.methodinject.Apple" scope="prototype" />
<bean id="banana" class="com.john.spring.methodinject.Banana" scope="prototype" />
<!-- 苹果果盘 -->
<bean id="applePlate" class="com.john.spring.methodinject.FruitPlate">
<lookup-method name="getFruit" bean="apple" />
</bean>
<!-- 香蕉果盘 -->
<bean id="bananaPlate" class="com.john.spring.methodinject.FruitPlate">
<lookup-method name="getFruit" bean="banana" />
</bean>
<!-- method lookup inject end -->
测试代码:
public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("classpath:resource/applicationContext.xml");
FruitPlate fp1= (FruitPlate)app.getBean("fruitPlate1");
FruitPlate fp2 = (FruitPlate)app.getBean("fruitPlate2");
fp1.getFruit();
fp2.getFruit();
}
测试结果:
I got Fruit
I got a fresh apple
I got Fruit
I got a fresh bananer
示例说明:
从上面例子我们可以看到,在代码中,我们没有用到Spring的任何类和接口,实现了与Spring代码的解耦。
其中,最为核心的部分就是lookup-method的配置和FruitPlate.getFruit()方法。上面代码中,我们可以看到getFruit()方法是个抽象方法,我们并没有实现它啊,那它是怎么拿到水果的呢。这里的奥妙就是Srping应用了CGLIB(动态代理)类库。Spring在初始化容器的时候对配置的bean做了特殊处理,Spring会对bean指定的class做动态代理,代理标签中name属性所指定的方法,返回bean属性指定的bean实例对象。每次我们调用fruitPlate1或者fruitPlate2这2个bean的getFruit()方法时,其实是调用了CGLIB生成的动态代理类的方法。关于CGLIB大家可自行在网上查阅。
lookup-method实现方式说明
说白了lookup-method注入是容器覆盖容器管理的bean上的方法并返回容器中另一个命名bean的能力
如果通过xml方式配置,我们可以这样做:
<bean class="beanClass">
<lookup-method name="method" bean="non-singleton-bean"/>
</bean>
method是beanClass中的一个方法,beanClass和method是不是抽象都无所谓,不会影响CGLIB的动态代理,根据项目实际需求去定义。non-singleton-bean指的是lookup-method中bean属性指向的必须是一个非单例模式的bean,当然如果不是也不会报错,只是每次得到的都是相同引用的bean(同一个实例),这样用lookup-method就没有意义了。
如果通过注解的方式,我们可以这样做:
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup("myCommand")
// @Lookup //这里也可以不写参数,让Spring容器根据类型来自动返回匹配此类型的bean(有多于一个匹配返回类型的bean会报错)
protected abstract Command createCommand();
}
lookup-method的目标method的签名标准
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
- public|protected要求方法必须是可以被子类重写和调用的;
- abstract可选,如果是抽象方法,CGLIB的动态代理类就会实现这个方法,如果不是抽象方法,就会覆盖这个方法,所以没什么影响;
- return-type就是non-singleton-bean的类型咯,当然可以是它的父类或者接口。
- no-arguments不允许有参数。
自动装配
自动装配允许我们不用显示给某个bean注入依赖的属性或者构造参数,而交由Spring自动帮我们注入所需的依赖。
Spring给我们提供了以下四种选项
自动装配模式 | 说明 |
| (默认值) 禁止Spring帮我们自动装配依赖,所有依赖由开发者显示注入。 |
| 按照bean的名称注入。比如beanA有一个属性beanB需要注入,如果beanA配置了自动装配模式是byName,那么Spring会在容器中找bean的名称为beanB的bean,找到后自动帮我们注入到beanA中。如果没找到,则不会注入。 |
| 按照bean的类型注入。byType模式需要保证容器中符合注入的类型的bean只有一个,如果匹配的类型有不止一个bean,那么会直接抛出运行时异常。如果没有一个匹配类型的bean,则不会注入。 |
| 类似于byType。但这种模式应用的对象是构造参数。如果构造参数需要注入参数类型的bean有不止一个,同样会抛出运行时异常。 |
byName方式
Java配置
@Setter
@Getter
public class Ceshi {
private String bname;
private Stu stu;
}
@Setter
@Getter
public class Stu {
private int age;
}
XML配置
<!-- 给stu的年龄赋值 -->
<bean id="stu" class="com.lanou3g.spring.simple.Stu">
<property name="age" value="17"></property>
</bean>
<bean id="ceshi" autowire="byName" class="com.lanou3g.spring.simple.Ceshi">
<property name="bname" value="历史"/>
</bean>
使用入口
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Ceshi ceshi=ctx.getBean("ceshi", Ceshi.class);
System.out.println(ceshi.getBname());
System.out.println(ceshi.getStu().getAge());
}
byType方式
使用方式如上
xml配置``
<!-- 给stu的年龄赋值 -->
<bean id="stu" class="com.lanou3g.spring.simple.Stu">
<property name="age" value="17"></property>
</bean>
<bean id="ceshi" autowire="byType" class="com.lanou3g.spring.simple.Ceshi">
<property name="bname" value="历史"/>
</bean>
constructor方式
Java代码
@Setter
@Getter
public class Ceshi {
private String bname;
private Stu stu;
public Ceshi(Stu stu) {
this.stu = stu;
}
}
xml配置
<!-- 给stu的年龄赋值 -->
<bean id="stu" class="com.lanou3g.spring.simple.Stu">
<property name="age" value="17"></property>
</bean>
<bean id="ceshi" autowire="constructor" class="com.lanou3g.spring.simple.Ceshi">
<property name="bname" value="历史"/>
</bean>
使用入口
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Ceshi ceshi=ctx.getBean("ceshi", Ceshi.class);
System.out.println(ceshi.getBname());
System.out.println(ceshi.getStu().getAge());
}
将某个bean从自动装配候选中排除
我们可以通过给bean添加autowire-candidate属性,并且设置为false,从而让这个bean不参与自动装配(不会被自动装配到其他bean上)