关于spring中单例Bean引用原型Bean产生的问题及解决

目录
  • spring单例Bean引用原型Bean产生的问题及解决
    • 问题描述
    • 为了更直观的发现问题,下面我们用代码演示一遍
    • 问题分析
  • spring Bean的几个相关问题
    • 1.Spring Bean 作用域
    • 2.什么是Spring inner beans
    • 3.什么是有状态、无状态
    • 4.Spring框架中的单例Bean是线程安全的么
    • 5.Spring Bean 的自动装配
    • 6.各种自动装配模式的区别
    • 7.在Spring中可以注入null或空字符串吗
    • 8.Spring框架中有哪些不同类型的事件(都继承自ApplicationContextEvent)
    • 9.Spring框架中都用到了哪些设计模型
    • 10.FileSystemResource、 ClassPathResource 有何区别
    • 11.使用Spring框架的好处是什么
    • 12.Spring5 新特性

spring单例Bean引用原型Bean产生的问题及解决

问题描述

spring里Bean都有一个作用域,用注解@Scope表示,容器默认使用的是单例,即“singleton”顾名思义就是指容器每次只为我们创建一次Bean,以后使用都是拿的同一个。

虽然平时开发的时候基本都是使用单例的,但不免有时候会使用到另一种类型,即原型模式,这时候就会产生一个问题:当我们单例Bean注入了原型Bean,并且调用了原型Bean的某个方法,我们希望的是单例Bean只初始化一次,而原型Bean每次调用应该都是重新生成的,然而,结果确与我们想的并不一样,无论是单例还是原型都只会初始化一次。

为了更直观的发现问题,下面我们用代码演示一遍

首先新建一个SpringConfig配置类:

@Configuration
@ComponentScan("com.whb")
public class AppConfig {
}

这个只是一个配置类,并且指定了容器扫描“com.whb”下面的类。

接着我们在com.whb下新建2个类:

@Component("dao")
@Scope("prototype")
public class IndexDao {
}
@Component
public  class IndexService  {
    @Autowired
    IndexDao indexDao;
    public void test() {
        System.out.println("service:"+this.hashCode());
        System.out.println("dao:"+indexDao.hashCode());
    }
}

IndexDao 指定原型模式,而IndexService则是默认的单例模式,我们往IndexSerevice里注入IndexDao,并且在test方法里打印出2个类的哈希值,来验证是否一致。

新建一个Test来测试

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext an = new AnnotationConfigApplicationContext(AppConfig.class);
        IndexService indexService = an.getBean(IndexService.class);
        indexService.test();
        IndexService indexService1 = an.getBean(IndexService.class);
        indexService1.test();
        IndexService indexService2 = an.getBean(IndexService.class);
        indexService2.test();
    }
}

这里我们直接从容器中取3次IndexService ,同时调用3次test()方法,结果如下:

问题来了,IndexService是单例哈希值不变可以理解,那为什么设置了原型的IndexDao也不变呢?

问题分析

其实仔细想一下,这问题很好理解。当我们第一次从容器中拿IndexService时,他会为我们创建一个实例,创建过程中发现IndexService还依赖着另一个属性,那么此时容器便会先去生成IndexDao的实例并且注入到IndexService中,然后创建出IndexService实例并且返回。

此时容器中有着一个IndexService和一个IndexDao。当我们第二次去拿IndexService时,容器发现已经有了一个实例,并且IndexService是单例的所有它直接就返回了那个存在着的IndexService实例。虽然IndexDao设置了原型,但由于IndexService只有一次机会设置属性所以从到尾容器并没有生成第二个IndexDao实例。这也就解释了为什么哈希值每次都是一样的。

真实情况下我们肯定不希望是这个结果,不然的话我们设置原型还有个毛线作用。

其实spring官方文档给出了2中解决方案,点击我查看文档说明

下面我直接贴出代码演示一遍:

方法一:实现ApplicationContextAware

我们来修改下IndexService的内容:

