注意:这旨在作为常见问题的规范答案。
我有一个带有字段()的Spring @Service类(MileageFeeCalculator),但是该字段是我尝试使用它时所用的。日志显示同时创建了bean和bean,但是每当尝试在服务bean上调用方法时,我都会得到一个。Spring为什么不自动接线该领域?@Autowired``rateService``null``MileageFeeCalculator``MileageRateService``NullPointerException``mileageCharge
@Service
MileageFeeCalculator
@Autowired``rateService``null``MileageFeeCalculator``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 } }
应该自动连接的服务bean,MileageFeeCalculator但不是:
@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也不知道自动对其进行接线。
@Autowired
null
new
Spring Inversion of Control(IoC)容器具有三个主要的逻辑组件:ApplicationContext应用程序可以使用的组件(bean)的注册表(称为),配置器系统通过匹配对象将对象的依赖项注入到它们中在上下文中具有bean的依赖关系,以及一个依赖关系解决程序,它可以查看许多不同bean的配置并确定如何以必要的顺序实例化和配置它们。
ApplicationContext
IoC容器不是魔术,除非您以某种方式告知Java对象,否则它无法了解Java对象。当您调用时new,JVM实例化新对象的副本并将其直接交给您- 它从未经历配置过程。您可以通过三种方式配置bean。
我已经在GitHub项目上使用Spring Boot启动了所有这些代码;您可以针对每种方法查看一个正在运行的项目,以查看使其工作所需的一切。 用标记NullPointerException:nonworking
NullPointerException
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可以自动装配并且遗留代码可以调用的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:
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