理解依赖注入

news2024/11/27 0:25:28

1 回顾Spring IoC容器

1.1 Spring IoC容器

Web应用程序由大量的程序组件组成,这些组件一起工作完成业务逻辑的执行。 这些组件通常是一个依赖另一个,互相协作实现所需功能。

Spring提供容器,也就是IoC容器,来管理这些组件,我们叫做Bean,Spring容器负责创建、初始化和销毁其管理的各种Bean,并根据Bean的依赖关系注入依赖的Bean,也就是依赖注入(DI)。

Spring IoC容器的类型:

  • BeanFactory容器(Bean工厂容器) 低级容器,提供容器的基本功能,包括Bean的管理和依赖注入。由org.springframework.beans.factory.BeanFactory接口表示
  • ApplicationContext容器(应用程序上下文容器) 高级容器,提供BeanFactory容器的功能,还提供一些高级的企业级功能。由org.springframework.context.ApplicationContext定义。如果没有什么特殊理由,直接使用ApplicationContext容器即可

下面给出一个例子,结合使用面向接口编程(将方法的声明/定义放入接口,将方法的具体实现放入实现类),使用IoC实现解耦(decoupling)

假设我们有若干个动物类:狗、猪、羊,它们都要吃东西,有一个方法eat()。

我们将这个方法的定义放入接口:

public interface Animal {

	void eat();

}

将这个方法的具体实现放入实现类:

public class Dog implements Animal{

	public Dog() {
		System.out.println("实例化了Dog!!!");
	}
	
	@Override
	public void eat() {
		System.out.println("这是一只狗,狗吃骨头!");
	}
}

 使用IoC思想,将bean交给容器管理。若需要换掉使用的bean,修改spring配置文件中的bean类型即可,实现降低程序的耦合性,方便后期维护

在XML文件中定义一个bean:

<bean id="animal" class="com.qdu.bean.Dog" />

在主类中,首先加载Spring配置文件,创建并启动容器

ApplicationContext ctx
			=new ClassPathXmlApplicationContext("config/spring-beans.xml");

 需要该bean的地方通过调用getBean()或依赖注入获取bean

Animal animal1=(Animal)ctx.getBean("animal");
animal1.eat();

1.2 上下文容器中bean的生命周期

 1.2.1 生命周期回调

容器控制着bean的创建和销毁。

为了执行一些自定义代码,容器提供了回调方法,回调方法主要包括:

  • 初始化回调的方法:用于执行一些初始化操作
  • 销毁前回调的方法:用于执行一些类似释放资源的操作

1.2.2 控制Bean的生命周期事件

Spring框架提供了以下方法来控制Bean的生命周期事件:

  1. InitializingBean和DisposableBean回调接口
  2. 可用于注入特定依赖的Aware接口
  3. bean配置文件中的自定义init()和destroy()方法
  4. @PostConstruct 和 @PreDestroy注解

a. 使用InitializingBean和DisposableBean回调接口,实现初始化和释放资源任务。

  1. 有的时候,使用一个bean之前可能需要执行一些初始化工作(准备工作),就可以利用bean的初始化回调方法执行初始化操作。可以通过实现InitializingBean接口,实现afterPropertiesSet()方法,在该方法中编写初始化工作代码
  2. 有的时候,一个bean被摧毁之前需要释放这个bean占用的资源,就可以利用bean的摧毁回调方法来执行释放操作。可通过实现DisposableBean接口,实现destroy()方法,在该方法中编写bean摧毁之前要执行的工作
public class Pig implements InitializingBean,DisposableBean{

	public Pig() {
		System.out.println("实例化了Pig!!!");
	}
	
	public void eat() {
		System.out.println("这是一只猪,猪吃饲料~");
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		//在这里编写你的初始化工作的代码
		System.out.println("......初始化代码.");
	}

	@Override
	public void destroy() throws Exception {
		//在bean被摧毁前,如果要执行一些任务,在此方法中编写代码
		System.out.println("~~~~~~我要die了,在这里做点释放资源的操作");
	}
	
}

b. bean配置文件中的自定义init()和destroy()方法

1. 局部定义

可通过bean标记的init-method属性指定初始化时要调用的方法

可通过bean标记的destroy-method属性指定bean被摧毁前要调用的方法

<bean class="com.qdu.bean.Dog" 
			init-method="init" destroy-method="dispose" />

在class中声明的Dog类中实现自定义init()和destory()方法:
 

public class Dog {

	//方法名自定义,但是方法不能有参数
	//自定义的一个方法init,用于包含初始化工作代码
	public void init() {
		System.out.println("调用了Dog类中的init......");
	}
	
