SpringBoot實(shí)戰(zhàn)之處理異常案例詳解
前段時(shí)間寫了一篇關(guān)于實(shí)現(xiàn)統(tǒng)一響應(yīng)信息的博文,根據(jù)文中實(shí)戰(zhàn)操作,能夠解決正常響應(yīng)的一致性,但想要實(shí)現(xiàn)優(yōu)雅響應(yīng),還需要優(yōu)雅的處理異常響應(yīng),所以有了這篇內(nèi)容。
作為后臺(tái)服務(wù),能夠正確的處理程序拋出的異常,并返回友好的異常信息是非常重要的,畢竟我們大部分代碼都是為了 處理異常情況。而且,統(tǒng)一的異常響應(yīng),有助于客戶端理解服務(wù)端響應(yīng),并作出正確處理,而且能夠提升接口的服務(wù)質(zhì)量。
SpringBoot提供了異常的響應(yīng),可以通過/error請(qǐng)求查看效果:

這是從瀏覽器打開的場(chǎng)景,也就是請(qǐng)求頭不包括content-type: applicaton/json,大白板一個(gè),和友好完全不搭邊。

這是請(qǐng)求頭包括content-type: applicaton/json時(shí)的響應(yīng),格式還行,但是我們還需要加工一下,實(shí)現(xiàn)自定義的異常碼和異常信息。
本文主要是針對(duì)RESTful請(qǐng)求的統(tǒng)一響應(yīng),想要實(shí)現(xiàn)的功能包括:
- 自動(dòng)封裝異常,返回統(tǒng)一響應(yīng)
- 異常信息國(guó)際化
定義異常響應(yīng)類
當(dāng)程序發(fā)送錯(cuò)誤時(shí),不應(yīng)該將晦澀的堆棧報(bào)告信息返回給API客戶端,從某種意義講,這是一種不禮貌的和不負(fù)責(zé)任的行為。
我們?cè)?a href="http://www.dbjr.com.cn/article/221714.htm" rel="external nofollow" target="_blank">SpringBoot 實(shí)戰(zhàn):一招實(shí)現(xiàn)結(jié)果的優(yōu)雅響應(yīng)中,定義了一個(gè)響應(yīng)類,為什么還要再定義一個(gè)異常響應(yīng)類呢?其實(shí)是為了語義明確且職責(zé)單一。類圖如下:

具體代碼如下:
基礎(chǔ)類BaseResponse:
@Data
public abstract class BaseResponse {
private Integer code;
private String desc;
private Date timestamp = new Date();
private String path;
public BaseResponse() {
}
public BaseResponse(final Integer code, final String desc) {
this.code = code;
this.desc = desc;
}
public BaseResponse(final Integer code, final String desc, final String path) {
this.code = code;
this.desc = desc;
this.path = path;
}
}
異常類ErrorResponse:
@EqualsAndHashCode(callSuper = true)
@Data
public class ErrorResponse extends BaseResponse {
public ErrorResponse(final Integer code, final String desc) {
super(code, desc);
}
public ErrorResponse(final Integer code, final String desc, final WebRequest request) {
super(code, desc, extractRequestURI(request));
}
public ErrorResponse(final HttpStatus status, final Exception e) {
super(status.value(), status.getReasonPhrase() + ": " + e.getMessage());
}
public ErrorResponse(final HttpStatus status, final Exception e, final WebRequest request) {
super(status.value(), status.getReasonPhrase() + ": " + e.getMessage(), extractRequestURI(request));
}
private static String extractRequestURI(WebRequest request) {
final String requestURI;
if (request instanceof ServletWebRequest) {
ServletWebRequest servletWebRequest = (ServletWebRequest) request;
requestURI = servletWebRequest.getRequest().getRequestURI();
} else {
requestURI = request.getDescription(false);
}
return requestURI;
}
}
定義異常枚舉類
為了能夠規(guī)范響應(yīng)碼和響應(yīng)信息,我們可以定義一個(gè)枚舉類。

