问题引入:
在springboot 项目写了个定时任务,里面有段代码通过Feign 调用远程服务,发现通过接口调用可以程序正常执行, 通过配置定时任务发现定时任务没执行,看日志是报了NP.
问题跟踪:
写了个demo 重现以上错误:
@Api(tags = "XXX控制器")
@RestController
@RequestMapping("/tenderBid")
@Slf4j
public class TenderBidController extends JeecgController<Tender, ITenderService> {
@Autowired
private IdeliverRemoteService ideliverRemoteService;
@Autowired
private ITenderBidDetailService tenderBidDetailService;
// final IdeliverRemoteService ideliverRemoteService;
//
// public TenderBidController(IdeliverRemoteService ideliverRemoteService) {
// this.ideliverRemoteService = ideliverRemoteService;
// }
/**
* @return java.lang.String
* @Title: 测试定时任务, 每6秒执行一次(注意方法不能带参数)
* @Author: ken
* @Description:
* @Date: 2023/2/20 17:01
* @Param: []
**/
@Scheduled(cron ="*/6 * * * * ?")
@RequestMapping(value = "/testTask", method = RequestMethod.POST)
public void testTask() {
log.info("now is:{}", LocalDateTime.now().toString());
com.alibaba.fastjson2.JSONObject vo = new com.alibaba.fastjson2.JSONObject();
vo.put("sign", "8145e202-d8b5-f613-5e24-50bda4bbd75c");
//IdeliverRemoteService ideliverRemoteService = (IdeliverRemoteService) SpringContextUtils.getBean("ideliver-service");
//IdeliverRemoteService ideliverRemoteService = (IdeliverRemoteService)AppContextUtil.getBean("ideliver-service");
//IdeliverRemoteService ideliverRemoteService = AppContextUtil.getFeignBean("ideliver-service", IdeliverRemoteService.class);
log.info("TenderController --> ideliverRemoteService:{}", ideliverRemoteService);
try {
com.alibaba.fastjson2.JSONObject result = ideliverRemoteService.getSolutionKeyWord(vo);
log.info("TenderController --> testTask result: {}", JSONObject.toJSONString(result));
} catch (Exception e) {
log.error("TenderController --> testTask error:{}", e.getMessage(), e);
}
}
注入的Feign
@Service
@FeignClient(name = "ideliver-service", url = "${service.ideliver.url:}", configuration = IdeliverFeignConfig.class)
public interface IdeliverRemoteService {
/**
* @Title: 查询解决方案对应的关键词
* @Author: ken
* @Description:
* @Date: 2023/2/22 16:10
* @Param: [vo]
* @return com.alibaba.fastjson2.JSONObject
**/
@PostMapping(value = "${service.ideliver.api.SolutionKeyWord:/ideliver/SolutionKeyWord}")
JSONObject getSolutionKeyWord(@RequestBody(required=false) JSONObject vo);
}
通过日志发现是获取到了 ideliverRemoteService的值:
ideliverRemoteService:HardCodedTarget(type=IdeliverRemoteService, name=ideliver-service, url=https://ideliver.xxx.ltd)
跟踪代码,在 IdeliverFeignConfig 类找到原因,其中有段代码从前端获取Cookie 未判断空,导致报错:
@Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
// 2.从request对象中获取cookie
String cookies = request.getHeader("Cookie");
requestTemplate.header("Cookie", cookies);
};
}
代码加非空判断,问题解决:
@Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if(Objects.isNull(requestAttributes)){
return;
}
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
// 2.从request对象中获取cookie
String cookies = request.getHeader("Cookie");
requestTemplate.header("Cookie", cookies);
};
}
问题结论:
程序报错一定要认真定位为题, 不能自以为, 开始以为是没有注入进来, 最后debug 定位解决为题. 还有一点对于一些地方,如果远程调用,异常捕获和日志打印是非常必要的,不然很难定位到问题. 可以通过如下写法轻易打印出日志:
**log.error("TenderController --> testTask error:{}", e.getMessage(), e);**
问题补充:
关于 FeignClient 的注入问题, 经测试是第一种和第四种可以注入, 第二种和第三种是无法注入的,大家有空可以试验下:
/**
* @Title: 配置类,解决定时任务无法注入的问题
* @Author: ken
* @Description:
* @Date: 2023/3/6 16:02
**/
@Component
public class AppContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
AppContextUtil.applicationContext = applicationContext;
}
public static Object getBean(String beanName) {
return applicationContext.getBean(beanName);
}
public static <T> T getFeignBean(String beanName, Class<T> tClass) {
//我这边主要在gateway用到这两行代码
FeignContext feignContext = applicationContext.getBean("feignContext", FeignContext.class);
return feignContext.getInstance(beanName, tClass);
}
}
每一次的积累都有收获~