前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >spring框架(1)— 依赖注入

spring框架(1)— 依赖注入

作者头像
Mister24
发布2018-05-14 10:25:27
9200
发布2018-05-14 10:25:27
举报
文章被收录于专栏:java初学java初学

依赖注入

  spring核心容器就是一个超级大工厂,所以的对象(数据源、hibernate SessionFactory等基础性资源)都会被当做spring核心容器的管理对象——spring把容器中的一切对象统称为Bean。

  Spring对Bean没有任何要求,只要是一个java类,spring就可以管理这个java类,并把它当做Bean处理。对于spring框架而言,一切java对象都是Bean。

代码语言:javascript
复制
package service;

public class Axe 
{
	public String chop()
	{
		return "使用斧头砍柴";
	}
}

  这个Axe类是一个普通的java类。

代码语言:javascript
复制
package service;

public class Person 
{
	private Axe axe;
	//设值注入所需的setter方法
	public void setAxe(Axe axe)
	{
		this.axe = axe;
	}
	public void useAxe()
	{
		System.out.print("我打算去砍柴!");
		//调用axe的chop()方法
		//声明Person对象依赖于Axe
		System.out.println(axe.chop());
	}
}

  这个Person类的useAxe()方法需要调用Axe对象的chop()方法,这种A对象需要调用B对象方法的情形,被称为依赖。

  Spring核心容器是整个应用的超级工厂,所有的java对象都会讲给Spring的容器管理——这些java对象被称为Spring容器中的Bean。

  Spring对于bean的管理需要在xml中进行配置。

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN"
    "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
	<!--配置名为person的Bean,其实现类为service.Person-->
	<bean id="person" class="service.Person">
	<!--控制调用setAxe()方法,将容器中的ax Bean作为参数传递进去-->
		<property name="axe" ref="axe"/>
	</bean>
	<!--配置名为axe的Bean,其实现类为service.Axe-->
	<bean id="axe" class="service.Axe"></bean>

</beans>

  配置文件的根元素是<beans .../>,根元素中包含多个<bean .../>元素,每个元素定义一个Bean。在这里配置了两个Bean,分别是service.Person和service.Axe。

  只要将java类配置到xml中,spring就可以对其进行管理。

代码语言:javascript
复制
<bean id="person" class="service.Person">

  配置文件会将<bean .../>元素默认以反射方式类调用这个类的无参构造器,spring解析这一元素之后得到两个字符串,其中idStr的值为"person"(对应的是id属性的值),classStr的值为"service.Person"(对于的是class属性的值)。

代码语言:javascript
复制
String idStr = ...;
String classStr = ...;
Class clazz = Class.forName(classStr);
Object obj = clazz.newInstance();
//container代表spring容器
container.put(idStr, obj);

  spring框架通过反射根据<bean .../>元素的class属性创建了一个java对象,并以<bean .../>元素的id属性的值为key,将该对象放入spring容器中——这个java对象就成为了spring容器中的Bean。

  在spring配置文件中配置Bean时,class属性的值必须是Bean实现类的完整类名。

上面配置文件中还包括一个<property .../> 子元素,它驱动spring在底层以反射执行一次setter方法。name属性决定了执行哪些setter方法,value或者ref决定执行setter方法的传入参数。

  • 如果传入的是基本类型及其包装类、String等类型,则使用value属性指定传入参数;
  • 如果以容器中其他Bean作为传入参数,则使用ref属性指定传入的参数。

  spring框架只要看到<property .../>子元素,就会在底层反射执行一次setter方法,该Bean一旦创建,spring就会立即根据<property .../>子元素来执行setter方法。也就是说:

  1. <bean .../>元素驱动spring调用构造器创建对象;
  2. <property .../>元素驱动spring执行setter方法。

  这两步是先后执行的,中间几乎没有时间间隔。

  上面的配置中<property .../>元素的name属性为axe,该元素驱动spring以反射方式执行person Bean中的setAxe()方法;ref属性值为axe,该属性值指定以容器名为axe的Bean作为执行setter方法的传入参数。

  也就是说,在spring的底层会执行如下代码:

