代码编织梦想

05-单例bean中使用多例bean(三种方式)

通常情况下,我们使用的bean都是单例的,如果一个bean需要依赖于另一个bean的时候,可以在当前bean中声明另外一个bean引用,然后注入依赖的bean,此时被依赖的bean在当前bean中自始至终都是同一个实例。

先来个案例回顾一下

package com.javacode2018.lesson001.demo13.normal;

public class ServiceA {
}
package com.javacode2018.lesson001.demo13.normal;

public class ServiceB {

    private ServiceA serviceA;

    public ServiceA getServiceA() {
        return serviceA;
    }

    public void setServiceA(ServiceA serviceA) {
        this.serviceA = serviceA;
    }
}

上面2个类,ServiceA和ServiceB,而ServiceB中需要用到ServiceA,可以通过setServiceA将serviceA注入到ServiceB中,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-4.3.xsd">

    <bean id="serviceA" class="com.javacode2018.lesson001.demo13.normal.ServiceA" scope="prototype"/>

    <bean id="serviceB" class="com.javacode2018.lesson001.demo13.normal.ServiceB">
        <property name="serviceA" ref="serviceA"/>
    </bean>

</beans>

上面serviceA的scope是prototype,表示serviceA是多例的,每次从容器中获取serviceA都会返回一个新的对象。

而serviceB的scope没有配置,默认是单例的,通过property元素将serviceA注入。

来个测试案例,如下:

package com.javacode2018.lesson001.demo13;


import com.javacode2018.lesson001.demo13.normal.ServiceA;
import com.javacode2018.lesson001.demo13.normal.ServiceB;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class LookupMethodTest {

    @Test
    public void normalBean() {
        String beanXml = "classpath:/com/javacode2018/lesson001/demo13/normalBean.xml";
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(beanXml);

        System.out.println(context.getBean(ServiceA.class)); //@1
        System.out.println(context.getBean(ServiceA.class)); //@2

        System.out.println("serviceB中的serviceA");
        ServiceB serviceB = context.getBean(ServiceB.class); //@3
        System.out.println(serviceB.getServiceA()); //@4
        System.out.println(serviceB.getServiceA()); //@5
    }
}

@1和@2从容器中按照类型查找ServiceA对应的bean。

@3:从容器中获取ServiceB

@4和@5:获取serviceB中的serviceA对象

运行normalBean()看一下效果:

com.javacode2018.lesson001.demo13.normal.ServiceA@5bfa9431
com.javacode2018.lesson001.demo13.normal.ServiceA@5db250b4
serviceB中的serviceA
com.javacode2018.lesson001.demo13.normal.ServiceA@223f3642
com.javacode2018.lesson001.demo13.normal.ServiceA@223f3642

从输出中可以看出,@1和@2输出了不同的ServiceA,而@4和@5输出的是同一个serviceA,这是因为serviceB是单例的,serviceB中的serviceA会在容器创建serviceB的时候,从容器中获取一个serviceA将其注入到serviceB中,所以自始至终serviceB中的serviceA都是同一个对象。

如果我们希望beanB中每次使用beanA的时候beanA都是一个新的实例,我们怎么实现呢?

我们可以在serviceB中加个方法去获取serviceA,这个方法中我们主动去容器中获取serviceA,那么每次获取到的都是不同的serviceA实例。

那么问题来了,我们如何在serviceB中获取到spring容器呢?

spring中有个接口ApplicationContextAware

org.springframework.context.ApplicationContextAware

