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,一经查实,立即删除!

相关文章

linux(centOS7,mini),python环境的搭建

今天想试一试python在linux下的工作&#xff0c;在vmware中安装了centOS7版本的linux&#xff0c;先前装过一个带GUI的&#xff0c;但是感觉在虚拟机理跑的太慢&#xff0c;干脆直接装一个最精简的mini版&#xff0c;试一下ifconfig&#xff0c;vim啥的全部commend not found。…

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…

mysql维护

1.通过 show engine innodb status命令来查看这些线程的状态&#xff1a; mysql>show engine innodb status\G *************************** 1. row ***************************Type: InnoDBName: #####################################################################…

VMware设置及linux静态ip设置

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

InnoDB内存优化

1&#xff0e;InnoDB缓存机制 InnoDB用一块内存区做IO缓存池&#xff0c;该缓存池不仅用来缓存InnoDB的索引块&#xff0c;而且也用来缓存InnoDB的数据块&#xff0c;这一点与MyISAM不同。 在内部&#xff0c;InnoDB缓存池逻辑上由 free list、flush list和LRU list组成。顾名…

问题 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,…

改善带有 order by子句或group子句SQL的性能

如果通过 show global status 看到 sort_merge_passes 的值很大&#xff0c;可以考虑通过调整参数sort_buffer_size的值来增大排序缓存区&#xff0c;以改善带有 order by子句或group子句SQL的性能。 对于无法通过索引进行连接操作的查询&#xff0c;可以尝试通过增大&#xf…

springboot系列十五、springboot集成PageHelper

一、介绍 项目中经常会遇到分页&#xff0c;PageHelper为我们解决了这个问题。本质上实现了Mybatis的拦截器&#xff0c;作了分页处理。 二、配置PageHelper 1、引入依赖 pagehelper-spring-boot-starter对了pagehelper做了封装&#xff0c;减少 了配置文件&#xff0c;只需要在…

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…

第十一章 Helm-kubernetes的包管理器(上)

Helm - K8s的包管理器 11.1 Why Helm K8s能够很好的组织和编排容器&#xff0c;但它缺少一个更高层次的应用打包工具&#xff0c;Helm就是干这个的。 比如对于一个MySQL服务&#xff0c;K8s需要部署如下对象&#xff1a; &#xff08;1&#xff09;Service&#xff0c;让外界能…

C# - JSON详解

C# - JSON详解 转载于:https://www.cnblogs.com/macT/p/10214396.html

弗尤博客(十一)之搜索博文

在首页中右侧在textbox输入值&#xff0c;单击搜索Botton 跳转到 找一找 页面 其中textbox文本值传递过去并且显示在控件中&#xff0c;搜索结果也一起显示&#xff08;datalist&#xff09;转载于:https://www.cnblogs.com/frankybpx/p/10214409.html

史上最全的前端开发面试题(含详细答案)

本文由我收集网络 自己平时面试的 或者面试别人时的一些前端面试题&#xff0c;初学者阅后也要用心钻研其中的原理&#xff0c;重要知识需要系统学习、透彻学习&#xff0c;形成自己的知识链。万不可投机取巧&#xff0c;切勿临时抱佛脚只求面试侥幸混过关. 知识最重要的是学习…

MySQL之IFNULL()、ISNULL、NULLIF用法

MySQL之IFNULL()、ISNULL、NULLIF用法 IFNULL语法说明 IFNULL(expr1,expr2) 如果 expr1 不是 NULL&#xff0c;IFNULL() 返回 expr1&#xff0c;否则它返回 expr2。 IFNULL()返回一个数字或字符串值&#xff0c;取决于它被使用的上下文环境。 举个栗子&#xff1a; 1 mysql…

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

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

数据库的原理,一篇文章搞定(一)

https://blog.csdn.net/zhangcanyan/article/details/51439012 一提到关系型数据库&#xff0c;我禁不住想&#xff1a;有些东西被忽视了。关系型数据库无处不在&#xff0c;而且种类繁多&#xff0c;从小巧实用的 SQLite 到强大的 Teradata 。但很少有文章讲解数据库是如何工作…

配置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…