一尘不染

为什么我的Spring @Autowired字段为空?

spring

我有一个具有字段()的Spring @Service类(MileageFeeCalculator),但是该字段是我尝试使用它时所用的。日志显示该bean和该bean都在创建,但是每当我尝试在服务bean上调用该方法时,都会得到一个a 。Spring为什么不自动接线该领域?@AutowiredrateServicenullMileageFeeCalculatorMileageRateServiceNullPointerExceptionmileageCharge

控制器类:

@Controller
public class MileageFeeController {    
    @RequestMapping("/mileage/{miles}")
    @ResponseBody
    public float mileageFee(@PathVariable int miles) {
        MileageFeeCalculator calc = new MileageFeeCalculator();
        return calc.mileageCharge(miles);
    }
}

服务等级:

@Service
public class MileageFeeCalculator {

    @Autowired
    private MileageRateService rateService; // <--- should be autowired, is null

    public float mileageCharge(final int miles) {
        return (miles * rateService.ratePerMile()); // <--- throws NPE
    }
}

应该自动连接的服务bean,MileageFeeCalculator但不是:

@Service
public class MileageRateService {
    public float ratePerMile() {
        return 0.565f;
    }
}

当我尝试时GET /mileage/3,出现以下异常:

java.lang.NullPointerException: null
    at com.chrylis.example.spring_autowired_npe.MileageFeeCalculator.mileageCharge(MileageFeeCalculator.java:13)
    at com.chrylis.example.spring_autowired_npe.MileageFeeController.mileageFee(MileageFeeController.java:14)
    ...

阅读 678

收藏
2020-04-11

共1个答案

一尘不染

带注释的字段@Autowirednull因为Spring不了解MileageFeeCalculator你使用其创建的副本,new也不知道自动对其进行接线。

Spring Inversion of Control(IoC)容器具有三个主要的逻辑组件:ApplicationContext应用程序可以使用的组件(bean)的注册表(称为),配置器系统通过匹配对象将对象的依赖项注入到它们中在上下文中具有bean的依赖关系,以及一个依赖关系解决程序,它可以查看许多不同bean的配置并确定如何以必要的顺序实例化和配置它们。

IoC容器不是魔术,除非你以某种方式告知Java对象,否则它无法了解Java对象。当你调用时new,JVM实例化新对象的副本并将其直接交给你-它从未经历配置过程。你可以通过三种方式配置bean。

我已经在GitHub项目上使用Spring Boot启动了所有这些代码;你可以针对每种方法查看一个正在运行的项目,以查看使其工作所需的一切。用标记NullPointerException:nonworking

Inject your beans

最可取的选择是让Spring自动连接所有bean。这需要最少的代码量,并且最易于维护。要使自动装配工作如你所愿,还可以MileageFeeCalculator像这样自动装配:

@Controller
public class MileageFeeController {

    @Autowired
    private MileageFeeCalculator calc;

    @RequestMapping("/mileage/{miles}")
    @ResponseBody
    public float mileageFee(@PathVariable int miles) {
        return calc.mileageCharge(miles);
    }
}

如果你需要为不同的请求创建服务对象的新实例,仍然可以通过Spring bean scopes使用注入。

通过注入@MileageFeeCalculator服务对象起作用的标记:working-inject-bean

Use@Configurable

如果确实需要new自动创建的对象,则可以将Spring @Configurable注释与AspectJ编译时编织一起使用以注入对象。这种方法将代码插入到对象的构造函数中,以警告Spring正在创建它,以便Spring可以配置新实例。这需要在构建中进行一些配置(例如使用进行编译ajc)并打开Spring的运行时配置处理程序(@EnableSpringConfigured使用JavaConfig语法)。Roo Active Record系统使用此方法来允许new你的实体实例获取注入的必要持久性信息。

@Service
@Configurable
public class MileageFeeCalculator {

    @Autowired
    private MileageRateService rateService;

    public float mileageCharge(final int miles) {
        return (miles * rateService.ratePerMile());
    }
}

通过@Configurable在服务对象上使用而起作用的标记:working-configurable

Manual bean lookup: not recommended

这种方法仅适用于在特殊情况下与遗留代码接口。几乎总是最好创建一个Spring可以自动连接并且遗留代码可以调用的Singleton适配器类,但是可以直接向Spring应用程序上下文请求一个bean。

为此,你需要一个Spring可以引用该ApplicationContext对象的类:

@Component
public class ApplicationContextHolder implements ApplicationContextAware {
    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;   
    }

    public static ApplicationContext getContext() {
        return context;
    }
}

然后,你的旧代码可以调用getContext()并检索所需的bean:

@Controller
public class MileageFeeController {    
    @RequestMapping("/mileage/{miles}")
    @ResponseBody
    public float mileageFee(@PathVariable int miles) {
        MileageFeeCalculator calc = ApplicationContextHolder.getContext().getBean(MileageFeeCalculator.class);
        return calc.mileageCharge(miles);
    }
}

通过在Spring上下文中手动查找服务对象而起作用的标记: working-manual-lookup

2020-04-11