public  class IndexService implements ApplicationContextAware{
    private ApplicationContext applicationContext;
    public void print() {
        System.out.println("service:"+this.hashCode());
        System.out.println("dao:"+applicationContext.getBean("dao").hashCode());
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

我们通过实现ApplicationContextAware这个接口并重写setApplicationContext()这个方法可以拿到ApplicationContext对象,从而用这个对象获取容器中的Bean.

结果如下:

可以看到service还是每次都一样,符合单例设置,但是dao确每次都改变了,说明原型设置也生效了。

上面虽然解决了这个问题,但是这种方法还是有一定的弊端:

ApplicationContextAware是spring提供给我们的接口,IndexService是我们业务的类,我们直接实现可以说是增大了和spring框架的耦合程度。因此spring还提供了第二种方法:

方法二:使用@LookUp注解

我们继续修改IndexService的内容:

@Component
public  abstract class IndexService {
    public void print() {
        System.out.println("service:"+this.hashCode());
        System.out.println("dao:"+getIndexDao().hashCode());
    }
    @Lookup("dao")
    public abstract IndexDao getIndexDao();
}

可以看到使用这种方式代码简洁了很多,我们只需要声明一个抽象方法,并在该方法上面添加@Lookup(“dao”)注解,“dao”表示要注入的类名,spring容器就会自动帮我们注入IndexDao实例。

结果如下:

总结:在spring开发中可能会遇到各种各样的问题,但其实最好的解决办法就是查阅文档,毕竟这可是第一手资料!!

spring Bean的几个相关问题

1.Spring Bean 作用域

Spring 容器中的 bean 可以分为 5 个范围(scope配置项)。

1)、singleton(单例模式) :这个模式是默认的,使用该属性定义Bean时,IOC容器仅创建一个Bean实例,IOC容器每次返回的是同一个Bean实例。

2)、prototype(原型模式) :使用该属性定义Bean时,IOC容器可以创建多个Bean实例,每次返回的都是一个新的实例。

3)、request(HTTP请求) :该属性仅对HTTP请求产生作用,每次HTTP请求都会创建一个新的Bean,在请求完成以后,bean 会失效并被垃圾回收器回收。

4)、Session(会话) :该属性仅用于HTTP Session,同一个Session共享一个Bean实例。不同Session使用不同的实例。在 session 过期后,bean 会随之失效。

5)、global-session(全局会话) :该属性仅用于HTTP Session,所有的Session共享一个Bean实例。

2.什么是Spring inner beans

将这一个bean声明为另一个Bean的内部bean。内部bean可以用setter注入“属性”和构造方法注入“构造参数”的方式来实现。无论何时此内部bean被使用,仅仅作为被作为一个属性被调用。

3.什么是有状态、无状态

  • 单例:某个类系统范围内只有一个实例
  • 多例:某个类在系统范围内同时有多个实例
  • 无状态类:类中没有状态信息,一般是无成员变量或成员变量的值是不变的。
  • 有状态类:类中有状态信息,一般表现成员变量的值可变,在某一时该被调用而改变状态,之后再调用时获取其正确的状态。

4.Spring框架中的单例Bean是线程安全的么

单例模式确保某一个类只有一个实例,当多用户同时请求一个服务时,容器会给每一个请求分配一个线程,这是多个线程会并发执行该请求多对应的成员方法,如果这个单例是无状态的,那么就是线程安全的,如果这个单例是有状态的就不是线程安全的。

解决有状态单例线程安全的措施: 对于有状态的单例可以实现全局共享,状态的修改最好加锁,保证线程的安全性。

5.Spring Bean 的自动装配

1)、 首先用@Component注解类

@Component
Class public Student{

2)、 在启动类上添加@ComponentScan注解的类, spring才能自动装配bean

@ComponentScan
Class public Application{

3)、 开启默认扫描,spring将扫描由@Component注解的类,并且创建个实力

6.各种自动装配模式的区别

1)、 no: 这是 Spring 框架的默认设置,在缺省情况下,自动配置是通过“ref”属性手动设定。

