In daily project development, the singleton pattern can be said to be the most commonly used design pattern, projects often need to use the method of Service logic layer in the singleton pattern to achieve some functions. It is often possible to use @Resource or @AutoWired to inject instances automatically, but this approach causes NullPointException problems in singleton mode. So this paper does a study on this issue.

Demo code address

Issue a preliminary

Generally, our projects are developed in layers, and the most classic is probably the following structure:

├─ UserDao - DAO layer, responsible for interaction with data sources, get data. ├─ UserService - Service logic layer, responsible for business logic implementation. ├ ─ UserController -- a control layer that provides an interface to interact with the outside world.Copy the code

You need a singleton object that requires UserService to provide the UserService. The code is as follows:

@Slf4j
public class UserSingleton {

    private static volatile UserSingleton INSTANCE;

    @Resource
    private UserService userService;

    public static UserSingleton getInstance(a) {
        if (null == INSTANCE) {
            synchronized (UserSingleton.class) {
                if (null == INSTANCE) {
                    INSTANCE = newUserSingleton(); }}}return INSTANCE;
    }

    public String getUser(a) {
        if (null == userService) {
            log.debug("UserSingleton userService is null");
            return "UserSingleton Exception: userService is null";
        }
        returnuserService.getUser(); }}Copy the code

Then create a UserController to call the usersingleton.getUser () method to see what data is returned.

@RestController
public class UserController {

    @Resource
    private UserService userService;

    /** * In normal mode, the Controller automatically inject Service. * *@return  user info
     */
    @GetMapping("/user")
    public String getUser(a){
        return userService.getUser();
    }

    /** * use the auto-injected UserService method ** in the singleton@return  UserSingleton Exception: userService is null
     */
    @GetMapping("/user/singleton/ioc")
    public String getUserFromSingletonForIoc(a){
        returnUserSingleton.getInstance().getUser(); }}Copy the code

As you can see, the automatic injection of UserService into the UserController gets the data normally.

But if you use automatic injection in singleton mode, UserService is an empty object.

So it is not possible to use @Resource or @AutoWired annotations to get an object instance of UserService in a singleton. If there is no NullPointException, a NullPointException is reported.

Cause of the problem

Automatic dependency injection cannot be used in singleton mode because singleton objects use the static flag. INSTANCE is a static object, and static objects are loaded before the Spring container. So you can’t use automatic dependency injection here.

Problem solving

Instead of using automatic dependency injection, you can manually instantiate the UserService when new UserSingleton() initializes the object. But this approach can be a pitfall, or it can only be done in certain circumstances. First look at the code:

@Slf4j
public class UserSingleton {

    private static volatile UserSingleton INSTANCE;

    @Resource
    private UserService userService;

    // To distinguish it from the auto-dependency injection object above.
    // Add the suffix ForNew to indicate that this is created by new Object()
    private UserService userServiceForNew;

    private UserSingleton(a) {
        userServiceForNew = new UserServiceImpl();
    }

    public static UserSingleton getInstance(a) {
        if (null == INSTANCE) {
            synchronized (UserSingleton.class) {
                if (null == INSTANCE) {
                    INSTANCE = newUserSingleton(); }}}return INSTANCE;
    }

    public String getUser(a) {
        if (null == userService) {
            log.debug("UserSingleton userService is null");
            return "UserSingleton Exception: userService is null";
        }
        return userService.getUser();
    }

    public String getUserForNew(a) {
        if (null == userServiceForNew) {
            log.debug("UserSingleton userService is null");
            return "UserSingleton Exception: userService is null";
        }
        returnuserServiceForNew.getUser(); }}Copy the code

Here is the code for UserService.

public interface UserService {

    /** * Get user information **@return  @link{String}
     */
    String getUser(a);

    /** * Get user information, get data from DAO layer **@return* /
    String getUserForDao(a);
}


@Slf4j
@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserDao userDao;

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

    @Override
    public String getUserForDao(a){
        if(null == userDao){
            log.debug("UserServiceImpl Exception: userDao is null");
            return "UserServiceImpl Exception: userDao is null";
        }
        returnuserDao.select(); }}Copy the code

Create a UserController to call the method in the singleton to verify.

@RestController
public class UserController {

    @Resource
    private UserService userService;

    // In normal mode, Service is automatically injected into Controller.
    @GetMapping("/user")
    public String getUser(a){
        return userService.getUser();
    }

    // Use the method of UserService automatically injected in the singleton
    UserSingleton Exception: userService is null
    @GetMapping("/user/singleton/ioc")
    public String getUserFromSingletonForIoc(a){
        return UserSingleton.getInstance().getUser();
    }

    // Use the manually instantiated UserService method in the singleton
    // The return value is: user info
    @GetMapping("/user/singleton/new")
    public String getUserFromSingletonForNew(a){
        return UserSingleton.getInstance().getUserForNew();
    }

    // Use the method of UserService manually instantiated in the singleton. In UserService, data is obtained through the DAO
    The return value is: UserServiceImpl Exception: userDao is NULL
    @GetMapping("/user/singleton/new/dao")
    public String getUserFromSingletonForNewFromDao(a){
        returnUserSingleton.getInstance().getUserForNewFromDao(); }}Copy the code

As you can see from the above code, manual instantiation can solve the problem to some extent. However, automatic dependency injection is also used in UserService, such as @Resource Private UserDao UserDao; , and the method used in the singleton is useful to the userDao, and the userDao is an empty object.

That is, although UserService is manually instantiated in the singleton, the UserDao in UserService cannot be injected automatically. The reason is the same as the failure to inject UserService automatically in a singleton. So this approach can only solve the problem to a certain extent.

Final solution

We can create a tool class implements ApplicationContextAware interface, used to obtain ApplicationContext context object, and then through the ApplicationContext. GetBean () to dynamic, for instance. The code is as follows:

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/** * Spring utility class, used to dynamically fetch beans **@author James
 * @date2020/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(a) {
        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) {
        returnapplicationContext.getBean(name, clazz); }}Copy the code

And then modify our singleton.

@Slf4j
public class UserSingleton {

    private static volatile UserSingleton INSTANCE;

    // Add the ForTool suffix to distinguish the objects created in the previous two ways.
    private UserService userServiceForTool;

    private UserSingleton(a) {
        userServiceForTool = SpringContextUtils.getBean(UserService.class);
    }

    public static UserSingleton getInstance(a) {
        if (null == INSTANCE) {
            synchronized (UserSingleton.class) {
                if (null == INSTANCE) {
                    INSTANCE = newUserSingleton(); }}}return INSTANCE;
    }

    /** * Get the UserService object using SpringContextUtils and get the data from the UserDao *@return* /
    public String getUserForToolFromDao(a) {
        if (null == userServiceForTool) {
            log.debug("UserSingleton userService is null");
            return "UserSingleton Exception: userService is null";
        }
        returnuserServiceForTool.getUserForDao(); }}Copy the code

Test it in UserController and see the results.

@RestController
public class UserController {
  /** * Method of UserService using SpringContextUtils. In UserService, data is obtained by DAO **@return  user info for dao
   */
  @GetMapping("/user/singleton/tool/dao")
  public String getUserFromSingletonForToolFromDao(a){
      returnUserSingleton.getInstance().getUserForToolFromDao(); }}Copy the code

User info for DAO is returned.

other

This article source address

Welcome to my Github spring-Boot-Example and spring-Cloud-example projects to provide you with more Spring Boot and Spring Cloud tutorials and sample code. Bloggers constantly update relevant documents in their spare time.

spring-boot-example

spring-cloud-example

For more technical articles, please visit my blog at JemGeek.com

Click to read the original article