	//方法名自定义,但是方法不能有参数
	//自定义的一个方法dipsose,用于包含bean摧毁前要执行的工作代码
	public void dispose() {
		System.out.println("调用了Dog类中的dispose......");
	}
	
	public Dog() {
		System.out.println("实例化了Dog!!!");
	}
	
	public void eat() {
		System.out.println("这是一只狗,狗吃骨头!");
	}
}

2. 全局定义

可全局配置初始化方法和摧毁bean之前要调用的方法。

可通过配置<beans>这个根元素的default-init-method来设置全局的初始化方法。

可通过配置<beans>这个根元素的default-destory-method来设置全局的初始化方法

<?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:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd"
        default-init-method="init" 
        default-destroy-method="dispose">


    <bean class="com.qdu.bean.Dog"  />
	

</beans>

考虑到代码重复利用,可以考虑保持各个类的初始化方法和摧毁前要调用的方法同名

c. @PostConstruct 和 @PreDestroy注解

使用@PostConstruct注解和@PreDestroy注解修饰自定义的init和destroy方法。

如果希望一个方法在bean初始化的时候被调用,执行初始化工作,则使用@PostConstruct注解修饰该方法:

@PostConstruct
public void init() {
	System.out.println("调用了Sheep类中的init......");
}

如果希望一个方法在bean被摧毁之前被调用,执行释放资源等工作,则使用@PreDestroy注解修饰该方法:

@PreDestroy
public void cleanUp() {
	System.out.println("调用了Sheep类中的cleanUp......");
}

d. 用于注入特定依赖的Aware接口

有时候可能需要获取某个特定的依赖,如想获取bean所在的上下文容器,则可以实现对应的Aware接口,并实现对应的方法

spring提供一些xxxAware接口,用于注入需要的一些依赖,例如,用于获取bean所属的上下文容器。如果希望在一个bean中获取bean所属的上下文容器,可以让bean实ApplicationContextAware

public class Mouse implements ApplicationContextAware{
	
	//使用一个引用变量,用于引用所属的容器对象
	private ApplicationContext ctx;

	public Mouse() {
		System.out.println("实例化Mouse!!!");
	}

	public void eat() {
		System.out.println("这是一只Mouse,老鼠吃大米!!!");
		Cat cat=ctx.getBean(Cat.class);
		cat.eat();
	}
	
	//该方法将bean所属的容器对象传入该类,使用一个变量接收,然后就可以使用上下文容器了
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		ctx=applicationContext;
	}
}

2 DI-依赖注入

DI(Dependency Injection)- 依赖注入是IoC思想的一种实现方式,就是将 依赖对象的创建和维护转移到 被依赖对象类的外部 来实现。

也就是说:一个对象是被动接受依赖对象而不是自己主动去找,换句话说就是对象自己不需要负责寻找或创建它所依赖的对象。

而是由容器(container)来维护对象之间的依赖关系,并根据依赖关系将需要的对象注入。

如果对象A中使用了对象B,则对象A依赖对象B,不直接在A中new一个B对象,而是交给容器,让容器根据A和B的依赖关系,实例化B,并将B对象传递给A,这个过程就是依赖注入。

2.1 setter实现依赖注入

2.1.1 注入简单值(基本数据类型或字符串)

通过value指定要注入的值

<bean id="addr1" class="com.qdu.bean.Address">
	<!-- property标记用于实现注入依赖 -->
	<property name="province" value="山东" />
	<property name="city" value="青岛" />
	<property name="detailedAddress" value="宁夏路308号 青岛大学" />
</bean>

spring还提供一种简写形式 : p:属性名="属性值" 

<bean id="addr3" class="com.qdu.bean.Address" p:province="四川"
		p:city="成都" p:detailedAddress="小吃街" />

在Address类中,必须要给对应的属性生成setter访问器,因为要使用setXXX()方法来注入/设置需要的值

public class Address {

    private String province; //省
    private String city; 	 //市
    private String detailedAddress; //详细地址

	@Override
	public String toString() {
		return "地址::" + province + "省" + city + "市" + detailedAddress;
	}

	public void setProvince(String province) {
		System.out.println("setProvince()方法被调用........");
		this.province = province;
	}

	public void setCity(String city) {
		this.city = city;
	}

	public void setDetailedAddress(String detailedAddress) {
		this.detailedAddress = detailedAddress;
	}
}

2.1.2 注入引用的其他bean(引用类型)

 使用ref属性指定要注入的bean的id或name

