Spring:Bean(创建方式,抽象继承,工厂Bean,生命周期)

news2024/12/27 11:33:00

1,Bean的创建

1.1,调用构造器创建Bean

调用Bean类的无参构造函数来创造对象,因此要求提供无参构造函数。在这种情况下class元素是必须的,值就是Bean对象的实现类。

  • 如果采用设值注入,Spring容器将使用默认的构造器来创建Bean实例,Spring对Bean实例的所有属性进行默认初始化,即所有基本数据类型的值初始化为0或false;所有引用类型的值初始化为null。接下来BeanFactory会根据配置文件决定依赖关系,先实例化被依赖的Bean实例,然后为Bean注入依赖关系,最后将一个完整的Bean实例返回给程序。
  • 如果采用构造注入,则要求配置文件为<bean.../>元素添加<constructor-arg.../>子元素,每个<constructor-arg.../>子元素配置一个构造器参数。Spring容器将使用带对应的构造器来创建Bean实例,Spring调用构造器传入的参数即可用于初始化Bean的实例变量,最后也将一个完整的Bean实例返回给程序。

1.2,使用静态工厂方法创建Bean

【问题】如何在Spring中不再使用Spring创建Bean实例,而是把Bean创建过程转移到开发者手中?

  • 使用静态工厂方法创建Bean实例时,class属性也必须指定,但此时class属性并不是指定Bean实例的实现类,而是静态工厂类。因为Spring需要知道是用哪个工厂来创建Bean实例。
  • 另外,还需要使用factory-method来指定静态工厂方法名,Spring将调用静态工厂方法(可能包含一组参数),来返回一个Bean实例,一旦获得了指定Bean实例,Spring后面的处理步骤与采用普通方法创建Bean实例则完全一样。需要注意的是,当使用静态工厂方法来创建Bean时,这个factory-method必须要是静态的。

<bean.../>元素需要指定如下两个属性:

  • class:该属性的值为静态工厂类的类名。
  • factory-method:该属性指定静态工厂方法来生产Bean实例。
public interface Being {
    public void testBeing();
}
------------------------------
public class Cat implements Being {
    private String msg;
    public void setMsg(String msg) {
        this.msg = msg;
    }
    public void testBeing() {
        System.out.println(msg + ",猫爱吃鱼");
    }
}
-------------------------------
public class Dog implements Being {
    private String msg;
    public void setMsg(String msg) {
        this.msg = msg;
    }
    public void testBeing() {
        System.out.println(msg + ",狗爱吃骨头。");
    }
}
package Bean;

public class BeingFactory {
    public static Being getBeing(String arg){
        if (arg.equals("dog")){
            return new Dog();
        }else{
            return new Cat();
        }
    }
}

静态工厂类,该类的getBeing()方法是一个静态工厂方法,该方法根据传入的参数决定返回Cat对象,还是Dog对象。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="dog" class="Bean.BeingFactory" factory-method="getBeing">
        <constructor-arg value="dog"/>
        <property name="msg" value="我是dog"/>
    </bean>
    <bean id="cat" class="Bean.BeingFactory" factory-method="getBeing">
        <constructor-arg value="cat"/>
        <property name="msg" value="我是cat"/>
    </bean>
</beans>

cat和dog两个Bean配置的class属性和factory-method属性完全相同——这是因为这两个实例都是由同一个静态工厂类、同一个静态工厂方法生产得到。配置这两个Bean实例时指定的静态工厂方法的参数值不同,配置工厂方法的参数值使用<contructor-arg.../>元素。

一旦<bean.../>元素指定了factory-method属性,Spring就不再调用构造器来创建Bean实例,而是调用工厂方法来创建Bean实例。如果同时指定了class和factory-method两个属性,Spring就会调用静态工厂方法来创建Bean。

使用静态工厂方法创建实例时必须提供工厂类和产生实例的静态工厂方法。通过静态工厂方法创建实例时需要对Spring配置文件做如下改变:

  • class属性不在是Bean实例的实现类,而是生成Bean实例的静态工厂类。
  • 使用factory-method指定生产Bean实例的静态工厂方法。
  • 如果静态工厂方法需要参数,使用<constructor-arg />元素为其配置。