public interface ApplicationContextAware extends Aware {
    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

上面这个接口有一个方法setApplicationContext,这个接口给了自定义的bean中获取applicationContext的能力,当我们的类实现这个接口之后,spring容器创建bean对象的时候,如果bean实现了这个接口,那么容器会自动调用setApplicationContext方法,将容器对象applicationContext传入,此时在我们的bean对象中就可以使用容器的任何方法了。

下面我们就通过ApplicationContextAware接口来实现单例bean中使用多例bean的案例。

①单例bean中使用多例bean:ApplicationContext接口的方式

ServiceA.java

package com.javacode2018.lesson001.demo13.applicationcontextaware;

public class ServiceA {
}

ServiceB.java

package com.javacode2018.lesson001.demo13.applicationcontextaware;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class ServiceB implements ApplicationContextAware { //@1

    public void say(){
        ServiceA serviceA = this.getServiceA();//@2
        System.out.println("this:"+this+",serviceA:"+ serviceA);
    }

    public ServiceA getServiceA() {
        return this.context.getBean(ServiceA.class);//@3
    }

    private ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }
}

注意上面代码,ServiceB实现了ApplicationContextAware接口,然后实现了这个接口中的setApplicationContext方法,spring容器在创建ServiceB的时候会自动调用setApplicationContext方法。

@3:从容器中主动去获取ServiceA,这样每次获取到的ServiceA都是一个新的实例。

@2:say方法中调用getServiceA方法获取ServiceA对象,然后将其输出。

alicationcontextaware.xml

<?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-4.3.xsd">

    <bean id="serviceA" class="com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceA" scope="prototype"/>

    <bean id="serviceB" class="com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceB"/>

</beans>

上面定义了2个bean,第一个是多例的。

测试用例

@Test
public void alicationcontextaware() {
    String beanXml = "classpath:/com/javacode2018/lesson001/demo13/alicationcontextaware.xml";
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(beanXml);

    System.out.println(context.getBean(com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceA.class)); //@1
    System.out.println(context.getBean(com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceA.class)); //@2

    System.out.println("serviceB中的serviceA");
    com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceB serviceB = context.getBean(com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceB.class); //@3
    serviceB.say();
    serviceB.say();
}

运行输出:

com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceA@78047b92
com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceA@8909f18
serviceB中的serviceA
this:com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceB@79ca92b9,serviceA:com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceA@1460a8c0
this:com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceB@79ca92b9,serviceA:com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceA@4f638935

最后2行是是serviceB中的say方法输出的,可以看出serviceB是一个对象,而serviceA是不同的对象。

②单例bean中使用多例bean:lookup-method方式实现

上面这种方式实现了单例bean中使用多例bean的需求,但是用到spring中的接口ApplicationContextAware,此时对spring的api有耦合的作用,我们一直推行高内聚低耦合,所以我们得寻求更好的办法。

能不能有这样的功能,当serviceB中调用getServiceA的时候,系统自动将这个方法拦截,然后去spring容器中查找对应的serviceA对象然后返回,spring中的lookup-method就可以实现这样的功能。

下面我们使用lookup-method来实现一下。

ServiceA.java

package com.javacode2018.lesson001.demo13.lookupmethod;

public class ServiceA {
}

ServiceB.java

package com.javacode2018.lesson001.demo13.lookupmethod;

public class ServiceB {

    public void say() {
        ServiceA serviceA = this.getServiceA();
        System.out.println("this:" + this + ",serviceA:" + serviceA);
    }

    public ServiceA getServiceA() { //@1
        return null;
    }

}

注意上面的@1,这个方法中返回了一个null对象,下面我们通过spring来创建上面2个bean对象,然后让spring对上面的getServiceA方法进行拦截,返回指定的bean,如下:

lookupmethod.xml

<?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-4.3.xsd">

    <bean id="serviceA" class="com.javacode2018.lesson001.demo13.lookupmethod.ServiceA" scope="prototype"/>

    <bean id="serviceB" class="com.javacode2018.lesson001.demo13.lookupmethod.ServiceB">
        <lookup-method name="getServiceA" bean="serviceA"/>
    </bean>

</beans>

注意上面的配置,重点在于这行配置:

<lookup-method name="getServiceA" bean="serviceA"/>

当我们调用serviceB中的getServiceA方法的时候,这个方法会拦截,然后会按照lookup-method元素中bean属性的值作为bean的名称去容器中查找对应bean,然后作为getServiceA的返回值返回,即调用getServiceA方法的时候,会从spring容器中查找id为serviceA的bean然后返回。