<bean id="emp1" class="com.qdu.bean.Employee">
	<!-- 如果注入简单值,使用value属性指定要注入的值 -->
	<property name="empId" value="E001" />
	<property name="empName" value="小强" />
	<property name="empSalary" value="50000" />
	<!-- 如果注入引用的其他bean,使用ref属性指定要注入的bean的id或name -->
	<property name="address" ref="addr1" />
</bean>

引用的其他bean使用p:属性名-ref="引用的bean的id或name"

<bean id="emp2" class="com.qdu.bean.Employee" p:empId="E002"
		p:empName="小明" p:empSalary="60000" p:address-ref="addr2" />

2.1.3 注入集合(Set、List、Map、Properties)

set标记用于注入set集合的值

<property name="branchNames">
	<!-- set标记用于注入set集合的值 -->
	<set>
		<!-- 如果集合中是引用的其他bean,使用ref标记,通过bean指定引用的bean的id或name -->
		<!-- <ref bean="id或name" /> -->
		<!-- 如果集合中是简单值,使用value指定值即可 -->
		<value>山东分公司</value>
		<value>山西分公司</value>
		<value>广东分公司</value>
		<value>广西分公司</value>
		<value>海南分公司</value>
	</set>
</property>

list标记用于注入list集合的值

<property name="partnerNames">
	<list>
		<!-- 如果集合中是引用的其他bean,使用ref标记,通过bean指定引用的bean的id或name -->
		<!-- <ref bean="id或name" /> -->
		<!-- 如果集合中是简单值,使用value指定值即可 -->
		<value>Oracle</value>
		<value>Microsoft</value>
		<value>IBM</value>
		<value>Google</value>
	</list>
</property>

<property name="employeeList">
	<list>
		<!-- 如果集合中是引用的其他bean,使用ref标记,通过bean指定引用的bean的id或name -->
		<ref bean="emp1" />
		<ref bean="emp2" />
	</list>
</property>

map标记用于注入map集合的值

<property name="branchManagers">
	<map>
		<!-- entry用于指定一项数据的注入 -->
		<!-- 如果键是简单值,则使用key指定其值,如果是引用的其他bean,使用key-ref指定bean的id或name -->
		<!-- 如果值是简单值,则使用value指定其值,如果是引用的其他bean,使用value-ref指定bean的id或name -->
		<entry key="山东分公司" value-ref="emp1" />
		<entry key="山西分公司" value-ref="emp2" />
	</map>
</property>

注入Properties集合,可以使用props标记或value标记

<property name="extraInfo1">
	<props>
		<prop key="registeredCapital">1000000</prop>
		<prop key="legalPerson">小胖</prop>
		<prop key="type">Joint venture</prop>
	</props>
</property>


<property name="extraInfo2">
	<value>
		registeredCapital=2000000
		legalPerson=Allen
		type=Joint venture
	</value>
</property>

2.2 构造函数实现依赖注入

2.2.1 注入简单值(基本数据类型或字符串)

constructor-arg标记用于实现以setter的方式注入依赖。

如果使用setter注入,constructor-arg标记的name属性是类中的属性名;

如果使用构造函数注入,constructor-arg标记的name属性指定的是构造函数的参数的名称。

使用构造函数注入,需要生成对应参数的构造函数

<bean id="addr1" class="com.qdu.bean.Address">
	<constructor-arg name="pro" value="山东" />
	<constructor-arg name="city" value="青岛" />
	<constructor-arg name="detailedAddress" value="宁夏路308号 青岛大学" />
</bean>

使用构造函数注入,也可不写参数名称,但是必须保证参数的顺序是对的。

可以使用参数索引(0表示第一个参数),这样参数顺序随便写。

但是如果不写参数名称,也不写参数索引,则参数顺序必须正确。

<bean id="addr2" class="com.qdu.bean.Address">
	<constructor-arg index="0" value="山东" />
	<constructor-arg index="1" value="济南" />
	<constructor-arg index="2" value="泉城广场" />
</bean>

spring还提供一种简写形式 : c:参数名="值"

<bean id="addr3" class="com.qdu.bean.Address" c:pro="四川"
	c:city="成都" c:detailedAddress="小吃街" />

在Address类中要实现构造函数:

public class Address {

    private String province; //省
    private String city; 	 //市
    private String detailedAddress; //详细地址
    
    //如果使用property标记实现依赖注入,则必须要给对应的属性生成setter访问器
    //因为使用setXXX()方法来注入/设置需要的值
    