当我们指定Spring使用静态工厂方法来创建Bean实例时,Spring将先解析配置文件,并根据配置文件指定的信息,通过反射调用静态工厂类的静态工厂方法,并将该静态工厂方法的返回值作为Bean实例。在这个过程中,Spring不再负责创建Bean实例,Bean实例是由用户提供的静态工厂方法提供的。

当静态工厂方法创建了Bean实例后,Spring依然可以管理该Bean实例的依赖关系,包括为其注入所需要的依赖Bean、管理其生命周期等。

1.3,调用示例工厂方法创建Bean

实例工厂方法与静态工厂方法只有一点不同:调用静态工厂方法只需要使用工厂类即可,调用实例工厂方法则必须使用工厂实例。所以在Spring配置上也只有一点区别:配置静态工厂方法使用class指定静态工厂类,配置实例工厂方法则使用factory-bean指定工厂实例。

实例工厂和静态工厂的区别:

  • 配置实例工厂方法创建Bean,必须将实例工厂配置成Bean实例;而配置静态工厂方法创建Bean,则无须配置工厂Bean。
  • 配置实例工厂方法创建Bean,必须使用factory-bean属性确定工厂Bean;而配置静态工厂方法创建Bean,则使用class元素确定静态工厂类。

实例工厂和静态工厂的相同:

  • 都需要使用factory-method属性指定产生Bean实例的工程方法。
  • 工厂方法如果需要参数,都使用<constructor-arg.../>元素指定参数值。
  • 普通的设值注入,都使用<property.../>元素确定参数值。

使用实例工厂方法时,配置Bean实例的<bean.../>元素无须class属性,因为Spring容器不再直接实例化该Bean,Spring容器仅仅调用实例工厂的工厂方法,工厂方法负责创建Bean实例。

采用实例工厂方法创建Bean的<bean…/>元素时需要指定如下属性:

  • factory-bean:工厂Bean的id。
  • factory-method:实例工厂的工厂方法。
public interface Person {
    public String sayHello(String name);
    public String sayGoodBye(String name);
}
----------------------------------
public class American implements Person{
    public String sayHello(String name) {
        return name+",Hellow";
    }

    public String sayGoodBye(String name) {
        return name+",Good Bye";
    }
}
----------------------------------
public class Chinese implements Person {

    public String sayHello(String name) {
        return name + ",您好";
    }
    public String sayGoodBye(String name) {
        return name + ",下次再见";
    }
}
public class PersonFactory {
    public Person getPerson(String ethnic){
        if (ethnic.equalsIgnoreCase("chin")){
            return new Chinese();
        }else{
            return new American();
        }
    }
}

PersonFactory是负责产生Person对象的实例工厂,该工厂类里提供了一个getPerson()方法,该方法根据传入的ethnic参数决定产生哪种Person对象。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="personFactory" class="Bean.PersonFactory"/>
    <bean id="chinese" factory-bean="personFactory" factory-method="getPerson">
        <constructor-arg value="chin"/>
    </bean>
    <bean id="american" factory-bean="personFactory" factory-method="getPerson">
        <constructor-arg value="ame"/>
    </bean>
</beans>

2,深入理解容器中的Bean

2.1,抽象&继承

在实际开发中,可能出现:随着项目越来越大,Spring配置文件中出现了多个<bean.../>配置具有大致相同的配置信息,只有少量信息不同,这将导致配置文件出现很多重复的内容。这样可能导致:配置文件臃肿、后期难以修改维护。

【抽象Bean】为了解决上面的问题,可以考虑把多个<bean.../>配置中相同的信息提取出来,集中成配置模板——这个配置模板并不是这种的Bean,因此Spring不应该创建该配置模板,于是需要为该<bean.../>配置增加abstruct="true"。

抽象Bean不能被实例化,Spring容器不会创建抽象Bean实例(不能通过getBean()显示地获得抽象Bean实例)。抽象Bean的价值在于被继承,抽象Bean通常作为父Bean被继承。抽象Bean只是配置信息面板,指定abstruct=“阻止”Spring实例化该Bean,因此抽象Bean可以不指定class属性。继承仅仅是指配置上的继承,并不意味着这两个bean之间存在继承关系。继承bean配置仅仅是为了复用其配置信息。

