Spring中的bean的理解 - Go语言中文社区

Spring中的bean的理解


首先看一下spring中创建bean的方式
1. 使用构造器创建Bean实例
使用构造器来创建Bean实例是最常见的情况,如果不采用构造注入,Spring底层会调用Bean类的无参构造器来创建实例,因此要求该Bean提供无参的构造器。在这种情况下,class元素是必须的,class属性的值就是Bean实例的实现类。Spring对Bean实例的所有属性执行默认初始化,即所有基本类型为0或false,引用类型为null。
接下来,BeanFactory会根据配置文件决定依赖关系,先实例化被依赖的Bean实例然后为注入依赖关系,最后将一个完整的Bean实例返回给程序。
如果采用构造注入,则要求配置文件为bean元素添加constructor-arg子元素,每个constructor-arg子元素配置一个构造器参数,Spring容器使用带对应参数的构造器来创建Bean实例。Spring调用构造器传入的参数即可用于初始化Bean的实例变量,最后也将一个完成的Bean实例返回给程序。
2. 使用静态工厂方法创建Bean
使用静态工厂方法创建Bean实例时,class属性也必须指定,但此时class属性并不是指定Bean实例的实现类,而是静态工厂类。除此之外,还需要使用factory-method属性来指定静态工厂方法,Spring将调用静态工厂方法返回一个Bean实例,得到Bean实例后,Spring后面的处理步骤与采用普通方法创建Bean完全一样。
下面示例一个Bean通过静态工厂来创建,所以这个bean元素的class属性就是指定的静态工厂类,factory-method指定工厂的静态方法。如果factory-method需要参数,则需要使用constructor-arg元素传入。

//定义一个宠物接口类

public interface Pet {
	public void testPet();
}
//定义两个宠物实现类

public class Dog implements Pet {
	private String name;
	public void setName(String name){
		this.name = name;
	}
	@Override
	public void testPet() {
		System.out.println(name+":狗喜欢吃骨头!");
	}
}


public class Cat implements Pet {
	private String name;
	public void setName(String name){
		this.name = name;
	}
	@Override
	public void testPet() {
		System.out.println(name+":猫喜欢吃鱼!");
	}
}
//定义一个静态工厂类

public class PetFactory {
	public static Pet getPet(String arg){
		if("dog".equals(arg)){
			return new Dog();
		}else{
			return new Cat();
		}
	}
//测试方法
public static void main(String[] args) throws Exception {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
		Pet p1 = ctx.getBean("dog", Pet.class);
		p1.testPet();
		Pet p2 = ctx.getBean("cat", Pet.class);
		p2.testPet();
	}

接下来看重点配置文件写法,beans.xml:
之前提到:这个bean元素的class属性就是指定的静态工厂类,factory-method指定工厂的静态方法。如果factory-method需要参数,则需要使用constructor-arg元素传入。

<bean id = "dog" class = "com.my.factory.PetFactory" factory-method="getPet">
<constructor-arg value="dog" />
<property name="name" value = "大黑" />
</bean>
<bean id = "cat" class = "com.my.factory.PetFactory" factory-method="getPet">
<constructor-arg value="cat" />
<property name="name" value = "小花" />
</bean>

3. 调用实例工厂方法创建Bean
实例工厂方法与静态工厂方法只有一点不同:调用静态工厂方法只需要工厂类即可,而调用实例工厂方法则需要工厂实例。所以配置实例工厂方法与配置静态工厂方法基本相似,只有一点区别:配置静态工厂方法使用class指定静态工厂类,而配置实例工厂方法则使用factory-bean指定工厂实例。
采用实例工厂方法创建Bean的bean元素时需要指定如下两个属性:
factory-bean:该属性的值为工厂Bean的id。
factory-method:该属性指定实例工厂的工厂方法。
在这里插入图片描述
上面的PetOneFactory就是一个简单的Pet工厂,getPet方法就是负责生产Pet的工厂方法。单独定义一个bean来生成工厂对象,在下面的bean定义中使用factory-bean属性引用上面的工厂Bean。其他跟静态工厂方式创建Bean完全一致。
接下来 深入看一下 容器中的Bean
深入理解容器中的Bean

