1. Background introduction
I created two Springboot projects, A as a public project, packaged into Jar package for other projects; B uses the Jar package provided by A. The code of the SceneCaseDebugAPI class in Project A is as follows:
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.jayway.jsonpath.JsonPath;
import com.umetrip.qa.model.*;
import com.umetrip.qa.service.GatewayCaseService;
import com.umetrip.qa.service.SceneCaseService;
import com.umetrip.qa.service.ScenePublicParameterService;
import com.umetrip.qa.service.SceneStepsService;
import com.umetrip.qa.tester.singlecase.SingleCaseDebugAPI;
import com.umetrip.qa.utils.JsonStringUtils;
import com.umetrip.qa.utils.OkhttpUtil;
import com.umetrip.qa.utils.PrettyTimeTool;
import com.umetrip.qa.utils.request.ApiRequestCommonUtil;
import com.umetrip.qa.utils.request.RequestHttp_error;
import org.jsoup.helper.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.regex.Pattern;
/**
* @description: 普通java类要使用 @Autowired注解 的话,需要使用@Component注解申明该类是spring组件
* @time: 2021-02-23 11:12
*/
@Component
public class SceneCaseDebugAPI {
@Autowired
static SceneCaseService sceneCaseService;
@Autowired
static SceneStepsService sceneStepsService;
@Autowired
static ScenePublicParameterService scenePublicParameterService;
@Autowired
static GatewayCaseService gatewayCaseService;
@Autowired
static SingleCaseDebugAPI singleCaseDebugAPI;
public static ApiResult sceneCaseCommonRequest(JSONObject sceneCaseJson){
ApiResult apiResult = new ApiResult();
//步骤1:从DB中查询出场景用例的详细信息
System.out.println("sceneCase: " + JSONObject.toJSONString(sceneCaseJson));
SceneCase sceneCase = JSONObject.parseObject(JSONObject.toJSONString(sceneCaseJson), SceneCase.class);
String singleSceneCaseRedisKey = "SceneCase" + "_" + sceneCase.getId() + "_" + sceneCase.getSceneName() + "_" + "Result";
List<SceneSteps> sceneSteps = sceneStepsService.findAllSceneStepsBySceneCaseId(sceneCase.getId());
// 清空redis中场景用例的用例步骤信息
// baseRedisDao.remove(singleSceneCaseRedisKey);
//设置一个数组存储场景用例步骤的结果
List<String> stepsResult = new ArrayList<>();
try{
for(SceneSteps step : sceneSteps){
System.out.println("当前正在处理的场景用例步骤信息为:" + JSONObject.toJSONString(step));
JSONObject stepJson = JSONObject.parseObject(JSONObject.toJSONString(step), JSONObject.class);
String sceneStepType = step.getSceneStepType();
if(sceneStepType.equals("HTTP用例")){
ResMsg resMsg = executeHttpCaseStep(sceneCase.getId(), step);
stepJson = JSONObject.parseObject(JSONObject.toJSONString(resMsg), JSONObject.class);
stepsResult.add(resMsg.getResult());
}else if(sceneStepType.equals("等待")){
Thread.sleep(step.getSceneStepWaitingtime());
}else if(sceneStepType.equals("GET请求")){
ResMsg resMsg = executeGetRequestStep(sceneCase.getId(), step);
stepJson = JSONObject.parseObject(JSONObject.toJSONString(resMsg), JSONObject.class);
stepsResult.add(resMsg.getResult());
}else if(sceneStepType.equals("条件")){
stepJson.put("debugCaseStartTime", new Date());
boolean result = executeConditionStep(sceneCase.getId(), step);
if(result){
stepJson.put("sceneCaseStepResult", "T");
stepsResult.add("T");
}else {
stepJson.put("sceneCaseStepResult", "F");
stepsResult.add("F");
}
}
PrettyTimeTool.createNewInterfaceCaseDebugLog(stepJson.getDate("debugCaseStartTime"), "SingleInterfaceCase", sceneCase.getId(), stepJson);
}
}catch (Exception e){
e.printStackTrace();
apiResult.setCode(400);
apiResult.setResult("场景用例执行失败");
}
// 还需要增加场景用例结果判断逻辑,只要有一个失败步骤失败,场景用例结果即为失败;否则为成功
if(stepsResult.contains("F")){
apiResult.setCode(400);
apiResult.setResult("场景用例执行失败");
}else {
apiResult.setCode(200);
apiResult.setResult("场景用例执行成功");
}
return apiResult;
}
public static ResMsg executeGetRequestStep(Integer sceneCaseId, SceneSteps step){
System.out.println("执行Get请求步骤");
ResMsg resMsg = new ResMsg();
ApiRequestCommonUtil apiRequestCommonUtil = new ApiRequestCommonUtil();
String newUrl = step.getSceneStepGetUrl();
JSONArray params = JSONArray.parseArray(step.getSceneStepGetParams());
JSONArray header = JSONArray.parseArray(step.getSceneStepGetHeader());
System.out.println("请求参数的数据:" + params.size() + " 请求头的数据:" + header.size());
for(int i = 0; i < params.size(); i++){
JSONObject paramsTableOneRow = params.getJSONObject(i);
String key = paramsTableOneRow.getString("key");
String val = paramsTableOneRow.getString("value");
if(val.contains("${")){
String dbParam = val.substring(2, val.length() - 1);
System.out.println("1 dbParam: " + dbParam);
// 从场景内公共参数查找变量,当查找不到时,需要去全局公共参数中查找(目前尚未实现)
ScenePublicParameter scenePublicParameter = apiRequestCommonUtil.findPublicParamValue(sceneCaseId, dbParam);
val = scenePublicParameter.getSceneParameterVal();
}
if (i == 0) {
System.out.println("2 newUrl: " + newUrl + " key:" + key + " val:" + val);
newUrl = newUrl + "?" + key + "=" + val;
} else {
System.out.println("3 newUrl: " + newUrl + " key:" + key + " val:" + val);
newUrl = newUrl + "&" + key + "=" + val;
}
}
System.out.println("处理后的url:" + newUrl);
// 处理header
if(header.size() != 0){
for(int j = 0; j < header.size(); j++){
JSONObject headerTableOneRow= header.getJSONObject(j);
String key = headerTableOneRow.getString("key");
String val = headerTableOneRow.getString("value");
if(val.contains("${")){
String dbParam = val.substring(2, val.length() - 1);
System.out.println("1 dbParam: " + dbParam);
ScenePublicParameter scenePublicParameter = apiRequestCommonUtil.findPublicParamValue(sceneCaseId, dbParam);
val = scenePublicParameter.getSceneParameterVal();
}
headerTableOneRow.put("value", val);
}
}
System.out.println("header str: " + JSONArray.toJSONString(header));
long startTime = System.currentTimeMillis();
Date deBugCaseStartTime = new Date();
// 请求url和header已经准备完毕,真正发送GET请求
String getResult = OkhttpUtil.dynamicGet(newUrl, header);
long endTime = System.currentTimeMillis();
long consumetime = endTime - startTime;
// 判断是否需要将响应体中数据存储至场景内的公共参数表
if(!StringUtil.isBlank(step.getSceneStepExtractJsonPath())){
String[] jsonPathList = step.getSceneStepExtractJsonPath().split("\r|\n");
for(int i = 0; i<jsonPathList.length; i++){
getResponseBodyJsonPathVal(sceneCaseId, getResult, jsonPathList[i]);
}
}
//需要加断言处理
System.out.println("Get请求的返回结果:" + getResult);
RequestHttp_error requestHttp = new RequestHttp_error();
requestHttp.assertResponse(getResult, step.getSceneStepGetExpectResponse(), resMsg);
resMsg.setConsumetime(consumetime);
resMsg.setInterfaceType("SceneCase");
resMsg.setRequestBodyParameters(JSONObject.toJSONString(params));
resMsg.setResponsebody(getResult);
resMsg.setUrl(newUrl);
resMsg.setHttpHeaders(JSONObject.toJSONString(header));
resMsg.setTestpoint(step.getSceneStepName());
resMsg.setDebugCaseStartTime(deBugCaseStartTime);
return resMsg;
}
public static boolean executeConditionStep(Integer sceneCaseId, SceneSteps step){
System.out.println("执行条件步骤");
boolean res = false;
ApiRequestCommonUtil apiRequestCommonUtil = new ApiRequestCommonUtil();
String firstExpression = step.getSceneFirstExpression();
String secondExpression = step.getSceneSecondExpression();
String judgeCondition = step.getSceneExpressionJudgeCondition();
if(secondExpression.contains("${")){
String dbParam = secondExpression.substring(2, secondExpression.length() - 1);
System.out.println("1 dbParam: " + dbParam);
ScenePublicParameter scenePublicParameter = apiRequestCommonUtil.findPublicParamValue(sceneCaseId, dbParam);
secondExpression = scenePublicParameter.getSceneParameterVal();
}
switch (judgeCondition){
case "等于":
res = firstExpression.equals(secondExpression);
break;
case "不等于":
res = !firstExpression.equals(secondExpression);
break;
case "包含":
res = firstExpression.contains(secondExpression);
break;
case "不包含":
res = !firstExpression.contains(secondExpression);
break;
case "大于":
res = Integer.parseInt(firstExpression) > Integer.parseInt(secondExpression);
break;
case "小于":
res = Integer.parseInt(firstExpression) < Integer.parseInt(secondExpression);
break;
}
System.out.println("条件步骤的运行结果:" + res);
return res;
}
public static ResMsg executeHttpCaseStep(Integer sceneCaseId, SceneSteps step){
System.out.println("执行Http用例步骤");
Integer gatewaySingleCaseId = step.getSceneGatewaySingleCaseId();
GatewayCase gatewayCase = gatewayCaseService.findByPrimaryKey(gatewaySingleCaseId);
System.out.println("gateway case rparams before:" + gatewayCase.getRparams());
JSONObject jsonObjectDB = JSONObject.parseObject(JsonStringUtils.StringInDBtoJSONString(gatewayCase.getRparams()));
ApiRequestCommonUtil.processRequestParameterization(sceneCaseId, jsonObjectDB);
System.out.println("gateway case rparams after:" + JsonStringUtils.JSONStringtoStringInDB(JSONObject.toJSONString(jsonObjectDB)));
gatewayCase.setRparams(JsonStringUtils.JSONStringtoStringInDB(JSONObject.toJSONString(jsonObjectDB)));
JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(gatewayCase), JSONObject.class);
jsonObject.put("env", "gray");
jsonObject.put("interfaceType", "GateWay");
Date deBugCaseStartTime = new Date();
ResMsg resMsg = singleCaseDebugAPI.singleCaseDebug(jsonObject);
resMsg.setDebugCaseStartTime(deBugCaseStartTime);
// jsonpath
if(!StringUtil.isBlank(gatewayCase.getExtractJsonPath())){
String[] jsonPathList = gatewayCase.getExtractJsonPath().split("\r|\n");
System.out.println("executeHttpCaseStep jsonPathList length:" + jsonPathList.length);
for(int i = 0; i<jsonPathList.length; i++){
getResponseBodyJsonPathVal(sceneCaseId, resMsg.getResponsebody(), jsonPathList[i]);
}
}
System.out.println("Http用例执行的结果:" + JSONObject.toJSONString(resMsg));
return resMsg;
}
public static void getResponseBodyJsonPathVal(Integer sceneCaseId, String responseBody, String jsonPathKey){
System.out.println("jsonPathKey: " + jsonPathKey);
try{
Object actualValue = JsonPath.read(responseBody, jsonPathKey);
System.out.println("actualValue: " + actualValue.toString());
// 存入数据库的字段,以jsonpath分隔出的最后一个为准
String[] paramList = jsonPathKey.split("\\.");
System.out.println("paramList length: " + paramList.length);
String param = paramList[paramList.length - 1];
System.out.println("param: " + param);
boolean isNumber = isNumber(actualValue.toString());
boolean isBoolean = isBoolean(actualValue.toString());
ScenePublicParameter scenePublicParameterDB = scenePublicParameterService.findPublicParamValueBySceneCaseIdAndName(sceneCaseId, param);
if(scenePublicParameterDB == null){
System.out.println("新增~~");
ScenePublicParameter scenePublicParameter = new ScenePublicParameter();
scenePublicParameter.setSceneCaseId(sceneCaseId);
scenePublicParameter.setSceneParameterName(param);
if(isNumber){
// 这种类型怎么办
scenePublicParameter.setSceneParameterType("整数");
scenePublicParameter.setSceneParameterVal(String.valueOf(actualValue));
} else if(isBoolean){
scenePublicParameter.setSceneParameterType("布尔");
scenePublicParameter.setSceneParameterVal(String.valueOf(actualValue));
} else {
scenePublicParameter.setSceneParameterType("字符串");
scenePublicParameter.setSceneParameterVal((String)actualValue);
}
scenePublicParameterService.insert(scenePublicParameter);
}else {
System.out.println("更新~~");
if(isNumber){
scenePublicParameterDB.setSceneParameterVal(String.valueOf(actualValue));
} else if(isBoolean){
scenePublicParameterDB.setSceneParameterVal(String.valueOf(actualValue));
} else {
scenePublicParameterDB.setSceneParameterVal((String)actualValue);
}
scenePublicParameterService.update(scenePublicParameterDB);
}
}catch (Exception e){
e.printStackTrace();
}
}
public static boolean isNumber(String str) {
//采用正则表达式的方式来判断一个字符串是否为数字,这种方式判断面比较全
//可以判断正负、整数小数
boolean isInt = Pattern.compile("^-?[1-9]\\d*$").matcher(str).find();
boolean isDouble = Pattern.compile("^-?([1-9]\\d*\\.\\d*|0\\.\\d*[1-9]\\d*|0?\\.0+|0)$").matcher(str).find();
return isInt || isDouble;
}
public static boolean isBoolean(String str){
return str.equals("true") || str.equals("false");
}
}
Among them, five objects, sceneCaseService, sceneStepsService..., are obtained from the Spring container using @Autowired, and they are all declared as static variables. But when using the above variables, I found that the above five variables will report a null pointer error
2. Problem analysis
We all know that static variables and static methods belong to classes; ordinary variables and ordinary methods belong to instantiated objects (that is, objects processed by new); Spring dependency injection is to instantiate objects in the container;
When we use static variables or static methods, we do not need to create an instantiated object of a class, so when using @Autowired to decorate a static variable, the static variable is not actually instantiated into an object, so the static variable is null
Three, the solution
3.1 Add @Autowired to the construction method
@Component
public class RuleFunctions {
@Autowired
private static RuleEntityItemInfoBiz ruleEntityItemInfoBiz;
@Autowired
public RuleFunctions(RuleEntityItemInfoBiz ruleEntityItemInfoBiz) {
RuleFunctions.ruleEntityItemInfoBiz = ruleEntityItemInfoBiz;
}
public static double calculateCurrentMonthIncomeTax(String fileId, String salaryMonth, String taxPlanId){
//此处省略方法实现逻辑
}
}
3.2 Use the set method
@Component
public class RuleFunctions {
@Autowired
private static RuleEntityItemInfoBiz ruleEntityItemInfoBiz;
@Autowired
public void setRuleEntityItemInfoBiz(RuleEntityItemInfoBiz ruleEntityItemInfoBiz) {
RuleFunctions.ruleEntityItemInfoBiz = ruleEntityItemInfoBiz;
}
public static double calculateCurrentMonthIncomeTax(String fileId, String salaryMonth, String taxPlanId){
//此处省略方法实现逻辑
}
}
3.3 Use @PostConstruct annotation
3.3.1 @PostConstruct annotation
The @PostConstruct annotation is located in the javax.annotation package and can be used to modify a non-static void return method.
The execution timing of the method modified by the @PostConstruct annotation is: it runs when the server loads the Servlet, and it will only be loaded once by the server; after the constructor, it is executed before the init() method
The method parameter specification modified by @PostConstruct annotation: Except for the special case of interceptor, no parameters are allowed in other cases, otherwise the Spring framework will report IllegalStateException; and if the return value is void, it can actually have a return value, at least not Will report an error, just ignore
The @PostConstruct annotation is used in the Spring framework. The execution sequence of the method modified by this annotation in the entire Bean initialization process is as follows:
Constructor (construction method) -> @Autowired (dependency injection) -> @PostConstruct (annotated method)
3.3.2 Practical examples
@Component
public class RuleFunctions {
@Autowired
private RuleEntityItemInfoBiz ruleEntityItemInfoBiz;
private static RuleEntityItemInfoBiz staticRuleEntityItemInfoBiz;
/**
*注释用于在完成依赖注入以后执行任何初始化之后需要执行的方法。必须在类投入使用之前调用此方法。
*/
@PostConstruct
public void beforeInit() {
staticRuleEntityItemInfoBiz = ruleEntityItemInfoBiz;
}
public static double calculateCurrentMonthIncomeTax(String fileId, String salaryMonth, String taxPlanId){
//此处省略方法实现逻辑
}
}
Reference blog post: Solve the problem that spingboot uses @Resource to inject static variables and report null pointers