一、依赖注入的类型
1.构造函数注入
(1)应用场景
在使用组件之前,必须有一个依赖类的实例,并且该依赖是不可变的,不为null。这时候推荐选择构造函数注入,相反,没有依赖实例就无法创建组件。
(2)实现方式
①xml:
public class MessageProvider{
private String message;
public MessageProvider(String message){
this.message = message;
}
}
<bean id="messageProvider" class=".....">
<constructor-arg value="hahahah" />
</bean>
如果包含多个参数,通常要在<constructor-arg> 标签上使用index标记 。从 0开始。
②注解:
@Service
public class MessageProvider{
private String message;
@Autowired
public MessageProvider(@Value("hello") String message){
this.message = message;
}
}
可以用@Value注解,并且支持Spel表达式。还可以创建message的bean来进行注入。对于多参数可以使用@Qualifier()注解标记在每个参数前面(是参数前面,不是方法前面)
③构造注入的好处:
这个构造器注入的方式,能够保证注入的组件不可变,并且确保需要的依赖不为空。此外,构造器注入的依赖总是能够在返回客户端(组件)代码的时候保证完全初始化的状态
- 依赖不可变:其实说的就是final关键字。
- 依赖不为空(省去了我们对其检查):当要实例化FooController的时候,由于自己实现了有参数的构造函数,所以不会调用默认构造函数,那么就需要Spring容器传入所需要的参数,所以就两种情况:1、有该类型的参数->传入,OK 。2:无该类型的参数->报错。所以保证不会为空,Spring总不至于传一个null进去吧 。
- 完全初始化的状态:这个可以跟上面的依赖不为空结合起来,向构造器传参之前,要确保注入的内容不为空,那么肯定要调用依赖组件的构造方法完成实例化。而在Java类加载实例化的过程中,构造方法是最后一步(之前如果有父类先初始化父类,然后自己的成员变量,最后才是构造方法,这里不详细展开)。所以返回来的都是初始化之后的状态。
2.setter注入
(1)应用场景 大多数应用场景都可适用,尤其是依赖可选的环境。
(2)实现方式
①xml:
@Service
public class MessageProvider{
private String message;
public void setMessage(String message)
{
this.message=message;
}
}
<bean id="messageProvider" class=".....">
<property name="message" value="hahahah" />
</bean>
②注解:
@Service
public class MessageProvider{
private String message;
@Autowired
public void setMessage(String message)
{
this.message=message;
}
}
使用@Resource()效果相同
③setter注入的好处:后期可以改变相关依赖,对于多依赖情况相比构造函数注入更加简洁。最好设置默认值,否则需要判空。
3.字段注入
(1)实现方式
①注解:
@Service
public class Demo{
@Autowired
private MessageProvider messageProvider;
}
(2)字段注入存在的问题
①依赖无法对外部暴露。
②与容器耦合度强,在new 对象时,如果调用其依赖的方法会报空指针异常。
③容易造成一个类额依赖过多,违反单一责任原则
④不能精确的确定想要的依赖类型(如果字段为接口,那么其实现类都能注入进来)
4.查找方法注入
(1)应用场景 当两个bean的生命周期不相同时,使用方法查找,但效率低
(2)实现方式
①注解
@Component
@Scope(value="prototype")
public class Singer{
private String singInfo="i am a singer";
public void sing()
{
System.out.println(singInfo);
}
}
public class Demo{
@Lookup("singer")
public Singer getSinger()
{
return null;
}
}
注意,使用@lookup注解标记的参数,需要返回参数为null
②xml
public class Singer{
private String singInfo="i am a singer";
public void sing()
{
System.out.println(singInfo);
}
}
<bean id="" >
<lookup-method name="getMysinger" bean="singer" />
</bean>
public abstract class Demo{
public abstract Singer getMySinger();
}
需要注意该方法是抽象的 bean是返回的bean的id,name是方法名
5.方法替换
(1)应用场景 一些不可修改的类,但想按照自己的逻辑运行时
(2)实现方式
package com.lhf.replacement;
public class TestServiceImpl {
public void say() {
System.out.println("我说你是猪1");
}
public void say(String mes) {
System.out.println("我说你是猪2");
}
public void say(Object mes) {
System.out.println("我说你是猪3");
}
}
ackage com.lhf.replacement;
import org.springframework.beans.factory.support.MethodReplacer;
import java.lang.reflect.Method;
public class Replace implements MethodReplacer {
@Override
public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
System.out.println("替换原来的方法");
return null;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="replace" class="com.lhf.replacement.Replace"/>
<bean id="testService" class="com.lhf.replacement.TestServiceImpl">
<replaced-method name="say" replacer="replace">
<arg-type/>
</replaced-method>
<replaced-method name="say" replacer="replace">
<arg-type match="java.lang.String"/>
</replaced-method>
<replaced-method name="say" replacer="replace">
<arg-type match="java.lang.Object"/>
</replaced-method>
</bean>
</beans>
二、依赖查找的类型
1.依赖拉取
(1) 应用场景:几乎不用
(2)实现方式
2.上下文依赖查找
(1)应用场景:几乎不用
(2)实现方式 :通过实现类似接口
三、注入方式的选择
field代码简洁,尽管面临许多问题但还是程序员最常用的注入方式,使用强依赖时使用构造注入,可选依赖时使用setter注入。