diff --git a/examples/quickstart/src/test/functional/org/springside/examples/quickstart/functional/rest/TaskRestFT.java b/examples/quickstart/src/test/functional/org/springside/examples/quickstart/functional/rest/TaskRestFT.java index 4919136b..59644f25 100644 --- a/examples/quickstart/src/test/functional/org/springside/examples/quickstart/functional/rest/TaskRestFT.java +++ b/examples/quickstart/src/test/functional/org/springside/examples/quickstart/functional/rest/TaskRestFT.java @@ -11,7 +11,7 @@ import org.junit.BeforeClass; import org.junit.Test; import org.junit.experimental.categories.Category; import org.springframework.http.HttpStatus; -import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.RestTemplate; import org.springside.examples.quickstart.data.TaskData; import org.springside.examples.quickstart.entity.Task; @@ -92,7 +92,7 @@ public class TaskRestFT extends BaseFunctionalTestCase { try { restTemplate.getForObject(taskUri, Task.class); fail("Get should fail while feth a deleted task"); - } catch (HttpClientErrorException e) { + } catch (HttpStatusCodeException e) { assertEquals(HttpStatus.NOT_FOUND, e.getStatusCode()); } } @@ -105,7 +105,7 @@ public class TaskRestFT extends BaseFunctionalTestCase { try { restTemplate.postForLocation(resoureUrl, titleBlankTask); fail("Create should fail while title is blank"); - } catch (HttpClientErrorException e) { + } catch (HttpStatusCodeException e) { assertEquals(HttpStatus.BAD_REQUEST, e.getStatusCode()); Map messages = jsonMapper.fromJson(e.getResponseBodyAsString(), Map.class); assertEquals(1, messages.size()); @@ -117,7 +117,7 @@ public class TaskRestFT extends BaseFunctionalTestCase { try { restTemplate.put(resoureUrl + "/1", titleBlankTask); fail("Update should fail while title is blank"); - } catch (HttpClientErrorException e) { + } catch (HttpStatusCodeException e) { assertEquals(HttpStatus.BAD_REQUEST, e.getStatusCode()); Map messages = jsonMapper.fromJson(e.getResponseBodyAsString(), Map.class); assertEquals(1, messages.size()); diff --git a/examples/showcase/src/main/java/org/springside/examples/showcase/demos/hystrix/dependency/DependencyResourceController.java b/examples/showcase/src/main/java/org/springside/examples/showcase/demos/hystrix/dependency/DependencyResourceController.java index 53b0b75e..80f6fcef 100644 --- a/examples/showcase/src/main/java/org/springside/examples/showcase/demos/hystrix/dependency/DependencyResourceController.java +++ b/examples/showcase/src/main/java/org/springside/examples/showcase/demos/hystrix/dependency/DependencyResourceController.java @@ -19,7 +19,7 @@ import org.springside.modules.utils.Threads; */ @Controller public class DependencyResourceController { - public static final int TIMEOUT = 15000; + public static final int TIMEOUT = 30000; public static String status = "normal"; @@ -27,7 +27,7 @@ public class DependencyResourceController { private AccountEffectiveService accountService; /** - * 根据控制器中的状态而演示不同的行为,如正常返回,15秒后返回或直接报错 + * 根据控制器中的状态而演示不同的行为,如正常返回,30秒后返回或直接报错 */ @RequestMapping(value = "/hystrix/resource/{id}", method = RequestMethod.GET) @ResponseBody @@ -37,7 +37,7 @@ public class DependencyResourceController { return handleRequest(id); } - // 演示超时,10秒后返回. + // 演示超时,30秒后返回. if ("timeout".equals(status)) { Threads.sleep(TIMEOUT); return handleRequest(id); diff --git a/examples/showcase/src/main/java/org/springside/examples/showcase/demos/hystrix/service/GetUserCommand.java b/examples/showcase/src/main/java/org/springside/examples/showcase/demos/hystrix/service/GetUserCommand.java index 71e74533..99b28dcf 100644 --- a/examples/showcase/src/main/java/org/springside/examples/showcase/demos/hystrix/service/GetUserCommand.java +++ b/examples/showcase/src/main/java/org/springside/examples/showcase/demos/hystrix/service/GetUserCommand.java @@ -3,13 +3,19 @@ package org.springside.examples.showcase.demos.hystrix.service; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; -import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.RestTemplate; import org.springside.examples.showcase.webservice.rest.UserDTO; import com.netflix.hystrix.HystrixCommand; import com.netflix.hystrix.exception.HystrixBadRequestException; +/** + * HystrixCommand的实现类, 将访问远程资源的逻辑封装在T run()函数内. + * 如需对异常, 超时, 短路等进行自行处理,返回默认值则实现T getFallback()函数. + * + * @author calvin + */ public class GetUserCommand extends HystrixCommand { private static Logger logger = LoggerFactory.getLogger(GetUserCommand.class); @@ -18,7 +24,7 @@ public class GetUserCommand extends HystrixCommand { private Long id; /** - * 构造函数,注入配置,命令用到的资源访问类和命令参数. + * 构造函数,注入配置,用到的资源访问类和命令参数. */ protected GetUserCommand(Setter commandConfig, RestTemplate restTemplate, Long id) { super(commandConfig); @@ -34,15 +40,15 @@ public class GetUserCommand extends HystrixCommand { try { logger.info("Access restful resource"); return restTemplate.getForObject("http://localhost:8080/showcase/hystrix/resource/{id}", UserDTO.class, id); - } catch (HttpClientErrorException e) { + } catch (HttpStatusCodeException e) { throw handleException(e); } } /** - * 处理异常,对于客户端本身的异常,抛出HystrixBadRequestException,不计算入短路统计内。 + * 处理异常,对于客户端自己的异常,抛出HystrixBadRequestException,不算入短路统计内。 */ - protected Exception handleException(HttpClientErrorException e) { + protected Exception handleException(HttpStatusCodeException e) { HttpStatus status = e.getStatusCode(); if (status.equals(HttpStatus.BAD_REQUEST)) { throw new HystrixBadRequestException(e.getResponseBodyAsString(), e); diff --git a/examples/showcase/src/main/java/org/springside/examples/showcase/demos/hystrix/service/UserService.java b/examples/showcase/src/main/java/org/springside/examples/showcase/demos/hystrix/service/UserService.java index acda10d4..c3b43cb1 100644 --- a/examples/showcase/src/main/java/org/springside/examples/showcase/demos/hystrix/service/UserService.java +++ b/examples/showcase/src/main/java/org/springside/examples/showcase/demos/hystrix/service/UserService.java @@ -1,6 +1,6 @@ package org.springside.examples.showcase.demos.hystrix.service; -import java.util.Map; +import java.util.HashMap; import javax.annotation.PostConstruct; @@ -9,7 +9,6 @@ import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import org.springside.examples.showcase.webservice.rest.UserDTO; -import com.google.common.collect.Maps; import com.netflix.hystrix.HystrixCircuitBreaker; import com.netflix.hystrix.HystrixCommand.Setter; import com.netflix.hystrix.HystrixCommandGroupKey; @@ -29,8 +28,7 @@ import com.netflix.hystrix.util.HystrixRollingNumberEvent; @Service public class UserService { - private boolean runInNewThread = true; - + private boolean isolateThreadPool = true; private Setter commandConfig; private RestTemplate restTemplate; @@ -45,8 +43,8 @@ public class UserService { /** * 演示获取Hystrix的Metrics. */ - public Map getHystrixMetrics() { - Map metricsMap = Maps.newHashMap(); + public MetricsMap getHystrixMetrics() { + MetricsMap metricsMap = new MetricsMap(); HystrixCommandKey key = HystrixCommandKey.Factory.asKey("GetUserCommand"); HystrixCommandMetrics metrics = HystrixCommandMetrics.getInstance(key); @@ -67,19 +65,6 @@ public class UserService { metricsMap.put("latency50", metrics.getTotalTimePercentile(50)); metricsMap.put("latency90", metrics.getTotalTimePercentile(90)); metricsMap.put("latency100", metrics.getTotalTimePercentile(100)); - } else { - metricsMap.put("circuitOpen", false); - metricsMap.put("totalRequest", 0); - metricsMap.put("errorPercentage", 0); - metricsMap.put("success", 0); - metricsMap.put("timeout", 0); - metricsMap.put("failure", 0); - metricsMap.put("shortCircuited", 0); - metricsMap.put("threadPoolRejected", 0); - metricsMap.put("semaphoreRejected", 0); - metricsMap.put("latency50", 0); - metricsMap.put("latency90", 0); - metricsMap.put("latency100", 0); } return metricsMap; @@ -89,7 +74,7 @@ public class UserService { * 演示Hystrix Command的各种配置项, 并构造Thread-Safed的RestTemplate. */ @PostConstruct - private void init() { + public void init() { // 初始化RestTemplate restTemplate = new RestTemplate(); @@ -100,37 +85,46 @@ public class UserService { HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter(); commandConfig.andCommandPropertiesDefaults(commandProperties); - // 设置短路计算规则 // - commandProperties + // 设置短路规则 // + // 设置短路后的保护时间 ,默认为5秒,改为20秒方便演示 + commandProperties.withCircuitBreakerSleepWindowInMilliseconds(20000) // 多少百分比的失败在rolling windows内发生,计算为短路。默认为50%,无改变. .withCircuitBreakerErrorThresholdPercentage(50) // 至少多少请求在rolling window内发生,才开始触发短路的计算,默认为20, 设为3方便演示. .withCircuitBreakerRequestVolumeThreshold(3) - // rolling windows 长度,默认为20秒,改为180秒方便演示。同时相应改变桶的数量. - .withMetricsRollingStatisticalWindowInMilliseconds(180000) - .withMetricsRollingStatisticalWindowBuckets(180); - - // 设置短路后的保护时间 ,默认为5秒,改为20秒方便演示// - commandProperties.withCircuitBreakerSleepWindowInMilliseconds(20000); + // rolling windows 长度,默认为20秒,改为120秒方便演示。同时相应改变桶的数量. + .withMetricsRollingStatisticalWindowInMilliseconds(120000) + .withMetricsRollingStatisticalWindowBuckets(120); // 设置超时与并发控制 // - if (runInNewThread) { + if (isolateThreadPool) { // 使用隔离的Hystrix线程池 // 线程超时,默认为1秒,设为3秒方便演示. commandProperties.withExecutionIsolationThreadTimeoutInMilliseconds(3000); // 线程池属性, 线程池大小,默认为10,无改变。待执行队列的大小,默认为5,无改变. commandConfig.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(10) .withQueueSizeRejectionThreshold(5)); - } else { - // RestTemplate设置为10秒超时. - ((SimpleClientHttpRequestFactory) restTemplate.getRequestFactory()).setConnectTimeout(10000); + } else { // 使用原有的调用者线程 + // 依靠RestTemplate本身的超时机制,设为10秒。 + ((SimpleClientHttpRequestFactory) restTemplate.getRequestFactory()).setReadTimeout(10000); - // 设置使用信号量模式而不是默认的线程池模式,设置并发,默认为10,无改变. + // 设置使用原有的调用者线程,设置并发,默认为10,无改变. commandProperties.withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE) .withExecutionIsolationSemaphoreMaxConcurrentRequests(10); } } - public void setRunInNewThread(boolean runInNewThread) { - this.runInNewThread = runInNewThread; + public void setIsolateThreadPool(boolean isolateThreadPool) { + this.isolateThreadPool = isolateThreadPool; + } + + /** + * 简易Map,对不存在的统计项返回0. + */ + public static class MetricsMap extends HashMap { + @Override + public Object get(Object key) { + Object result = super.get(key); + return result != null ? result : "0"; + } } } diff --git a/examples/showcase/src/main/java/org/springside/examples/showcase/demos/hystrix/web/HystrixDemoController.java b/examples/showcase/src/main/java/org/springside/examples/showcase/demos/hystrix/web/HystrixDemoController.java index 814e10d4..afddb8cd 100644 --- a/examples/showcase/src/main/java/org/springside/examples/showcase/demos/hystrix/web/HystrixDemoController.java +++ b/examples/showcase/src/main/java/org/springside/examples/showcase/demos/hystrix/web/HystrixDemoController.java @@ -31,7 +31,6 @@ public class HystrixDemoController { model.addAttribute("allStatus", allStatus); model.addAttribute("statusHolder", new StatusHolder(DependencyResourceController.status)); model.addAttribute("metrics", userService.getHystrixMetrics()); - return "hystrix/hystrix"; } @@ -53,6 +52,17 @@ public class HystrixDemoController { return "redirect:/hystrix"; } + /** + * 从默认的Hystrix线程池模式切换为使用原调用者线程的模式. + */ + @RequestMapping(value = "/hystrix/disableIsolateThreadPool") + public String disableIsolateThreadPool() { + userService.setIsolateThreadPool(false); + userService.init(); + return "redirect:/hystrix"; + + } + @PostConstruct public void init() { allStatus.put("normal", "正常"); @@ -62,10 +72,10 @@ public class HystrixDemoController { } /** - * 给Spring Form Tag使用的类 + * 给Spring Form Tag使用的类. */ public static class StatusHolder { - public String value; + private String value; public StatusHolder(String value) { this.value = value; diff --git a/examples/showcase/src/main/java/org/springside/examples/showcase/demos/hystrix/web/HystrixExceptionHandler.java b/examples/showcase/src/main/java/org/springside/examples/showcase/demos/hystrix/web/HystrixExceptionHandler.java index 3cfaa049..19734ffa 100644 --- a/examples/showcase/src/main/java/org/springside/examples/showcase/demos/hystrix/web/HystrixExceptionHandler.java +++ b/examples/showcase/src/main/java/org/springside/examples/showcase/demos/hystrix/web/HystrixExceptionHandler.java @@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; +import org.springside.modules.utils.Exceptions; import org.springside.modules.web.MediaTypes; import com.netflix.hystrix.exception.HystrixBadRequestException; @@ -22,7 +23,7 @@ import com.netflix.hystrix.exception.HystrixRuntimeException.FailureType; @ControllerAdvice public class HystrixExceptionHandler extends ResponseEntityExceptionHandler { /** - * 处理Hystrix Runtime异常. + * 处理Hystrix Runtime异常, 异常分为两类,一类是Command内部抛出异常,一类是Hystrix自身的保护机制 */ @ExceptionHandler(value = { HystrixRuntimeException.class }) public final ResponseEntity handleException(HystrixRuntimeException e, WebRequest request) { @@ -34,7 +35,7 @@ public class HystrixExceptionHandler extends ResponseEntityExceptionHandler { // 对命令抛出的异常进行特殊处理 if (type.equals(FailureType.COMMAND_EXCEPTION)) { status = HttpStatus.INTERNAL_SERVER_ERROR; - message = "HystrixRuntimeException:" + e.getCause().getMessage(); + message = Exceptions.getErrorMessageWithNested(e); } logger.error(message, e); @@ -49,13 +50,11 @@ public class HystrixExceptionHandler extends ResponseEntityExceptionHandler { */ @ExceptionHandler(value = { HystrixBadRequestException.class }) public final ResponseEntity handleException(HystrixBadRequestException e, WebRequest request) { - Exception realException = (Exception) e.getCause(); - - logger.error("HystrixClientException:" + realException.getMessage(), realException); + String message = Exceptions.getErrorMessageWithNested(e); + logger.error(message, e); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.parseMediaType(MediaTypes.TEXT_PLAIN_UTF_8)); - return handleExceptionInternal(realException, realException.getMessage(), headers, HttpStatus.BAD_REQUEST, - request); + return handleExceptionInternal(e, message, headers, HttpStatus.BAD_REQUEST, request); } } diff --git a/examples/showcase/src/main/webapp/WEB-INF/views/hystrix/hystrix.jsp b/examples/showcase/src/main/webapp/WEB-INF/views/hystrix/hystrix.jsp index bedb6b95..324c7072 100644 --- a/examples/showcase/src/main/webapp/WEB-INF/views/hystrix/hystrix.jsp +++ b/examples/showcase/src/main/webapp/WEB-INF/views/hystrix/hystrix.jsp @@ -1,5 +1,4 @@ <%@ page contentType="text/html;charset=UTF-8" %> -<%@ page import="org.springside.examples.showcase.demos.hystrix.dependency.DependencyResourceController,com.netflix.hystrix.*,com.netflix.hystrix.HystrixCommandMetrics.HealthCounts,com.netflix.hystrix.util.*" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="form" uri="http://www.springside.org.cn/tags/form" %> @@ -19,7 +18,7 @@ $('#hystrixResponse').html('Status: '+jqXHR.status+', Content: ' + jqXHR.responseText ); }).fail( function(jqXHR, textStatus){ $('#hystrixResponse').removeClass("alert alert-success").addClass("alert alert-warning"); - $('#hystrixResponse').html('Status: '+jqXHR.status+', Content: ' + jqXHR.statusText ); + $('#hystrixResponse').html('Status: '+jqXHR.status+', Content: ' + jqXHR.responseText ); }); } @@ -39,25 +38,26 @@

主要用户故事

  • 在默认的正常状态,访问Hystrix服务和依赖资源,均返回正常结果。
  • -
  • 将资源状态切换为"超时",依赖资源需要在15秒后才返回结果访问,此时访问Hystrix服务,3秒后超时,返回503。
  • -
  • 三次超时后满足短路条件(60秒滚动窗口内起码有3个请求,50%失败),再次访问服务,立即返回503。
  • -
  • 10秒短路保护期内,所有访问都立即返回503,不会访问依赖资源。
  • -
  • 保护期过后,会放行一个请求,如果还是超时,则继续保持短路状态。如果成功则重置所有计数器。
  • +
  • 将资源状态切换为"超时",依赖资源需要在15秒后才返回结果访问,此时访问Hystrix服务,3秒后超时返回503。
  • +
  • 两次超时后满足短路条件(60秒滚动窗口内起码有3个请求,50%失败),再次访问服务,立即返回503。
  • +
  • 20秒短路保护期内,所有访问立即返回503,日志内不会显示访问依赖资源。
  • +
  • 保护期过后,放行一个请求,如果还是超时,则继续保持短路状态。如果成功则重置所有计数器。

其他用户故事

  • 将资源状态切换为"服务端失败",访问 Hystrix服务,即时返回500错误。
  • 将资源状态切换为"错误请求",依赖资源返回400错误,访问Hystrix服务,返回500错误,但错误率变化统计无变化。
  • -
  • 切换为调用者线程模式。
  • +
  • 使用JMeter高并发测Hystrix试线程池用光之后的保护.
  • +
  • 启动后切换为调用者线程模式, 依靠RestTemplate自身的超时机制,延长为10秒。

监控结果

- - + +
是否短路:${metrics['circuitOpen']}
3分钟窗口内,请求数:${metrics['totalRequest']}, 失败百分比:${metrics['errorPercentage']}%,成功:${metrics['success']}, 超时:${metrics['timeout']},失败:${metrics['failure']},短路:${metrics['shortCircuited']}, 线程池满:${metrics['threadPoolRejected']}
50%延时:${metrics['latency50']}ms,90%延时:${metrics['latency90']}ms,100%延时:${metrics['latency100']}ms
2分钟窗口内,请求数:${metrics['totalRequest']}, 失败百分比:${metrics['errorPercentage']}%,成功:${metrics['success']}, 超时:${metrics['timeout']},失败:${metrics['failure']},短路:${metrics['shortCircuited']}, 线程池满:${metrics['threadPoolRejected']}
最快的50%请求延时:${metrics['latency50']}ms,90%请求延时:${metrics['latency90']}ms,100%请求延时:${metrics['latency100']}ms
\ No newline at end of file diff --git a/examples/showcase/src/test/functional/org/springside/examples/showcase/functional/rest/UserJaxRsFT.java b/examples/showcase/src/test/functional/org/springside/examples/showcase/functional/rest/UserJaxRsFT.java index 4aadc5fd..eb150783 100644 --- a/examples/showcase/src/test/functional/org/springside/examples/showcase/functional/rest/UserJaxRsFT.java +++ b/examples/showcase/src/test/functional/org/springside/examples/showcase/functional/rest/UserJaxRsFT.java @@ -4,7 +4,7 @@ import static org.junit.Assert.*; import org.junit.BeforeClass; import org.junit.Test; -import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.RestTemplate; import org.springside.examples.showcase.functional.BaseFunctionalTestCase; import org.springside.examples.showcase.webservice.rest.UserDTO; @@ -33,7 +33,7 @@ public class UserJaxRsFT extends BaseFunctionalTestCase { assertEquals(new Long(1), user.getTeamId()); try { user = restTemplate.getForObject(resoureUrl + "/{id}.json", UserDTO.class, 1L); - } catch (HttpClientErrorException e) { + } catch (HttpStatusCodeException e) { fail(e.getMessage()); } assertEquals("admin", user.getLoginName()); diff --git a/examples/showcase/src/test/functional/org/springside/examples/showcase/functional/rest/UserRestFT.java b/examples/showcase/src/test/functional/org/springside/examples/showcase/functional/rest/UserRestFT.java index 07b230b6..2c563b0f 100644 --- a/examples/showcase/src/test/functional/org/springside/examples/showcase/functional/rest/UserRestFT.java +++ b/examples/showcase/src/test/functional/org/springside/examples/showcase/functional/rest/UserRestFT.java @@ -19,7 +19,7 @@ import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory; -import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.RestTemplate; import org.springside.examples.showcase.functional.BaseFunctionalTestCase; import org.springside.examples.showcase.webservice.rest.UserDTO; @@ -56,6 +56,7 @@ public class UserRestFT extends BaseFunctionalTestCase { jdkTemplate = new RestTemplate(); // (optional)设置20秒超时 ((SimpleClientHttpRequestFactory) jdkTemplate.getRequestFactory()).setConnectTimeout(20000); + ((SimpleClientHttpRequestFactory) jdkTemplate.getRequestFactory()).setReadTimeout(20000); // 设置使用HttpClient4.0 httpClientRestTemplate = new RestTemplate(); @@ -100,7 +101,7 @@ public class UserRestFT extends BaseFunctionalTestCase { HttpEntity xml = jdkTemplate.exchange(resoureUrl + "/{id}.xml", HttpMethod.GET, requestEntity, String.class, 1L); System.out.println("xml output is " + xml.getBody()); - } catch (HttpClientErrorException e) { + } catch (HttpStatusCodeException e) { fail(e.getMessage()); } } @@ -122,7 +123,7 @@ public class UserRestFT extends BaseFunctionalTestCase { // 直接取出JSON串 String json = httpClientRestTemplate.getForObject(resoureUrl + "/{id}.json", String.class, 1L); System.out.println("json output is " + json); - } catch (HttpClientErrorException e) { + } catch (HttpStatusCodeException e) { fail(e.getMessage()); } } @@ -141,7 +142,7 @@ public class UserRestFT extends BaseFunctionalTestCase { try { jdkTemplate.exchange(resoureUrl + "/{id}.xml", HttpMethod.GET, requestEntity, UserDTO.class, 1L); fail("Get should fail with error username/password"); - } catch (HttpClientErrorException e) { + } catch (HttpStatusCodeException e) { assertEquals(HttpStatus.UNAUTHORIZED, e.getStatusCode()); } } diff --git a/modules/core/src/main/java/org/springside/modules/utils/Exceptions.java b/modules/core/src/main/java/org/springside/modules/utils/Exceptions.java index 684a9838..6e98f997 100644 --- a/modules/core/src/main/java/org/springside/modules/utils/Exceptions.java +++ b/modules/core/src/main/java/org/springside/modules/utils/Exceptions.java @@ -35,6 +35,16 @@ public class Exceptions { return stringWriter.toString(); } + /** + * 获取组合本异常信息与底层异常信息的异常描述, 适用于本异常为统一包装异常类,底层异常才是根本原因的情况。 + */ + public static String getErrorMessageWithNested(Exception e) { + Throwable nestedException = e.getCause(); + return new StringBuilder().append(e.getMessage()).append(" nested exception is ") + .append(nestedException.getClass().getName()).append(":").append(nestedException.getMessage()) + .toString(); + } + /** * 判断异常是否由某些底层的异常引起. */