代码语言:javascript
复制
//解析<property .../>元素的name属性得到该字符串值为"axe"
String nameStr = ...;
//解析<property .../>元素的ref属性得到该字符串的值为"axe"
String refStr = ...;
String setterName = "set" + nameStr.substring(0, 1).toUpperCase() + nameStr.substring(1);
//获取spring容器中名为refStr的Bean,该Bean将会作为传入参数
Object paramBean = container.get(refStr);
//此处的clazz是从反射得到的Class对象
Method setter = clazz.getMethod(setterName, paramBean.getClass());
//此处的obj参数是之前一段反射代码创建的对象
setter.invoke(obj, paramBean);

  上述的代码是反射代码的实例,通过<property .../>的name属性决定调用哪个setter方法,并且根据value和ref决定调用setter方法的传入参数。

  id为person的<bean .../>元素还包括一个<property .../>子元素,因此spring会在创建完person Bean之后,立即以容器中id为axe的Bean被赋值给person对象的axe实例变量。

  接下来程序会通过spring容器来访问容器中的Bean,ApplicationContext是Spring容器中最常用的接口,该接口有如下两个实现类。

  1. ClassPathXmlApplicationContext:从类加载路径下搜索配置文件,并根据配置文件来创建spring容器;
  2. FileSystemXmlApplicationContext:从文件系统的相对路径或绝对路径下去搜索配置文件,并根据配置文件来创建spring容器。

在spring中,类加载路径是稳定的,因此通常使用ClassPathXmlApplicationContext来创建容器。

代码语言:javascript
复制
package service;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;

public class Main {
	public static void main(String[] args)
	{
		//引入配置文件,创建spring容器
		Resource r = new FileSystemResource("src/beans.xml");
		//加载配置文件
		BeanFactory f = new XmlBeanFactory(r);
		//获取id为person的Bean
		Person p = (Person)f.getBean("person", Person.class);
		//调用useAxe()方法
		p.useAxe();
	}

}

  spring获取Bean对象的方式有两种:

1.Object getBean(String id):根据容器中Bean的id来获取Bean,获取Bean之后需要进行强制类型转换;

2.T getBean(String name, Class<T> requiredType):根据容器中Bean的id来获取指定的Bean,但是该方法带一个泛型参数,因此获得Bean之后无需进行强制类型转换。

  获取Bean对象之后,可以调用方法、访问实例变量,即可以像使用java对象一样使用这个Bean对象。

1、依赖注入

1.调用者面向被依赖对象的接口编程;

  2.将被依赖对象的创建交给工厂完成;

  3.调用者通过工厂来获取被依赖组件。

  通过这三点,可以保证调用者主需与被依赖对象的接口耦合,这就避免了类层次的硬编码耦合,使用spring框架之后,调用者无需主动获取被依赖对象,只需被动接受spring容器为调用者的成员变量赋值即可(只要配置一个<property .../>子元素,spring就会执行对应的setter方法为调用者的成员变量赋值)。于是,使用了spring之后,调用者获取被依赖对象的方式由原来的主动获取变成了变动接受,这被称为控制反转(Inversion of Control,IoC)。

  从spring框架的角度来说,spring容器负责将被依赖对象赋值给调用者的成员变量——相当于为调用者注入它依赖的实例,因此这种方式被称为依赖注入(Dependency Injection)。

  使用了spring框架之后,主要有两个变化:

1.程序员无需使用new创建对象,所有的java对象的创建都交给spring容器完成;

2.当调用者需要调用被依赖对象的方法的时候,调用者无需主动获取被依赖对象,只需要等待spring容器注入即可。

2、注入方式

  • 设值注入:IoC容器使用成员变量的setter方法来注入被依赖对象;
  • 构造注入:IoC容器使用构造器来注入被依赖对象。

2.1 设值注入

  设值注入指的是IoC容器通过成员变量的setter方法来注入被依赖对象。

  spring推荐面向接口编程,不管是调用者还是被依赖的对象,都应该为之定义接口,程序应该面向它们的接口,而不是面向实现类编程,这样利于后期的维护和升级。

①Axe接口

代码语言:javascript
复制
package test;

public interface Axe 
{
	public String chop();
}

②Person接口

代码语言:javascript
复制
package test;

public interface Person 
{
	public void useAxe();

}

③Chinese类

代码语言:javascript
复制
package test;