	@Override
	public String toString() {
		return "地址::" + province + "省" + city + "市" + detailedAddress;
	}

	public Address() {
		super();
	}

	public Address(String pro, String city, String detailedAddress) {
		System.out.println("调用了带3个参数的构造函数实现依赖注入!!!");
		this.province = pro;
		this.city = city;
		this.detailedAddress = detailedAddress;
	}

	public void setProvince(String province) {
		System.out.println("setProvince()方法被调用........");
		this.province = province;
	}

	public void setCity(String city) {
		this.city = city;
	}

	public void setDetailedAddress(String detailedAddress) {
		this.detailedAddress = detailedAddress;
	}
}

2.2.2 注入引用的其他bean(引用类型)

使用ref属性指定要注入的bean的id或name

<bean id="emp1" class="com.qdu.bean.Employee">
	<!-- 如果注入简单值,使用value属性指定要注入的值 -->
	<constructor-arg name="empId" value="E001" />
	<constructor-arg name="empName" value="小强" />
	<constructor-arg name="empSalary" value="50000" />
	<!-- 如果注入引用的其他bean,使用ref属性指定要注入的bean的id或name -->
	<constructor-arg name="address" ref="addr1" />
</bean>

如果使用c:参数名写法,则简单值使用c:参数名="参数值"

引用的其他bean使用c:参数名-ref="引用的bean的id或name"

<bean id="emp2" class="com.qdu.bean.Employee" c:empId="E002"
	c:empName="小明" c:empSalary="60000" c:address-ref="addr2" />

2.2.3 注入集合(Set、List、Map、Properties)

set标记用于注入set集合的值

<constructor-arg name="branchNames">
    <set>
	    <!-- 如果集合中是引用的其他bean,使用ref标记,通过bean指定引用的bean的id或name -->
	    <!-- <ref bean="id或name" /> -->
	    <!-- 如果集合中是简单值,使用value指定值即可 -->
	    <value>山东分公司</value>
	    <value>山西分公司</value>
	    <value>广东分公司</value>
	    <value>广西分公司</value>
	    <value>海南分公司</value>
    </set>
</constructor-arg>

list标记用于注入list集合的值

<constructor-arg name="partnerNames">
    <list>
	    <!-- 如果集合中是引用的其他bean,使用ref标记,通过bean指定引用的bean的id或name -->
	    <!-- <ref bean="id或name" /> -->
	    <!-- 如果集合中是简单值,使用value指定值即可 -->
	    <value>Oracle</value>
	    <value>Microsoft</value>
	    <value>IBM</value>
	    <value>Google</value>
    </list>
</constructor-arg>

<constructor-arg name="partnerNames">
    <list>
	    <!-- 如果集合中是引用的其他bean,使用ref标记,通过bean指定引用的bean的id或name -->
	    <ref bean="emp1" />
	    <ref bean="emp2" />
    </list>
</constructor-arg>

map标记用于注入map集合的值

<constructor-arg name="branchManagers">
	<map>
		<!-- entry用于指定一项数据的注入 -->
		<!-- 如果键是简单值,则使用key指定其值,如果是引用的其他bean,使用key-ref指定bean的id或name -->
		<!-- 如果值是简单值,则使用value指定其值,如果是引用的其他bean,使用value-ref指定bean的id或name -->
		<entry key="山东分公司" value-ref="emp1" />
		<entry key="山西分公司" value-ref="emp2" />
	</map>
</constructor-arg>

注入Properties集合,可以使用props标记或value标记

<constructor-arg name="extraInfo1">
	<props>
		<prop key="registeredCapital">1000000</prop>
		<prop key="legalPerson">小胖</prop>
		<prop key="type">Joint venture</prop>
	</props>
</constructor-arg>

<constructor-arg name="extraInfo2">
	<value>
		registeredCapital=2000000
		legalPerson=Allen
		type=Joint venture
	</value>
</constructor-arg>

2.3 自动装配

在应用程序对象之间创建和管理关联的过程叫做装配,这构成了DI的核心。以上两种方法为显示装配(显式声明Bean之间的依赖),自动装配(autowiring)不显式声明Bean之间的依赖,让容器选择合适的依赖项注入。

2.3.1 byName

 byName是按照名称自动装配,意思是要注入的属性的属性名和要注入的bean的id或name(别名)相同,即可实现按照名称自动装配。

<bean id="person" class="com.qdu.bean.Boy" autowire="byName">
	<property name="name" value="小兰" />
	<!-- 这里注释了代码,表示不使用显式装配(wire) -->
	<!-- <property name="axe" ref="fuzi" /> -->
	<!-- <property name="pet" ref="chongwu" /> -->