将大部分相同的信息配置成抽象Bean之后,将实际的Bean实例配置成该抽象Bean的子Bean即可。子Bean定义可以从父Bean继承实现类、构造参数、属性值等配置信息,除此之外,子Bean配置可以增加新的配置信息,并可指定新的配置信息覆盖父Bean的定义子 Bean 也可以覆盖从父 Bean 继承过来的配置)。并不是 <bean> 元素里的所有属性都会被继承. 比如: autowire、abstract、singleton、scope、lazy-init、depends-on 等,这些属性总是从子Bean定义中获得,或采用默认值。

<bean id="personTemplate" abstract="true">
    <property name="name" value="ysy"/>
    <property name="axe" value="steelAxe"/>
</bean>
<bean id="chinese" class="Bean.Chinese" parent="personTemplate"/>

如果父Bean(抽象Bean)指定了class属性:那么子Bean连class属性都可以省略,子Bean将采用与父Bean相同的实现类。除此之外,子Bean也可覆盖父Bean的配置信息:当子Bean拥有和父Bean相同配置时,子Bean的配置信息取胜。

Bean继承于Java继承的区别:

  • Spring中的子Bean与父Bean可以是不同类型,但Java中的继承则保证子类是一种特殊的父类
  • Spring中的继承是参数值的延续;Java中的继承是类之间的方法和属性的延续
  • Spring中的子Bean不可作为父Bean使用,不具备多态性;Java中的子类对象完全可作为父类对象使用

2.2,工厂Bean(FactoryBean)

一般情况下,Spring通过反射机制利用<bean>的class属性指定实现类实例化Bean,在某些情况下,实例化Bean过程比较复杂,如果按照传统的方式,则需要在<bean>中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。

此处的工厂Bean,与前面介绍的实例工厂方法创建Bean,或者静态工厂方法创建Bean的工厂有所区别:前面那些工厂是标准的工厂模式,Spring只是负责调用工厂方法来创建Bean实例;此处的工厂Bean是Spring一种特殊Bean,这种工厂必须实现FactoryBean接口。

FactoryBean接口是工厂Bean的标准接口,把工厂Bean(实现FactoryBean接口的Bean)部署在容器中之后,如果程序通过getBean()方法来获取它时,容器返回的不是FactoryBean实现类的实例,而是返回FactoryBean的产品(即该工厂Bean的getObject()方法的返回值,getObject()代理了getBean()方法)。

FactoryBean接口提供的相关方法:

  • T getObject()返回由FactoryBean创建的Bean实例,如果isSingleton()返回true,则该实例会放到Spring容器中单实例缓存池中;
  • Boolean isSingleton()返回由FactoryBean创建的Bean实例的作用域是singleton还是prototype;
  • Class<T>getObjectType()返回FactoryBean创建的Bean类型。

实现FactoryBean接口的最大作用在于:Spring容器并不是简单地返回该Bean的实例,而是返回该Bean实例的getObject()方法的返回值,而getObject()方法则由开发者负责实现。

import org.springframework.beans.factory.FactoryBean;
import org.springframework.expression.spel.ast.Projection;
import java.lang.reflect.Field;

public class GetFieldFactoryBean implements FactoryBean<Object> {
    private String targetClass;
    private String targetField;
    public void setTargetClass(String targetClass) {
        this.targetClass = targetClass;
    }

    public void setTargetField(String targetField) {
        this.targetField = targetField;
    }

    public Object getObject() throws Exception {
        Class<?> clazz = Class.forName(targetClass);
        Field field = clazz.getField(targetField);
        return field.get(null);
    }

    public Class<?> getObjectType() {
        return Projection.class;
    }

    public boolean isSingleton() {
        return false;    //不需要单例模式
    }
}

GetFieldFactoryBean是一个标准的工厂Bean,该工厂Bean的关键代码在于所实现的getObject()方法,该方法的执行体使用反射先获取targetClass对应的Class对象,再获取targetField对应的类变量的值。GetFieldFactoryBean的targetClass、targetField都提供了setter方法,因此可以接受Spring的设值注入,这样即可让GetFieldFactoryBean获取指定类的、指定静态Field的值。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="theValue" class="Bean.GetFieldFactoryBean">
        <property name="targetClass" value="java.sql.ResultSet"/>
        <property name="targetField" value="TYPE_SCROLL_SENSITIVE"/>
    </bean>
</beans>