2)、byName: 可以根据 bean 名称设置依赖关系。当向一个 bean 中自动装配一个属性时,容器将根据 bean 的名称自动在在配置文件中查询一个匹配的 bean。

3)、byType: 可以根据 bean 类型设置依赖关系。当向一个 bean 中自动装配一个属性时,容器将根据 bean 的类型自动在在配置文件中查询一个匹配的 bean。

4)、constructor: 在构造函数参数的byType方式。**5)、autodetect:**如果找到默认的构造函数,使用“自动装配用构造”; 否则,使用“按类型自动装配”(在Spring3.0以后的版本此模式已被废弃,已经不再合法了)。

7.在Spring中可以注入null或空字符串吗

没问题

8.Spring框架中有哪些不同类型的事件(都继承自ApplicationContextEvent)

1)上下文更新事件(ContextRefreshedEvent): 当ApplicationContext初始化或刷新完成后触发的事件。

2)上下文开始事件(ContextStartedEvent): 当ApplicationContext启动后触发的事件

3)上下文停止事件(ContextStoppedEvent): 当ApplicationContext停止后触发的事件。

4)上下文关闭事件(ContextClosedEvent): 当ApplicationContext被关闭时触发该事件。当容器被关闭时,其管理的所有单例Bean都被销毁。

5)请求处理事件(RequestHandledEvent): 在Web应用中,当一个http请求(request)结束触发该事件。

9.Spring框架中都用到了哪些设计模型

1)代理模式: AOP就是基于动态代理的,把公共的代码抽象出来,封装到一个模块中用于代理,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

2)单例模式: 在spring配置文件中定义的bean默认为单例模式。

使用单例模式的好处:

(1)对于频繁使用的对象,可以省略创建对象所花费的时间,减少系统的开销;

(2)减少new操作的次数,系统内存使用率就会降低,这将减轻GC压力,缩短GC停顿时间。

3)模板方法: 它定义了一系列操作的模型,子类继承之后可以在模型 不变的情况下去实现自定义的操作。它可以用来解决代码重复的问题。 Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。

4)适配器模式: 将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作。Spring AOP 的实现是基于代理模式,但是 Spring AOP 的增强或通知(Advice)使用到了适配器模式,与之相关的接口是AdvisorAdapter。Advice 常用的类型有:BeforeAdvice(目标方法调用前,前置通知)、AfterAdvice(目标方法调用后,后置通知)、AfterReturningAdvice(目标方法执行结束后,return之前)。

5)装饰器模式: 装饰者模式可以动态地给对象添加一些额外的属性或行为。当我们需要修改原有的功能,但我们又不愿直接去修改原有的代码时,设计一个Decorator套在原有代码外面。对于InputStream,ileInputStream 、BufferedInputStream都是对InputStream功能的扩展。

6)观察者模式: 它表示的是一种对象与对象之间具有依赖关系,当一个对象发生改变的时候,这个对象所依赖的对象也会做出反应。Spring 事件驱动模型就是观察者模式很经典的一个应用。

7)工厂模式: BeanFactory用来创建对象的实例。

8)委派模式: Spring 提供了 DispatcherServlet 来对请求进行分发。

10.FileSystemResource、 ClassPathResource 有何区别

ClassPathResource 在环境变量中读取配置文件,FileSystemResource 在配置文件中读取配置文件。

11.使用Spring框架的好处是什么

1)轻量: Spring 是轻量的。

2)控制反转: 在Spring中对象有 IOC容器创建,并且通过配置注入到配置变量中。Spring通过控制反转实现了低耦合。

3)面向切面的编程(AOP): Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开。

4)容器(IOC): IOC可以创建、管理应用中对象、对象生命周期和对象之间的关系。**5)MVC框架:**提供MVC框架,将控制逻辑代码、数据存储、试图展示分层。

6)事务管理: Spring 提供一个持续的事务管理接口,封装事物操作代码。

7)异常处理: Spring可以全局捕捉异常。

12.Spring5 新特性

1) 支持JDK 8+和Java EE7+以上版本

2) 运行时兼容JDK9