</bean>

byName 通过匹配 bean 的 id是否跟 setter 对应,对应则自动装配。比如以上代码,bean的id为person;如果我的person类中有一个setName(),则能够自动装配。

public interface Person {

	void kanChai();
}
public class Boy implements Person {

	private String name;
	private Axe axe;
	private Pet pet;
	
	public void setAxe(Axe axe) {
		this.axe = axe;
	}

	public void setName(String name) {
		this.name = name;
	}


	public void setPet(Pet pet) {
		this.pet = pet;
	}

	@Override
	public void kanChai() {
		System.out.println("砍柴人:"+name);
		System.out.println("Boy砍柴,力气大!!!");
		axe.cut();
		pet.showSkill();
	}
}

当一个bean带有autowire byName的属性时:

  1. 将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。
  2. 去spring容器中寻找是否有此字符串名称id的对象。
  3. 如果有,就取出注入;如果没有,就报空指针异常

2.3.2 byType

byType是按照类型自动装配。如果要注入的属性的数据类型和容器中某个bean的类型匹配,则实现自动装配。

<bean id="person" class="com.qdu.bean.Girl" autowire="byType">
	<property name="name" value="小明" />
	<!-- 这里不使用显式装配 -->
	<!-- <property name="axe" ref="fuzi" /> -->
	<!-- <property name="pet" ref="chongwu" /> -->
</bean>

自动装配跟 id 无关,在配置bean 时不写 id 都不会报错。

但是 byType 的自动装配存在一个很严重的问题,因为不是通过唯一的 id 来匹配,而是通过类型来匹配,所以容器中不能存在多个相同类型的 bean,否则会抛出NoUniqueBeanDefinitionException异常。

2.3.3 constructor

constructor表示使用对应的构造函数实现自动装配,所以需要生成对应的构造函数

<bean id="person" class="com.qdu.bean.Girl" autowire="constructor">
	<property name="name" value="小明" />
</bean>
public class Girl implements Person {

	private String name;
	private Axe axe;
	private Pet pet;
	
	@Override
	public void kanChai() {
		System.out.println("砍柴人:"+name);
		System.out.println("Girl砍柴,力气更大!!!");
		pet.showSkill();
		axe.cut();
	}

	public Girl() {
		super();
	}

	//如果使用构造函数实现自动装配,需要生成对应的构造函数
	//使用constructor实现自动装配,只要构造函数的数据类型对了即可,参数名无所谓
	public Girl(Axe a, Pet p) {
		super();
		System.out.println("带2个参数的构造函数~~~");
		this.axe = a;
		this.pet = p;
	}

	public void setName(String name) {
		this.name = name;
	}

	public void setAxe(Axe axe) {
		this.axe = axe;
	}

	public void setPet(Pet pet) {
		this.pet = pet;
	}
}

2.3.4 注解

除了使用bean标记注册类为spring管理的bean,也可直接开启包扫描:component-scan标记用于开启组件的包扫描,指定包名,多个包名用逗号隔开。

默认会扫描指定包下使用了@Component、@Controller、@Service、@Repository等注解修饰的类,将类注册为spring管理的bean。

<context:component-scan base-package="com.qdu.bean">
</context:component-scan>
  1. 使用@Component等注解将一个类注册为spring管理的bean,则bean的名称默认为首字母小写的类名,也可自行指定bean的名称。
  2. 在字段前使用@Autowired注解,则使用反射实现注入。@Autowired注解按照类型进行装配,如果存在多个匹配类型,会报错。@Autowired可用于构造函数前、setter方法前、其他任何用于注入的方法前、字段前。
  3. @Resource注解也可用于实现依赖注入。如果没有指定名称或类型,它会先按照名称进行装配;如果没有匹配的bean则继续按照类型进行装配;如果指定了名称,则按照名称进行装配
    ;如果指定了类型,则按照类型进行装配。
@Component("person")
public class Girl implements Person {

	private String name;
	
	@Autowired
	private Axe axe;
	
	
	//@Resource(name="dog")
	//@Resource
	private Pet pet;

	
	@Autowired
	public Girl(Axe axe) {
		System.out.println("用于注入axe属性的构造函数~~~");
		this.axe=axe;
	}
	
	@Autowired
	public void setPet(Pet pet) {
		System.out.println("用于注入pet属性的setter方法~~~");
		this.pet=pet;
	}
	
