spring项目使用EMQX,使用@Autowired注入失败报错空指针问题记录

本文详细描述了Java客户端使用MQTT订阅消息的过程,涉及MQTTConnect和MQTTListener部分代码。重点分析了在回调中使用@Autowired注入Service时遇到的空指针问题,提出了解决方案:在需要时动态使用ApplicationContext加载Bean。

java客户端使用MQTT订阅消息大致流程

  1. 首先通过消息服务器节点的地址,new 一个MqttClient连接对象然后给MqttClient设置回调。
  2. 这里是通过new 一个MqttCallback对象(注入失败问题就在这) 来实现对topic中消息的监听和处理,所以服务端的处理心跳消息的主要业务逻辑均在MqttCallback实例中的messageArrived方法中。
  3. 建立连接。
  4. 订阅topic。

MQTTConnect部分代码

    /**
     * 客户端connect连接mqtt服务器
     *
     * @param mqttCallback 回调函数
     **/
   public void setMqttClient(MqttCallback mqttCallback) throws MqttException {
        MqttConnectOptions options = mqttConnectOptions(USER_NAME, PASS_WORD);
        mqttClient.setCallback(mqttCallback);
        mqttClient.connect(options);
    }

    /**
     * MQTT连接参数设置
     */
    private MqttConnectOptions mqttConnectOptions(String userName, String passWord) throws MqttException {
        mqttClient = new MqttClient(HOST, clientId, new MemoryPersistence());
        MqttConnectOptions options = new MqttConnectOptions();
        options.setUserName(userName);
        options.setPassword(passWord.toCharArray());
        options.setConnectionTimeout(10);///默认:30
        options.setAutomaticReconnect(true);//默认:false
        options.setCleanSession(false);//默认:true
        //options.setKeepAliveInterval(20);//默认:60
        return options;
    }

MQTTListener部分代码

/**
* 这里采用了自定义一个ApplicationListener监听类,
* 在onApplicationEvent()方法中执行连接mqtt服务和订阅主题。
**/
@Slf4j
@Component
public class MQTTListener implements ApplicationListener<ContextRefreshedEvent> {

    private final MQTTConnect server;

    @Autowired
    public MQTTListener(MQTTConnect server) {
        this.server = server;
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        try {
            // 连接mqtt服务
            server.setMqttClient(new Callback(server));
            // 订阅主题
            server.sub("com/iot/init");
            server.sub("com/iot/init1");
            server.sub("com/iot/init2");
            server.sub("com/iot/init3");
        } catch (MqttException e) {
            log.error(e.getMessage(), e);
        }
    }
}

问题分析

由于我们需要在消息监听回调中执行业务逻辑,比如根据消息中的某个字段使用mapper查询数据库。所以在Callback类中用@Autowired引入的项目中的serviceImpl。这里可以看到注入的serviceImpl出现了空指针。

在这里插入图片描述

问题原因

在这里插入图片描述
在MQTTListener中我们使用了new来创建CallBack这个bean,那么我们在CallBack这个类的示例中注入bean就会失败,出现null。
这里原因可能是new出来的实例对象不由spring管理,所以其内部注入的bean也就失败了。(这里如果说的不对,希望大佬不吝赐教)

解决方法

解决方法就是使用到服务的时候,动态去加载Bean。
  1. 使用applicationContext的getBean()方法在使用的地方动态注入,比如以下的方式。
dynamicRoutingDataSource = applicationContext.getBean(DynamicRoutingDataSource.class);
  1. 在MQTTListener设置回调CallBack类时,使用注入的CallBack类的Bean。

在这里插入图片描述

总结

在Spring项目中,尽量统一把bean交给Spring容器管理,少使用new创建实例对象。

参考

记一次Spring项目中使用@Autowired注入bean,使用时报空指针异常的问题
Spring @Autowired注入为null,空指针异常