从上面的程序可以看出,部署工厂Bean与部署普通Bean其实没有任何区别,同样只需为该Bean配置id、class两个属性即可,但Spring对FactoryBean接口的实现类的处理有所不同。

Spring会自动检测容器中的所有Bean,如果发现某个Bean实现类实现了FactoryBean接口,Spring容器就会在实例化该Bean、根据<property.../>执行setter方法之后,额外调用该Bean的getObject()方法,并将该方法的返回值作为容器中的Bean。

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println(context.getBean("theValue"));
System.out.println(context.getBean("&theValue"));
========================================================
1005
Bean.GetFieldFactoryBean@185d8b6

FactoryBean是一个接口,当在IOC容器中的Bean实现了FactoryBean后,通过getBean(String BeanName)获取到的Bean对象并不是FactoryBean的实现类对象,而是这个实现类中的getObject()方法返回的对象。要想获取FactoryBean的实现类,就要getBean(&BeanName),在BeanName之前加上&。

2.3,强制初始化Bean

在大多数情况下,Bean之间的依赖非常直接,Spring容器在返回Bean实例之前,先要完成Bean依赖关系的注入。假如Bean A依赖于Bean B,程序请求Bean A时,Spring容器会自动初始化Bean B,再将Bean B注入Bean A,最后将具备完整依赖的Bean A返回给程序。

在极端的情况下,Bean之间的依赖不够直接。比如,某个类的初始化块中使用其他Bean,Spring总是先初始化主调Bean,当执行初始化块时,被依赖Bean可能还没实例化,此时将引发异常。

为了显式指定被依赖Bean在目标Bean之前初始化,可以使用depends-on属性,该属性可以在初始化主调Bean之前,强制初始化一个或多个Bean。

<bean id="person" class="Bean.Person" depends-on="manager"></bean>

3,Bean的生命周期管理

Spring可以管理singleton作用域的Bean的生命周期,Spring可以精确地知道该Bean何时被创建何时被初始化完成、容器何时准备销毁该Bean实例。

  • 对于prototype作用域的Bean:Spring容器仅仅负责创建,当容器创建了Bean实例之后,Bean实例完全交给客户端代码管理,容器不再跟踪其生命周期。每次客户端请求prototype作用域的Bean时,Spring都会产生一个新的实例,Spring容器无法知道它曾经创建了多少个prototype作用域的Bean,也无从知道这些propertype作用域的Bean什么时候才会被销毁。因此,Spring无法管理propertype作用域的Bean。
  • 对于singleton作用域的Bean:每次客户端代码请求时都返回同一个共享实例,客户端代码不能控制Bean的销毁Spring容器负责跟踪Bean实例的产生、销毁。Spring容器可以在创建Bean之后,进行某些通用资源申请;还可以在销毁Bean实例之前,先回收某些资源,比如数据库连接。Spring知道Bean何时结束、何时销毁,Spring可以管理实例化结束之后和销毁之前的行为。

3.1,Bean的生命周期

生命周期代码描述
实例化

ApplicationContext

scope = singleton

实例化一个 Bean,也就是我们常说的 new。
设置属性setXXX按照 Spring 上下文对实例化的 Bean 进行配置,也就是 IOC 注入。
执行setBeanName方法

(可选)bean类继承BeanNameAware接口

(可选)bean类继承了BeanFactoryAware接口

