一般的な方法を使用して5xxエラーコードを管理します。具体的には、Springアプリケーション全体でデータベースがダウンしている場合を考えてみましょう。スタックトレースではなく、かなりエラーのjsonが必要です。
コントローラには、さまざまな例外用の@ControllerAdvice
クラスがあり、これは、リクエストの途中でdbが停止する場合もキャッチします。でもこれは全てではない。また、カスタムCorsFilter
がOncePerRequestFilter
を拡張し、doFilter
を呼び出すと、CannotGetJdbcConnectionException
が取得され、@ControllerAdvice
によって管理されなくなります。私はオンラインでいくつかのことを読みましたが、それは私を混乱させただけです。
だから私はたくさんの質問があります:
ExceptionTranslationFilter
が見つかりましたが、これはAuthenticationException
またはAccessDeniedException
のみを処理します。HandlerExceptionResolver
を実装することを考えましたが、これには疑問があります。管理するカスタム例外はありません。これよりも明確な方法が必要です。また、try/catchを追加してHandlerExceptionResolver
の実装を呼び出そうとしました(十分なはずです、私の例外は特別なものではありません)が、これは応答で何も返さず、ステータス200と空体。これに対処する良い方法はありますか?ありがとう
だからこれは私がやったことです:
フィルターの基本を読みました here そして、フィルターチェーンの最初にあるカスタムフィルターを作成し、そこで発生する可能性のあるすべてのランタイム例外をキャッチするtry catchを作成する必要があることがわかりました。次に、jsonを手動で作成し、応答に含める必要があります。
だからここに私のカスタムフィルターがあります:
public class ExceptionHandlerFilter extends OncePerRequestFilter {
@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (RuntimeException e) {
// custom error response class used across my project
ErrorResponse errorResponse = new ErrorResponse(e);
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.getWriter().write(convertObjectToJson(errorResponse));
}
}
public String convertObjectToJson(Object object) throws JsonProcessingException {
if (object == null) {
return null;
}
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(object);
}
}
そして、それをweb.xmlのCorsFilter
の前に追加しました。そしてそれは動作します!
<filter>
<filter-name>exceptionHandlerFilter</filter-name>
<filter-class>xx.xxxxxx.xxxxx.api.controllers.filters.ExceptionHandlerFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>exceptionHandlerFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
私はこの問題に自分自身で遭遇し、以下の手順を実行して、登録されたフィルターにスローされたExceptionController
の@ControllerAdvise
で注釈付けされたExceptions
を再利用しました。
明らかに例外を処理する方法はたくさんありますが、私の場合、私は頑固で、同じコードをコピー/貼り付けたくないので、ExceptionController
で例外を処理したかったのです。 ExceptionController
)に処理/ロギングコードがあります。フィルターからではなくスローされる他の例外と同様に、美しいJSON
応答を返したいと思います。
{
"status": 400,
"message": "some exception thrown when executing the request"
}
とにかく、私は自分のExceptionHandler
を利用することができたので、以下の手順に示すように、少し余分な作業を行う必要がありました。
ステップ
@ControllerAdvise
を使用して例外を処理するSpringコントローラー、つまりMyExceptionControllerがあります。サンプルコード
//sample Filter, to be added in web.xml
public MyFilterThatThrowException implements Filter {
//Spring Controller annotated with @ControllerAdvise which has handlers
//for exceptions
private MyExceptionController myExceptionController;
@Override
public void destroy() {
// TODO Auto-generated method stub
}
@Override
public void init(FilterConfig arg0) throws ServletException {
//Manually get an instance of MyExceptionController
ApplicationContext ctx = WebApplicationContextUtils
.getRequiredWebApplicationContext(arg0.getServletContext());
//MyExceptionHanlder is now accessible because I loaded it manually
this.myExceptionController = ctx.getBean(MyExceptionController.class);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
try {
//code that throws exception
} catch(Exception ex) {
//MyObject is whatever the output of the below method
MyObject errorDTO = myExceptionController.handleMyException(req, ex);
//set the response object
res.setStatus(errorDTO .getStatus());
res.setContentType("application/json");
//pass down the actual obj that exception handler normally send
ObjectMapper mapper = new ObjectMapper();
PrintWriter out = res.getWriter();
out.print(mapper.writeValueAsString(errorDTO ));
out.flush();
return;
}
//proceed normally otherwise
chain.doFilter(request, response);
}
}
そして今、通常の場合にException
を処理するサンプルのSpringコントローラー(つまり、通常フィルターレベルでスローされない例外、フィルターでスローされた例外に使用したい例外)
//sample SpringController
@ControllerAdvice
public class ExceptionController extends ResponseEntityExceptionHandler {
//sample handler
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ExceptionHandler(SQLException.class)
public @ResponseBody MyObject handleSQLException(HttpServletRequest request,
Exception ex){
ErrorDTO response = new ErrorDTO (400, "some exception thrown when "
+ "executing the request.");
return response;
}
//other handlers
}
フィルタでスローされたExceptionController
にExceptions
を使用したい人とソリューションを共有します。
一般的な方法が必要な場合は、web.xmlでエラーページを定義できます。
<error-page>
<exception-type>Java.lang.Throwable</exception-type>
<location>/500</location>
</error-page>
そしてSpring MVCにマッピングを追加します:
@Controller
public class ErrorController {
@RequestMapping(value="/500")
public @ResponseBody String handleException(HttpServletRequest req) {
// you can get the exception thrown
Throwable t = (Throwable)req.getAttribute("javax.servlet.error.exception");
// customize response to what you want
return "Internal server error.";
}
}
したがって、上記の回答の融合に基づいて私がやったことは...すでに@ControllerAdvice
で注釈付けされたGlobalExceptionHandler
があり、例外を処理するためにそのコードを再利用する方法を見つけたいと思いましたそれはフィルターから来ます。
私が見つけた最も簡単な解決策は、例外ハンドラをそのままにして、次のようにエラーコントローラーを実装することでした。
@Controller
public class ErrorControllerImpl implements ErrorController {
@RequestMapping("/error")
public void handleError(HttpServletRequest request) throws Throwable {
if (request.getAttribute("javax.servlet.error.exception") != null) {
throw (Throwable) request.getAttribute("javax.servlet.error.exception");
}
}
}
したがって、例外によって引き起こされるエラーは最初にErrorController
を通過し、@Controller
コンテキスト内から再スローすることで例外ハンドラーにリダイレクトされますが、他のエラー(例外によって直接引き起こされないもの) )ErrorController
を変更せずにパススルーします。
これが実際に悪い考えである理由は何ですか?
これはデフォルトのSpring Boot/errorハンドラをオーバーライドすることによる私のソリューションです
package com.mypackage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.ErrorAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import Java.util.Map;
/**
* This controller is vital in order to handle exceptions thrown in Filters.
*/
@RestController
@RequestMapping("/error")
public class ErrorController implements org.springframework.boot.autoconfigure.web.ErrorController {
private final static Logger LOGGER = LoggerFactory.getLogger(ErrorController.class);
private final ErrorAttributes errorAttributes;
@Autowired
public ErrorController(ErrorAttributes errorAttributes) {
Assert.notNull(errorAttributes, "ErrorAttributes must not be null");
this.errorAttributes = errorAttributes;
}
@Override
public String getErrorPath() {
return "/error";
}
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest aRequest, HttpServletResponse response) {
RequestAttributes requestAttributes = new ServletRequestAttributes(aRequest);
Map<String, Object> result = this.errorAttributes.getErrorAttributes(requestAttributes, false);
Throwable error = this.errorAttributes.getError(requestAttributes);
ResponseStatus annotation = AnnotationUtils.getAnnotation(error.getClass(), ResponseStatus.class);
HttpStatus statusCode = annotation != null ? annotation.value() : HttpStatus.INTERNAL_SERVER_ERROR;
result.put("status", statusCode.value());
result.put("error", statusCode.getReasonPhrase());
LOGGER.error(result.toString());
return new ResponseEntity<>(result, statusCode) ;
}
}
アプリケーションの状態をテストし、問題が発生した場合にHTTPエラーを返すには、フィルターをお勧めします。以下のフィルターは、すべてのHTTP要求を処理します。 javaxフィルターを使用したSpring Bootの最短ソリューション。
実装にはさまざまな条件があります。私の場合、applicationManagerはアプリケーションの準備ができているかどうかをテストします。
import ...ApplicationManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import Java.io.IOException;
@Component
public class SystemIsReadyFilter implements Filter {
@Autowired
private ApplicationManager applicationManager;
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (!applicationManager.isApplicationReady()) {
((HttpServletResponse) response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "The service is booting.");
} else {
chain.doFilter(request, response);
}
}
@Override
public void destroy() {}
}
@ kopelitsaの答え に基づいたソリューションを提供したかったのです。主な違いは次のとおりです。
HandlerExceptionResolver
を使用して、コントローラー例外処理を再利用します。まず、通常のRestController/Controllerで発生する例外を処理するクラス(@RestControllerAdvice
または@ControllerAdvice
で注釈が付けられたクラスと@ExceptionHandler
で注釈が付けられたメソッド)があることを確認する必要があります。これにより、コントローラーで発生する例外が処理されます。次に、RestControllerAdviceを使用した例を示します。
@RestControllerAdvice
public class ExceptionTranslator {
@ExceptionHandler(RuntimeException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorDTO processRuntimeException(RuntimeException e) {
return createErrorDTO(HttpStatus.INTERNAL_SERVER_ERROR, "An internal server error occurred.", e);
}
private ErrorDTO createErrorDTO(HttpStatus status, String message, Exception e) {
(...)
}
}
Spring Securityフィルターチェーンでこの動作を再利用するには、フィルターを定義し、セキュリティ構成にフックする必要があります。フィルターは、例外を上記で定義された例外処理にリダイレクトする必要があります。以下に例を示します。
@Component
public class FilterChainExceptionHandler extends OncePerRequestFilter {
private final Logger log = LoggerFactory.getLogger(getClass());
@Autowired
@Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (RuntimeException e) {
log.error("Spring Security Filter Chain RuntimeException:", e);
resolver.resolveException(request, response, null, e);
}
}
}
次に、作成したフィルターをSecurityConfigurationに追加する必要があります。先行するすべてのフィルターの例外がキャッチされないため、非常に早い段階でチェーンにフックする必要があります。私の場合、LogoutFilter
の前に追加するのが妥当でした。デフォルトのフィルターチェーンとその順序を参照してください 公式ドキュメント 。以下に例を示します。
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private FilterChainExceptionHandler filterChainExceptionHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(filterChainExceptionHandler, LogoutFilter.class)
(...)
}
}
提供された他の細かい答えを補完するために、私は最近、例外をスローする可能性のあるフィルターを含む単純なSpringBootアプリでsingle error/exception処理コンポーネントを望んでいたため、コントローラーメソッドから潜在的に他の例外がスローされました。
幸いなことに、コントローラのアドバイスとSpringのデフォルトエラーハンドラのオーバーライドを組み合わせて、一貫した応答ペイロードを提供したり、ロジックを共有したり、フィルターからの例外を検査したり、特定のサービススローされた例外をトラップしたりすることを妨げるものは何もないようです。
例えば。
@ControllerAdvice
@RestController
public class GlobalErrorHandler implements ErrorController {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ValidationException.class)
public Error handleValidationException(
final ValidationException validationException) {
return new Error("400", "Incorrect params"); // whatever
}
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Exception.class)
public Error handleUnknownException(final Exception exception) {
return new Error("500", "Unexpected error processing request");
}
@RequestMapping("/error")
public ResponseEntity handleError(final HttpServletRequest request,
final HttpServletResponse response) {
Object exception = request.getAttribute("javax.servlet.error.exception");
// TODO: Logic to inspect exception thrown from Filters...
return ResponseEntity.badRequest().body(new Error(/* whatever */));
}
@Override
public String getErrorPath() {
return "/error";
}
}
上記の回答で提案されたさまざまな方法を読んだ後、カスタムフィルターを使用して認証例外を処理することにしました。次のメソッドを使用して、エラー応答クラスを使用して、応答ステータスとコードを処理できました。
カスタムフィルターを作成し、addFilterAfterメソッドを使用してセキュリティ構成を変更し、CorsFilterクラスの後に追加しました。
@Component
public class AuthFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//Cast the servlet request and response to HttpServletRequest and HttpServletResponse
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
// Grab the exception from the request attribute
Exception exception = (Exception) request.getAttribute("javax.servlet.error.exception");
//Set response content type to application/json
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
//check if exception is not null and determine the instance of the exception to further manipulate the status codes and messages of your exception
if(exception!=null && exception instanceof AuthorizationParameterNotFoundException){
ErrorResponse errorResponse = new ErrorResponse(exception.getMessage(),"Authetication Failed!");
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter writer = httpServletResponse.getWriter();
writer.write(convertObjectToJson(errorResponse));
writer.flush();
return;
}
// If exception instance cannot be determined, then throw a Nice exception and desired response code.
else if(exception!=null){
ErrorResponse errorResponse = new ErrorResponse(exception.getMessage(),"Authetication Failed!");
PrintWriter writer = httpServletResponse.getWriter();
writer.write(convertObjectToJson(errorResponse));
writer.flush();
return;
}
else {
// proceed with the initial request if no exception is thrown.
chain.doFilter(httpServletRequest,httpServletResponse);
}
}
public String convertObjectToJson(Object object) throws JsonProcessingException {
if (object == null) {
return null;
}
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(object);
}
}
SecurityConfigクラス
@Configuration
public class JwtSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
AuthFilter authenticationFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterAfter(authenticationFilter, CorsFilter.class).csrf().disable()
.cors(); //........
return http;
}
}
ErrorResponseクラス
public class ErrorResponse {
private final String message;
private final String description;
public ErrorResponse(String description, String message) {
this.message = message;
this.description = description;
}
public String getMessage() {
return message;
}
public String getDescription() {
return description;
}}
@ControllerAdviceが動作するはずなので、奇妙なことです。正しい例外をキャッチしていますか?
@ControllerAdvice
public class GlobalDefaultExceptionHandler {
@ResponseBody
@ExceptionHandler(value = DataAccessException.class)
public String defaultErrorHandler(HttpServletResponse response, DataAccessException e) throws Exception {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
//Json return
}
}
また、CorsFilterでこの例外をキャッチして、次のような500エラーを送信してください。
@ExceptionHandler(DataAccessException.class)
@ResponseBody
public String handleDataException(DataAccessException ex, HttpServletResponse response) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
//Json return
}