Spring的IOC有五种注入方式:构造器注入、setter方法注入、静态工厂注入、实例工厂注入、根据注解注入。
对上述分类可分为两种:基于XML配置、基于注解配置
- 基于xml配置:setter注入、构造器注入、静态工厂注入、实例工厂注入;
- 基于注解配置:注解方式注入
XML配置使用场景:
1.Bean实现类来自第三方类库:如DataSource等
2.需要命名控件配置如:context,aop,mvc等
注解配置使用场景:
项目中自身开发使用的类,可直接在代码中使用注解:如@Service,@Controller等
1.构造器注入:
在applicationContext.xml配置文件中注册UserService,将UserDaoJdbc通过constructor-arg标签注入到UserService的某个有参数的构造方法
<!-- 注册userService -->
<bean id="userService" class="com.lyu.spring.service.impl.UserService">
<constructor-arg ref="userDaoJdbc"></constructor-arg>
</bean>
<!-- 注册jdbc实现的dao -->
<bean id="userDaoJdbc" class="com.lyu.spring.dao.impl.UserDaoJdbc"></bean>
如果只有一个有参数的构造方法并且参数类型与注入的bean的类型匹配,那就会注入到该构造方法中
public class UserService implements IUserService {
private IUserDao userDao;
public UserService(IUserDao userDao) {
this.userDao = userDao;
}
public void loginUser() {
userDao.loginUser();
}
}
//测试
@Test
public void testDI() {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
// 获取bean对象
UserService userService = ac.getBean(UserService.class, "userService");
// 模拟用户登录
userService.loginUser();
}
注意:
(1)如果有多个有参数的构造方法并且每个构造方法的参数列表里面都有要注入的属性,那userDaoJdbc会注入到哪里呢?
结果:会注入到只有一个参数的构造方法中,并且经过测试注入哪一个构造方法与构造方法的顺序无关
(2)如果只有一个构造方法,但是有两个参数,一个是待注入的参数,另一个是其他类型的参数,那么这次注入可以成功吗?
结果:失败了,即使在costract-arg标签里面通过name属性指定要注入的参数名userDao也会失败.
(3)如果我们想向有多个参数的构造方法中注入值该在配置文件中怎么写呢?
public class UserService implements IUserService {
private IUserDao userDao;
private User user;
public UserService(IUserDao userDao, User user) {
this.userDao = userDao;
this.user = user;
}
public void loginUser() {
userDao.loginUser();
}
}
参考写法:通过name属性指定要注入的值,与构造方法参数列表参数的顺序无关。
<!-- 注册userService -->
<bean id="userService" class="com.lyu.spring.service.impl.UserService">
<constructor-arg name="userDao" ref="userDaoJdbc"></constructor-arg>
<constructor-arg name="user" ref="user"></constructor-arg>
</bean>
<!-- 注册实体User类,用于测试 -->
<bean id="user" class="com.lyu.spring.entity.User"></bean>
<!-- 注册jdbc实现的dao -->
<bean id="userDaoJdbc" class="com.lyu.spring.dao.impl.UserDaoJdbc"></bean>
(4)如果有多个构造方法,每个构造方法只有参数的顺序不同,那通过构造方法注入多个参数会注入到哪一个呢?
结果:哪个构造方法在前就注入哪一个,这种情况下就与构造方法顺序有关。
2.setter注入:
配置文件如下
<!-- 注册userService -->
<bean id="userService" class="com.lyu.spring.service.impl.UserService">
<!-- 写法一 -->
<!-- <property name="UserDao" ref="userDaoMyBatis"></property> -->
<!-- 写法二 -->
<property name="userDao" ref="userDaoMyBatis"></property>
</bean>
<!-- 注册mybatis实现的dao -->
<bean id="userDaoMyBatis" class="com.lyu.spring.dao.impl.UserDaoMyBatis"></bean>
注:上面这两种写法都可以,spring会将name值的每个单词首字母转换成大写,然后再在前面拼接上"set"构成一个方法名,然后去对应的类中查找该方法,通过反射调用,实现注入。
setter注入
public class UserService implements IUserService {
private IUserDao userDao1;
public void setUserDao(IUserDao userDao1) {
this.userDao1 = userDao1;
}
public IUserDao getUserDao(){
return userDao1;
}
public void loginUser() {
userDao1.loginUser();
}
}
3.静态工厂注入:
利用静态工厂方法可以把bean注入到IOC容器中。在XML文件中配置bean时,要指定class的属性为工厂的类;factory-method属性指定工厂类中工厂方法,用于创建bean;constrctor-arg用于给工厂方法传递参数。
//实体类
package org.spring.chapter2.helloworld;
public class HelloImpl3 implements HelloApi {
private String message;
private int index;
public HelloImpl3() {
this.message="Hello World in Empty Constructor";
}
public HelloImpl3(String message,int index) {
this.message = message;
this.index = index;
}
@Override
public void sayHello() {
System.out.println(index+":"+message);
}
}
//静态工厂
package org.spring.chapter2.helloworld;
public class InJectByStaticFactory {
public static HelloApi newInstance(String message,int index){
return new HelloImpl3(message,index);
}
}
//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" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- 通过构造器参数索引方式依赖注入 -->
<bean id="byIndex" class="org.spring.chapter2.helloworld.InJectByStaticFactory" factory-method="newInstance">
<constructor-arg index="0" value="Hello Spring By Index" />
<constructor-arg index="1" value="1"></constructor-arg>
</bean>
</beans>
//测试
@Test
public void testStaticConstructInject(){
ApplicationContext context = new ClassPathXmlApplicationContext("org/spring/chapter2/helloworld/helloworld5.xml");
//1:按照参数索引依赖注入的Bean
HelloApi byIndex = context.getBean("byIndex",HelloApi.class);
byIndex.sayHello();
}
4.实例工厂注入:
实例化静态工厂注入bean,需要先实例化一个工厂类,然后通过由实例化工厂对象中的一个方法来创建bean,并注入到容器中。
在 bean 的 factory-bean 属性里指定拥有该工厂方法的 Bean;在 factory-method 属性里指定该工厂方法的名称;使用 construtor-arg 元素为工厂方法传递方法参数。
//beans.xml
<!--首先创建一个工厂的bean-->
<bean id="carFactory" class="com.lzj.spring.beans.factory.InstanceFactory"></bean>
<!--factory-bean指定前面已经创建的bean; factory-method指定工厂实例中用于创建car的方法; constructor-arg指定创建car方法中传入的参数-->
<bean id="car2" factory-bean="carFactory" factory-method="getCar">
<constructor-arg value="baoma"></constructor-arg>
</bean>
//测试
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
Car car = (Car) ctx.getBean("car2");
System.out.println(car);
5.基于注解的注入:
在使用注解注入之前,需要先进行注册,可使用以下四种注解可以注册bean,每种注解可以任意使用,只是语义上有所差异:
- @Component:可以用于注册所有bean
- @Repository:主要用于注册dao层的bean
- @Controller:主要用于注册控制层的bean
- @Service:主要用于注册服务层的bean
基于注解的注入分两类
- @Autowired Spring的注解
- @Resource java的注解
@Autowired可对类成员变量、方法及构造函数进行标注,完成自动装配的工作。通过@Autowired的使用来消除上述setter注入的set,get方法。
在使用@Autowired之前,需要 在applicationContext.xml中加入:
<!-- 该 BeanPostProcessor 将自动对标注 @Autowired 的 Bean 进行注入 -->
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
Spring 通过一个 BeanPostProcessor 对 @Autowired 进行解析,所以要让 @Autowired 起作用必须事先在 Spring 容器中声明 AutowiredAnnotationBeanPostProcessor Bean。
对类成员变量进行标注
//对类成员变量标注
public class Boss {
@Autowired
private Car car;
@Autowired
private Office office;
}
当 Spring 容器启动时,AutowiredAnnotationBeanPostProcessor 将扫描 Spring 容器中所有 Bean,当发现 Bean 中拥有 @Autowired 注释时就找到和其匹配(默认按类型匹配)的 Bean,并注入到对应的地方中去。
按照上面的配置,Spring 将直接采用 Java 反射机制对 Boss 中的 car 和 office 这两个私有成员变量进行自动注入。
对方法进行标注
//对方法进行标注
public class Boss {
private Car car;
private Office office;
@Autowired
public void setCar(Car car) {
this.car = car;
}
@Autowired
public void setOffice(Office office) {
this.office = office;
}
}
@Autowired 将查找被标注的方法的入参类型的 Bean,并调用方法自动注入这些 Bean。
对构造函数进行标注
//对构造函数进行标注
package com.baobaotao;
public class Boss {
private Car car;
private Office office;
@Autowired
public Boss(Car car ,Office office){
this.car = car;
this.office = office ;
}
}
由于 Boss() 构造函数有两个入参,分别是 car 和 office,@Autowired 将分别寻找和它们类型匹配的 Bean,将它们作为 Boss(Car car ,Office office) 的入参来创建 Boss Bean。
注意:
- @Autowired默认根据类型,匹配不到则根据bean名字。
- 一个接口只有一个实现的情况下:属性名字怎么写都无所谓,因为按照类型匹配就只有一个bean
- 一个接口多个实现的情况下:属性名字跟组件名字一致,组件名字可以在声明的时候指定,比如 @Service(“abc”)
- 属性名字跟组件名字不一致:配合@Qualifier 注解指定组件名字
例如:
@Service
public class HelloServiceImpl implements HelloService {
@Override
public void sayHello() {
System.out.println("say hello impl");
}
}
@Controller
public class HelloController {
@Autowired
@Qualifier("helloServiceImpl")
private HelloService abc;
public void hello() {
abc.sayHello();
}
}
@Resource java的注解
默认以byName的方式去匹配与属性名相同的bean的id,如果没有找到就会以byType的方式查找,如果byType查找到多个的话,使用@Qualifier注解(spring注解)指定某个具体名称的bean。
@Resource
@Qualifier("userDaoMyBatis")
private IUserDao userDao;
public UserService(){
}
注意:在匹配到多个 bean 的情况下,JDK自带的ByName需要遍历所有的bean,而@Qualifier 直接一次定位。因此,@Qualifer 注解出现的意义或许就是 Spring 为了解决 JDK 自带的 ByName 遍历匹配效率低下的问题。