如果这个 Bean 已经实现了 BeanNameAware 接口,会调用它实现的setBeanName(String) 方法,此处传递的就是 Spring 配置文件中 Bean 的 id 值
执行ApplicationContext方法(可选)bean类继承了ApplicationContextAware接口如果这个 Bean 已经实现了 ApplicationContextAware 接口,会调用 setApplicationContext(ApplicationContext)方法,传入 Spring 上下文(同样这个方式也 可以实现步骤 4 的内容,但比 4 更好,因为 ApplicationContext 是 BeanFactory 的子接 口,有更多的实现方法)
执行postProcessBeforeInitialization方法(可选)bean类继承了BeanPostProcessor接口如果这个 Bean 关联了 BeanPostProcessor 接口,将会调用 postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor 经常被用 作是 Bean 内容的更改,并且由于这个是在 Bean 初始化结束时调用那个的方法,也可以被应 用于内存或缓存技术。
执行afterPropertiesSet方法(可选)bean类继承了InitializingBean接口
执行自定义bean初始化方法(可选)bean中<bean init-method="init"/>如果 Bean 在 Spring 配置文件中配置了 init-method 属性会自动调用其配置的初始化方法。
执行postProcessAfterInitialization方法(可选)bean类继承了BeanPostProcessor接口如果这个 Bean 关联了 BeanPostProcessor 接口,将会调用 postProcessAfterInitialization(Object obj, String s)方法。 注:以上工作完成以后就可以应用这个 Bean 了,那这个 Bean 是一个 Singleton 的,所以一 般情况下我们调用同一个 id 的 Bean 会是在内容地址相同的实例,当然在 Spring 配置文件中 也可以配置非 Singleton。
使用我们的bean
执行destory(可选)bean类实现了DisposableBean接口当 Bean 不再需要时,会经过清理阶段,如果 Bean 实现了 DisposableBean 这个接口,会调 用那个其实现的 destroy()方法;
调用指定的销毁方法<bean destory-method="fun1"/>最后,如果这个 Bean 的 Spring 配置中配置了 destroy-method 属性,会自动调用其配置的 销毁方法。

3.2,依赖关系注入之后的行为

spring就是一个大的bean容器,管理各种不同的bean及之间的关系,在初始化bean的时候,spring也预留一些接口类,方便spring加载管理bean之前或之后额外处理bean的其它业务。

Spring IOC 容器对 Bean 的生命周期进行管理的过程:

  • 通过构造器或工厂方法创建 Bean 实例
  • 为 Bean 的属性设置值和对其他 Bean 的引用
  • 注入依赖关系之后,调用 Bean 的初始化方法
  • Bean 可以使用了
  • 当容器关闭时,调用 Bean 的销毁方法

Spring提供两种方式在Bean全部属性设置成功后执行特定行为:

  • 实现InitializingBean接口:也可达到同样的效果,就是让Bean类实现InitializingBean接口,该接口提供一个方法void afterPorpertiesSet() throws Exception;Spring会在为该Bean注入依赖关系之后,调用该Bean所实现的afterPropertiesSet()方法。
import org.springframework.beans.factory.InitializingBean;

public class Person implements InitializingBean {
    public void afterPropertiesSet() throws Exception {
        System.out.println("正在执行初始化方法!");
    }
}
  • 使用init-method属性:使用init-method属性指定某个方法应在Bean全部依赖关系设置结束后自动执行。使用这种方式不需要将代码与Spring的接口耦合再一起,代码污染小。