<think> 我们正在解决Spring Boot AOP环绕增强方法中,使用@Autowired注入的bean出现空指针异常的问题。原因在于Aspect切面类没有通过Spring容器管理,导致依赖注入失败。以下是解决方案: ### 解决方案 #### 1. 确保切面类被Spring管理 在切面类上添加`@Component`或`@Aspect`和`@Component`组合注解,使其成为Spring Bean。 ```java @Aspect @Component // 关键:确保切面被Spring容器管理 public class LogAspect { @Autowired private SomeService someService; // 现在可以正常注入 @Around("...") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { someService.doSomething(); // 不再为null return joinPoint.proceed(); } } ``` #### 2. 检查切面类扫描路径 确保切面类位于Spring Boot主启动类同级或子包下,或通过`@ComponentScan`显式指定包路径。 ```java @SpringBootApplication @ComponentScan(basePackages = "com.example.aop") // 显式扫描切面所在包 public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` #### 3. 避免在切面中使用静态字段注入 静态字段不会通过依赖注入赋值,应用实例字段替代。 ❌ **错误示例**: ```java @Aspect @Component public class LogAspect { @Autowired private static SomeService someService; // 静态字段注入无效 } ``` ✅ **正确做法**: ```java @Aspect @Component public class LogAspect { @Autowired private SomeService someService; // 非静态字段 } ``` #### 4. 使用构造器注入(推荐) 避免字段注入循环依赖问题,改用构造器注入。 ```java @Aspect @Component public class LogAspect { private final SomeService someService; @Autowired // Spring 4.3+可省略 public LogAspect(SomeService someService) { this.someService = someService; } } ``` --- ### 技术原理 - **Spring AOP代理机制**:切面类必须是Spring Bean,否则其内部`@Autowired`无法生效[^4]。 - **依赖注入时机**:静态字段在类加载时初始化,早于Spring依赖注入,导致`null`值[^3]。 - **构造器注入优势**:避免循环依赖,并保证注入完成时Bean已完全初始化[^1]。 --- ### 相关问题 1. 如何在非Spring管理的类中使用`@Autowired`注入Bean? 2. Spring Boot中静态工具类如何安全访问Spring Bean? 3. 如何解决AOP切面中`@Value`读取配置为空的问题? [^1]: 引用自站内内容:`@Autowired`在`@Bean`方法参数上的应用 [^3]: 引用自站内内容:静态字段注入失效问题 [^4]: 引用自站内内容:Spring组件扫描机制 </think> 以下是解决 Spring Boot AOP 环绕增强方法中 `@Autowired` 注入 Bean 空指针异常的完整方案,结合了引用内容和最佳实践: --- ### 问题原因分析 在 AOP 切面中直接使用 `@Autowired` 注入 Bean 时,由于切面类的实例化可能早于 Spring 容器初始化完成,导致依赖注入失败,出现空指针异常[^4]。核心原因是: 1. **初始化顺序问题**:切面类可能在依赖 Bean 初始化前被加载 2. **静态字段注入失效**:尝试在静态字段上使用 `@Autowired` 无效[^3] 3. **代理机制限制**:AOP 代理对象的依赖注入需要特殊处理 --- ### 解决方案及代码实现 #### 方案一:确保切面类被 Spring 管理(推荐) ```java @Aspect @Component // 关键注解:让 Spring 管理切面生命周期 public class LogAspect { // 1. 使用实例字段注入(非静态!) @Autowired private UserService userService; // 会被正常注入 @Around("@annotation(com.example.LogOperation)") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { // 2. 安全使用注入的Bean String result = userService.getOperationLog(); System.out.println("[AOP] 操作日志: " + result); return joinPoint.proceed(); } } ``` #### 方案二:延迟加载注入(解决初始化顺序问题) ```java @Aspect @Component public class LogAspect { // 使用ObjectProvider延迟获取Bean @Autowired private ObjectProvider<UserService> userServiceProvider; @Around("@annotation(com.example.LogOperation)") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { // 在方法内获取Bean实例 UserService userService = userServiceProvider.getIfAvailable(); if(userService != null) { userService.recordLog(); } return joinPoint.proceed(); } } ``` #### 方案三:构造器注入(最安全方式) ```java @Aspect @Component public class LogAspect { private final UserService userService; // 3. 使用构造器注入Spring 4.3+ 可省略 @Autowired@Autowired public LogAspect(UserService userService) { this.userService = userService; } @Around("@annotation(com.example.LogOperation)") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { userService.recordLog(); // 保证非空 return joinPoint.proceed(); } } ``` --- ### 关键配置步骤 1. **启用组件扫描** 确保切面类在主应用包或子包下: ```java @SpringBootApplication @ComponentScan("com.example.aop") // 明确扫描切面包 public class Application {} ``` 2. **避免静态字段注入** ❌ 错误方式:`@Autowired private static UserService userService;` ✅ 正确方式:使用实例字段或构造器注入[^3] 3. **验证 Bean 加载顺序** 在启动日志检查切面类是否被标记为 Spring Bean: ``` [DEBUG] Creating shared instance of singleton bean 'logAspect' ``` --- ### 技术原理 1. **Spring 生命周期管理** `@Component` 确保切面由 Spring 容器管理,解决初始化顺序问题[^4] 2. **依赖注入类型选择** 构造器注入强制依赖在对象创建时解决,避免 NPE[^1] 3. **ObjectProvider 机制** 延迟加载依赖项,应对循环依赖和初始化顺序问题[^2] --- ### 相关问题 1. 如何在非 Spring 托管的工具类中使用注入的 Bean? 2. Spring Boot 中如何处理静态工具类的依赖注入? 3. 如何确保 AOP 切面中的 `@Value` 配置注入不为空? 4. Spring 构造器注入和字段注入有何性能差异? [^1]: `@Autowired` 在 `@Bean` 方法参数上的应用 [^2]: 单元测试中注解的混淆问题 [^3]: 静态字段注入失败问题 [^4]: Spring 组件扫描机制
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值