• spring框架(1)— 依赖注入


    依赖注入

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

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

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

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

    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中进行配置。

    <?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就可以对其进行管理。

    <bean id="person" class="service.Person">
    

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

    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的底层会执行如下代码:

    //解析<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来创建容器。

    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接口

    package test;
    
    public interface Axe 
    {
    	public String chop();
    }
    

    ②Person接口

    package test;
    
    public interface Person 
    {
    	public void useAxe();
    
    }
    

    ③Chinese类

    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类

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

    ⑤beans.xml配置文件

    <?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类

    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。

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

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

    <bean id="steelAxe" class="test.SteelAxe"></bean>
    

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

    <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类

    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配置文件

    <?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>
    

    ③测试

    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 .../>可以指定参数的值,  

    <bean id="roles" class="test.Roles">
    	<constructor-arg value="1"/>
    	<constructor-arg value="小明"/>
    

      这一段的内容相当于:

    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类

    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类

    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 + "]"; } }

    ③配置文件

    <?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>
    

    ④测试

    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());
    	}	
    }
    

    ⑤输出结果

    Roles [id=1, roleName=小明, users=User [id = 2, name = 小华]]
    

    3.两种注入方法的对比

      设置注入的适用场景:

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

      

      构造注入的适用场景:

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

    总结:

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

      

    能让一个男孩子热血的,不仅有梦想,还有姑娘。
  • 相关阅读:
    学校的破网,你别再掉了
    PhotoShop SDK的获取
    我的C++博客开张了
    一个新的嵌入式门户
    试用Bloglines.com
    PhotoShop的插件体系
    VB6 to VB.NET Migration Guide Community Preview #4
    看看Microsoft都买了些什么
    Borland CTO辞职
    PhotoShop插件的开发
  • 原文地址:https://www.cnblogs.com/Mr24/p/6522875.html
Copyright © 2020-2023  润新知