Проблема внедрения зависимостей в одноэлементном режиме Spring Boot

Spring Boot

Можно сказать, что при ежедневной разработке проектов одноэлементный шаблон является наиболее часто используемым шаблоном проектирования, и в проектах часто требуется использовать метод уровня логики службы для реализации определенных функций в одноэлементном шаблоне. обычно можно использовать@Resourceили@Autowiredдля автоматического внедрения экземпляра, но этот метод появится в шаблоне singletonNullPointExceptionПроблема. Поэтому в этой статье будет проведено небольшое исследование по этому вопросу.

адрес демо-кода

Предварительное изучение проблемы

Как правило, наши проекты разрабатываются слоями, и наиболее классической может быть следующая структура:

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

На данный момент требуется одноэлементный объект, и этот объект требуетUserServiceдля предоставления услуг пользователям. код показывает, как показано ниже:

@Slf4j
public class UserSingleton {

    private static volatile UserSingleton INSTANCE;

    @Resource
    private 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 {

    @Resource
    private 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аномальный.

причина проблемы

Причина, по которой автоматическое внедрение зависимостей нельзя использовать в шаблоне singleton, заключается в том, что объект singleton используетstaticотметка,INSTANCEЭто статический объект, и загрузка статических объектов имеет приоритет над контейнером Spring. Таким образом, автоматическая инъекция зависимостей здесь не может быть использована.

решение проблем

На самом деле решить эту проблему очень просто, если вы не используете автоматическую инъекцию зависимостей, вnew UserSingleton()При инициализации объекта создайте экземпляр вручнуюUserServiceВот и все. Но этот метод может иметь ямы, или он может быть достигнут только в некоторых случаях. Сначала посмотрите на код:

@Slf4j
public class UserSingleton {

    private static volatile UserSingleton INSTANCE;

    @Resource
    private 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 {

    @Resource
    private UserDao userDao;

    @Override
    public String getUser() {
        return "user info";
    }

    @Override
    public 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 {

    @Resource
    private 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;

    @Override
    public 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, проверка пройдена.

разное

Исходный адрес этой статьи

Добро пожаловать на мой githubspring-boot-exampleиspring-cloud-exampleпроекты, которые предоставят вам большеspring bootиspring cloudУчебник и пример кода. Блогеры будут продолжать обновлять соответствующие документы в свободное время.

spring-boot-example

spring-cloud-example

Для получения дополнительных технических статей, пожалуйста, обратите внимание на домашнюю страницу моего блога:JemGeek.com

Нажмите, чтобы прочитать исходный текст