  1. 抽象Bean与子Bean
    在实际开发中,有可能会出现这样的情况:随着项目越来越大,Spring配置文件出现了多个bean,配置具有大致相同的配置信息,只有少量信息不同,这将导致配置文件出现很多重复的内容。如果保留这些配置,则可能导致的问题是:
    配置文件臃肿。
    后期难以修改、维护。
    为了解决上面问题,可以考虑把多个bean配置中相同的信息提取出来,集中成配置模板——这个配置模板并不是真正的Bean。因此Spring不应该创建该配置模板,于是需要为该bean配置增加abstract属性值为true表示这是个抽象Bean。
    抽象Bean不能被实例化,Spring容器不会创建抽象Bean实例。抽象Bean的价值在于被继承,抽象Bean通常作为父Bean被继承。抽象Bean只是配置信息的模板,指定abstract为true即可阻止Spring实例化该Bean,因此抽象Bean可以不指定class属性。
    将大部分相同信息配置成抽象Bean之后,将实际的Bean实例配置成该抽象Bean的子Bean即可。子Bean定义可以从父Bean继承实现类、构造参数、属性值能配置信息,除此之外,子Bean配置可以增加新的配置信息,并可以指定新的配置信息覆盖父Bean的定义。
    通过为一个bean元素指定parent属性即可指定该Bean是一个子Bean,parent属性指定该Bean所继承的父Bean的id。子Bean无法从父Bean继承如下属性:depends-on、autowire、singleton、scope、lazy-init,这些属性只能从子Bean定义中获取,或采用默认值。
<bean id=”steelAxe” class=”com.langsin.impl.SteelAxe” />
<bean id=”personTemplete” abstract=”true”>
	<property name=”name” value=”zhangsan” />
	<property name=”axe” ref=”steelAxe” />
</bean>
<bean id=”chinese” class=”com.langsin.impl.Chinese” parent=”personTemplete”/>
<bean id=”american” class=”com.langsin.impl.American” parent=”personTemplete”/>

在配置文件中chinese和americanBean都指定了parent=“personTemplete”,表明这两个Bean都可以从父Bean那里继承得到配置信息。虽然这两个Bean没有直接指定proerty子元素,但他们会从personTemplete模板那里继承得到两个property子元素。

那么bean继承和Java继承有什么区别呢?
Spring中的Bean继承与Java中的继承截然不同,前者是实例与实例之间参数的延续,后者则是一般到特殊的细化。前者是对象与对象之间的关系,后者则是类与类之间的关系。Spring中Bean的继承和Java中Bean的继承有如下区别:
Spring中的子Bean和父Bean可以是不同类型,但Java中的继承则可保证子类是一种特殊的父类。
Spring中Bean的继承是实例之间的关系,因此主要表现为参数值的延续;而Java中的继承是类之间的关系,主要表现为方法、属性的延续。
Spring中的子Bean不可作为父Bean使用,不具备多态性;Java中的子类实例完全可以当成父类实例使用。

容器中的工厂Bean
此处的工厂Bean,与前面介绍的实例工厂Bean,或者静态工厂Bean有所区别:前面那些Bean是标准的工厂模式,Spring只是负责调用工厂方法来创建Bean的实例;此处的工厂Bean是Spring的一种特殊Bean,这种工厂Bean必须实现FactoryBean接口。
FactoryBean接口是工厂Bean的标准接口,把实现FactoryBean接口的工厂Bean部署在容器中后,如果程序通过getBean方法来获取它时,容器返回的不是FactoryBean实现类的实例,而是返回FactoryBean的产品。(即通过工厂所创建的对象被返回)
FactoryBean接口提供如下三个方法:
T getObject():负责返回该工厂Bean生成的Java实例。
Class<?> getObjectType():返回该工厂Bean生成的Java实例的实现类。
boolean isSingleton():表示该工厂Bean生成的Java实例是否是单例模式。
配置FactoryBean与配置普通Bean的定义没有区别,但当程序向Spring容器请求获取该Bean时,容器返回该FactoryBean的产品,而不是返回该FactoryBean本身。所以,实现FactoryBean接口的最大作用在于:Spring容器返回的是该Bean实例的getObject()方法的返回值。而getObject()方法由开发者负责实现,所以返回什么类型就由开发者自己决定。

import org.springframework.beans.factory.FactoryBean;
public class GetMyObjectFactoryBean implements FactoryBean<Object> {
	private String targetClass;
	public void setTargetClass(String targetClass){
		this.targetClass = targetClass;
	}
	@Override
	public Object getObject() throws Exception {
		Class<?> clazz = Class.forName(this.targetClass);
		return clazz.newInstance();
	}
	@Override
	public Class<?> getObjectType() {
		return Object.class;
	}
	@Override
	public boolean isSingleton() {
		return false;
	}
}

上面的GetMyObjectFactoryBean是一个标准的工厂Bean,从配置文件来看,部署工厂Bean与部署普通Bean其实没有任何区别,同样只需要为该Bean配置id、class属性即可。但Spring对FactoryBean接口的实现类的处理有所不同。Spring容器会自动检测容器中所有的Bean,如果发现某个Bean实现了FactoryBean接口,Spring容器就会在实例化该Bean、根据property执行setter方法之后,额外要调用该Bean的getObject方法,并将返回值作为容器中的Bean。

测试代码
public static void main(String[] args) throws Exception {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
		System.out.println(ctx.getBean("myframe"));
		System.out.println(ctx.getBean("&myframe"));
		//在Bean id前面加&符号,会返回FactoryBean本身
	}

强制初始化Bean
在大多数情况下,Bean之间的依赖非常直接,Spring容器返回Bean实例之前,先要完成Bean依赖关系的注入。假如Bean A依赖于Bean B,程序请求Bean A时,Spring容器会自动先自动初始化Bean B,再将Bean B注入Bean A,最后将具备完整依赖的BeanA返回给程序。
在极端的情况下,Bean之间的依赖关系不够直接。比如某个类的初始化块中使用其他Bean,Spring总是先初始化主调Bean,当执行初始化块时,被依赖的Bean可能还没有实例化,此时将引发异常。
为了显示指定被依赖Bean在目标Bean之前初始化,可以使用depends-on属性,该属性可以在初始化主调Bean之前,强制初始化一个或多个Bean。配置文件如下:

<bean id="one" class="com.langsin.factory.One" />
<bean id="two" class="com.langsin.factory.Two" />

如上述没有设置Bean one与Bean two之间的依赖关系,所以Spring容器根据配置的先后关系先加载one再加载two,One、Two两个类中只有构造器,并且只是做了简单的输出操作,代码如下:

示例1:
package com.langsin.factory;
public class One {
	public One(){
		System.out.println("打印One。。。。。。。。");
	}
}
示例2:
package com.langsin.factory;
public class Two {
	public Two(){
		System.out.println("打印Two。。。。。。。。。。");
	}
}

结果为:
在这里插入图片描述
在Bean one中加入强制依赖为two,例如加载关系后,则执行效果如下:
在这里插入图片描述
容器中的Bean生命周期
Spring可以管理singleton作用域的Bean的生命周期,Spring可以精确地知道该Bean何时被创建、何时被初始化完成、容器何时准备销毁该Bean实例。
对于prototype作用域的Bean,Spring容器仅仅负责创建,当容器创建Bean实例之后,Bean实例完全交给客户端代码管理,容器不再跟踪其生命周期。每次客户端请求prototype作用域的Bean时,Spring都会产生一个实例交给客户端程序,就不再过问了。
对于singleton作用域的Bean,每次客户端代码请求时都返回同一个共享实例,客户端代码不能控制Bean的销毁,Spring容器负责跟踪Bean实例的产生、销毁。Spring容器可以在创建Bean之后,进行某些通用资源申请;还可以在销毁Bean实例之前,先回收某些资源,比如数据库连接。
对于singleton作用域的Bean,Spring容器知道Bean何时实例化结束、何时销毁,Spring可以管理实例化结束之后和销毁之前的行为,即Bean的存活期间的行为。
具体的行为请看 https://www.cnblogs.com/zrtqsk/p/3735273.html
依赖关系注入之后的行为
Spring提供两种方式在Bean全部属性设置成功后执行特定情况。
1.使用init-method属性。
2.实现InitializingBean接口。
第一种方式,使用init-method属性指定某个方法应在Bean全部依赖关系设置结束后自动执行。使用这种方式不需要将代码与Spring的接口耦合在一起,代码污染小。
第二种方式,让Bean类实现InitializingBean接口,该接口提供一个方法,afterPropertysSet(),Spring容器会在为该Bean注入依赖关系之后,调用该Bean所实现的afterPropertysSet方法。
下面示例中的Bean既实现了InitializingBean接口,也包含了一个普通的初始化方法——在配置文件中将该方法配置成初始化方法。

public class InitActionBean implements InitializingBean,ApplicationContextAware {
	private String name;
	//name对应的get set 方法
	public InitActionBean(){
		System.out.println("InitActionBean的构造器被调用。。。。");
	}
	public void setName(String name){
		this.name = name;
		System.out.println("调用setName方法完成赋值操作。。。。。");
	}
	@Override
	public void setApplicationContext(ApplicationContext arg0)
			throws BeansException {
		System.out.println("设置Spring容器的对象。。。。。");
	}
	@Override
	public void afterPropertiesSet() throws Exception {
		System.out.println("初始化结束后,调用afterPropertiesSet方法。。。。");
	}
	public void init(){
		System.out.println("调用指定的init方法完成初始化之后的操作。。。。");
	}
}

Bean销毁之前的行为
与定制初始化行为相似,Spring也提供两种方式定制Bean实例销毁之前的特定行为,这两种方式如下:
使用destroy-method属性。
实现DisposableBean接口。
让Bean类实现DisposableBean接口,需实现接口中声明的destroy方法,该方法在Bean被销毁前调用,第二种方式同样为侵入式设计,不推荐采用。销毁之前的行为与依赖关系注入之后的行为原理一样,不再进行详细解释。
协调作用域不同步的Bean
当两个singleton作用域的Bean存在依赖关系时,或者当prototype作用域的Bean依赖singleton作用域的Bean时,使用Spring提供的依赖注入进行管理即可。
singleton作用域的Bean只有一次初始化的机会,它的依赖关系也只在初始化阶段被设置,当singleton作用域的Bean依赖prototype作用域的Bean时,Spring容器会在初始化singleton作用域的Bean之前,先创建被依赖的prototype Bean,然后才初始化singleton Bean,并将prototype Bean注入到singleton Bean中,这就会导致任何时候通过singleton Bean去访问prototype Bean时,得到的永远是最初那个prototype Bean,任何人访问都将得到相同的信息,这就会产生不同步的现象。解决该问题又如下两种思路:
放弃依赖注入:singleton作用域的Bean每次需要prototype作用域的Bean时,主动向容器请求一个新的Bean实例,即可保证每次注入的的都是最新的实例。
使用lookup-method元素:使用该元素指定方法注入。
第一种方式显然不合适,将会造成代码的耦合,在通常情况下,推荐采用第二种做法,使用方法注入。
使用lookup子元素可以让Spring容器重写容器中Bean的抽象或者具体方法,返回查找容器中其他Bean的结果,被查找的Bean通常是一个prototype Bean。Spring通过使用JDK动态代理或cglib库修改客户端的二进制码,从而实现上述要求。
为了使用lookup方法注入,大致需要如下两步。
1、将调用者Bean的实现类定义为抽象类,并定义一个方法来获取被依赖的Bean。
2、在bean元素中添加lookup-method子元素让Spring容器为调用者Bean提供一个实现类,并实现指定的抽象方法。

singleton Bean:
public abstract class Hunter {
	private Dog dog;
	public abstract Dog getDog();
	public void hunt(){
		System.out.println("我的猎狗为:"+this.getDog());
	}
}
被依赖的prototype Bean:
public class Dog implements Pet {
	private String name;
	public void setName(String name){
		this.name = name;
	}
	public String getName(){
		return this.name;
	}
	@Override
	public void testPet() {
		System.out.println(name+":狗喜欢吃骨头!");
	}
}

配置文件:

<bean id="hunDog" class="com.langsin.factory.Dog" scope="prototype"/>
<bean id="hunter" class="com.langsin.init.Hunter">
		<lookup-method name="getDog" bean="hunDog"/>
</bean>

测试代码:

public static void main(String[] args) throws Exception {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
		Hunter hun = ctx.getBean("hunter", Hunter.class);
		hun.hunt();
		hun = ctx.getBean("hunter", Hunter.class);
		hun.hunt();
	}

在这里插入图片描述

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_39411208/article/details/88395875
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-03-01 20:51:55
  • 阅读 ( 968 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