注意:这旨在成为常见问题的规范答案。
我有一个 Spring@Service类 ( MileageFeeCalculator),它有一个@Autowired字段 ( rateService),但该字段是null我尝试使用它的时候。日志显示MileageFeeCalculatorbean 和MileageRateServicebean 都在创建,但是NullPointerException每当我尝试mileageCharge在我的服务 bean 上调用该方法时都会得到一个。为什么 Spring 不自动装配该字段?
@Service
MileageFeeCalculator
@Autowired
rateService
null
MileageRateService
NullPointerException
mileageCharge
控制器类:
@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 } }
应该自动装配MileageFeeCalculator但不是的服务 bean:
@Service public class MileageRateService { public float ratePerMile() { return 0.565f; } }
当我尝试时GET /mileage/3,我得到了这个异常:
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) ...
带注释的字段@Autowired是null因为 Spring 不知道MileageFeeCalculator您创建的副本,new也不知道自动装配它。
new
Spring Inversion of Control (IoC) 容器具有三个主要的逻辑组件:ApplicationContext可供应用程序使用的组件(bean)的注册表(称为与上下文中的 bean 的依赖关系,以及一个依赖关系求解器,它可以查看许多不同 bean 的配置并确定如何以必要的顺序实例化和配置它们。
ApplicationContext
IoC 容器并不神奇,它无法知道 Java 对象,除非您以某种方式告知它们。当您调用new时,JVM 会实例化一个新对象的副本并将其直接交给您——它从不经过配置过程。您可以通过三种方式配置 bean。
我已经在这个 GitHub 项目上发布了所有这些代码,使用 Spring Boot 启动;您可以查看每种方法的完整运行项目,以了解使其工作所需的一切。 用 标记NullPointerException:nonworking
nonworking
最好的选择是让 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
@MileageFeeCalculator
working-inject- bean
如果您确实需要new自动装配创建的对象,您可以使用 Spring@Configurable注释和 AspectJ 编译时编织来注入您的对象。这种方法将代码插入到对象的构造函数中,以提醒 Spring 正在创建它,以便 Spring 可以配置新实例。这需要在您的构建中进行一些配置(例如使用 编译ajc)并打开 Spring 的运行时配置处理程序(@EnableSpringConfigured使用 JavaConfig 语法)。Roo Active Record 系统使用这种方法来允许new您的实体实例获得必要的持久性信息注入。
@Configurable
ajc
@EnableSpringConfigured
@Service @Configurable public class MileageFeeCalculator { @Autowired private MileageRateService rateService; public float mileageCharge(final int miles) { return (miles * rateService.ratePerMile()); } }
@Configurable 在服务对象上使用的标签:working- configurable
working- configurable
这种方法仅适用于在特殊情况下与遗留代码交互。几乎总是最好创建一个 Spring 可以自动装配并且遗留代码可以调用的单例适配器类,但是可以直接向 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:
getContext()
@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
working-manual- lookup