spring基础(二)-控制反转IOC和依赖注入DI

转载文章:https://blog.csdn.net/sinat_21843047/article/details/80297951

IOC

  1. 定义Ioc—Inversion of Control:即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。传统的Java SE程序设计,我们直接在对象内部通过new就行对象创建,是程序主动创建依赖对象,而IOC是有一个专门的容器来创建这些对象,即由ioc容器来控制对象的创建
    在这里插入图片描述

IOC的好处

传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。

IOC与DI

  1. DI—Dependency Injection,即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。

理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:

●谁依赖于谁:当然是应用程序依赖于IoC容器;

●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;

●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;

●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

参考文章:https://blog.csdn.net/a909301740/article/details/78379720

依赖注入的三种方式

  1. 构造器注入
    构造器注入主要是依赖于构造方法实现的,构造方法可以是有参的也可以是无参的,(我们平时创建一个类都是通过类的构造方法来创建并且赋初始值)同样的,在spring采用反射机制,通过构造方法来完成注入(赋值)。

代码实现

  1. 写一个相关的bean类
public class UserService implements IUserService {

	private IUserDao userDao;
	
	public UserService(IUserDao userDao) {
		this.userDao = userDao;
	}
	
	public void loginUser() {
		userDao.loginUser();
	}

}

  1. 相关的配置(通过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>
  1. 测试类
@Test
public void testDI() {
	ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
	// 获取bean对象
	UserService userService = ac.getBean(UserService.class, "userService");
	// 模拟用户登录
	userService.loginUser();
}

constructor-arg元素用于定义类构造方法的参数,其中type用于定义参数的类型,也可以使用index来定义参数的位置,而这里的value是用于设置值,以上的代码就是通过一个Spring去装配一个Bean

setter注入

代码实现

  1. 首先是bean类(一定要写setter方法)

name属性值与类中的成员变量名以及set方法的参数名都无关,只与对应的set方法名有关,下面的这种写法是联合第二个代码是可以运行成功的

public class UserService implements IUserService {

	private IUserDao userDao1;
	
	public void setUserDao(IUserDao userDao1) {
		this.userDao1 = userDao1;
	}
	
	public void loginUser() {
		userDao1.loginUser();
	}

}

  1. 配置文件
    (下面的两种写法都可以,spring会将name值的每个单词首字母转换成大写,然后再在前面拼接上"set"构成一个方法名,然后去对应的类中查找该方法,通过反射调用,实现注入,主要就是他是找set方法,并不是找属性名字)
<!-- 注册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>

  1. 注意点:如果通过set方法注入属性,那么spring会通过默认的空参构造方法来实例化对象,所以如果在类中写了一个带有参数的构造方法,一定要把空参数的构造方法写上,否则spring没有办法实例化对象,导致报错。

基于注解的注入

  1. 首先介绍一下bean的一个属性autowire的三个属性值:constructor,byName,byType.
  • constructor:通过构造方法就行自动注入,spring会匹配与构造方法参数类型一致的bean进行注入,如果由一个多参数的构造方法,一个只有一个参数的构造方法,在容器中查找到多个匹配多参数构造方法的bean,那么spring会优先将bean注入到多参数的构造方法中。

  • byName:被注入bean的id名字必须与set方法后半截匹配,并且id名称的第一个单词首字母必须小写,这一点与手动set注入不同。

  • byType:查找所有的set方法,将符合参数类型的bean注入。

主要有四种注解可以注册bean,每种注解可以任意使用,只是语义上有所差异:

@Component:可以用于注册所有bean
@Repository:主要用于注册dao层的bean
@Controller:主要用于注册控制层的bean
@Service:主要用于注册服务层的bean

@Resource

  1. 定义:java的注解,默认以byName的方式去匹配与属性名相同的bean的id,如果没有找到就会以byType的方式查找,如果byType查找到多个的话,使用@Qualifier注解(spring注解)指定某个具体名称的bean。
@Resource
@Qualifier("userDaoMyBatis")
private IUserDao userDao;
public UserService(){
}

@Autowired

  1. 定义:spring注解,默认是以byType的方式去匹配类型相同的bean,如果只匹配到一个,那么就直接注入该bean,无论要注入的 bean 的 name 是什么;如果匹配到多个,就会调用 DefaultListableBeanFactory 的 determineAutowireCandidate 方法来决定具体注入哪个bean。determineAutowireCandidate 方法的内容如下:
// candidateBeans 为上一步通过类型匹配到的多个bean,该 Map 中至少有两个元素。
protected String determineAutowireCandidate(Map<String, Object> candidateBeans, DependencyDescriptor descriptor) {
    //  requiredType 为匹配到的接口的类型
   Class<?> requiredType = descriptor.getDependencyType();
   // 1. 先找 Bean 上有@Primary 注解的,有则直接返回
   String primaryCandidate = this.determinePrimaryCandidate(candidateBeans, requiredType);
   if (primaryCandidate != null) {
       return primaryCandidate;
   } else {
       // 2.再找 Bean 上有 @Order,@PriorityOrder 注解的,有则返回
       String priorityCandidate = this.determineHighestPriorityCandidate(candidateBeans, requiredType);
       if (priorityCandidate != null) {
           return priorityCandidate;
       } else {
           Iterator var6 = candidateBeans.entrySet().iterator();

           String candidateBeanName;
           Object beanInstance;
           do {
               if (!var6.hasNext()) {
                   return null;
               }

               // 3. 再找 bean 的名称匹配的
               Entry<String, Object> entry = (Entry)var6.next();
               candidateBeanName = (String)entry.getKey();
               beanInstance = entry.getValue();
           } while(!this.resolvableDependencies.values().contains(beanInstance) && !this.matchesBeanName(candidateBeanName, descriptor.getDependencyName()));

           return candidateBeanName;
       }
   }
}

determineAutowireCandidate 方法的逻辑是:

  • 先找 Bean 上有@Primary 注解的,有则直接返回 bean 的 name。
  • 再找 Bean 上有 @Order,@PriorityOrder 注解的,有则返回 bean 的 name。
  • 最后再以名称匹配(ByName)的方式去查找相匹配的 bean。

可以简单的理解为先以 ByType 的方式去匹配,如果匹配到了多个再以 ByName 的方式去匹配,找到了对应的 bean 就去注入,没找到就抛出异常。

还有一点要注意:如果使用了 @Qualifier 注解,那么当自动装配匹配到多个 bean 的时候就不会进determineAutowireCandidate 方法(亲测),而是直接查找与 @Qualifer 指定的 bean name 相同的 bean 去注入,找到了就直接注入,没有找到则抛出异常。

发布了36 篇原创文章 · 获赞 11 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/s_xchenzejian/article/details/100887591