javascript
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,驗證通過。
?
總結
以上是生活随笔為你收集整理的Spring Boot 单例模式中依赖注入问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Javascript 处理 JSON 数
- 下一篇: 图论算法(三)--最短路径 的Bellm