public class Chinese implements Person
{
	private Axe axe;
	public void setAxe(Axe axe)
	{
		this.axe = axe;
	}
	//实现Person接口定义的useAxe()方法
	public void useAxe()
	{
		System.out.println(axe.chop());
	}
}

④StoneAxe类

代码语言:javascript
复制
package test;

public class StoneAxe implements Axe
{
	public String chop()
	{
		return "石斧砍柴好慢";
	}

}

⑤beans.xml配置文件

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN"
    "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="chinese" class="test.Chinese">
	<property name="axe" ref="stoneAxe"/></bean>
<bean id="stoneAxe" class="test.StoneAxe"></bean>
</beans>

⑥Main类

代码语言:javascript
复制
package test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

//import org.springframework.context.ApplicationContext;
public class Main {
	public static void main(String[] args)
	{
		ApplicationContext ctx = new FileSystemXmlApplicationContext("D:/java/workspace_j2ee/SpringDemo3/src/beans.xml");
		Person p = (Person)ctx.getBean("chinese", Person.class);
		p.useAxe();
	}

}

注意使用FileSystemXmlApplicationContext的时候,可以直接使用"src/beans.xml"作为beans.xml的路径,spring可以找的到配置文件的位置。

  假设Axe有另外的实现类:SteelAxe。

代码语言:javascript
复制
package test;

public class SteelAxe implements Axe{
	public String chop()
	{
		return "钢斧砍柴更快";
	}

}

  此时,需要将SteelAxe部署在spring容器中去,只需要在beans.xml中添加配置信息。

代码语言:javascript
复制
<bean id="steelAxe" class="test.SteelAxe"></bean>

  这一行定义了一个Axe实例,id是steelAxe,实现的类是SteelAxe,然后需要修改chinese的配置信息。

代码语言:javascript
复制
<bean id="chinese" class="test.Chinese">
	<!-- <property name="axe" ref="stoneAxe"/></bean> -->
	<property name="axe" ref="stoneAxe"/></bean>

  因为chinese实例与具体的Axe实现类之间没有任何关系,chinese仅仅与接口Axe耦合,这样就能保证chinese实例与Axe的松耦合——这是spring强调面向接口编程的原因。

  Bean与Bean之间的依赖关系由spring管理,spring采用setter方法为目标Bean注入所依赖的Bean,让Bean之间的耦合从代码层次上分离出来,依赖注入是一种优秀的解耦方式。

  所以,可以看出spring的IoC容器的三个基本要点:

  ①应用程序的各组件面向接口编程,面向接口编程可以将组件之间的耦合关系提升到接口层次,从而利于项目后期拓展;

  ②应用程序各组件不再由程序主动创建,而是由spring容器来负责产生并初始化;

  ③spring采用配置文件或注解来管理Bean的实现类、依赖关系,spring容器根据配置文件或注解,利用反射机制来创建实例,并将其注入依赖关系。

2、 构造注入

这种方式在构造实例的时候,已经为其完成了依赖关系的初始化,这种利用构造器来设置依赖关系的方式,被称为构造注入

  驱动spring在底层以反射方式执行带指定参数的构造器,当执行带参数的构造器时,就可利用构造器参数对成员变量执行初始化——这是构造注入的本质。

  <bean .../>元素默认总是驱动spring调用无参数的构造器来创建对象,使用<consrtructor-arg .../>子元素,每个<constructor-arg .../>子元素代表一个构造器参数,如果<bean .../>元素包含N个<constructor-arg .../>子元素,就会驱动spring调用带N个参数的构造器来创建对象。

①Roles类

代码语言:javascript
复制
package test;

public class Roles {
	private int id;
	private String roleName;
	public Roles(){
	}
	public Roles(int id, String roleName)
	{
		this.id = id;
		this.roleName = roleName;
	}
	public String toString()
	{
		return "Users [id = " + id + ", name= " + roleName + "]"; 
	}
}

②beans.xml配置文件

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN"
    "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="roles" class="test.Roles">
	<constructor-arg value="1"/>
	<constructor-arg value="小明"/>
</bean>
</beans>

③测试