	@Override
	public void kanChai() {
		System.out.println("砍柴人:"+name);
		System.out.println("Girl砍柴,力气更大!!!");
		axe.cut();
		pet.showSkill();
	}
}
如果使用@Autowired,那么是按照类型进行自动装配;如果存在多个匹配类型,会抛出异常
可使用@Qualifier注解进一步限定要注入的bean,这个注解不能在构造函数前面使用
@Component
public class Girl implements Person {

	private String name;

	private Axe axe;


	@Autowired
	@Qualifier("pig")
	private Pet pet;

	public Girl() {
	}

	public Girl(Pet pet) {
		System.out.println("调用构造函数注入pet属性..................");
		this.pet = pet;
	}

	//qualifier 限定符
	
	@Autowired
	@Qualifier("steelAxe")
	//@Required //表示该属性必须要注入,在spring5.1开始,已经废除,不用关注了,直接使用构造函数注入替代即可
	public void setAxe(Axe axe) {
		System.out.println("调用setAxe()注入axe属性..................");
		this.axe = axe;
	}

	public void setName(String name) {
		this.name = name;
	}

	public void setPet(Pet pet) {
		this.pet = pet;
	}

	@Override
	public void kanChai() {
		System.out.println("Girl砍柴,力气更大!!!");
		axe.cut();
		pet.showSkill();
	}
}

上述代码中,我们使用 @Qualifier 注解,并指定一个唯一的值,选择我们要注入的bean

如pet我们选择注入Pig类,Pig类上@Component并未指定bean的名称,故使用默认名称。

3 bean的继承

在使用bean标记注册一个bean为spring管理的bean时,可使用parent属性指定继承的bean的id或name,这样一个bean会继承另一个bean的配置和依赖注入(如init-method)。

 如果又单独针对某个属性指定了依赖注入,会覆盖继承的依赖注入。

<bean id="emp1" class="com.qdu.bean.Employee" 
		  init-method="init" destroy-method="destroy">
	<property name="empId" value="E001" />
	<property name="empName" value="小明" />
	<property name="empSalary" value="20000.3" />
	<property name="address" ref="addr1" />
</bean>


<bean id="emp2" class="com.qdu.bean.Employee" parent="emp1">
	<property name="address" ref="addr2" />
</bean>

4 使用工厂方法

默认情况下,spring容器调用无参构造函数创建bean实例。如果希望调用指定方法创建交给spring容器管理的bean实例,可通过bean标记的factory-method属性指定工厂方法的名称。

  • 如果工厂方法是静态方法,则使用bean标记的class属性指定的是工厂类的全限定名称
  • 如果工厂方法是非静态方法,则使用bean标记的factory-bean指定工厂对象bean的id或name
  • 如果工厂方法带有参数,可使用constructor-arg标记指定传入的参数的信息

4.1 使用静态工厂方法返回自身实例

如果想通过一个工厂类的静态工厂方法创建bean,可通过factory-method属性指定创建实例的方法的名称。class指定工厂类的类型,也就是工厂类的全限定名称。

<bean class="com.qdu.bean.A" factory-method="getA" />

A类的代码:

public class A {

	//即使构造函数是私有的,spring也能通过反射访问该构造函数,实现实例化
	private A() {
		System.out.println("A的私有构造函数!!!");
	}

	//有的时候,我们不希望直接简单调用构造函数创建一个对象
	//有的时候,我们希望spring通过调用我们指定的方法来获取bean的对象
	public static A getA() {
		System.out.println("调用了A类的静态工厂方法返回A的实例!!!");
		return new A();
	}

	public void methodOfA() {
		System.out.println("调用了A类的方法MethodOfA.............");
	}
}

4.2 使用静态工厂方法返回其他类实例

假设有一个Animal接口,在此基础上我们实现了dog、pig、sheep类,我们要调用Animal的静态方法来获取dog的实例。

<bean id="animal" class="com.qdu.bean.AnimalFactory"
	                  factory-method="getAnimal1" />

其中,class指定的应该是工厂方法所在的类的名称,factory-method属性指定创建实例的方法的名称。 实际创建的对象不是AnimalFactory类型,而是dog类型

AnimalFactory类:

public class AnimalFactory {

	public static Animal getAnimal1() {
		System.out.println("调用AnimalFactory类的静态工厂方法getAnimal1().................");
		return new Dog();
	}
	
	
}

4.3 使用非静态工厂方法返回其他类实例

接上例,这次我们希望在工厂类AnimalFactory中用非静态方法实例化一个pig类

首先我们要在XML配置文件中注册AnimalFactory