测试用例

LookupMethodTest中加个方法,如下:

@Test
public void lookupmethod() {
    String beanXml = "classpath:/com/javacode2018/lesson001/demo13/lookupmethod.xml";
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(beanXml);

    System.out.println(context.getBean(com.javacode2018.lesson001.demo13.lookupmethod.ServiceA.class)); //@1
    System.out.println(context.getBean(com.javacode2018.lesson001.demo13.lookupmethod.ServiceA.class)); //@2

    System.out.println("serviceB中的serviceA");
    com.javacode2018.lesson001.demo13.lookupmethod.ServiceB serviceB = context.getBean(com.javacode2018.lesson001.demo13.lookupmethod.ServiceB.class); //@3
    serviceB.say();
    serviceB.say();
}

运行看看效果:

com.javacode2018.lesson001.demo13.lookupmethod.ServiceA@619713e5
com.javacode2018.lesson001.demo13.lookupmethod.ServiceA@708f5957
serviceB中的serviceA
this:com.javacode2018.lesson001.demo13.lookupmethod.ServiceB$$EnhancerBySpringCGLIB$$aca8be5a@68999068,serviceA:com.javacode2018.lesson001.demo13.lookupmethod.ServiceA@7722c3c3
this:com.javacode2018.lesson001.demo13.lookupmethod.ServiceB$$EnhancerBySpringCGLIB$$aca8be5a@68999068,serviceA:com.javacode2018.lesson001.demo13.lookupmethod.ServiceA@2ef3eef9

注意最后2行的输出,serviceA是调用this.getServiceA()方法获取 ,源码中这个方法返回的是null,但是spring内部对这个方法进行了拦截,每次调用这个方法的时候,都会去容器中查找serviceA,然后返回,所以上面最后2行的输出中serviceA是有值的,并且是不同的serviceA实例。

lookup-method:看其名字,就知道意思:方法查找,调用name属性指定的方法的时候,spring会对这个方法进行拦截,然后去容器中查找lookup-method元素中bean属性指定的bean,然后将找到的bean作为方法的返回值返回。

这个地方底层是使用cglib代理实现的,后面有篇文章会详细介绍代理的2种实现,到时候大家注意下,spring中很多牛逼的功能都是靠代理实现的。

spring提供的还有一个功能,同样可以可以解决上面单例bean中用到多例bean的问题,也就是下面我们要说的replaced-method。

③replaced-method:方法替换

replaced-method:方法替换,比如我们要调用serviceB中的getServiceA的时候,我们可以对serviceB这个bean中的getServiceA方法进行拦截,把这个调用请求转发到一个替换者处理。这就是replaced-method可以实现的功能,比lookup-method更强大更灵活。

replaced-method的使用3个步骤

步骤一:定义替换者

自定义一个替换者,替换者需要实现spring中的MethodReplacer接口,看一下这个接口的定义:

package org.springframework.beans.factory.support;

import java.lang.reflect.Method;

public interface MethodReplacer {

    /**
     * @param obj 被替换方法的目标对象
     * @param method 目标对象的方法
     * @param args 方法的参数
     * @return return value for the method
     */
    Object reimplement(Object obj, Method method, Object[] args) throws Throwable;

}

当调用目标对象需要被替换的方法的时候,这个调用请求会被转发到上面的替换者的reimplement方法进行处理。

如:

package com.javacode2018.lesson001.demo14;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.MethodReplacer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.lang.reflect.Method;
import java.util.Map;

/**
 * servieB的方法替换者
 */
public class ServiceBMethodReplacer implements MethodReplacer, ApplicationContextAware {