代码语言:javascript
复制
package test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class SpringTest {
	public static void main(String[] args)
	{
		ApplicationContext ctx = new FileSystemXmlApplicationContext("src/bean.xml");
		Roles r = (Roles)ctx.getBean("roles");
		System.out.println(r.toString());
	}	
}

  设值注入是先通过无参数的构造器创建一个Bean实例,然后调用setter方法注入依赖关系,而构造注入则直接调用有参数的构造器,当Bean实例创建完成之后,已经完成了依赖关系的注入。

  <constructor-arg .../>可以指定参数的值,  

代码语言:javascript
复制
<bean id="roles" class="test.Roles">
	<constructor-arg value="1"/>
	<constructor-arg value="小明"/>

  这一段的内容相当于:

代码语言:javascript
复制
Roles role = new Role(1, "小明");

  有时候,如果包含的构造器中的参数不同,但是构造器的名称相同,假设Test(String, String) 和Test(String, int),假如通过<constructor-arg value="1">由于spring只能解析出"1"字符串,但是到底转换为哪一个明确的构造器的数据类型,就无从判断了,所以spring允许为<constructor-arg .../>元素指定一个type类型,比如说<constructor-arg value="1", type="int"/>,此时就完成了int类型参数的配置。

  假如存在依赖,看下面的例子:

①Users类

代码语言:javascript
复制
package test;

public class Users {
	private int id;
	private String name;
	public Users(){
	}
	public Users(int id, String name)
	{
		this.id = id;
		this.name = name;
	}
	public String toString()
	{
		return "User [id = " + id + ", name = " + name + "]";
	}

}

②Roles类

代码语言:javascript
复制
package test;

public class Roles {
	private int id;
	private String roleName;
	//用户
	private Users users;
	public Roles(){
	}
	public Roles(int id, String roleName, Users users)
	{
		this.id = id;
		this.roleName = roleName;
		this.users = users;
	}
	public String toString()
	{
           //因为这里是在字符串加法中调用的Users对象,所以会隐式调用user.toString()方法,也可以显示调用users.toString方法
		 return "Roles [id=" + id + ", roleName=" + roleName + ", users="  
	                + users + "]";  
	}
}

③配置文件

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN"
    "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="roles" class="test.Roles">
	<constructor-arg value="1"/>
	<constructor-arg value="小明"/>
	<constructor-arg ref="users"/>
</bean>
<bean id = "users" class = "test.Users">
	<constructor-arg value = "2"/>
	<constructor-arg value = "小华"/>
</bean>
</beans>

④测试

代码语言:javascript
复制
package test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class SpringTest {
	public static void main(String[] args)
	{
		ApplicationContext ctx = new FileSystemXmlApplicationContext("src/bean.xml");
		Roles r = (Roles)ctx.getBean("roles");
		System.out.println(r.toString());
	}	
}

⑤输出结果

代码语言:javascript
复制
Roles [id=1, roleName=小明, users=User [id = 2, name = 小华]]

3.两种注入方法的对比

设置注入的适用场景:

  1. 与传统的JavaBean的写法相似,更容易理解,通过setter方法设定依赖关系显得更加直观,自然;
  2. 对于复杂的依赖关系,如果采用构造注入, 会导致构造器过于臃肿,难以阅读,spring在创建Bean实例的时候,需要同时实例化其依赖的全部实例,因而导致性能下降,而如果使用设值注入,会比较轻松;
  3. 尤其是在某些成员变量可选的情况下,多参数的构造器很笨重。

  构造注入的适用场景:

  1. 构造注入可以在构造器中决定依赖关系的注入顺序,优先依赖的优先注入,例如,组件中其它依赖关系的注入,常常需要依赖于Datasource的注入,采用构造注入可以设置注入的顺序;
  2. 对于依赖关系无需变化的Bean,构造注入更加实用。因为没有setter方法,所有的依赖关系都在构造器中设定,因此,无需担心后续代码对依赖关系产生破坏;
  3. 依赖关系只能在构造器中设定,则只有组件的创建者才能改变组件的依赖关系。对组件的调用者而言,组件内部的依赖关系完全是透明的,更符合高内聚的原则。

总结:

建议以设置注入为主,构造注入为辅。对于依赖关系无需变化的注入,尽量采用构造注入,而其他依赖关系的注入,考虑使用设值注入的方式。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017-03-08 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档