public class Person {
    public void init() throws Exception {
        System.out.println("正在执行初始化方法!");
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="person" class="Bean.Person" init-method="init"></bean>
</beans>

对于实现InitializingBean接口的Bean,无须使用init-method属性来指定初始化方法,配置该Bean实例与配置普通Bean实例完全一样,Spring容器会自动监测Bean实例是否实现了特定生命周期接口,并由此决定是否需要执行生命周期的方法。

如果即采用init-method属性指定初始化方法,又实现InitializingBean接口来指定初始化方法,Spring容器会执行两个初始化方法:先执行InitializingBean接口中定义的方法,然后执行init-method属性指定的方法。

3.3,当容器关闭时的行为

  • 实现DisposableBean接口:让Bean实现DisposableBean接口,该接口提供一个方法:void destroy() throws Exception,该方法就是Bean实例销毁之前应该执行的方法。
import org.springframework.beans.factory.DisposableBean;
public class Person implements DisposableBean {
    public void destroy() throws Exception {
        System.out.println("已被摧毁!");
    }
}

配置该Bean与配置普通Bean没有任何区别,Spring可以自动检测容器中的DisposableBean,在销毁Bean实例之前,Spring会自动调用该Bean实例的destroy()方法。 

【问题】singleton作用域的Bean通常会随着容器的关闭而销毁,但是:ApplicationContext容器在什么时候关闭呢?在基于Web的ApplicationContext实现中,系统已经提供了响应的代码保证关闭Web应用时恰当的关闭Spring容器。如果是非Web应用的环境下,为了让Spring容器优雅地关闭,并调用singletonBean上的响应析构回调方法,则需要在JVM里注册一个关闭钩子(shutdown hook),这样就可保证Spring容器被恰当关闭,且自动执行singleton Bean实例的析构回调函数。

为了注册关闭钩子,只需要调用在AbstractApplicationContext中提供的registerShutdownHook()方法即可。

AbstractApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println(context.getBean("person"));
context.registerShutdownHook();
  • 使用destroy-method属性:指定某个方法在Bean销毁之前被自动执行。使用这种方式,不需要将代码与Spring的接口耦合在一起,代码污染小。

除此之外,如果容器中的很多Bean都需要指定特定的生命周期行为,则可以利用<bean.../>元素的default-init-method属性和default-destroy-method属性,这两个属性的作用类似于<bean.../>元素的init-method和destroy-method属性的作用,区别是default-init-method属性和default-destroy-method属性是属于<bean.../>元素的,它们将使容器中的所有Bean生效。

3.4,协调作用域不同步的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时,得到的永远是最初那个protype Bean——这样就相当于singleton Bean把它所依赖的prototype Bean变成了singleton行为。

由于singleton Bean具有单例行为,当客户端多次请求singleton Bean时,Spring返回给客户端的将是同一个singleton Bean实例,这不存在任何问题。问题是:如果客户端通过该singleton Bean去调用prototype Bean的方法时——始终都是调用同一个prototype Bean实例,这就违背了设置prototype Bean的初衷——本来希望它具有prototype行为,但实际上它却表现出singleton行为。

通常情况下,使用方法注入,使用lookup方法注入,使用lookup方法注入可以让Spring容器重写容器中Bean的抽象或具体方法,返回查找容器中其他Bean的结果,被查找的Bean通常是一个non-singleton Bean。Spring通过使用JDK动态代理或cglib库修改客户端的二进制码,从而实现上述要求。

假设程序中有一个Chinese类型的Bean,该Bean包含一个hunt()方法,执行该方法时需要依赖于Dog的方法——而程序每次执行hunt()方法时都使用不同的Dog Bean,因此首先需要将Dog Bean配置为prototpy作用域。

除此之外,不能直接使用普通依赖注入,将Dog Bean注入Chinese Bean中,还需要使用lookup方法注入来管理Dog Bean与Chinese Bean之间的依赖关系。

  • 将调用者Bean的实现定义为抽象类,并定义一个抽象方法来获取被依赖的Bean。
  • 在<bean.../>元素中添加<lookup-method.../>子元素让Spring为调用者Bean的实现类实现指定的抽象方法。
public interface Person {
    public void hunt();
}
-----------------------------
public abstract class Chinese implements Person{
    public abstract Dog getDog();
    public void hunt() {
        System.out.println("我带着:" + getDog() + "出去打猎");
        System.out.println(getDog().run());
    }
}
-----------------------------
public class Dog {
    private String name;

    public void setName(String name) {
        this.name = name;
    }
    public String run(){
        return this.toString()+name+"迅速奔跑";
    }
}

<lookup-method .../>告诉Spring需要实现那个抽象方法。Spring为抽象方法提供实体之后,这个方法就会变成具体方法,这个类也就变成了具体类,接下来Spring就可以创建该Bean的实例了。

使用<lookup-method.../>元素需要指定如下两个属性:

  • name:指定需要让Spring实现的方法。
  • bean:指定Spring实现该方法的返回值。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="chinese" class="Bean.Chinese">
        <lookup-method name="getDog" bean="Dog"/>
    </bean>
    <bean id="Dog" class="Bean.Dog" scope="prototype">
        <property name="name" value="旭旭狗狗"/>
    </bean>
</beans>

通常情况下,Java类里的所有方法都应该由程序员来负责实现,系统无法为任何方法提供实现。但在有些情况下,系统可以实现一些极其简单的方法,例如,此处Spring将负责实现getDog()方法,Spring实现该方法的逻辑是固定的,它总是采用传统的getBean()方法来实现。

-----------插曲,不是程序代码-------------
public Dog getDog(){
    //获取Spring容器
    return context.getBean("Dog");
}
import Bean.Person;
import org.springframework.beans.BeansException;
import org.springframework.context.support.*;
public class Test {
    public static void main(String[] args) throws BeansException  {
        AbstractApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Person person1 = (Person) context.getBean("chinese");
        Person person2 = (Person) context.getBean("chinese");
        System.out.println(person1 == person2);
        person1.hunt();
        person2.hunt();
    }
}
==============================================
true
我带着:Bean.Dog@1fc2b765出去打猎
Bean.Dog@75881071旭旭狗狗迅速奔跑
我带着:Bean.Dog@2a70a3d8出去打猎
Bean.Dog@289d1c02旭旭狗狗迅速奔跑

执行结果表明:使用lookup方法注入后,系统每次调用getDog()方法时都将生成一个新的gunDog实例,这就可以保证当singleton作用域的Bean需要prototype Bean实例时,直接调用getDog()方法即可获取全新的实例,从而避免一直使用最早注入的Bean实例。

要保证lookup方法注入每次产生新的Bean实例,必须将目标Bean部署成prototype作用域;否则,如果容器中只有一个被依赖的Bean实例,即使采用lookup方法注入,每次也依然返回同一个Bean实例。

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

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

相关文章

ViT面试知识点

文章目录 VITCLIPBlipSAMLSegFast TransformerYOLO系列问题 BatchNorm是对一个batch-size样本内的每个特征做归一化&#xff0c;LayerNorm是对每个样本的所有特征做归一化。 Layer Normalization&#xff08;层归一化&#xff0c;简称LayerNorm&#xff09;是一种在深度学习中…

了解数据库并发产生的问题

在数据库管理系统中&#xff0c;并发控制是一个至关重要的方面。随着多个用户或进程同时访问和修改数据库中的数据&#xff0c;如果没有适当的并发控制机制&#xff0c;就可能导致数据不一致、丢失更新、脏读、不可重复读和幻读等问题。在单用户系统中&#xff0c;数据库操作是…

qt QFontDialog详解

1、概述 QFontDialog 是 Qt 框架中的一个对话框类&#xff0c;用于选择字体。它提供了一个可视化的界面&#xff0c;允许用户选择所需的字体以及相关的属性&#xff0c;如字体样式、大小、粗细等。用户可以通过对话框中的选项进行选择&#xff0c;并实时预览所选字体的效果。Q…

【JavaSE】(2) 方法

一、认识方法 1. 方法的定义 修饰符 返回类型 方法名(形参类型 形参名, ......){......return 返回值; } 示例代码&#xff1a; 2. 方法的作用 增强代码的可复用性。&#xff08;避免重复造轮子&#xff09;增强代码的易管理性。&#xff08;改方法就行&#xff0c;不用到处…

享元模式及其运用场景:结合工厂模式和单例模式优化内存使用

介绍 享元模式&#xff08;Flyweight Pattern&#xff09;是一种结构型设计模式&#xff0c;它通过共享对象来减少内存使用&#xff0c;尤其是对于大量相似对象的场景。享元模式通常与工厂模式和单例模式结合使用&#xff0c;从而有效地控制和复用对象的创建。在享元模式中&am…

【RabbitMQ】03-交换机

1. 交换机 2. Fanout交换机 广播。生产者向exchange发消息 SpringBootTest public class SpringAmqpTest {Autowiredpublic RabbitTemplate rabbitTemplate;Testvoid testSimple() {String exchangName "hmall.fabout";rabbitTemplate.convertAndSend(exchangName…

【赵渝强老师】安装部署Memcached

Memcached是一个高性能的分布式的内存对象缓存系统。通过使用Memcached可以支持高负载的网站系统&#xff0c;以分担数据库的压力。Memcached通过在内存里维护一个统一的巨大的Hash表来存储各种格式的数据&#xff0c;包括图像、视频、文件以及数据库检索的结果等。但是Memcach…

代码要走的路:编程“三部曲”

代码要成为可以运行的程序&#xff0c;总共有3步&#xff1a; 1&#xff0e;编辑&#xff08;edit&#xff09; 这里的编辑不是像出版编辑那样&#xff0c;只把现成的东西修修改改&#xff0c;而是指编写代码。 编写代码是实实在在的原创&#xff0c;不是整理加工&#xff0…

支持向量机相关证明 解的稀疏性

主要涉及拉格朗日乘子法&#xff0c;对偶问题求解

漫途焊机安全生产监管方案,提升安全生产管理水平!

随着智能制造时代的到来&#xff0c;企业安全生产管理的重要性日益凸显。特别是在现代工厂中&#xff0c;焊机的安全生产监管成为了一个不容忽视的重要环节。传统的焊机安全生产监管方式存在诸多不足&#xff0c;如人工巡检频率低、数据延迟、安全隐患发现不及时等问题。因此&a…

【dvwa靶场:XSS系列】XSS (Reflected)低-中-高级别,通关啦

一、低级low 简单拿捏 <script>alert(123)</script>二、中级middle 源码过滤了script但是没有过滤大小写&#xff0c;改成大写S <Script>alert(123)</script>三、高级high 比中级高&#xff0c;过滤了script并且以及大小写&#xff0c;使用其他标…

太速科技-634-基于3U PXIe的VU3P FMC+数据接口板

基于3U PXIe的VU3P FMC数据接口板 一、产品概述 板卡是一款基于 3U PXIE 总线架构的高性能数据预处理FMC 载板&#xff0c;具有 1 个 FMC&#xff08;HPC&#xff09;接口&#xff0c;1 个 X8 GTH 背板互联接口&#xff0c;可以实现 1 路 PCIe x8。板卡主控芯片采用Xilin…

【LLM Agents体验】Dify框架的安装指南

Dify简介&#xff1a; 核心功能‌12 ‌Dify是一款开源的大语言模型(LLM)应用开发平台&#xff0c;融合了后端即服务&#xff08;Backend as a Service, BaaS&#xff09;和LLMOps的理念&#xff0c;使开发者可以快速搭建生产级的生成式AI应用。LLMOps涵盖了大型语言模型的开发、…

推荐一款PowerPoint转Flash工具:iSpring Suite

iSpring Suite是一款PowerPoint转Flash工具&#xff0c;使用iSpring Suite 8可以轻松的将PPT演示文档转换为对Web友好的Flash影片格式。软件界面简洁&#xff0c;使用方便。为什么要转换成flash格式呢?Flash格式的最大特点是体积小巧、易于分发&#xff0c;兼容所有的操作系统…

数据库->视图

目录 一、视图 1.什么是视图 ​编辑 2.创建视图 1.语法 3.使用视图 4.视图的功能 1.屏蔽相关字段 2.对外提供统一访问规范 3.视图和真实表进行表连接查询 5.修改数据 6.注意事项 7.删除视图 1.语法 8.视图的优点 1. 简单性 2. 安全性 3. 逻辑数据独⽴性 4. 重…

影响神经网络速度的因素- FLOPs、MAC、并行度以及计算平台

影响神经网络速度的四个主要因素分别是 FLOPs&#xff08;浮点操作数&#xff09;、MAC&#xff08;内存访问成本&#xff09;、并行度以及计算平台。这些因素共同作用&#xff0c;直接影响到神经网络的计算速度和资源需求。 1. FLOPs&#xff08;Floating Point Operations&a…

Java Development Kit (JDK) 详解

什么是 JDK&#xff1f; JDK 是 Java Development Kit 的缩写&#xff0c;是一组用于开发 Java 应用程序的软件开发工具和库的集合。JDK 包含了 Java 运行时环境&#xff08;JRE&#xff09;和 Java 虚拟机&#xff08;JVM&#xff09;&#xff0c;以及一系列开发工具和库。 …

Rust 力扣 - 1652. 拆炸弹

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 我们只需要遍历长度长度为k的窗口&#xff0c;然后把窗口内数字之和填充到结果数组中的对应位置即可 题解代码 impl Solution {pub fn decrypt(code: Vec<i32>, k: i32) -> Vec<i32> {let n c…

AAA 数据库事务隔离级别及死锁

目录 一、事务的四大特性&#xff08;ACID&#xff09; 1. 原子性(atomicity)&#xff1a; 2. 一致性(consistency)&#xff1a; 3. 隔离性(isolation)&#xff1a; 4. 持久性(durability)&#xff1a; 二、死锁的产生及解决方法 三、事务的四种隔离级别 0 .封锁协议 …

华为HarmonyOS借助AR引擎帮助应用实现虚拟与现实交互的能力3-获取设备位姿

设备位姿描述了物体在真实世界中的位置和朝向。AR Engine提供了世界坐标下6自由度&#xff08;6DoF&#xff09;的位姿计算&#xff0c;包括物体的位置&#xff08;沿x、y、z轴方向位移&#xff09;和朝向&#xff08;绕x、y、z轴旋转&#xff09;。通过AR Engine&#xff0c;您…