枚舉接口ResponseEnum:
public interface ResponseEnum {
Integer getCode();
String getMessage();
default String getLocaleMessage() {
return getLocaleMessage(null);
}
String getLocaleMessage(Object[] args);
}
枚舉類CommonResponseEnum:
public enum CommonResponseEnum implements ResponseEnum {
BAD_REQUEST(HttpStatus.BAD_REQUEST.value(), "Bad Request"),
NOT_FOUND(HttpStatus.NOT_FOUND.value(), "Not Found"),
METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED.value(), "Method Not Allowed"),
NOT_ACCEPTABLE(HttpStatus.NOT_ACCEPTABLE.value(), "Not Acceptable"),
REQUEST_TIMEOUT(HttpStatus.REQUEST_TIMEOUT.value(), "Request Timeout"),
UNSUPPORTED_MEDIA_TYPE(HttpStatus.UNSUPPORTED_MEDIA_TYPE.value(), "Unsupported Media Type"),
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Server Error"),
SERVICE_UNAVAILABLE(HttpStatus.SERVICE_UNAVAILABLE.value(), "Service Unavailable"),
ILLEGAL_ARGUMENT(4000, "Illegal Argument"),
DATA_NOT_FOUND(4004, "Data Not Found"),
USER_NOT_FOUND(4104, "User Not Found"),
MENU_NOT_FOUND(4204, "Menu Not Found"),
INTERNAL_ERROR(9999, "Server Error"),
;
private final Integer code;
private final String message;
private MessageSource messageSource;
CommonResponseEnum(final Integer code, final String message) {
this.code = code;
this.message = message;
}
@Override
public Integer getCode() {
return code;
}
@Override
public String getMessage() {
return message;
}
@Override
public String getLocaleMessage(Object[] args) {
return messageSource.getMessage("response.error." + code, args, message, LocaleContextHolder.getLocale());
}
public void setMessageSource(final MessageSource messageSource) {
this.messageSource = messageSource;
}
@Component
public static class ReportTypeServiceInjector {
private final MessageSource messageSource;
public ReportTypeServiceInjector(final MessageSource messageSource) {
this.messageSource = messageSource;
}
@PostConstruct
public void postConstruct() {
for (final CommonResponseEnum anEnum : CommonResponseEnum.values()) {
anEnum.setMessageSource(messageSource);
}
}
}
}
需要注意的是,我們?cè)诋惓C杜e類中定義了ReportTypeServiceInjector類,這個(gè)類的作用是為枚舉類注入MessageSource對(duì)象,是為了實(shí)現(xiàn)異常信息的國(guó)際化。這部分功能Spring已經(jīng)封裝好了,我們只需要在resources目錄中定義一組messages.properties文件就可以了,比如:
message.properties定義默認(rèn)描述:
response.error.4000=[DEFAULT] Illegal Arguments response.error.4004=[DEFAULT] Not Found
messages_zh_CN.properties定義中文描述:
response.error.4004=對(duì)應(yīng)數(shù)據(jù)未找到
response.error.9999=系統(tǒng)異常,請(qǐng)求參數(shù): {0}
messages_en_US.properties定義英文描述:
response.error.4004=Not Found
自定義異常類
Java和Spring中提供了很多可用的異常類,可以滿足大部分場(chǎng)景,但是有時(shí)候我們希望異常類可以攜帶更多信息,所以還是需要自定義異常類:
- 可以攜帶我們想要的信息;
- 有更加明確語義;
- 附帶效果,可以知道這是手動(dòng)拋出的業(yè)務(wù)異常。
上代碼:
@Data
@EqualsAndHashCode(callSuper = true)
public class CodeBaseException extends RuntimeException {
private final ResponseEnum anEnum;
private final Object[] args;// 打印參數(shù)
private final String message;// 異常信息
private final Throwable cause;// 異常棧
public CodeBaseException(final ResponseEnum anEnum) {
this(anEnum, null, anEnum.getMessage(), null);
}
public CodeBaseException(final ResponseEnum anEnum, final String message) {
this(anEnum, null, message, null);
}
public CodeBaseException(final ResponseEnum anEnum, final Object[] args, final String message) {
this(anEnum, args, message, null);
}
public CodeBaseException(final ResponseEnum anEnum, final Object[] args, final String message, final Throwable cause) {
this.anEnum = anEnum;
this.args = args;
this.message = message;
this.cause = cause;
}
}
自定義異常信息處理類
前期準(zhǔn)備工作完成,接下來定義異常信息處理類。
Spring自帶的異常信息處理類往往不能滿足我們實(shí)際的業(yè)務(wù)需求,這就需要我們定義符合具體情況的異常信息處理類,在自定義異常信息處理類中,我們可以封裝更為詳細(xì)的異常報(bào)告。我們可以擴(kuò)展Spring提供的ResponseEntityExceptionHandler類定義自己的異常信息處理類,站在巨人的肩膀上,快速封裝自己需要的類。
通過源碼可以看到,ResponseEntityExceptionHandler類的核心方法是public final ResponseEntity<Object> handleException(Exception ex, WebRequest request),所有的異常都在這個(gè)方法中根據(jù)類型進(jìn)行處理,我們只需要實(shí)現(xiàn)具體的處理方法即可:
@RestControllerAdvice
@Slf4j
public class UnifiedExceptionHandlerV2 extends ResponseEntityExceptionHandler {
private static final String ENV_PROD = "prod";
private final MessageSource messageSource;
private final Boolean isProd;
public UnifiedExceptionHandlerV2(@Value("${spring.profiles.active:dev}") final String activeProfile, final MessageSource messageSource) {
this.messageSource = messageSource;
this.isProd = new HashSet<>(Arrays.asList(activeProfile.split(","))).contains(ENV_PROD);
}
@Override
protected ResponseEntity<Object> handleExceptionInternal(final Exception e, final Object body, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
log.info("請(qǐng)求異常:" + e.getMessage(), e);
if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) {
request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, e, WebRequest.SCOPE_REQUEST);
}
return new ResponseEntity<>(new ErrorResponse(status, e), headers, HttpStatus.OK);
}
@Override
protected ResponseEntity<Object> handleBindException(final BindException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
log.info("參數(shù)綁定異常", ex);
final ErrorResponse response = wrapperBindingResult(status, ex.getBindingResult());
return new ResponseEntity<>(response, headers, HttpStatus.OK);
}
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(final MethodArgumentNotValidException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
log.info("參數(shù)校驗(yàn)異常", ex);
final ErrorResponse response = wrapperBindingResult(status, ex.getBindingResult());
return new ResponseEntity<>(response, headers, HttpStatus.OK);
}
@ExceptionHandler(value = CodeBaseException.class)
@ResponseBody
public ErrorResponse handleBusinessException(CodeBaseException e) {
log.error("業(yè)務(wù)異常:" + e.getMessage(), e);
final ResponseEnum anEnum = e.getAnEnum();
return new ErrorResponse(anEnum.getCode(), anEnum.getLocaleMessage(e.getArgs()));
}
@ExceptionHandler(value = Exception.class)
@ResponseBody
public ErrorResponse handleExceptionInternal(Exception e) {
log.error("未捕捉異常:" + e.getMessage(), e);
final Integer code = INTERNAL_SERVER_ERROR.getCode();
return new ErrorResponse(code, getLocaleMessage(code, e.getMessage()));
}
/**
* 包裝綁定異常結(jié)果
*
* @param status HTTP狀態(tài)碼
* @param bindingResult 參數(shù)校驗(yàn)結(jié)果
* @return 異常對(duì)象
*/
private ErrorResponse wrapperBindingResult(HttpStatus status, BindingResult bindingResult) {
final List<String> errorDesc = new ArrayList<>();
for (ObjectError error : bindingResult.getAllErrors()) {
final StringBuilder msg = new StringBuilder();
if (error instanceof FieldError) {
msg.append(((FieldError) error).getField()).append(": ");
}
msg.append(error.getDefaultMessage() == null ? "" : error.getDefaultMessage());
errorDesc.add(msg.toString());
}
final String desc = isProd ? getLocaleMessage(status.value(), status.getReasonPhrase()) : String.join(", ", errorDesc);
return new ErrorResponse(status.value(), desc);
}
private String getLocaleMessage(Integer code, String defaultMsg) {
try {
return messageSource.getMessage("" + code, null, defaultMsg, LocaleContextHolder.getLocale());
} catch (Throwable t) {
log.warn("本地化異常消息發(fā)生異常: {}", code);
return defaultMsg;
}
}
}
如果感覺Spring的ResponseEntityExceptionHandler類不夠靈活,也可以完全自定義異常處理類:
@RestControllerAdvice
@Slf4j
public class UnifiedExceptionHandler {
private static final String ENV_PROD = "prod";
private final MessageSource messageSource;
private final Boolean isProd;
public UnifiedExceptionHandler(@Value("${spring.profiles.active:dev}") final String activeProfile, final MessageSource messageSource) {
this.messageSource = messageSource;
this.isProd = new HashSet<>(Arrays.asList(activeProfile.split(","))).contains(ENV_PROD);
}
@ExceptionHandler({
MissingServletRequestParameterException.class,// 缺少servlet請(qǐng)求參數(shù)異常處理方法
ServletRequestBindingException.class,// servlet請(qǐng)求綁定異常
TypeMismatchException.class,// 類型不匹配
HttpMessageNotReadableException.class,// 消息無法檢索
MissingServletRequestPartException.class// 缺少servlet請(qǐng)求部分
})
public ErrorResponse badRequestException(Exception e, WebRequest request) {
log.info(e.getMessage(), e);
return new ErrorResponse(BAD_REQUEST.getCode(), e.getMessage(), request);
}
@ExceptionHandler({
NoHandlerFoundException.class// 沒有發(fā)現(xiàn)處理程序異常
})
public ErrorResponse noHandlerFoundException(Exception e, WebRequest request) {
log.info(e.getMessage(), e);
return new ErrorResponse(NOT_FOUND.getCode(), e.getMessage(), request);
}
@ExceptionHandler({
HttpRequestMethodNotSupportedException.class// 不支持的HTTP請(qǐng)求方法異常信息處理方法
})
public ErrorResponse httpRequestMethodNotSupportedException(Exception e, WebRequest request) {
log.info(e.getMessage(), e);
return new ErrorResponse(METHOD_NOT_ALLOWED.getCode(), e.getMessage(), request);
}
@ExceptionHandler({
HttpMediaTypeNotAcceptableException.class// 不接受的HTTP媒體類型異常處方法
})
public ErrorResponse httpMediaTypeNotAcceptableException(Exception e, WebRequest request) {
log.info(e.getMessage(), e);
return new ErrorResponse(NOT_ACCEPTABLE.getCode(), e.getMessage(), request);
}
@ExceptionHandler({
HttpMediaTypeNotSupportedException.class// 不支持的HTTP媒體類型異常處理方法
})
public ErrorResponse httpMediaTypeNotSupportedException(Exception e, WebRequest request) {
log.info(e.getMessage(), e);
return new ErrorResponse(UNSUPPORTED_MEDIA_TYPE.getCode(), e.getMessage(), request);
}
@ExceptionHandler({
AsyncRequestTimeoutException.class// 異步請(qǐng)求超時(shí)異常
})
public ErrorResponse asyncRequestTimeoutException(Exception e, WebRequest request) {
log.info(e.getMessage(), e);
return new ErrorResponse(SERVICE_UNAVAILABLE.getCode(), e.getMessage(), request);
}
@ExceptionHandler({
MissingPathVariableException.class,// 請(qǐng)求路徑參數(shù)缺失異常處方法
HttpMessageNotWritableException.class,// HTTP消息不可寫
ConversionNotSupportedException.class,// 不支持轉(zhuǎn)換
})
public ErrorResponse handleServletException(Exception e, WebRequest request) {
log.error(e.getMessage(), e);
return new ErrorResponse(INTERNAL_SERVER_ERROR.getCode(), e.getMessage(), request);
}
@ExceptionHandler({
BindException.class// 參數(shù)綁定異常
})
@ResponseBody
public ErrorResponse handleBindException(BindException e, WebRequest request) {
log.error("參數(shù)綁定異常", e);
return wrapperBindingResult(e.getBindingResult(), request);
}
/**
* 參數(shù)校驗(yàn)異常,將校驗(yàn)失敗的所有異常組合成一條錯(cuò)誤信息
*/
@ExceptionHandler({
MethodArgumentNotValidException.class// 方法參數(shù)無效
})
@ResponseBody
public ErrorResponse handleValidException(MethodArgumentNotValidException e, WebRequest request) {
log.error("參數(shù)校驗(yàn)異常", e);
return wrapperBindingResult(e.getBindingResult(), request);
}
/**
* 包裝綁定異常結(jié)果
*/
private ErrorResponse wrapperBindingResult(BindingResult bindingResult, WebRequest request) {
final List<String> errorDesc = new ArrayList<>();
for (ObjectError error : bindingResult.getAllErrors()) {
final StringBuilder msg = new StringBuilder();
if (error instanceof FieldError) {
msg.append(((FieldError) error).getField()).append(": ");
}
msg.append(error.getDefaultMessage() == null ? "" : error.getDefaultMessage());
errorDesc.add(msg.toString());
}
final String desc = isProd ? getLocaleMessage(BAD_REQUEST.getCode(), "") : String.join(", ", errorDesc);
return new ErrorResponse(BAD_REQUEST.getCode(), desc, request);
}
/**
* 業(yè)務(wù)異常
*/
@ExceptionHandler(value = CodeBaseException.class)
@ResponseBody
public ErrorResponse handleBusinessException(CodeBaseException e, WebRequest request) {
log.error("業(yè)務(wù)異常:" + e.getMessage(), e);
final ResponseEnum anEnum = e.getAnEnum();
return new ErrorResponse(anEnum.getCode(), anEnum.getLocaleMessage(e.getArgs()), request);
}
/**
* 未定義異常
*/
@ExceptionHandler(value = Exception.class)
@ResponseBody
public ErrorResponse handleExceptionInternal(Exception e, WebRequest request) {
log.error("未捕捉異常:" + e.getMessage(), e);
final Integer code = INTERNAL_SERVER_ERROR.getCode();
return new ErrorResponse(code, getLocaleMessage(code, e.getMessage()), request);
}
private String getLocaleMessage(Integer code, String defaultMsg) {
try {
return messageSource.getMessage("" + code, null, defaultMsg, LocaleContextHolder.getLocale());
} catch (Throwable t) {
log.warn("本地化異常消息發(fā)生異常: {}", code);
return defaultMsg;
}
}
}
從上面兩個(gè)類可以看出,比較核心的是這么幾個(gè)注解:
- @ExceptionHandle:負(fù)責(zé)處理controller標(biāo)注的類中拋出的異常的注解
- @RestControllerAdvice:能夠?qū)ExceptionHandler標(biāo)注的方法集中到一個(gè)地方進(jìn)行處理的注解,這個(gè)注解是復(fù)合注解,實(shí)現(xiàn)了
@ControllerAdvice和@ResponseBody的功能。
借用譚朝紅博文中的圖片(藍(lán)色箭頭表示正常的請(qǐng)求和響應(yīng),紅色箭頭表示發(fā)生異常的請(qǐng)求和響應(yīng)):

