關(guān)于Controller 層返回值的公共包裝類的問(wèn)題
場(chǎng)景:在微服務(wù)中,一般返回?cái)?shù)據(jù)都會(huì)有個(gè)返回碼、返回信息和返回消息體,但是每次返回時(shí)候調(diào)用或者是封裝,太過(guò)麻煩,有沒(méi)有什么辦法不用每次都封裝呢?
答案是有的。
返回值對(duì)象 ResponseData
package com.study.auth.comm;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.annotation.JSONField;
import com.alibaba.fastjson.serializer.SerializerFeature;
import java.io.Serializable;
/**
* @Package: com.study.auth.comm
* @Description: <返回?cái)?shù)據(jù)>
* @Author: MILLA
* @CreateDate: 2018/4/8 9:10
* @UpdateUser: MILLA
* @UpdateDate: 2018/4/8 9:10
* @Version: 1.0
*/
public final class ResponseData<T> implements Serializable {
private static final long serialVersionUID = 7824278330465676943L;
private static final String SUCCESS_CODE = "1000";
private static final String SUCCESS_MSG = "success";
/**
* 響應(yīng)編碼
*/
@JSONField(serialzeFeatures = {SerializerFeature.WriteMapNullValue}, ordinal = 1)
private String code;
/**
* 響應(yīng)提示
*/
@JSONField(serialzeFeatures = {SerializerFeature.WriteMapNullValue}, ordinal = 2)
private String msg;
/**
* 返回的數(shù)據(jù)
*/
@JSONField(serialzeFeatures = {SerializerFeature.WriteMapNullValue}, ordinal = 10)
private T data;
public static ResponseData success() {
return initData(SUCCESS_CODE, SUCCESS_MSG, null);
}
public static ResponseData error(String code) {
String msg = PropertiesReaderUtil.getProperty(code, null);
return initData(code, msg, null);
}
public static ResponseData error(String code, String msg) {
return initData(code, msg, null);
}
public static <T> ResponseData success(T t) {
return initData(SUCCESS_CODE, SUCCESS_MSG, t);
}
public static <T> ResponseData errorData(String code, T data) {
String msg = PropertiesReaderUtil.getProperty(code, null);
return initData(code, msg, data);
}
public static <T> ResponseData errorData(String code, String msg, T data) {
return initData(code, msg, data);
}
private static <T> ResponseData initData(String code, String msg, T t) {
ResponseData data = new ResponseData(SUCCESS_CODE);
if (!isBlank(msg)) {
data.setMsg(msg);
}
if (!isBlank(code)) {
data.setCode(code);
}
if (t != null) {
data.setData(t);
}
return data;
}
private static boolean isBlank(CharSequence cs) {
int strLen;
if (cs != null && (strLen = cs.length()) != 0) {
for (int i = 0; i < strLen; ++i) {
if (!Character.isWhitespace(cs.charAt(i))) {
return false;
}
}
return true;
} else {
return true;
}
}
public ResponseData() {
}
public ResponseData(String code) {
this.code = code;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
@Override
public String toString() {
return JSON.toJSONString(this);
}
}

如上圖的包裝,還是太繁瑣了。
裝飾者模式使用-增強(qiáng)類InitializingAdviceDecorator通過(guò)實(shí)現(xiàn)InitializingBean和裝飾者模式對(duì)Controller層的返回值進(jìn)行包裝,大致思路:
通過(guò)RequestMappingHandlerAdapter獲取所有的返回值處理對(duì)象HandlerMethodReturnValueHandler創(chuàng)建一個(gè)新的集合存儲(chǔ)上一步獲取的集合(因?yàn)樯弦徊降慕Y(jié)果是unmodifiableList類型的)遍歷該集合找到HandlerMethodReturnValueHandler對(duì)象,將這個(gè)位置的handler替換程自定義的handler將新獲到的集合重新設(shè)置到RequestMappingHandlerAdapter的setReturnValueHandlers方法中
package com.study.auth.config;
import com.study.auth.comm.ResponseData;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.MethodParameter;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;
import java.util.ArrayList;
import java.util.List;
/**
* @Package: com.study.auth.config.core
* @Description: <增強(qiáng)controller層返回值>
* @Author: milla
* @CreateDate: 2020/09/04 14:42
* @UpdateUser: milla
* @UpdateDate: 2020/09/04 14:42
* @UpdateRemark: <>
* @Version: 1.0
*/
@Configuration
public class InitializingAdviceDecorator implements InitializingBean {
@Autowired
private RequestMappingHandlerAdapter adapter;
@Override
public void afterPropertiesSet() {
//獲取所有的handler對(duì)象
List<HandlerMethodReturnValueHandler> returnValueHandlers = adapter.getReturnValueHandlers();
//因?yàn)樯厦娣祷氐氖莡nmodifiableList,所以需要新建list處理
List<HandlerMethodReturnValueHandler> handlers = new ArrayList(returnValueHandlers);
this.decorateHandlers(handlers);
//將增強(qiáng)的返回值回寫(xiě)回去
adapter.setReturnValueHandlers(handlers);
}
/**
* 使用自定義的返回值控制類
*
* @param handlers
*/
private void decorateHandlers(List<HandlerMethodReturnValueHandler> handlers) {
for (HandlerMethodReturnValueHandler handler : handlers) {
if (handler instanceof RequestResponseBodyMethodProcessor) {
//找到返回值的handler并將起包裝成自定義的handler
ControllerReturnValueHandler decorator = new ControllerReturnValueHandler((RequestResponseBodyMethodProcessor) handler);
int index = handlers.indexOf(handler);
handlers.set(index, decorator);
break;
}
}
}
/**
* 自定義返回值的Handler
* 采用裝飾者模式
*/
private class ControllerReturnValueHandler implements HandlerMethodReturnValueHandler {
//持有一個(gè)被裝飾者對(duì)象
private HandlerMethodReturnValueHandler handler;
ControllerReturnValueHandler(RequestResponseBodyMethodProcessor handler) {
this.handler = handler;
}
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return true;
}
/**
* 增強(qiáng)被裝飾者的功能
*
* @param returnValue 返回值
* @param returnType 返回類型
* @param mavContainer view
* @param webRequest 請(qǐng)求對(duì)象
* @throws Exception 拋出異常
*/
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
//如果已經(jīng)封裝了結(jié)構(gòu)體就直接放行
if (returnValue instanceof ResponseData) {
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
return;
}
//正常返回success
ResponseData success = ResponseData.success(returnValue);
handler.handleReturnValue(success, returnType, mavContainer, webRequest);
}
}
}
配置文件讀取類PropertiesReaderUtil
package com.study.auth.comm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Properties;
/**
* @Package: com.milla.navicat.comm
* @Description: <讀取配置properties工具類>
* @Author: MILLA
* @CreateDate: 2018/8/10 10:30
* @UpdateUser: MILLA
* @UpdateDate: 2018/8/10 10:30
* @UpdateRemark: <>
* @Version: 1.0
*/
public final class PropertiesReaderUtil {
private static final String ENCODING = "UTF-8";
private static final Logger logger = LoggerFactory.getLogger(PropertiesReaderUtil.class);
private static Properties propsZH;
private static Properties propsCN;
private static String name = null;
static {
//加載英文
//loadProps(false);
//加載中文
loadProps(true);
}
/**
* 第一種,通過(guò)類加載器進(jìn)行獲取properties文件流
* 第二種,通過(guò)類進(jìn)行獲取properties文件流
* in = PropertyUtil.class.getResourceAsStream("/properties/message_ZH.properties");
* in = PropertiesReaderUtil.class.getClassLoader().getResourceAsStream("properties/message_ZH.properties");
*/
synchronized static private void loadProps(boolean isZh) {
logger.debug("start loading properties");
InputStream in = null;
if (isZh) {
propsZH = new Properties();
name = "properties/message_ZH.properties";
in = PropertiesReaderUtil.class.getClassLoader().getResourceAsStream(name);
} else {
propsCN = new Properties();
name = "properties/message_EN.properties";
in = PropertiesReaderUtil.class.getClassLoader().getResourceAsStream(name);
}
try {
if (isZh) {
propsZH.load(new InputStreamReader(in, ENCODING));
} else {
propsCN.load(new InputStreamReader(in, ENCODING));
}
} catch (Exception e) {
logger.debug("loading properties error :{}", e);
} finally {
try {
if (null != in) {
in.close();
}
} catch (IOException e) {
logger.debug("closing properties io error :{}", e);
}
}
}
public static String getProperty(String key) {
return getPropertyZH(key);
}
public static String getProperty(String key, String defaultValue) {
return getPropertyZH(key, defaultValue);
}
public static String getPropertyZH(String key) {
if (null == propsZH) {
loadProps(true);
}
return propsZH.getProperty(key);
}
public static String getPropertyZH(String key, String defaultValue) {
if (null == propsZH) {
loadProps(true);
}
return propsZH.getProperty(key, defaultValue);
}
public static String getPropertyCN(String key) {
if (null == propsCN) {
loadProps(false);
}
return propsCN.getProperty(key);
}
public static String getPropertyCN(String key, String defaultValue) {
if (null == propsCN) {
loadProps(false);
}
return propsCN.getProperty(key, defaultValue);
}
}
配置文件message_ZH.properties路徑為:properties/message_ZH.properties
也可添加國(guó)家化英文或者是其他語(yǔ)言配置
1001=用戶未登錄
#====非業(yè)務(wù)返回碼=========
1100=服務(wù)器內(nèi)部錯(cuò)誤
1101=空指針異常
1102=數(shù)據(jù)類型轉(zhuǎn)換異常
1103=IO異常
1104=該方法找不到異常
1105=數(shù)組越界異常
1106=請(qǐng)求體缺失異常
1107=類型匹配異常
1108=請(qǐng)求參數(shù)缺失異常
1109=請(qǐng)求方法不支持異常
1110=請(qǐng)求頭類型不支持異常
1111=參數(shù)解析異常
1112=必要參數(shù)不能為空
#=======================
統(tǒng)一異常捕捉類RestfulExceptionHandler此時(shí),基本能保證增強(qiáng)Controller層的返回值了,如果有需要的話,可能通過(guò)@RestControllerAdvice注解,針對(duì)拋出的異常使用返回值對(duì)象進(jìn)行包裝
package com.study.auth.exception;
import com.alibaba.fastjson.JSONException;
import com.study.auth.comm.PropertiesReaderUtil;
import com.study.auth.comm.ResponseData;
import com.study.auth.constant.CommonConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.TypeMismatchException;
import org.springframework.boot.json.JsonParseException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.security.auth.login.AccountException;
import java.io.IOException;
import java.sql.SQLException;
/**
* @Package: com.study.auth.exception
* @Description: <所有異常攔截類>
* @Author: milla
* @CreateDate: 2020/09/04 15:35
* @UpdateUser: milla
* @UpdateDate: 2020/09/04 15:35
* @UpdateRemark: <>
* @Version: 1.0
*/
@Slf4j
@RestControllerAdvice
public class RestfulExceptionHandler {
private ResponseData responseData(String code, Exception e) {
log.error("異常代碼:{},異常描述:{},異常堆棧:", code, PropertiesReaderUtil.getProperty(code), e);
return ResponseData.error(code);
}
private ResponseData<String> responseData(String code, String message, Exception e) {
log.error("異常代碼:{},異常描述:{},異常堆棧:", code, message, e);
return ResponseData.error(code, message);
}
/**
* 運(yùn)行時(shí)異常
*
* @param e 異常
* @return
*/
@ExceptionHandler(Exception.class)
public ResponseData runtimeExceptionHandler(Exception e) {
return responseData(CommonConstant.EX_RUN_TIME_EXCEPTION, e);
}
/**
* 處理SQLSyntaxErrorException
*
* @param e 異常
* @return
*/
@ExceptionHandler(SQLException.class)
public ResponseData<String> sqlException(SQLException e) {
return responseData(CommonConstant.EX_RUN_TIME_EXCEPTION, e.getMessage(), e);
}
/**
* 處理CustomerMessageException
*
* @param e 異常
* @return
*/
@ExceptionHandler(CustomMessageException.class)
public ResponseData<String> customerMessageException(CustomMessageException e) {
return responseData(CommonConstant.EX_RUN_TIME_EXCEPTION, e.getMessage(), e);
}
/**
* 處理AccountException
*
* @param e 異常
* @return
*/
@ExceptionHandler(AccountException.class)
public ResponseData<String> accountException(AccountException e) {
return responseData(e.getMessage(), e);
}
//---------------------------------------jdk/spring自帶的異常----------------------------------
/**
* 處理IllegalArgumentException
*
* @param e 異常
* @return
*/
@ExceptionHandler(IllegalArgumentException.class)
public ResponseData<String> illegalArgumentException(IllegalArgumentException e) {
return responseData(CommonConstant.EX_RUN_TIME_EXCEPTION, e.getMessage(), e);
}
/**
* 空指針異常
*
* @param e 異常
* @return
*/
@ResponseStatus
@ExceptionHandler(NullPointerException.class)
public ResponseData nullPointerExceptionHandler(NullPointerException e) {
return responseData(CommonConstant.EX_NULL_POINTER_EXCEPTION, e);
}
/**
* 類型轉(zhuǎn)換異常
*
* @param e 異常
* @return
*/
@ExceptionHandler(ClassCastException.class)
public ResponseData classCastExceptionHandler(ClassCastException e) {
return responseData(CommonConstant.EX_CLASS_CAST_EXCEPTION, e);
}
/**
* IO異常
*
* @param e 異常
* @return
*/
@ExceptionHandler(IOException.class)
public ResponseData iOExceptionHandler(IOException e) {
return responseData(CommonConstant.EX_IO_EXCEPTION, e);
}
/**
* 未知方法異常
*
* @param e 異常
* @return
*/
@ExceptionHandler(NoSuchMethodException.class)
public ResponseData noSuchMethodExceptionHandler(NoSuchMethodException e) {
return responseData(CommonConstant.EX_NO_SUCH_METHOD_EXCEPTION, e);
}
/**
* 數(shù)組越界異常
*
* @param e 異常
* @return
*/
@ExceptionHandler(IndexOutOfBoundsException.class)
public ResponseData indexOutOfBoundsExceptionHandler(IndexOutOfBoundsException e) {
return responseData(CommonConstant.EX_INDEX_OUT_OF_BOUNDS_EXCEPTION, e);
}
/**
* 請(qǐng)求body缺失異常
*
* @param e 異常
* @return
*/
@ExceptionHandler({HttpMessageNotReadableException.class})
public ResponseData requestNotReadable(HttpMessageNotReadableException e) {
return responseData(CommonConstant.EX_HTTP_MESSAGE_NOT_READABLE_EXCEPTION, e);
}
/**
* 類型匹配異常
*
* @param e 異常
* @return
*/
@ExceptionHandler({TypeMismatchException.class})
public ResponseData requestTypeMismatch(TypeMismatchException e) {
return responseData(CommonConstant.EX_HTTP_MESSAGE_NOT_READABLE_EXCEPTION, e);
}
/**
* 方法不支持異常
*
* @param e 異常
* @return
*/
@ExceptionHandler({HttpRequestMethodNotSupportedException.class})
public ResponseData methodNotSupported(HttpRequestMethodNotSupportedException e) {
return responseData(CommonConstant.EX_HTTP_REQUEST_METHOD_NOT_SUPPORTED_EXCEPTION, e);
}
/**
* 請(qǐng)求頭不支持異常
*
* @param e 異常
* @return
*/
@ExceptionHandler({HttpMediaTypeNotSupportedException.class})
public ResponseData mediaTypeNotAcceptable(HttpMediaTypeNotSupportedException e) {
return responseData(CommonConstant.EX_HTTP_MEDIA_TYPE_NOT_ACCEPTABLE_EXCEPTION, e);
}
/**
* 參數(shù)解析異常
*
* @param e 異常
* @return
*/
@ExceptionHandler(JSONException.class)
public ResponseData runtimeExceptionHandler(JSONException e) {
return responseData(CommonConstant.PARAMS_PARSE_EXCEPTION, e);
}
/**
* 參數(shù)解析異常
*
* @param e 異常
* @return
*/
@ExceptionHandler(JsonParseException.class)
public ResponseData runtimeExceptionHandler(JsonParseException e) {
return responseData(CommonConstant.PARAMS_PARSE_EXCEPTION, e);
}
/**
* 請(qǐng)求參數(shù)缺失異常
*
* @param e 異常
* @return
*/
@ExceptionHandler({MissingServletRequestParameterException.class})
public ResponseData requestMissingServletRequest(MissingServletRequestParameterException e) {
return responseData(CommonConstant.EX_MISSING_SERVLET_REQUEST_PARAMETER_EXCEPTION, e);
}
/**
* 參數(shù)不能為空
*
* @param e 異常
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseData exceptionHandler(MethodArgumentNotValidException e) {
return responseData(CommonConstant.PARAMS_IS_NULL, e);
}
}
常量類 CommonConstant
package com.study.auth.constant;
/**
* @Package: com.study.auth.constant
* @Description: <公共常量類>
* @Author: milla
* @CreateDate: 2020/09/04 15:37
* @UpdateUser: milla
* @UpdateDate: 2020/09/04 15:37
* @UpdateRemark: <>
* @Version: 1.0
*/
public final class CommonConstant {
/**
* 當(dāng)前用戶名稱
*/
public static final String C_CURRENT_ACCOUNT = "current_account";
/**
* 用戶未登錄
*/
public static final String EX_NO_TOKEN_EXCEPTION = "1001";
//--------------------------------非業(yè)務(wù)返回碼---------------------------------------
/**
* 運(yùn)行時(shí)異常
*/
public static final String EX_RUN_TIME_EXCEPTION = "1100";
/**
* 空指針異常
*/
public static final String EX_NULL_POINTER_EXCEPTION = "1101";
/**
* 數(shù)據(jù)轉(zhuǎn)換異常
*/
public static final String EX_CLASS_CAST_EXCEPTION = "1102";
/**
* IO異常
*/
public static final String EX_IO_EXCEPTION = "1103";
/**
* 找不到該方法異常
*/
public static final String EX_NO_SUCH_METHOD_EXCEPTION = "1104";
/**
* 數(shù)組越界異常
*/
public static final String EX_INDEX_OUT_OF_BOUNDS_EXCEPTION = "1105";
/**
* 請(qǐng)求體缺失異常
*/
public static final String EX_HTTP_MESSAGE_NOT_READABLE_EXCEPTION = "1106";
/**
* TYPE匹配異常
*/
public static final String EX_TYPE_MISMATCH_EXCEPTION = "1107";
/**
* 請(qǐng)求參數(shù)丟失
*/
public static final String EX_MISSING_SERVLET_REQUEST_PARAMETER_EXCEPTION = "1108";
/**
* 請(qǐng)求方法類型不支持異常
*/
public static final String EX_HTTP_REQUEST_METHOD_NOT_SUPPORTED_EXCEPTION = "1109";
/**
* MEDIA 類型不支持異常
*/
public static final String EX_HTTP_MEDIA_TYPE_NOT_ACCEPTABLE_EXCEPTION = "1110";
/**
* 參數(shù)解析異常
*/
public static final String PARAMS_PARSE_EXCEPTION = "1111";
/**
* 參數(shù)不能為空
*/
public static final String PARAMS_IS_NULL = "1112";
//-----------------------------------------------------------------------------------
}
自定義異常類 CustomMessageException
package com.study.auth.exception;
/**
* @Package: com.study.auth.exception
* @Description: <自定義異常類>
* @Author: MILLA
* @CreateDate: 2019/8/15 18:39
* @UpdateUser: MILLA
* @UpdateDate: 2019/8/15 18:39
* @UpdateRemark: <>
* @Version: 1.0
*/
public class CustomMessageException extends RuntimeException {
public CustomMessageException() {
super();
}
public CustomMessageException(String message) {
super(message);
}
public CustomMessageException(String message, Throwable cause) {
super(message, cause);
}
public CustomMessageException(Throwable cause) {
super(cause);
}
protected CustomMessageException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
所需依賴因?yàn)槭褂昧税⒗锏膄astJson工具類還需要進(jìn)入該類的依賴
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
至此,可以愉快的使用該返回值的增強(qiáng)類了,在為服務(wù)中,還以將該代碼重構(gòu)到comm中,供多個(gè)服務(wù)共同使用,避免重復(fù)早輪子
到此這篇關(guān)于關(guān)于Controller 層返回值的公共包裝類的問(wèn)題的文章就介紹到這了,更多相關(guān)Controller 層返回值包裝類內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot2+JPA之悲觀鎖和樂(lè)觀鎖實(shí)戰(zhàn)教程
這篇文章主要介紹了Spring Boot2+JPA之悲觀鎖和樂(lè)觀鎖實(shí)戰(zhàn)教程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
Mybatis如何動(dòng)態(tài)創(chuàng)建表
這篇文章主要介紹了Mybatis如何動(dòng)態(tài)創(chuàng)建表問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-04-04
使用java打印心型、圓形圖案的實(shí)現(xiàn)代碼
這篇文章主要介紹了使用java打印心型、圓形圖案的實(shí)現(xiàn)代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12
Java Comparable和Comparator對(duì)比詳解
這篇文章主要介紹了Java Comparable和Comparator對(duì)比詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
Java NumberFormat 類的詳解及實(shí)例
這篇文章主要介紹了Java NumberFormat 類的詳解及實(shí)例的相關(guān)資料,數(shù)字格式化類按照本地風(fēng)格習(xí)慣進(jìn)行的數(shù)字顯示,需要的朋友可以參考下2017-08-08
springboot使用Thymeleaf報(bào)錯(cuò)常見(jiàn)的幾種解決方案
這篇文章主要介紹了springboot使用Thymeleaf報(bào)錯(cuò)常見(jiàn)的幾種解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11
IDEA2022創(chuàng)建Maven Web項(xiàng)目教程(圖文)
本文主要介紹了IDEA2022創(chuàng)建Maven Web項(xiàng)目教程,文中通過(guò)圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07

