当前位置:   article > 正文

解决controller层注入的service为null

service为null


1. 问题描述

由于过于自信,写的接口没有测试就给到了前端,前端在调用后接口出现NullPointerException空指针异常:

2. 问题排查

根据详细的错误日志发现,代码中通道@Autowired注解注入的Service类对象为NULL。
有人可能会说会不会是Service层的实现类上忘记加了@Service注解,显然不是。如果Service层的实现类没有加@Service注解,而又在Controller层进行注入,程序在启动时就会报错,以下是报错信息:
在这里插入图片描述

我做了一个测试,以下是测试Service层实现类代码:

//@Service
public class UserServiceImpl implements UserService {
    /**
     * 获取用户姓名
     */
    @Override
    public String getUserName() {
        return "张三";
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

都不用到启动这一步,如果你使用了IDEA开发中举,IDEA就会直接给出警告:

无法自动装配。找不到 'UserService' 类型的 Bean。
  • 1

在这里插入图片描述
所以肯定不是实现类没有加注解的原因。
后来经过筛查以及和其他Controller层的类进行对比差异,发现有个接口方法的修饰符是private,而且只有这一个方法是被private修饰的。于是我将其改为public,最后进行测试,不会报错了。

在这里插入图片描述
错误示例:

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/getUserName")
    private String getUserName() {
        return userService.getUserName();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

3. 问题复现

在上面我把private改成public后,再去测试就好了,但原因是什么呢?
下面我自己建了一个测试demo进行了问题复现。

  • Service层
public interface UserService {
    /**
     * 获取用户姓名
     */
    String getUserName();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • Service实现类
@Service
public class UserServiceImpl implements UserService {
    /**
     * 获取用户姓名
     */
    @Override
    public String getUserName() {
        return "张三";
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • Controller层(直接将Controller类的接口方法修饰符改为private用于测试)
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/getUserName")
    private String getUserName() {
        return userService.getUserName();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • HTTP请求
GET http://localhost:8080/user/getUserName
  • 1

运行后,请求结果如下:

在这里插入图片描述
根据结果可以看出,改成private后也是可以正常运行的呀!这是怎么回事啊?
我又排查了一遍,发现项目使用的框架有全局日志切面。难道AOP无法代理private方法吗?在百度求证查了半天有些人是这么说过。

下来就来测试一下,写一个测试AOP:

@Aspect
@Component
@Slf4j
public class AopTest {
    @Pointcut("execution(* com.example.demo.demos.web.controller..*(..))")
    public void controllerPointcut() {
    }

    // 在切入点之前执行的通知
    @Before("controllerPointcut()")
    public void beforeControllerMethod() {
        System.out.println("Before executing Controller method");
        // 可以在这里添加自定义的拦截逻辑
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

在上述代码中,controllerPointcut()方法使用execution表达式定义了一个切入点,该表达式匹配com.example.demo.demos.web.controller包及其子包下的所有类的所有方法。然后,在beforeControllerMethod()方法中使用@Before("controllerPointcut()")注解指定在切入点匹配的方法执行之前执行的通知。

然后再测试一下:

在这里插入图片描述
OK,问题复现出来了。下面进行原理分析。

4. 原因分析

加上了动态代理以后,空指针的异常问题被复现出来了,那默认的代理是什么呢?背过八股文的都知道肯定是:cglib

cglib的代理方式是setSuperClass,是不会代理父类的private方法的。也就是说AOP无法代理private,那么到底是不是这原因导致bean=null呢?

下面做一个对比测试,Controller层的方法先改为public修饰,然后在return userService.getUserName();打上断点

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/getUserName")
    public String getUserName() {
        return userService.getUserName();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

这个时候userService不为空,证明是可以正常调用并输出结果的

在这里插入图片描述
结果如下:
在这里插入图片描述
在这里插入图片描述
然后将Controller层的方法改为private修饰,在return userService.getUserName();打上断点观察

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/getUserName")
    private String getUserName() {
        return userService.getUserName();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在这里插入图片描述
从上图可以看出,这时候的userService为NULL。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/224764?site
推荐阅读
相关标签
  

闽ICP备14008679号