Spring Boot 单例模式中依赖注入问题

在日常项目开发中,单例模式可以说是最常用到的设计模式,项目也常常在单例模式中需要使用 Service 逻辑层的方法来实现某些功能。通常可能会使用 @Resource 或者 @Autowired 来自动注入实例,然而这种方法在单例模式中却会出现 NullPointException 的问题。那么本篇就此问题做一下研究。

演示代码地址

问题初探

一般我们的项目是分层开发的,最经典的可能就是下面这种结构:

├── UserDao -- DAO 层,负责和数据源交互,获取数据。
├── UserService -- 服务逻辑层,负责业务逻辑实现。
└── UserController -- 控制层,负责提供与外界交互的接口。

此时需要一个单例对象,此对象需要 UserService 来提供用户服务。代码如下:

@Slf4j
public class UserSingleton {private static volatile UserSingleton INSTANCE;@Resourceprivate UserService userService;public static UserSingleton getInstance() {if (null == INSTANCE) {synchronized (UserSingleton.class) {if (null == INSTANCE) {INSTANCE = new UserSingleton();}}}return INSTANCE;}public String getUser() {if (null == userService) {log.debug("UserSingleton userService is null");return "UserSingleton Exception: userService is null";}return userService.getUser();}
}

然后创建一个 UserController 来调用 UserSingleton.getUser() 方法看看返回数据是什么。

@RestController
public class UserController {@Resourceprivate UserService userService;/*** 正常方式,在 Controller 自动注入 Service。** @return  user info*/@GetMapping("/user")public String getUser(){return userService.getUser();}/*** 使用单例对象中自动注入的 UserService 的方法** @return  UserSingleton Exception: userService is null*/@GetMapping("/user/singleton/ioc")public String getUserFromSingletonForIoc(){return UserSingleton.getInstance().getUser();}
}

 

user-info.png

可以看到,在 UserController 中自动注入 UserService 是可以正常获取到数据的。

 

UserSingleton-exception.png

但是如果使用在单例模式中使用自动注入的话,UserService 是一个空的对象。

所以使用 @Resource 或者 @Autowired 注解的方式在单例中获取 UserService 的对象实例是不行的。如果没有做空值判断,会报 NullPointException 异常。

问题产生原因

之所以在单例模式中无法使用自动依赖注入,是因为单例对象使用 static 标记,INSTANCE 是一个静态对象,而静态对象的加载是要优先于 Spring 容器的。所以在这里无法使用自动依赖注入。

问题解决方法

解决这种问题,其实也很简单,只要不使用自动依赖注入就好了,在 new UserSingleton() 初始化对象的时候,手动实例化 UserService 就可以了嘛。但是这种方法可能会有一个坑,或者说只能在某些情况下可以实现。先看代码:

@Slf4j
public class UserSingleton {private static volatile UserSingleton INSTANCE;@Resourceprivate UserService userService;// 为了和上面自动依赖注入的对象做区分。// 这里加上 ForNew 的后缀代表这是通过 new Object()创建出来的private UserService userServiceForNew;private UserSingleton() {userServiceForNew = new UserServiceImpl();}public static UserSingleton getInstance() {if (null == INSTANCE) {synchronized (UserSingleton.class) {if (null == INSTANCE) {INSTANCE = new UserSingleton();}}}return INSTANCE;}public String getUser() {if (null == userService) {log.debug("UserSingleton userService is null");return "UserSingleton Exception: userService is null";}return userService.getUser();}public String getUserForNew() {if (null == userServiceForNew) {log.debug("UserSingleton userService is null");return "UserSingleton Exception: userService is null";}return userServiceForNew.getUser();}
}

下面是 UserService 的代码。