3) 运行时兼容Java EE8 API

4) 反应式编程模型

5) 使用注解进行编程

6) 函数式编程

7) 提供专门的 HTTP/2 特性支持

8) 使用 Spring WebFlux 执行集成测试

9) 使用 JUnit 5 执行条件和并发测试

10) 支持Kotlin

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • spring中向一个单例bean中注入非单例bean的方法详解

    目录 前言 错误实例演示 实现ApplicationContextAware接口 lookup method lookup method签名 总结 前言 看到这个题目相信很多小伙伴都是懵懵的,平时我们的做法大都是下面的操作 @Component public class People{ @Autowired private Man man; } 这里如果Man是单例的,这种写法是没有问题的,但如果Man是原型的,这样是否会存在问题. 错误实例演示 这里有一个原型(生命周期为prototype)的

  • Spring中Bean的单例和多例使用说明

    目录 Bean的单例和多例使用 实战演示 Spring单例bean与原型bean区别和创建过程 singletonScope与prototypeScope Bean的单例和多例使用 在Spring中,bean可以被定义为两种模式:prototype(多例)和singleton(单例) singleton(单例):只有一个共享的实例存在,所有对这个bean的请求都会返回这个唯一的实例. prototype(多例):对这个bean的每次请求都会创建一个新的bean实例,类似于new. Spring

  • Spring如何解决单例bean线程不安全的问题

    首先我们应该知道线程安全问题一般发生在成员变量上,这是为什么啦? 因为成员变量是存放在堆内存中,而堆内存又是线程共享的,这就造成了线程安全问题 因为Spring中的Bean默认是单例的,所以在定义成员变量时也有可能会发生线程安全问题.下面我们就来研究下如何解决Spring中单例Bean的线程安全问题 @RestController //@Scope("prototype") public class BeanController { private int content=0; //基

  • 浅谈Spring单例Bean与单例模式的区别

    Spring单例Bean与单例模式的区别在于它们关联的环境不一样,单例模式是指在一个JVM进程中仅有一个实例,而Spring单例是指一个Spring Bean容器(ApplicationContext)中仅有一个实例. 首先看单例模式,在一个JVM进程中(理论上,一个运行的JAVA程序就必定有自己一个独立的JVM)仅有一个实例,于是无论在程序中的何处获取实例,始终都返回同一个对象,以Java内置的Runtime为例(现在枚举是单例模式的最佳实践),无论何时何处获取,下面的判断始终为真: // 基

  • 浅谈Spring中单例Bean是线程安全的吗

    Spring容器中的Bean是否线程安全,容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但是具体还是要结合具体scope的Bean去研究. Spring 的 bean 作用域(scope)类型 1.singleton:单例,默认作用域. 2.prototype:原型,每次创建一个新对象. 3.request:请求,每次Http请求创建一个新对象,适用于WebApplicationContext环境下. 4.session:会话,同一个会

  • Spring bean为什么默认是单例

    熟悉Spring开发的朋友都知道Spring提供了5种scope分别是singleton.prototype.request.session.global session. 如下图是官方文档上的截图,感兴趣的朋友可以进去看看这五种分别有什么不同.今天要介绍的是这五种中的前两种,也是Spring最初提供的bean scope singleton 和 prototype. Spring官方文档介绍如下图: 更多内容可以看官方文档介绍,非常详细: https://docs.spring.io/spri

  • Spring bean配置单例或多例模式方式

    目录 Spring bean配置单例或多例模式 单例 多例 Spring scope配置单例.多例模式 1.scope属性介绍 2.scope配置 3.单例模式底层实现模拟 Spring bean配置单例或多例模式 单例 spring bean 默认是单例默认,在对应.xml文件中的配置是: <bean id="user" class="..." scope="singleton"/> singleton就是配置这个bean是否是单例

  • 关于spring中单例Bean引用原型Bean产生的问题及解决

    目录 spring单例Bean引用原型Bean产生的问题及解决 问题描述 为了更直观的发现问题,下面我们用代码演示一遍 问题分析 spring Bean的几个相关问题 1.Spring Bean 作用域 2.什么是Spring inner beans 3.什么是有状态.无状态 4.Spring框架中的单例Bean是线程安全的么 5.Spring Bean 的自动装配 6.各种自动装配模式的区别 7.在Spring中可以注入null或空字符串吗 8.Spring框架中有哪些不同类型的事件(都继承自

  • Spring中单例和多例的深入理解

    Spring单例和多例的理解 1.什么是单例和多例 单例:所有请求用同一个对象来处理.通过单例模式,可以保证系统中一个类只有一个实例. 多例:每个请求用一个新的对象来处理. 2.Spring中的单例与多例 spring ioc容器的bean都是默认单例的,即spring依赖注入Bean实例默认是单例的. spring提供了5中scope,分别是singleton,prototype,request,session,global session,常用是前两种.点此查看官网介绍. 单例bean与多例

  • spring在IoC容器中装配Bean详解

    1.Spring配置概述 1.1.概述 Spring容器从xml配置.java注解.spring注解中读取bean配置信息,形成bean定义注册表: 根据bean定义注册表实例化bean: 将bean实例放入bean缓存池: 应用程序使用bean. 1.2.基于xml的配置 (1)xml文件概述 xmlns------默认命名空间 xmlns:xsi-------标准命名空间,用于指定自定义命名空间的schema文件 xmlns:xxx="aaaaa"-------自定义命名空间,xx

  • Spring Boot中单例类实现对象的注入方式

    Spring Boot 单例类实现对象的注入 1.最近接手了一个项目 项目用的是SpringBoot,但其中有个类用的是单例,为了不改变单例,且还需要引入Spring管理的Bean对象 2.对于一个单例类按照平时的注解方式添加 启动时会报空指针异常,因为static类对象是创建对象后,内存中还没有注入Bean信息,且无法初始化Bean实例,这里的解决办法是利用@PostConstruct来对单例类中对象的注入 @Component public class MesssageHandle impl

  • 详解Spring系列之@ComponentScan批量注册bean

    目录 回顾 本文内容 @ComponentScan基本原理和使用 基本原理 使用案例 定义配置类 容器扫描和使用 @ComponentScan进阶使用 源码简析 案例1:使用Filters过滤 案例2:使用自定义的bean名称生成策略 案例3:自定义bean的作用域策略 @Componet及其衍生注解使用 使用元注解和组合注解 总结 回顾 在前面的章节,我们介绍了@Comfiguration和@Bean结合AnnotationConfigApplicationContext零xml配置文件使用S

  • 详解Spring 中如何控制2个bean中的初始化顺序

    开发过程中有这样一个场景,2个 bean 初始化逻辑中有依赖关系,需要控制二者的初始化顺序.实现方式可以有多种,本文结合目前对 Spring 的理解,尝试列出几种思路. 场景 假设A,B两个 bean 都需要在初始化的时候从本地磁盘读取文件,其中B加载的文件,依赖A中加载的全局配置文件中配置的路径,所以需要A先于B初始化,此外A中的配置改变后也需要触发B的重新加载逻辑,所以A,B需要注入彼此. 对于下面的模型,问题简化为:我们需要initA()先于initB()得到执行. @Service pu

  • 详解Spring Boot 使用Java代码创建Bean并注册到Spring中

    从 Spring3.0 开始,增加了一种新的途经来配置Bean Definition,这就是通过 Java Code 配置 Bean Definition. 与Xml和Annotation两种配置方式不同点在于: 前两种Xml和Annotation的配置方式为预定义方式,即开发人员通过 XML 文件或者 Annotation 预定义配置 bean 的各种属性后,启动 spring 容器,Spring 容器会首先解析这些配置属性,生成对应都?Bean Definition,装入到 DefaultL

  • Java 如何从spring容器中获取注入的bean对象

    1.使用场景 控制层调用业务层时,控制层需要拿到业务层在spring容器中注入的对象 2.代码实现 import org.apache.struts2.ServletActionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.suppo

随机推荐