一、背景介绍
我创建了两个Springboot工程,A作为公共工程,打包成Jar包供其他工程使用;B使用A提供的Jar包。A工程中的SceneCaseDebugAPI类的代码如下:
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");
}
}
其中的sceneCaseService、sceneStepsService…等五个对象是使用@Autowired从Spring容器中获取的,并都声明成了静态变量。但是在使用上述变量时发现,上述五个变量会报空指针错误
二、问题分析
我们都知道静态变量,静态方法都是属于类的;普通变量、普通方法都是属于实例化对象的(即new处理的对象);Spring依赖注入是在容器中实例化对象;
当我们使用静态变量或静态方法时,不需要new出来一个类的实例化对象,所以使用@Autowired修饰一个静态变量时,该静态变量并没有真正实例化成一个对象,因此该静态变量为null
三、解决方法
3.1 将@Autowired加到构造方法上
@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 使用set方法
@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 使用@PostConstruct注解
3.3.1 @PostConstruct注解
@PostConstruct注解位于javax.annotation包下,可以用来修饰一个非静态的void返回的方法。
被@PostConstruct注解修饰的方法的执行时机是:在服务器加载Servlet的时候运行,并且只会被服务器加载一次;在构造函数之后,init()方法之前执行
被@PostConstruct注解修饰的方法参数规范:除了拦截器这种特殊情况以外,其他情况都不允许有参数,否则Spring框架会报IllegalStateException;而且返回值要是void,但实际也可以有返回值,至少不会报错,只会忽略
Spring框架中使用到@PostConstruct注解,该注解修饰的方法在整个Bean初始化过程中的执行顺序如下:
Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)
3.3.2 实际例子
@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){
//此处省略方法实现逻辑
}
}