    @Override
    public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
        return this.context.getBean(ServiceA.class);
    }

    private ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }
}
步骤二:定义替换者bean
<!-- 定义替换者bean -->
<bean id="serviceBMethodReplacer" class="com.javacode2018.lesson001.demo14.ServiceBMethodReplacer" />
步骤二:通过replaced-method元素配置目标bean需要被替换的方法
<bean id="serviceB" class="com.javacode2018.lesson001.demo14.ServiceB">
    <replaced-method name="getServiceA" replacer="serviceAMethodReplacer"/>
</bean>

注意上面的replaced-method元素的2个属性:

name:用于指定当前bean需要被替换的方法

replacer:替换者,即实现了MethodReplacer接口的类对应的bean

上面配置中当调用serviceB的getServiceA的时候,会自动调用serviceAMethodReplacer这个bean中的reimplement方法进行处理。

总结

  1. lookup-method:方法查找,可以对指定的bean的方法进行拦截,然后从容器中查找指定的bean作为被拦截方法的返回值
  2. replaced-method:方法替换,可以实现bean方法替换的效果,整体来说比lookup-method更灵活一些
  3. 单例bean中使用多例bean,本文中列出了3种方式,大家消化一下。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_46674772/article/details/126983083

spring中的scope详解-爱代码爱编程

0.思维导图 1. scope概论 spring中scope是一个非常关键的概念,简单说就是对象在spring容器(IOC容器)中的生命周期,也可以理解为对象在spring容器中的创建方式。 2. scope历史及分类 目前,scope的取值有5种取值: 在Spring 2.0之前,有singleton和p

spring中bean的单例和多例-爱代码爱编程

人工智能,零基础入门!http://www.captainbed.net/inner 在Spring中,bean可以被定义为两种模式:prototype(多例)和singleton(单例) singleton(单例):只有一个共享的实例存在,所有对这个bean的请求都会返回这个唯一的实例。 prototype(多例):对这个bean的每次请

springboot中接口有多个实现时,通过注解去选择合适的实现bean_liulimoyu的博客-爱代码爱编程

根据问题现状,创建好对应的代码格式,如下图: service包下的接口StudentService和它的两个实现类BoyStudentImpl和GirlStudentImpl分别如下图所示: 在controller下要引用这个service,一般是如下图方式去引用: @Autowired StudentService service;

用spring的时候出现了多个bean该怎么确定我想要的那个bean-爱代码爱编程

转载 : Spring中Bean装配的歧义性 本文链接:https://blog.csdn.net/u010502101/article/details/76563699 在spring容器中有多个同类的Bean时,该如何装载呢?例如有一个服务接口A,该接口有3个实现类,在容器中就会生成3个A的实现类的Bean,当对A进行装载时,容器不会判断装载哪

第三章 Spring bean多实例与动态代理笔记-爱代码爱编程

一、Bean的多例作用域 1、多实例bean的初始化 1)多实例bean在每次获取bean的时候都会触发getBean操作,因此每次获取都是创建了一个新的对象返回,不论是多线程获取或者是同一个线程获取两次,都是返回一个新的实例对象。 2)多例模式情况,如果出现循环依赖,会直接报错 org.springframework.beans.factory.

spring bean的单例和多例的使用场景和在单例bean中注入多例-爱代码爱编程

为什么用单例或者多例?何时用? 之所以用单例,是因为没必要每个请求都新建一个对象,这样子既浪费CPU又浪费内存; 当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。 之所以用多例,是为了防止并发问题;即一个请求

Spring注入多例Bean [笔记]-爱代码爱编程

在Spring开发体系中很少用到多例的Bean,所以这个知识点很容易被忘记。 Spring注入多例Bean的俩种方式: import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; impo

@Scope(“prototype“),Spring中的bean设为多例-爱代码爱编程

@Scope(“prototype”) spring中bean的scope属性,有如下5种类型: singleton 表示在spring容器中的单例,通过spring容器获得该bean时总是返回唯一的实例 prototype表示每次获得bean都会生成一个新的对象 request表示在一次http请求内有效(只适用于web应用) session表示在一

多实例Bean-爱代码爱编程