public interface UserService {/*** 获取用户信息** @return  @link{String}*/String getUser();/*** 获取用户信息,从 DAO 层获取数据** @return*/String getUserForDao();
}@Slf4j
@Service
public class UserServiceImpl implements UserService {@Resourceprivate UserDao userDao;@Overridepublic String getUser() {return "user info";}@Overridepublic String getUserForDao(){if(null == userDao){log.debug("UserServiceImpl Exception: userDao is null");return "UserServiceImpl Exception: userDao is null";}return userDao.select();}
}

创建一个 UserController 调用单例中的方法做下验证。

@RestController
public class UserController {@Resourceprivate UserService userService;// 正常方式,在 Controller 自动注入 Service。@GetMapping("/user")public String getUser(){return userService.getUser();}// 使用单例对象中自动注入的 UserService 的方法// 返回值是: UserSingleton Exception: userService is null@GetMapping("/user/singleton/ioc")public String getUserFromSingletonForIoc(){return UserSingleton.getInstance().getUser();}// 使用单例对象中手动实例化的 UserService 的方法// 返回值是: user info@GetMapping("/user/singleton/new")public String getUserFromSingletonForNew(){return UserSingleton.getInstance().getUserForNew();}// 使用单例对象中手动实例化的 UserService 的方法,在 UserService 中,通过 DAO 获取数据// 返回值是: UserServiceImpl Exception: userDao is null@GetMapping("/user/singleton/new/dao")public String getUserFromSingletonForNewFromDao(){return UserSingleton.getInstance().getUserForNewFromDao();}
}

通过上面的代码,可以发现,通过手动实例化的方式是可以一定程度上解决问题的。但是当 UserService 中也使用自动依赖注入,比如 @Resource private UserDao userDao;,并且单例中使用的方法有用到 userDao 就会发现 userDao 是个空的对象。

也就是说虽然在单例对象中手动实例化了 UserService ,但 UserService 中的 UserDao 却无法自动注入。其原因其实与单例中无法自动注入 UserService 是一样的。所以说这种方法只能一定程度上解决问题。

最终解决方案

我们可以创建一个工具类实现 ApplicationContextAware 接口,用来获取 ApplicationContext 上下文对象,然后通过 ApplicationContext.getBean() 来动态的获取实例。代码如下:

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;/*** Spring 工具类,用来动态获取 bean** @author James* @date 2020/4/28*/
@Component
public class SpringContextUtils implements ApplicationContextAware {private static ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {SpringContextUtils.applicationContext = applicationContext;}/*** 获取 ApplicationContext** @return*/public static ApplicationContext getApplicationContext() {return applicationContext;}public static Object getBean(String name) {return applicationContext.getBean(name);}public static <T> T getBean(Class<T> clazz) {return applicationContext.getBean(clazz);}public static <T> T getBean(String name, Class<T> clazz) {return applicationContext.getBean(name, clazz);}
}

然后改造下我们的单例对象。

@Slf4j
public class UserSingleton {private static volatile UserSingleton INSTANCE;// 加上 ForTool 后缀来和之前两种方式创建的对象作区分。private UserService userServiceForTool;private UserSingleton() {userServiceForTool = SpringContextUtils.getBean(UserService.class);}public static UserSingleton getInstance() {if (null == INSTANCE) {synchronized (UserSingleton.class) {if (null == INSTANCE) {INSTANCE = new UserSingleton();}}}return INSTANCE;}/*** 使用 SpringContextUtils 获取的 UserService 对象,并从 UserDao 中获取数据* @return*/public String getUserForToolFromDao() {if (null == userServiceForTool) {log.debug("UserSingleton userService is null");return "UserSingleton Exception: userService is null";}return userServiceForTool.getUserForDao();}
}

UserController 中进行测试,看一下结果。

@RestController
public class UserController {/*** 使用 SpringContextUtils 获取的的 UserService 的方法,在 UserService 中,通过 DAO 获取数据** @return  user info for dao*/@GetMapping("/user/singleton/tool/dao")public String getUserFromSingletonForToolFromDao(){return UserSingleton.getInstance().getUserForToolFromDao();}
}