<bean id="animalFactory" class="com.qdu.bean.AnimalFactory" />

如果希望通过工厂类A的非静态方法来实例化B ,这时候不再使用class属性来指定生成的bean的类型。

factory-bean属性指定创建bean实例(如这里的Pig)的工厂对象是哪个,指定其id或name。

factory-method属性指定创建bean实例的工厂方法的名称。

id为animal的bean实际是一个Pig实例,也即是getAnimal2()方法返回的实例

<bean id="animal" factory-bean="animalFactory" factory-method="getAnimal2" />

 AnimalFactory类:

public class AnimalFactory {

	public Animal getAnimal2() {
		System.out.println("调用AnimalFactory类的实例工厂方法getAnimal2().................");
		return new Pig();
	}
	
}

4.4 使用带参数的工厂方法

接上例,我们希望通过AnimalFactory这个工厂类的一个带参数的静态方法getAnimal3(int type)来创建spring管理的实例。

<bean id="animal" class="com.qdu.bean.AnimalFactory" factory-method="getAnimal3">

	<constructor-arg name="type" value="2" />

</bean>

constructor-arg在构造函数注入的时候是构造函数参数的名称,但是对于工厂方法,就是指定一个普通方法参数的信息(如这里传入的参数是type,传入的值为2)

AnimalFactory类:

public class AnimalFactory {

	private static Map<Integer,Animal> map;
	
	static {
		map=new HashMap<>();
		map.put(1,new Dog());
		map.put(2,new Pig());
		map.put(3,new Sheep());
	}
	
