构造器依赖注入和 Setter方法注入的区别
两种依赖方式都可以使用,构造器注入和Setter方法注入。最好的解决方案是用构造器参数实现强制依赖,setter方法实现可选依赖。
【1】Bean注入实例
① 单独bean,属性都为简单类型,通过属性注入
属性注入即通过setXxx()方法
注入Bean的属性值或依赖对象,由于属性注入方式具有可选择性和灵活性高
的优点,因此属性注入是实际应用中最常采用的
注入方式。
属性注入要求Bean提供一个默认的构造函数,并为需要注入的属性提供对应的Setter方法。Spring先调用Bean的默认构造函数实例化Bean对象,然后通过反射的方式调用Setter方法注入属性值。
<!-- 通过属性注入,利用反射,实例化bean。类必须有无参构造方法 -->
<bean id="hello" class="com.web.hello.HelloWorld">
<property name="name" value="spring"></property>
</bean>
这里也可以使用 @Autowired或者 @Resource注解在setXXXX方法上面。
字面值
可用字符串表示的值,可以通过 <value>
元素标签或 value 属性进行注入。基本数据类型及其封装类、String 等类型都可以采取字面值注入的方式。若字面值中包含特殊字符,可以使用 <![CDATA[]]>
把字面值包裹起来。
② 单独bean,属性都为简单类型,通过构造方法注入
使用构造函数注入的前提是Bean必须提供带参数的构造
函数。如果有多个重载的构造方法,可以通过name index type
进行精确匹配。
<!-- 通过构造方法注入,index,type,name可以区分重载的构造器 -->
<bean id="car" class="com.web.hello.Car">
<constructor-arg value="Audi" index="0"
type="java.lang.String" name="brank">
</constructor-arg>
<!--如果有多个重载的构造方法,可以通过name index type 进行精确匹配-->
<constructor-arg value="yiqi" index="1" ></constructor-arg>
<constructor-arg value="520020" index="2" type="int"> </constructor-arg>
</bean>
③ 复合类型,通过属性注入
person 类包含属性 car。如果car是外部bean,则可以引入。如果是内部bean则使用构造方法注入。
<!-- *******************属性注入***************** -->
<bean id="person" class="com.web.hello.Person">
<property name="name" value="Tom"></property>
<property name="age" value="24" ></property>
<!-- 引用外部bean -->
<!-- <property name="car" ref="car"></property>-->
<!-- ****内部bean ,不能被外部引用,只能在内部使用**** -->
<property name="car" >
<bean id="car3" class="com.web.hello.Car">
<constructor-arg value="Audi" index="0" type="java.lang.String" name="brank">
</constructor-arg>
<constructor-arg value="yiqi" index="1" ></constructor-arg>
<constructor-arg value="520020" index="2" type="int"></constructor-arg>
</bean>
</property>
</bean>
④ 复合类型,person包含list<Car>
cars
也就是其有一个集合类的成员属性。配置 java.util.List 类型的属性, 需要指定 <list>
标签, 在标签里包含一些元素。这些标签可以通过 <value>
指定简单的常量值, 通过 <ref>
指定对其他 Bean 的引用。通过<bean>
指定内置 Bean 定义。通过 <null/>
指定空元素。甚至可以内嵌其他集合。数组的定义和 List 一样, 都使用 <list>
。
配置 java.util.Set 需要使用 <set>
标签, 定义元素的方法与 List 一样。
<bean id="person3" class="com.web.collections.Person">
<property name="name" value="Mike"></property>
<property name="age" value="25" ></property>
<property name="cars" >
<list>
<!--引用外部bean,配置内部bean-->
<ref bean="car"/>
<ref bean="car2"/>
<bean class="com.web.hello.Car">
<constructor-arg value="Audi" index="0" type="java.lang.String" name="brank"></constructor-arg>
<constructor-arg value="yiqi" index="1" ></constructor-arg>
<constructor-arg value="520020" index="2" type="int"></constructor-arg>
</bean>
</list>
</property>
</bean>
⑤ 复合类型,person包含Map<String,Car>
carMap
也就是说起有一个Map类型的成员属性。如下所示,这里直接定义map-entry即可。
Java.util.Map 通过 <map>
标签定义, <map>
标签里可以使用多个 <entry>
作为子标签。每个条目包含一个键和一个值,必须在 <key>
标签里定义键。
因为键和值的类型没有限制, 所以可以自由地为它们指定 <value>,<ref>, <bean> 或 <null> 元素。
可以将 Map 的键和值作为 <entry>
的属性定义:简单常量使用 key 和 value 来定义; Bean 引用通过 key-ref 和 value-ref 属性定义。
<bean id="person4" class="com.web.collections.Person2">
<property name="name" value="Jane"></property>
<property name="age" value="19" ></property>
<property name="carMap">
<map>
<entry key="BB" value-ref="car2"/>
<entry key="AA" value-ref="car">
</entry>
<entry key="CC" >
<bean class="com.web.hello.Car">
<constructor-arg value="Audi" index="0" type="java.lang.String" name="brank"></constructor-arg>
<constructor-arg value="yiqi" index="1" ></constructor-arg>
<constructor-arg value="52020" index="2" type="int"></constructor-arg>
</bean>
</entry>
</map>
</property>
</bean>
⑥ 复合类型,拥有properties属性
properties类型同map一样,都是键值对。使用 <props>
定义 java.util.Properties, 该标签使用多个 <prop>
作为子标签. 每个 <prop>
标签必须定义 key 属性.
<bean id="datasource" class="com.web.collections.DataSource">
<property name="properties" >
<props>
<prop key="user">root</prop>
<prop key="password">123456</prop>
<prop key="url">jdbc:mysql:///test</prop>
<prop key="driver">com.mysql.jdbc.Driver</prop>
</props>
</property>
</bean>
⑦ 使用util:list定义独立集合,可以被多个bean共享
使用基本的集合标签定义集合时, 不能将集合作为独立的 Bean 定义, 导致其他 Bean 无法引用该集合, 所以无法在不同 Bean 之间共享集合。可以使用 util schema 里的集合标签定义独立的集合 Bean。 需要注意的是, 必须在 <beans>
根元素里添加 util schema 定义。
util:list
定义的独立集合,可以被多个bean共享。此外还有<util:map/>
、<util:set/>
、<util:properties/>
等可供使用。
<!-- **************配置独立的集合,以供其他bean调用**************** -->
<util:list id="cars">
<ref bean="car"/>
<ref bean="car2"/>
<bean class="com.web.hello.Car">
<constructor-arg value="Audi" index="0" type="java.lang.String" name="brank"></constructor-arg>
<constructor-arg value="dazhong" index="1" ></constructor-arg>
<constructor-arg value="52020" index="2" type="int"></constructor-arg>
</bean>
</util:list>
<!--
<util:map/>
<util:set/>
<util:properties/>
.....
-->
<bean id="person5" class="com.web.collections.Person">
<property name="name" value="Janu"></property>
<property name="age" value="23" ></property>
<!--引用定义的集合-->
<property name="cars" ref="cars">
</property>
</bean>
⑧ 使用P命名空间进行属性赋值
为了简化 XML 文件的配置,越来越多的 XML 文件采用属性而非子元素配置信息。Spring 从 2.5 版本开始引入了一个新的 p 命名空间,可以通过 <bean>
元素属性的方式配置 Bean 的属性。使用 p 命名空间后,基于 XML 的配置方式将进一步简化。
<bean id="person6" class="com.web.collections.Person"
p:age="21" p:name="Jane" p:cars-ref="cars">
</bean>
one result as follows :
Person [age=21, cars=[Car [brank=Audi, corp=yiqi, maxSpeed=0.0, price=520020],
Car [brank=Baoma, corp=yiqi, maxSpeed=520.0, price=300000],
Car [brank=Audi, corp=dazhong, maxSpeed=0.0, price=52020]], name=Jane]
⑨ xml配置下获取bean
获取applicationContext:
applicationContext = new ClassPathXmlApplicationContext("applicationcontext.xml");
获取bean的几种方式:
- 直接使用beanName
Person person6 = applicationContext.getBean("person6");
- 使用实现类型
bookShopDAO = applicationContext.getBean(BookShopDAOImpl.class);
也可以使用上行接口类型,接口不能实例化,Spring自动查找其实现类并实例化
bookShopDAO = applicationContext.getBean(BookShopDAO.class);
两个bookShopDAO的class类型都是BookShopDAOImpl !
⑩ 懒加载
lazy-init
(懒加载),表示该bean在容器初始化的时候不进行初始化。而在第一次调用的时候再进行加载。懒加载只针对单实例bean。
XML配置如下:
<bean name="role1" class="com.entity.Role" lazy-init="true">
它有两个值:true,false(默认)。也可以配置在beans标签上<beans default-lazy-init="true">
。
【2】工厂方法注入
上面示例为属性注入
和构造函数注入
,Spring还提供了工厂方法注入。
① 非静态工厂方法
有些工厂方法是非静态的,即必须实例化工厂类后才能调用工厂方法。
InstanceCarFactory工厂如下所示:
/*
* 实例工厂的方法:先创建工厂实例,再调用其方法来获得需要的bean
*/
private Map<String, Car> cars = null;
public InstanceCarFactory(){
cars = new HashMap<String, Car>();
cars.put("Audi", new Car("Audi", 300000.0));
cars.put("Baoma", new Car("Baoma", 400000.0));
cars.put("Ford", new Car("Ford", 200000.0));
}
public Car getCar(String name) {
return cars.get(name);
}
XML配置如下(factory-bean="carFactory" factory-method="getCar"
):
<!-- 配置工厂的实例 -->
<bean id="carFactory" class="com.web.factory.InstanceCarFactory"></bean>
<!-- 获取实际需要的bean -->
<bean id="car2" factory-bean="carFactory" factory-method="getCar">
<constructor-arg value="Ford"></constructor-arg>
</bean>
工厂类负责创建一个或多个目标类实例,工厂类方法一般以接口或抽象类变量的形式返回目标类实例,工厂类对外屏蔽了目标类的实例化步骤,调用者甚至不用知道具体的目标类是什么。
② 静态工厂方法
很多工厂类都是静态的,这意味着用户在无须创建工厂类实例的情况下就可以调用工厂类方法,因此,静态工厂方法比非静态工厂方法的调用更加方便。
StaticCarFactory代码如下:
private static Map<String, Car> cars = new HashMap<String, Car>();
static{
cars.put("Audi", new Car("Audi", 300000));
cars.put("Baoma", new Car("Baoma", 400000));
cars.put("Dazhong", new Car("Dazhong", 200000));
}
public static Car getCar(String name){
return cars.get(name);
}
XML配置如下:
<!--
class:指向静态工厂方法的全类名;
factory-method:指向静态工厂方法的名字;
constructor-arg:如果工厂方法需要传入参数,则使用constructor-arg来配置参数
-->
<bean id="car1" class="com.web.factory.StaticCarFactory" factory-method="getCar" >
<constructor-arg value="Audi"></constructor-arg>
</bean>
③ 通过FactoryBean
Spring 中有两种类型的 Bean: 一种是普通Bean, 另一种是工厂Bean, 即FactoryBean。
工厂 Bean 跟普通Bean不同, 其返回的对象不是指定类的一个实例, 其返回的是该工厂 Bean 的 getObject 方法所返回的对象。
XML配置如下:
<!--
class:指向FactoryBean的全类名;
property:配置FactoryBean的属性;
实际返回的是FactoryBean的getObject()方法返回的实例
-->
<bean id="car3" class="com.web.factory.CarFactoryBean">
<property name="brand" value="BMW"></property>
</bean>
CarFactoryBean代码如下:
需要实现FactoryBean<T>
接口
public class CarFactoryBean implements FactoryBean<Car>{
private String brand;
public void setBrand(String brand) {
this.brand = brand;
}
// 核心方法实现实例,这里返回一个Car实例
public Car getObject() throws Exception {
return new Car(brand, 500000.0);
}
public Class<?> getObjectType() {
return Car.class;
}
public boolean isSingleton() {
// TODO Auto-generated method stub
return true;
}
}
FactoryBean<T>
接口源码
实现该接口的bean,将会被作为一个工厂用来暴露某个对象(不是暴露自己哦)。当然,实现了该接口的bean也可以被作为一个正常bean。FactoryBean是以bean样式定义的,但是其getObject()方法暴露的对象是其创建的对象,通常非自身。
FactoryBeans 可以支持singleton 和 prototype,同样也可以根据需要在启动时创建对象或者在需要时实例化对象(懒加载)。SmartFactoryBean接口允许暴露更多细粒度的行为元数据。
这个接口在框架中被大量使用,比如AOP(org.springframework.aop.framework.ProxyFactoryBean)或者org.springframework.jndi.JndiObjectFactoryBean
。同样可以用于自定义组件,然后其仅适用于基础架构代码。
FactoryBean是一个编程协议。实现不应该依赖于注解驱动的注入或其他反射功能。
getObjectType()或者getObject()方法可能在项目启动引导过程的早期就被调用,甚至在任何后处理器设置之前。如果需要访问其他bean,请实现BeanFactoryAware,并以编程方式获取它们。
最后,FactoryBean对象参与包含BeanFactory的bean创建同步。通常不需要进行内部同步,只是为了在FactoryBean本身(或类似对象)内进行延迟初始化。
getObject方法说明
返回工厂管理的对象实例(共享或者独立的),支持Singleton 和Prototype 作用域。如果调用时此FactoryBean尚未完全初始化(例如,因为它涉及循环引用),将会抛出FactoryBeanNotInitializedException异常。从Spring2.0开始,FactoryBeans 允许返回null对象。该工厂认为其会作为一个正常值被使用而不再抛出FactoryBeanNotInitializedException
异常。
实现FactoryBean 接口的类被鼓励在适当时机抛出FactoryBeanNotInitializedException 异常。
getObjectType方法说明
返回该工厂Bean创建的对象的类型,如果事先不知道将会返回null。这允许在不实例化对象的情况下检查特定类型的bean,例如在自动注入上。对于正在创建单例对象的实现,此方法应尽量避免单例创建,它应该提前预估对象类型。
对于原型,在这里返回有意义的类型也是可取的。该方法可以在bean完全实例化前被调用。它不能依赖于初始化期间创建的状态;当然,如果可用,它仍然可以使用这种状态。
注意:自动注入将会忽略getObjectType方法返回null的FactoryBeans 。因此,强烈建议使用FactoryBean的当前状态正确实现此方法。
boolean isSingleton()方法说明
返回当前FactoryBean管理的对象是否为singleton。如果是,则getObject()方法将会返回一样的对象。注意,如果一个FactoryBean 声明其拥有一个singleton 对象,从getObject()
方法返回的对象可能会被拥有的BeanFactory缓存。因此不建议该方法返回true,触发FactoryBean 永远暴露一样的对象引用。
FactoryBean本身的单例状态通常由拥有它的BeanFactory提供;通常,它必须被定义为singleton。
注意,该方法返回false不一定表示返回的对象是独立实例。该接口的实现SmartFactoryBean可能明确声明独立实例通过它的isPrototype()
方法。
如果isSingleton方法返回 false,则不实现此扩展接口的普通 FactoryBean实现只会被假定为总是返回独立实例。
public interface FactoryBean<T> {
T getObject() throws Exception;
Class<?> getObjectType();
boolean isSingleton();
}
【3】使用注解注入
注解驱动模式下,xml配置渐渐不可见被各种注解替代。
① @Autowired
该注解可以声明在构造器、属性、参数、set方法或者配置方法上,表明其由spring的依赖注入特性自动注入。相对于JSR-330中的@Inject注解,@Autowired
增加了必需/可选
的语义。
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
//声明注解声明的依赖是否必需,默认为true
boolean required() default true;
}
构造器, 普通字段(即使是非 public), 一切具有参数的方法都可以应用@Authwired 注解。
默认情况下, 所有使用 @Authwired
注解的属性都需要被设置. 当 Spring 找不到匹配的 Bean 装配属性时, 会抛出异常, 若某一属性允许不被设置, 可以设置 @Authwired
注解的 required 属性为 false。
默认情况下, 当 IOC 容器里存在多个类型兼容的 Bean 时, 通过类型的自动装配将无法工作. 此时可以在 @Qualifier
注解里提供 Bean 的名称. Spring 允许对方法的入参标注 @Qualifiter
已指定注入 Bean 的名称。
@Authwired
注解也可以应用在数组类型的属性上, 此时 Spring 将会把所有匹配的 Bean 进行自动装配。
@Authwired
注解也可以应用在集合属性上, 此时 Spring 读取该集合的类型信息, 然后自动装配所有与之兼容的 Bean。
@Authwired
注解用在 java.util.Map
上时, 若该 Map 的键值为 String, 那么 Spring 将自动装配与之 Map 值类型兼容的 Bean, 此时 Bean 的名称作为键值。
Spring 还支持 @Resource
和 @Inject
注解,这两个注解和 @Autowired
注解的功用类似。@Resource
注解要求提供一个 Bean 名称的属性,若该属性为空,则自动采用标注处的变量或方法名作为 Bean 的名称。@Inject
和 @Autowired
注解一样也是按类型匹配注入的 Bean, 但没有 reqired 属性。
① 装配规则
默认优先ByType
进行自动装配,当发现装配类型于spring容器中存在两个及以上实例时,会采用ByName
的方式继续寻找对应的实例进行装配。
其可与@Qualifier
注解搭配使用,根据name找到唯一一个bean,找不到则抛出异常。
@Autowired
@Qualifier("testFacade")
TestFacade testFacade;
② 注入方式
标注在构造器上面
任何类只有一个构造函数可以声明为(@Autowired(required = true)
),标明其作为Spring Bean时可以自动装配。
当然,可以有多个构造函数声明@Autowired(required = false)
标明其作为注入的候选对象。此时将选择具有最大数量依赖项的构造函数(构造函数参数在Spring容器中匹配bean所能满足的)。
如果没有一个候选者能够满足要求,那么将使用主/默认构造函数(如果存在)。类似地,如果一个类声明了多个构造函数,但没有一个用@Autowired注解,那么将使用主/默认构造函数(如果存在)。如果一个类一开始只声明一个构造函数,那么它将始终被使用,即使没有注解。带注释的构造函数不必是公共的。
标注在属性字段
声明在属性上面将会在构造函数后被注入,但是在任何config方法被调用前。属性字段不比是public。
标注在方法
配置方法可能有任意名称、任意属性的参数,每一个参数将会从Spring 容器中找到一个匹配的bean进行注入。Bean属性的setXXX方法实际上只是这种通用配置方法的一个特例,这样的配置方法不必是public的。
标注在参数上
尽管{@Autowired}从技术上讲可以在SpringFramework 5.0后面框架的单个方法或构造函数参数上声明,但框架的大多数部分都忽略了此类声明。核心 Spring Framework中唯一积极支持自动注入参数的是JUnit Jupiter 在spring-test模块中。
TestContext frameworkreference documentation for details).
注入集合
比如Arrays, Collections, and Maps,当属性是这些类型的话,容器将会扫描并装配所有匹配值类型的bean。为此,map映射键必须声明为类型String,该类型将被解析为相应的bean名称。考虑到目标组件的ordered和@Order值,将对这样一个容器提供的集合进行排序,否则将遵循它们在容器中的注册顺序。或者,单个匹配的目标bean也可以是一般类型的Collection或Map本身,这样被注入。
需要注意的是,一定不要使用该注解引入BeanPostProcessor或者BeanFactoryPostProcessor。因为该注解就是被bean后置处理器处理的。
② @Resource
默认根据name装配,其次根据type装配。如果指定了name则根据name装配;如果指定了type则根据type装配。该注解被CommonAnnotationBeanPostProcessor
解析处理。
装配顺序如下:
- 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
- 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
- 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
- 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource {
// 资源的JNDI名称;
//对于属性来说默认值是这个属性字段名称;
//对于方法来说,默认是方法对应的JavaBean属性名称;
//对于类来说,该字段必须被指定
String name() default "";
/**
* 引用指向的资源的名称。它可以使用全局JNDI名称链接到任何兼容的资源.
*/
String lookup() default "";
//资源的Java type;对于属性字段来说默认是其type;
//对于方法来说默认是方法对应的属性的type;
//对于类来说,其必须指定
Class<?> type() default java.lang.Object.class;
//资源的两种可能的身份验证类型
enum AuthenticationType {
CONTAINER,
APPLICATION
}
//资源使用的身份验证类型
AuthenticationType authenticationType() default AuthenticationType.CONTAINER;
// 声明该资源是否可以被分享在不同的组件中
boolean shareable() default true;
// 资源应该映射的名称
String mappedName() default "";
//资源描述
String description() default "";
}
③ @Autowired与@Resource区分
- @Autowired默认按byType自动装配,而@Resource默认byName自动装配。
- @Autowired只包含一个参数:required,表示是否开启自动准入,默认是true。而@Resource包含七个参数,其中最重要的两个参数是:name 和 type。
- @Autowired如果要使用byName,需要使用@Qualifier一起配合。而@Resource如果指定了name,则用byName自动装配,如果指定了type,则用byType自动装配。
- @Autowired能够用在:构造器、方法、参数、成员变量和注解上,而@Resource能用在:类、成员变量和方法上。
- @Autowired是spring定义的注解,而@Resource是JSR-250定义的注解。
- 装配顺序不同。@Autowired默认按照type,如果定义了@Qualifier则按照name;@Resource默认按照name,其次按照type。