访问接口,返回结果是:user info for dao,验证通过。



 

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

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

相关文章

VO,BO,PO,DO,DTO的区别

面对这个图&#xff0c;让我们先从承上启下的DTO开始入手 DTO&#xff08;Data Transfer Object&#xff09;数据传输对象 这个传输通常指的前后端之间的传输 DTO是一个比较特殊的对象&#xff0c;他有两种存在形式&#xff1a; 在后端&#xff0c;他的存在形式是java对象&…

Windows下pip 离线包安装

pip在线安装十分方便&#xff0c;有时候某些服务器并没有直接联网&#xff0c;需要下载好安装包上传到服务器上进行安装&#xff0c;不经常用&#xff0c;还是有点小麻烦的。 安装Python之后&#xff0c;将下载好的安装包包放在Python安装的根目录下使用pip install packagenam…

VMware设置及linux静态ip设置

1. VMWARE虚拟机NAT模式上网设置 1.1. VM虚拟机设置 1.1.1. 虚拟机全局设置 启动虚拟机选择【虚拟网络编辑器】 如果需要管理员权限点【更改设置】&#xff0c;没有提示这忽略这一步 选择NAT模式&#xff0c;更改下面的子网IP&#xff0c;改成你需要的任何一个子网网段&…

问题 L: 超超的中等意思

问题 L: 超超的中等意思 时间限制: 1 Sec 内存限制: 128 MB提交: 366 解决: 27[提交] [状态] [命题人:jsu_admin]题目描述 已知p,q,k和一个难搞得多项式(pxqy)^k。想知道在给定a和b的值下计算多项式展开后x^a*y^b得系数s。输入 多组输入&#xff0c;每组数据一行输入p,q,k,a,…

SELinux 引起的 Docker 启动失败

问题描述 Linux OS 版本 CentOS Linux release 7.2.1511 (Core) 启动Docker service docker start 启动失败信息 原因分析 Error starting daemon: SELinux is not supported with the overlay2 graph driver on this kernel. Either boot into a newer kernel or disabl...nab…

postgresql模糊匹配正则表达式性能问题

postgresql 模糊匹配 目前建议使用like&#xff0c;~~,不建议使用正则表达式&#xff0c; 目前有性能问题https://yq.aliyun.com/articles/405097正则表达式效率比较低下&#xff1a;操作符 ~~ 等效于 LIKE&#xff0c; 而 ~~* 对应 ILIKE。 还有 !~~ 和 !~~* 操作符 分别代表 …

配置MySQL的环境变量

配置MySQL的环境变量 1.现在安装MySQL ——–下载最新版MySQL软件&#xff0c;将MySQL安装到系统目录中&#xff0c;记录当前安装目录&#xff1b; 如安装mysql到D:\wamp\mysql目录下 2.打开win7系统——右击计算机——单击属性-弹出win7系统页面 3.高级系统设置-环境变…

通过mysqldump备份数据库

使用mysqldump命令备份 mysqldump命令的作用是备份MySQL数据库。是将数据库中的数据备份成一个文本文件。表的结构和表中的数据将存储在生成的文本文件中。mysqldump命令的工作原理很简单。它先查出需要备份的表的结构&#xff0c;再在文本文件中生成一个CREATE语句。然后&…

ThinkPHP-保存生成的二维码

通过TP框架引入Qrcode插件&#xff0c;然后调用插件生成二维码&#xff0c;并保存1.引入qrcode插件&#xff1a; 2.功能页面-生成二维码按钮&#xff1a; 3.生成二维码-代码&#xff1a; 4.后台代码-通过vendor方法引入&#xff1a; //下载生成的二维码-引用方法1 pu…

工厂方法 Factory Method

背景&#xff1a;有一个应用框架&#xff0c;它可以向用户显示多个文档。在这个框架中&#xff0c;两个主要的抽象是类Application和Document.这两个类都是抽象的。客户必须通过它们的子类来做与举替应用相关的实现。 分析&#xff1a;因为被实例化的特定Document子类是与特定应…