多实例Bean 单例模式Bean 在Spring中,Bean默认是单例的,也就是说当我们向IOC容器中添加一个Bean,多次获取Bean,默认是同一个Bean,我们也可以显示地声明Bean为单例,使用@Scope设置属性为singleton即可 @Configuration public class MyConfiguration { @B

【spring5学习笔记】-----bean的多实例与单实例-爱代码爱编程

bean的多实例与单实例 首先介绍概念,什么是spring中bean的多实例和单实例。 在上面这个例子中,我们两次调用context的getBean()方法得到的两个对象的引用值是相等的。这说明此时spring是单实例的,两次调用方法得到的是同一个对象。spring修改使用多实例方案 // 配置中的scope有两种选择 singleton和prototy

Spring中单例bean注入多例bean的解决方法-爱代码爱编程

1、问题描述 在项目代码的使用过程,单例对象A中需要注入对象B。B对象要求是多例的。我们在对象B上添加注解“@Scope(“prototype”)”,代码运行过程中,发现A中注入的B对象始终是同一个,并没有实现多例的效果。 下面展示一些 内联代码片。 @Componment public class A { @Autowired private B

Spring 注入多例 Bean-爱代码爱编程

如何在单例 bean 中注入多例 Bean ? 目录 配置多例 Bean再来个单例 Bean错误示例方式一 使用 @Lookup方式二 使用 ObjectProvider方式三 使用 ScopedProxyMode@Lookup 的局限性使用 @Lookup 注入使用 ObjectProvider 注入启用 ScopedProxyMode结语 配

Spring系列第14篇:单例bean中使用多例bean,你未必会玩?-爱代码爱编程

lookup-method:方法查找 通常情况下,我们使用的bean都是单例的,如果一个bean需要依赖于另一个bean的时候,可以在当前bean中声明另外一个bean引用,然后注入依赖的bean,此时被依赖的bean在当前bean中自始至终都是同一个实例。 先来个案例回顾一下 package com.javacode2018.lesson001.d

Spring系列之单例bean中使用多例bean-爱代码爱编程

lookup-method:方法查找 通常情况下,我们使用的bean都是单例的,如果一个bean需要依赖于另一个bean的时候,可以在当前bean中声明另外一个bean引用,然后注入依赖的bean,此时被依赖的bean在当前bean中自始至终都是同一个实例。 先来个案例回顾一下 package com.javacode2018.lesson001.d

spring中的bean是单例还是多例?如何保证并发安全?_ldcaws的博客-爱代码爱编程

spring中的controller默认是单例的,不要使用非静态的成员变量,否则会发生并发安全性问题。 @RestController public class ScopeController { private int num = 0; @GetMapping("/testScope") public Object test

spring的bean为什么是单例的?spring可以配置多例对象么什么时候用单例?什么时候用多例?_double_d;的博客-爱代码爱编程

spring可以配置多例对象么 答:可以。spring中bean可以被定义为两个模式:单例和多例 单例:只有一个共享实例的存在,所有对这个bean的请求都会返回这个唯一的实例。不管new多少次。即所有请求都有一个对象来处理 多例:对这个bean的每次请求都会创建一个新的bean实例,类似于new。 什么时候用单例?什么时候用多例? 答:当对象

spring容器中获取 bean 实例的七种方式_java编程中的博客-爱代码爱编程

说明 一、写作原因 首先解释一下写这篇博文的原因,因为在使用spring框架的过程中获取bean是非常常见的操作,但是网上非常的博文大多承自一家之言,因此很多可操作性上并不强,本文是通过自己实战,亲自尝试之后提供的几种方案,供大家一起学习探讨。 二、源码出处 本文提供的测试代码为chapter-1-springmvc-quickstart中的单元测试类B

spring - 单实例bean 和 多实例bean 的不同_爱上云原生的博客-爱代码爱编程

概述 单实例 Bean Spring 的 IoC 容器 默认创建的 Bean 就是 单实例 的,也就是说无论调用多少次,获取的都是同一个对象。 // 定义一个 Bean @Bean public Person perso