	public static Animal getAnimal3(int type) {
		System.out.println("调用AnimalFactory类的静态工厂方法getAnimal3().................");
		return map.get(type);
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1287142.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【隐私计算】安全三方计算(3PC)的加法和乘法计算协议

ABY3中采用replicated secret sharing&#xff08;复制秘密分享&#xff09;机制&#xff0c;即2-out-of-3秘密分享&#xff0c;三个参与方的每一方都拥有share中的两份。下面来看一下这样做有什么好处。 2-out-of-3秘密分享 有 x , y x, y x,y两个操作数&#xff0c;先进行秘…

rcssci包横空出世,限制性立方样条全自动切点靓图

z致敬前辈:R语言统计与绘图 仅以本篇2800字真文一并纪念工作11年来潦倒的收入、间歇的鸡血、憋屈的倔强、幽暗的过往和心中的远方。 1 缘起 Restricted cubic splines (RCS)近年来火遍各类SCI期刊&#xff0c;初次接触的小伙伴们可以去搜索笔者前期的2篇RCS文章补充一下基础知…

Android wifi disable分析

总体流程 老套路基本不变&#xff1a; WifiSettings 通过 WifiManager 下cmd 给 WifiServiceWifiService 收到cmd后&#xff0c;先完成一部分列行检查&#xff08;如UID的权限、是否airPlayMode等等&#xff09;&#xff0c;之后将cmd下发给到WifiControllerWifiController 收…

ORACLE数据库实验总集 实验三 Oracle数据库物理存储结构管理

一、实验目的 &#xff08;1&#xff09;掌握 Oracle数据库数据文件的管理 &#xff08;2&#xff09;掌握 Oracle数据库控制文件的管理 &#xff08;3&#xff09;掌握 Oracle数据库重做日志文件的管理 &#xff08;4&#xff09;掌握 Oracle数据库归档管理&#xff0c; 二、…

周周爱学习之Redis重点总结

redis重点总结 在正常的业务流程中&#xff0c;用户发送请求&#xff0c;然后到缓存中查询数据。如果缓存中不存在数据的话&#xff0c;就会去数据库查询数据。数据库中有的话&#xff0c;就会更新缓存然后返回数据&#xff0c;数据库中也没有的话就会给用户返回一个空。 1.缓…

springboot整合阿里云oss上传图片,解决无法预览的问题

1.前置工作 需要申请一个域名,需要备案&#xff0c;对接这个踩了不少坑,写的很详细,guan fang tong guo bu 了,各位参考别的博客结合看吧,主要是域名配置,还有看service里面的实现 2.进入控制台 bucket列表 选择bucket 选择域名管理 复制你申请的域名,比如域名:abkhkajs…

苹果iOS免签应用打包,书签类顶部域名如何隐藏?

在iOS开发中&#xff0c;由于App Store的严格审核流程和各种政策限制&#xff0c;免签打包成为一些企业和开发人员选择的方案&#xff0c;以便更灵活地分发iOS应用。在这个过程中&#xff0c;许多开发者希望隐藏或最小化安装过程中顶部域名的显示&#xff0c;以提供更加原生的用…

家用洗地机哪个品牌最好最实用?热门洗地机测评

随着社会的不断进步&#xff0c;我们逐渐意识到日常生活中的许多任务需要消耗大量的时间和体力。一个典型的例子是卫生清洁工作&#xff0c;尤其是在大面积地区&#xff0c;如大型建筑物、商场或工厂。这些任务不仅繁琐&#xff0c;还可能影响生活质量和工作效率。为了应对这一…

Hadoop学习笔记(HDP)-Part.06 安装OracleJDK

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

给腰不好的朋友设计和制作一个捡羽毛球的辅助装置

我们知道羽毛球运动不挑场地&#xff08;有防风塑料球和业余使用的网子可以购买&#xff09;&#xff0c;不需要专业器材和场地&#xff0c;不需要跑多远就可以开展&#xff0c;非常方便普通人锻炼。 而且针对现在的不良生活方式&#xff1a;久坐&#xff0c;看电脑手机&#…

unity 2d入门飞翔小鸟按钮点击功能且场景切换(二)

1、素材包获取 链接: https://pan.baidu.com/s/1KgCtQ_7wt2mlbGbIaMVvmw 提取码: xxh8 2、将素材全部拉进去 3、创建新的场景 并且将场景添加到build settings里面 4、脚本 using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityE…

JOSEF 单相电压继电器 WY-31A1 DC220V 过压动作,导轨安装

系列型号 单相 JY-45A1电压继电器&#xff1b;JY-45B1电压继电器&#xff1b; JY-45C1电压继电器&#xff1b;JY-45D1电压继电器&#xff1b; JY-41A1电压继电器&#xff1b;JY-41B1电压继电器&#xff1b; JY-41C1电压继电器&#xff1b;JY-41D1电压继电器&#xff1b; …

计算机毕业设计 基于SpringBoot的大学生双创竞赛项目申报与路演管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

深入探讨Guava的缓存机制

第1章&#xff1a;引言 大家好&#xff0c;我是小黑&#xff0c;今天咱们聊聊Google Guava的缓存机制。缓存在现代编程中的作用非常大&#xff0c;它能提高应用性能&#xff0c;减少数据库压力&#xff0c;简直就是性能优化的利器。而Guava提供的缓存功能&#xff0c;不仅强大…

【开源】基于JAVA的城市桥梁道路管理系统

项目编号&#xff1a; S 025 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S025&#xff0c;文末获取源码。} 项目编号&#xff1a;S025&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示四、核心代码4.1 查询城市桥…

数据结构(超详细讲解!!)第二十六节 图(中)

1.存储结构 1.邻接矩阵 图的邻接矩阵表示法&#xff08;Adjacency Matrix&#xff09;也称作数组表示法。它采用两个数组来表示图&#xff1a; 一个是用于存储顶点信息的一维数组&#xff1b;另一个是用于存储图中顶点之间关联关系的二维数组&#xff0c;这个关联关系数组被…

基于ROPNet项目训练modelnet40数据集进行3d点云的配置

项目地址&#xff1a; https://github.com/zhulf0804/ROPNet 在 MVP Registration Challenge (ICCV Workshop 2021)&#xff08;ICCV Workshop 2021&#xff09;中获得了第二名。项目可以在win10环境下运行。 论文地址&#xff1a; https://arxiv.org/abs/2107.02583 网络简介…

vue2项目中添加字体文件

vue2项目中添加字体文件 1、下载相关文件&#xff0c;放置文件夹中&#xff0c;这里我是在assets文件中新建了fontFamily 2、在assets文件中新建css文件 3、在页面中使用 <style lang"less" scoped> import ../../assets/css/fonts.less;.total-wrap {displa…

深度学习火车票识别系统 计算机竞赛

文章目录 0 前言1 课题意义课题难点&#xff1a; 2 实现方法2.1 图像预处理2.2 字符分割2.3 字符识别部分实现代码 3 实现效果4 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 图像识别 火车票识别系统 该项目较为新颖&#xff0c;适…

详细介绍如何使用 SSD 进行实时物体检测:单次 MultiBox 探测器-含源码

介绍 在实时对象检测中,主流范例传统上采用多步骤方法,包括边界框、像素或特征重采样以及高质量分类器应用的提议。虽然这种方法已经实现了高精度,但其计算需求往往阻碍了其对实时应用的适用性。然而,单次多框检测器 (SSD) 代表了基于深度学习的对象检测的突破性飞跃。SSD…