寫個(gè)Demo測(cè)試一下
接下來我們寫個(gè)demo測(cè)試一下是否能夠?qū)崿F(xiàn)異常的優(yōu)雅響應(yīng):
@RestController
@RequestMapping("index")
@Slf4j
public class IndexController {
private final IndexService indexService;
public IndexController(final IndexService indexService) {
this.indexService = indexService;
}
@GetMapping("hello1")
public Response<String> hello1() {
Response<String> response = new Response<>();
try {
response.setCode(200);
response.setDesc("請(qǐng)求成功");
response.setData(indexService.hello());
} catch (Exception e) {
log.error("hello1方法請(qǐng)求異常", e);
response.setCode(500);
response.setDesc("請(qǐng)求異常:" + e.getMessage());
} finally {
log.info("執(zhí)行controller的finally結(jié)構(gòu)");
}
return response;
}
@GetMapping("hello2")
public Response<String> hello2(@RequestParam("ex") String ex) {
switch (ex) {
case "ex1":
throw new CodeBaseException(CommonResponseEnum.USER_NOT_FOUND, "用戶信息不存在");
case "ex2":
throw new CodeBaseException(CommonResponseEnum.MENU_NOT_FOUND, "菜單信息不存在");
case "ex3":
throw new CodeBaseException(CommonResponseEnum.ILLEGAL_ARGUMENT, "請(qǐng)求參數(shù)異常");
case "ex4":
throw new CodeBaseException(CommonResponseEnum.DATA_NOT_FOUND, "數(shù)據(jù)不存在");
}
throw new CodeBaseException(INTERNAL_ERROR, new Object[]{ex}, "請(qǐng)求異常", new RuntimeException("運(yùn)行時(shí)異常信息"));
}
}
啟動(dòng)服務(wù)之后,傳入不同參數(shù)獲取不同的異常信息:
// 請(qǐng)求 /index/hello2?ex=ex1
{
"code": 4104,
"desc": "User Not Found",
"timestamp": "2020-10-10T05:58:39.433+00:00",
"path": "/index/hello2"
}
// 請(qǐng)求 /index/hello2?ex=ex2
{
"code": 4204, "desc": "Menu Not Found",
"timestamp": "2020-10-10T06:00:34.141+00:00",
"path": "/index/hello2"
}
// 請(qǐng)求 /index/hello2?ex=ex3
{
"code": 4000,
"desc": "[DEFAULT] Illegal Arguments",
"timestamp": "2020-10-10T06:00:44.233+00:00",
"path": "/index/hello2"
}
// 請(qǐng)求 /index/hello2?ex=ex4
{
"code": 4004,
"desc": "對(duì)應(yīng)數(shù)據(jù)未找到",
"timestamp": "2020-10-10T06:00:54.178+00:00",
"path": "/index/hello2"
}
附上文中的代碼:https://github.com/howardliu-cn/effective-spring/tree/main/spring-exception-handler,收工。
推薦閱讀
- SpringBoot 實(shí)戰(zhàn):一招實(shí)現(xiàn)結(jié)果的優(yōu)雅響應(yīng)
- SpringBoot 實(shí)戰(zhàn):如何優(yōu)雅的處理異常
- SpringBoot 實(shí)戰(zhàn):通過 BeanPostProcessor 動(dòng)態(tài)注入 ID 生成器
- SpringBoot 實(shí)戰(zhàn):自定義 Filter 優(yōu)雅獲取請(qǐng)求參數(shù)和響應(yīng)結(jié)果
- SpringBoot 實(shí)戰(zhàn):優(yōu)雅的使用枚舉參數(shù)
- SpringBoot 實(shí)戰(zhàn):優(yōu)雅的使用枚舉參數(shù)(原理篇)
- SpringBoot 實(shí)戰(zhàn):在 RequestBody 中優(yōu)雅的使用枚舉參數(shù)
到此這篇關(guān)于SpringBoot實(shí)戰(zhàn)之處理異常案例詳解的文章就介紹到這了,更多相關(guān)SpringBoot實(shí)戰(zhàn)之處理異常內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringCLoud搭建Zuul網(wǎng)關(guān)集群過程解析
這篇文章主要介紹了SpringCLoud搭建Zuul網(wǎng)關(guān)集群過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03
解決lambda表達(dá)式內(nèi)出現(xiàn)異常無法throw拋出的問題
這篇文章主要介紹了lambda表達(dá)式內(nèi)出現(xiàn)異常無法throw拋出的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
SpringBoot中基于JWT的單token授權(quán)和續(xù)期方案步驟詳解
在前后端分離架構(gòu)中,用戶登錄后,后端生成JWT?Token返給前端存于LocalStorage,每次請(qǐng)求攜帶Token驗(yàn)證身份,后端校驗(yàn)其有效性,本文給大家介紹SpringBoot中基于JWT的單token授權(quán)和續(xù)期方案步驟詳解,感興趣的朋友一起看看吧2024-09-09
淺談Java內(nèi)部類——靜態(tài)內(nèi)部類
這篇文章主要介紹了Java靜態(tài)內(nèi)部類的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)Java內(nèi)部類的相關(guān)知識(shí),感興趣的朋友可以了解下2020-08-08
Spring中的BeanFactory與FactoryBean區(qū)別詳解
這篇文章主要介紹了Spring中的BeanFactory與FactoryBean區(qū)別詳解,BeanFactory是一個(gè)接口,它是spring中的一個(gè)工廠,FactoryBean也是一個(gè)接口,實(shí)現(xiàn)了3個(gè)方法,通過重寫其中方法自定義生成bean,需要的朋友可以參考下2024-01-01

