Spring通过DI(依赖注入)实现IOC(控制反转),常用的注入方式主要有三种:构造方法注入,setter注入,基于注解的注入。
构造方法注入
先简单看一下测试项目的结构,用maven构建的,四个包:
entity:存储实体,里面只有一个User类
dao:数据访问,一个接口,两个实现类
service:服务层,一个接口,一个实现类,实现类依赖于IUserDao
test:测试包
在spring的配置文件中注册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();
}
测试打印结果:jdbc-登录成功
注:模拟用户登录的loginUser方法其实只是打印了一条输出语句,jdbc实现的类输出的是:jdbc-登录成功,mybatis实现的类输出的是:mybatis-登录成功。
问题一:如果有多个有参数的构造方法并且每个构造方法的参数列表里面都有要注入的属性,那userDaoJdbc会注入到哪里呢?
public class UserService implements IUserService {
private IUserDao userDao;
private User user;
public UserService(IUserDao userDao) {
System.out.println("这是有一个参数的构造方法");
this.userDao = userDao;
}
public UserService(IUserDao userDao, User user) {
System.out.println("这是有两个参数的构造方法");
this.userDao = userDao;
this.user = user;
}
public void loginUser() {
userDao.loginUser();
}
}
结果:会注入到只有一个参数的构造方法中,并且经过测试注入哪一个构造方法与构造方法的顺序无关
问题二:如果只有一个构造方法,但是有两个参数,一个是待注入的参数,另一个是其他类型的参数,那么这次注入可以成功吗?
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();
}
}
结果:失败了,即使在costract-arg标签里面通过name属性指定要注入的参数名userDao也会失败.
问题三:如果我们想向有多个参数的构造方法中注入值该在配置文件中怎么写呢?
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>
问题四:如果有多个构造方法,每个构造方法只有参数的顺序不同,那通过构造方法注入多个参数会注入到哪一个呢?
public class UserService implements IUserService {
private IUserDao userDao;
private User user;
public UserService(IUserDao userDao, User user) {
System.out.println("这是第二个构造方法");
this.userDao = userDao;
this.user = user;
}
public UserService(User user, IUserDao userDao) {
System.out.println("这是第一个构造方法");
this.userDao = userDao;
this.user = user;
}
public void loginUser() {
userDao.loginUser();
}
}
结果:哪个构造方法在前就注入哪一个,这种情况下就与构造方法顺序有关。
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”构成一个方法名,然后去对应的类中查找该方法,通过反射调用,实现注入。
切记:name属性值与类中的成员变量名以及set方法的参数名都无关,只与对应的set方法名有关,下面的这种写法是可以运行成功的
public class UserService implements IUserService {
private IUserDao userDao1;
public void setUserDao(IUserDao userDao1) {
this.userDao1 = userDao1;
}
public void loginUser() {
userDao1.loginUser();
}
}
还有一点需要注意:如果通过set方法注入属性,那么spring会通过默认的空参构造方法来实例化对象,所以如果在类中写了一个带有参数的构造方法,一定要把空参数的构造方法写上,否则spring没有办法实例化对象,导致报错。
基于注解的注入
在介绍注解注入的方式前,先简单了解bean的一个属性autowire,autowire主要有三个属性值:constructor,byName,byType。
- constructor:通过构造方法进行自动注入,spring会匹配与构造方法参数类型一致的bean进行注入,如果有一个多参数的构造方法,一个只有一个参数的构造方法,在容器中查找到多个匹配多参数构造方法的bean,那么spring会优先将bean注入到多参数的构造方法中。
- byName:被注入bean的id名必须与set方法后半截匹配,并且id名称的第一个单词首字母必须小写,这一点与手动set注入有点不同。
- byType:查找所有的set方法,将符合符合参数类型的bean注入。
下面进入正题:注解方式注册bean,注入依赖
主要有四种注解可以注册bean,每种注解可以任意使用,只是语义上有所差异:
- @Component:可以用于注册所有bean
- @Repository:主要用于注册dao层的bean
- @Controller:主要用于注册控制层的bean
- @Service:主要用于注册服务层的bean
描述依赖关系主要有两种:
- @Resource:java的注解,默认以byName的方式去匹配与属性名相同的bean的id,如果没有找到就会以byType的方式查找,如果byType查找到多个的话,使用@Qualifier注解(spring注解)指定某个具体名称的bean。
@Resource
@Qualifier("userDaoMyBatis")
private IUserDao userDao;
public UserService(){
}
- @Autowired:spring注解,默认也是以byName的方式去匹配与属性名相同的bean的id,如果没有找到,就通过byType的方式去查找,如果查找到多个,用@Qualifier注解限定具体使用哪个。
@Autowired
@Qualifier("userDaoJdbc")
private IUserDao userDao;
写在最后:虽然有这么多的注入方式,但是实际上开发的时候自己编写的类一般用注解的方式注册类,用@Autowired描述依赖进行注入,一般实现类也只有一种(jdbc or hibernate or mybatis),除非项目有大的变动,所以@Qualifier标签用的也较少;但是在使用其他组件的API的时候用的是通过xml配置文件来注册类,描述依赖,因为你不能去改人家源码嘛。
========================== 分割线 ================================
看到文章底下的评论说 @Autowired 注解默认是以 ByType 方式进行注入的我也是呵呵了。网上很多人评论说 Spring 的 @Autowired 注解是以 ByType 方式进行注入的,你们就不假思索的跟风?你们写代码验证过吗?
为了避免更多的评论误人子弟,现贴上我的代码,仅为证明 Spring 的 @Autowired 注解默认是以 ByName 方式注入的:
项目结构如下:
一个 dao,一个 service,一个 test 用于测试
dao 有两个实现类
service 有一个实现类
在 spring 的配置文件已经注入:
<?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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.qjl.*">
</context:component-scan>
<!-- 注册dao接口的mybatis实现类 -->
<bean id="userDaoMyBatis" class="com.qjl.dao.impl.UserDaoMyBatisImpl">
</bean>
<!-- 注册dao接口的hibernate实现类 -->
<bean id="userDaoHibernate" class="com.qjl.dao.impl.UserDaoHibernateImpl">
</bean>
<!-- 注册IUserService唯一的实现类 -->
<bean id="userService" class="com.qjl.service.impl.UserServiceImpl">
</bean>
</beans>
MyBatis 的 dao 实现类:
package com.qjl.dao.impl;
import com.qjl.dao.IUserDAO;
/**
* 类名称:以MyBatis方式实现的UserDao
* 全限定性类名: com.qjl.dao.impl.UserDaoMyBatisImpl
* @author 曲健磊
* @date 2018年7月20日上午10:02:22
* @version V1.0
*/
public class UserDaoMyBatisImpl implements IUserDAO {
/**
* 测试dao方法
*/
@Override
public void testDao() {
System.out.println("mybatis实现的dao层");
}
}
Hibernate 的 dao 实现类:
package com.qjl.dao.impl;
import com.qjl.dao.IUserDAO;
/**
* 类名称:以Hibernate的方式实现的UserDao
* 全限定性类名: com.qjl.dao.impl.UserDaoHibernateImpl
* @author 曲健磊
* @date 2018年7月20日上午10:01:57
* @version V1.0
*/
public class UserDaoHibernateImpl implements IUserDAO {
/**
* 测试dao方法
*/
@Override
public void testDao() {
System.out.println("Hibernate实现的dao");
}
}
service 实现类如下(重点,喷子自己看):
package com.qjl.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import com.qjl.dao.IUserDAO;
import com.qjl.service.IUserService;
/**
* 类名称:服务层的实现类
* 全限定性类名: com.qjl.service.impl.UserServiceImpl
* @author 曲健磊
* @date 2018年7月20日上午10:06:30
* @version V1.0
*/
public class UserServiceImpl implements IUserService {
// 如果默认是以ByType方式注入的话,因为我配置了多个IUserDao的实现类
// 后台是会报错的,但是并没有报错,说明了什么?
// 证明了它默认不是以ByType方式注入的。
// 因为如果默认以ByType方式注入的话
// 在匹配到有多个实现类的情况下,没有使用Quilifer注解指明的情况下会报错。
// 说明Spring优先去查找与被注入的属性名相同的bean的id来进行注入,
// 也就是通过名称匹配的方式(ByName)来进行的自动注入
@Autowired
private IUserDAO userDaoHibernate;
/**
* 测试服务层方法
*/
@Override
public void testService() {
userDaoHibernate.testDao();
}
}
测试类如下:
package com.qjl.test;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.qjl.service.IUserService;
/**
* 类名称:测试autowired到底默认是以ByName方式注入还是以ByType方式注入
* 全限定性类名: com.qjl.test.AutowiredTest
* @author 曲健磊
* @date 2018年7月20日上午10:13:21
* @version V1.0
*/
public class AutowiredTest {
private ClassPathXmlApplicationContext app;
@Before
public void init() {
app = new ClassPathXmlApplicationContext("applicationContext.xml");
}
@Test
public void testAutowired() {
IUserService userService = (IUserService) app.getBean("userService");
userService.testService();
}
}
实验结果:
如果 Spring 默认是以 ByType 的方式进行 @Autowired 的自动注入,当匹配到多个dao 实现类的时候,而且还没有使用 @Quilifier 注解指明具体的那个实现类的时候为为什么不报错?
我就想问一下:你们不亲自写代码验证,有什么资格在这误人子弟?
====================================分割线 =====================================
又看到底下的杠精评论说:”你的项目又用注解又用配置文件,真实的项目都是全注解,你这种写法不会影响性能吗?”,我也是呵呵了,我不知道项目真实开发是用的全注解?我放在网盘中的项目的目的很明确,把上面的话拷过来:”现贴上我的代码,仅为证明 Spring 的 @Autowired 注解默认是以 ByName 方式注入的。”,又在这影响性能,又在这跟我说真实项目全注解?你是听不懂人话?
修改成全注解的代码也放到这里:
package com.qjl.dao.impl;
import org.springframework.stereotype.Repository;
import com.qjl.dao.IUserDAO;
/**
* 类名称:以Hibernate的方式实现的UserDao
* 全限定性类名: com.qjl.dao.impl.UserDaoHibernateImpl
* @author 曲健磊
* @date 2018年7月21日上午8:43:10
* @version V1.0
*/
@Repository("userDaoHibernate")
public class UserDaoHibernateImpl implements IUserDAO {
/**
* 测试dao方法
*/
@Override
public void testDao() {
System.out.println("Hibernate实现的dao");
}
}
package com.qjl.dao.impl;
import org.springframework.stereotype.Repository;
import com.qjl.dao.IUserDAO;
/**
* 类名称:以MyBatis方式实现的UserDao
* 全限定性类名: com.qjl.dao.impl.UserDaoMyBatisImpl
* @author 曲健磊
* @date 2018年7月21日上午8:43:25
* @version V1.0
*/
@Repository("userDaoMyBatis")
public class UserDaoMyBatisImpl implements IUserDAO {
/**
* 测试dao方法
*/
@Override
public void testDao() {
System.out.println("mybatis实现的dao层");
}
}
package com.qjl.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.qjl.dao.IUserDAO;
import com.qjl.service.IUserService;
/**
* 类名称:服务层的实现类
* 全限定性类名: com.qjl.service.impl.UserServiceImpl
* @author 曲健磊
* @date 2018年7月20日上午10:06:30
* @version V1.0
*/
@Service("userService")
public class UserServiceImpl implements IUserService {
// 如果默认是以ByType方式注入的话,因为我配置了多个IUserDao的实现类
// 后台是会报错的,但是并没有报错,说明了什么?
// 证明了它默认不是以ByType方式注入的。
// 因为如果默认以ByType方式注入的话
// 在匹配到有多个实现类的情况下,没有使用Quilifer注解指明的情况下会报错。
// 说明Spring优先去查找与被注入的属性名相同的bean的id来进行注入,
// 也就是通过名称匹配的方式(ByType)来进行的自动注入
@Autowired
private IUserDAO userDaoHibernate;
/**
* 测试服务层方法
*/
@Override
public void testService() {
userDaoHibernate.testDao();
}
}
配置文件如下:
<?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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.qjl.*">
</context:component-scan>
</beans>
运行结果:
全注解项目的网盘链接如下:https://pan.baidu.com/s/1GrRlT5cLAI3SMu17TA6l6Q
转自:https://blog.csdn.net/a909301740/article/details/78379720