解析.DBC文件, 读懂CAN通信矩阵,实现车内信号仿真

通常我们拿到某个ECU的通信矩阵数据库文件&#xff0c;.dbc后缀名的文件。 直接使用CANdb Editor打开&#xff0c;可以很直观的读懂信号矩阵的信息&#xff0c;例如下图&#xff1a; 现在要把上图呈现的信号从.dbc文件中解析出来&#xff0c;供实现自动化仿真总线信号使用&…

01-数据库基础

1 数据库系统概述 1.1 数据库的4个基本概念 数据&#xff08;Data&#xff09;:数据库中存储的基本对象数据库&#xff08;Database&#xff09;:长期储存在计算机内、有组织的、可共享的大量数据的集合。数据库管理系统&#xff08;DBMS&#xff09;:用户与操作系统之间的一层…

linux命令——crontab的使用方法

一、crond简介 crond是linux下用来周期性的执行某种任务或等待处理某些事件的一个守护进程&#xff0c;与windows下的计划任务类似&#xff0c;当安装完成操作系统后&#xff0c;默认会安装此服务工具&#xff0c;并且会自动启动crond进程&#xff0c;crond进程每分钟会定期检…

第一个程序 快速编译链接的办法

转载于:https://www.cnblogs.com/ZHONGZHENHUA/p/10223249.html

变量 和 注释

转自&#xff1a;白月黑羽Python3教程&#xff1a;http://www.python3.vip/doc/tutorial/python/0003/ Python语言中&#xff0c;所有的 数据 都被称之为 对象。 每个整数、小数、字符串&#xff0c;还有我们后面要学的 字典、元组、列表 等&#xff0c; 都是对象。 在Python程…

jQuery教程03-jQuery 元素、id、.class和*全选择器

jQuery 基础选择器 jQuery 选择器允许您对 HTML 元素组或单个元素进行操作。 jQuery 选择器基于元素的 id、类、类型、属性、属性值等"查找"&#xff08;或选择&#xff09;HTML 元素。 它基于已经存在的 CSS 选择器&#xff0c;除此之外&#xff0c;它还有一些自定…

前台一键备份数据库+PHP实现方式

一、实现思路 1、单击备份按钮传递参数到后台&#xff0c;ajax实现&#xff1a; function backupDatabase(){var back backupDatabase;$.ajax({url:system_backup.php?dobackupDatabase,type:POST,data:back,dataType:json,beforeSend:function(){intervalwindow.setInterv…

windows+PHP+shell_exec()无法执行的原因

今天使用shell_exec()命令执行wkhtmltopdf.exe 生成pdf文件时&#xff0c;总是无法生成PDF文件&#xff0c;使用var_dump()打印返回值&#xff0c;结果是null。可是检查代码并没有什么问题啊&#xff1f;执行程序的目录是 D:\Program Files\wkhtmltopdf\bin\wkhtmltopdf.exe …

通过wkhtmltopdf导出支持CSS样式的pdf文件

在公司项目中发现有下载pdf文件的功能&#xff0c;但是不能识别CSS样式&#xff0c;导致下载的pdf文件格式显得很别扭&#xff0c;虽然能看但是难看。然后就是百度啊&#xff0c;google啊&#xff0c;最后找到一款能够识别CSS样式的软件—wkhtmltopdf。wkhtmltopdf可以直接把任…

【算法基础笔记】常用的排序算法的时间、空间复杂度,部分排序算法原理

冒泡排序 比较相邻的元素。如果第一个比第二个大&#xff0c;就交换他们两个。对每一对相邻元素做同样的工作&#xff0c;从开始第一对到结尾的最后一对。在这一点&#xff0c;最后的元素应该会是最大的数。针对所有的元素重复以上的步骤&#xff0c;除了最后一个。持续每次对越…