利用java filter 实现业务异常拦截 异常信息集中处理

使用Filter捕获并处理业务异常

1、缘由:我们在做项目中肯定都会遇到自定义业务异常 ,然后将业务异常信息跳转的统一的信息提示页面的情况,比如我们在struts的时候我们会用到struts的异常处理机制,我们在业务层会跑出我们遇到业务处理异常 ,然后交由struts处理将提示信息到一个页面进行显示,来提示用户的相关操作,这里我们会根据以上情景设计一下怎么来显示该功能。

2、解决方法:我们的业务异常通常都是抛出的,就是unchecked exception,所以最后总会到达调用该方法的最外层进行捕获,一般我们都会在action中调用我们的业务方法 ,而业务方法可能会跑出异常,此时我们就可以在此时做些文章了, 我们可以利用filter,来捕获我们抛出的异常,然后跳转到一个统一页面,进行信息提示了。

3、具体代码:

①自定义异常类,我想这个就不用多说了 ,大家都是会的:

BsException.java

[java]  view plain copy print ?
  1. package com.ajun.exception;  
  2.   
  3.   
  4. /** 
  5.  * 业务异常类 
  6.  * @author ajun 
  7.  * @http://blog.youkuaiyun.com/ajun_studio 
  8.  */  
  9. public class BsException extends RuntimeException {  
  10.   
  11.     //异常代码  
  12.     private String key;  
  13.       
  14.     private Object[] values;//一些其他信息  
  15.       
  16.     public BsException() {  
  17.         super();  
  18.     }  
  19.   
  20.     public BsException(String message, Throwable throwable) {  
  21.         super(message, throwable);  
  22.     }  
  23.   
  24.     public BsException(String message) {  
  25.         super(message);  
  26.     }  
  27.   
  28.     public BsException(Throwable throwable) {  
  29.         super(throwable);  
  30.     }  
  31.       
  32.     public BsException(String message,String key){  
  33.         super(message);  
  34.         this.key = key;  
  35.     }  
  36.       
  37.     public BsException(String message,String key,Object value){  
  38.         super(message);  
  39.         this.key = key;  
  40.         this.values = new Object[]{value};  
  41.     }  
  42.       
  43.     public BsException(String message,String key,Object[] values){  
  44.         super(message);  
  45.         this.key = key;  
  46.         this.values = values;  
  47.     }  
  48.   
  49.     public String getKey() {  
  50.         return key;  
  51.     }  
  52.   
  53.     public Object[] getValues() {  
  54.         return values;  
  55.     }  
  56. }  
既然要用到filter ,自然少不了实现Filter接口,拦截你想要的请求,然后,就可以捕获异常了,此时你可以对你的chain.doFilter进行try catch,然后判断你想捕获的异常,很简单 ,请看代码:

[java]  view plain copy print ?
  1. package com.ajun.filter;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import javax.servlet.Filter;  
  6. import javax.servlet.FilterChain;  
  7. import javax.servlet.FilterConfig;  
  8. import javax.servlet.ServletException;  
  9. import javax.servlet.ServletRequest;  
  10. import javax.servlet.ServletResponse;  
  11. import javax.servlet.http.HttpServletRequest;  
  12. import javax.servlet.http.HttpServletResponse;  
  13.   
  14. import com.ajun.exception.BsException;  
  15.   
  16. /** 
  17.  * 业务异常过滤器 
  18.  * @author ajun 
  19.  * @http://blog.youkuaiyun.com/ajun_studio 
  20.  */  
  21. public class ExceptionFiler implements Filter {  
  22.   
  23.     private String errorPage;//跳转的错误信息页面  
  24.       
  25.       
  26.     public void destroy() {  
  27.           
  28.     }  
  29.   
  30.     public void doFilter(ServletRequest req, ServletResponse res,  
  31.             FilterChain chain) throws IOException, ServletException {  
  32.   
  33.          HttpServletResponse response = (HttpServletResponse) res;  
  34.          HttpServletRequest request = (HttpServletRequest) req;  
  35.          //捕获你抛出的业务异常  
  36.          try {  
  37.             chain.doFilter(req, res);  
  38.         } catch (RuntimeException e) {  
  39.             if(e instanceof BsException){//如果是你定义的业务异常  
  40.                 request.setAttribute("BsException", e);//存储业务异常信息类  
  41.                 request.getRequestDispatcher(errorPage).forward(request, response);//跳转到信息提示页面!!  
  42.             }  
  43.             e.printStackTrace();  
  44.         }  
  45.     }  
  46.        //初始化读取你配置的提示页面路径  
  47.     public void init(FilterConfig config) throws ServletException {  
  48.         //读取错误信息提示页面路径  
  49.         errorPage = config.getInitParameter("errorPage");  
  50.         if(null==errorPage || "".equals(errorPage)){  
  51.             throw new RuntimeException("没有配置错误信息跳转页面,请再web.xml中进行配置\n<init-param>\n<param-name>errorPage</param-name>\n<param-value>/error.jsp</param-value>\n </init-param>\n路径可以是你自己设定的任何有效路径页面!!");  
  52.             //System.out.println("没有配置错误信息跳转页面");  
  53.         }  
  54.     }  
  55.   
  56. }  

Filter在web.xml中的配置如下:

[html]  view plain copy print ?
  1. <filter>  
  2.     <filter-name>ExceptionFilter</filter-name>  
  3.     <filter-class>com.ajun.filter.ExceptionFiler</filter-class>  
  4.     <init-param>  
  5.         <param-name>errorPage</param-name>  
  6.         <param-value>/error.jsp</param-value>  
  7.     </init-param>  
  8. </filter>  
  9. <filter-mapping>  
  10.     <filter-name>ExceptionFilter</filter-name>  
  11.     <url-pattern>*.do</url-pattern>  
  12. </filter-mapping>  

下面写个servlet ,在请求的时候抛出你定义业务异常来试验一下吧

[java]  view plain copy print ?
  1. package com.ajun.servlet;  
  2.   
  3. import java.io.IOException;  
  4. import java.io.PrintWriter;  
  5.   
  6. import javax.servlet.ServletException;  
  7. import javax.servlet.http.HttpServlet;  
  8. import javax.servlet.http.HttpServletRequest;  
  9. import javax.servlet.http.HttpServletResponse;  
  10.   
  11. public class ExServlet extends HttpServlet {  
  12.   
  13.       
  14.     public void doGet(HttpServletRequest request, HttpServletResponse response)  
  15.             throws ServletException, IOException {  
  16.   
  17.         this.doPost(request, response);  
  18.           
  19.     }  
  20.   
  21.       
  22.     public void doPost(HttpServletRequest request, HttpServletResponse response)  
  23.             throws ServletException, IOException {  
  24.   
  25.         TestService t = new TestService();  
  26.         t.add();//模拟调用业务层方法,在此方法内抛出异常,此异常会在filter中进行捕获。  
  27.           
  28.     }  
  29.   
  30. }  
web.xml配置上述servlet

[html]  view plain copy print ?
  1. <servlet>  
  2.   <servlet-name>ExServlet</servlet-name>  
  3.   <servlet-class>com.ajun.servlet.ExServlet</servlet-class>  
  4. </servlet>  
  5.   
  6. <servlet-mapping>  
  7.   <servlet-name>ExServlet</servlet-name>  
  8.   <url-pattern>*.do</url-pattern>  
  9. </servlet-mapping>  

测试时在servlet中被调用的业务类

TestService.java

[java]  view plain copy print ?
  1. package com.ajun.servlet;  
  2.   
  3. import com.ajun.exception.BsException;  
  4.   
  5. public class TestService {  
  6.   
  7.     public void add(){  
  8.           
  9.         System.out.println("add () was called!!");  
  10.         if(true){  
  11.             throw new BsException("in add() throws exception!!");//抛出异常,根据你的业务逻辑  
  12.         }  
  13.     }  
  14. }  

现在部署项目,在你的浏览器内输入: http://localhost:8080/BsException/ajun.do

成功跳转到error.jsp:显示:in add() throws exception!!

在后台得到:

[java]  view plain copy print ?
  1. add () was called!!  
  2. com.ajun.exception.BsException: in add() throws exception!!  
  3.     at com.ajun.servlet.TestService.add(TestService.java:11)  
  4.     at com.ajun.servlet.ExServlet.doPost(ExServlet.java:26)  
  5.     at com.ajun.servlet.ExServlet.doGet(ExServlet.java:17)......  

成功了,赶紧去实现吧。。。
<think> 我们面对的核心问题是:在特定场景下无法捕获异常信息。根据用户提供的信息和之前的讨论,主要集中在三个方面: 1. 在Spring Security配置中自定义响应状态码时捕获不到异常信息 2. 在拦截器(Interceptor)中出的异常无法被@ControllerAdvice捕获 3. 在线程池中执行任务时出的异常捕获问题 下面将逐一分析并提供解决方案: ### 问题1:Spring Security自定义响应状态码时捕获不到异常信息 在之前的讨论中,我们已经给出了解决方案。这里再简要总结关键点: - 确保自定义的`AccessDeniedHandler`被正确配置,并且能够从异常对象中获取信息。 - 在异常时,确保异常信息被正确设置。 示例代码: ```java // 配置 http.exceptionHandling() .accessDeniedHandler((request, response, ex) -> { // 这里可以直接使用ex.getMessage()获取异常信息 response.setStatus(HttpStatus.METHOD_NOT_ALLOWED.value()); response.setContentType("application/json"); response.getWriter().write("{\"error\": \"" + ex.getMessage() + "\"}"); }) .authenticationEntryPoint(...); // 如果需要处理认证异常 // 在安全过滤器中异常 if (isIllegalMethod(request)) { throw new AccessDeniedException("非法HTTP方法: " + request.getMethod()); } ``` ### 问题2:拦截器中出的异常无法被@ControllerAdvice捕获 原因:拦截器的执行在Controller之前,而`@ControllerAdvice`是通过`HandlerExceptionResolver`来处理异常的,它主要处理控制器方法出的异常拦截器中出的异常在到达控制器之前,因此`@ControllerAdvice`无法捕获。 解决方案: 1. 在拦截器内部直接处理异常(推荐)[^1]。 ```java public class CustomInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { try { // 业务逻辑 } catch (Exception e) { // 直接处理异常 response.setStatus(HttpServletResponse.SC_BAD_REQUEST); response.getWriter().write("error message"); return false; // 中断请求 } return true; } } ``` 2. 使用`HandlerExceptionResolver`解析拦截器中的异常。可以自定义一个`HandlerExceptionResolver`,然后在拦截器中捕获异常并交给解析器处理。 ```java @Component public class CustomHandlerExceptionResolver implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // 处理异常 return new ModelAndView(); // 或者返回一个已经处理好的ModelAndView } } // 在拦截器中: public boolean preHandle(...) { try { // ... } catch (Exception e) { // 交给自定义的HandlerExceptionResolver new CustomHandlerExceptionResolver().resolveException(request, response, handler, e); return false; } } ``` 注意:这种方式需要将`CustomHandlerExceptionResolver`注入到拦截器中,避免new实例。 3. 使用`Filter`代替拦截器,然后在`Filter`中捕获异常,并跳转到一个错误控制器,由控制器统一返回错误信息。这样就能利用`@ControllerAdvice`。 ### 问题3:线程池中执行任务出的异常捕获问题 在线程池中执行的任务如果异常,默认情况下异常信息可能会被吞没。这是因为线程池会捕获任务出的异常,并将其设置到`Future`对象中。因此,我们需要通过`Future`对象来获取异常。 解决方案: 1. 使用`Future`获取执行结果并捕获异常: ```java ExecutorService executor = Executors.newFixedThreadPool(1); Future<?> future = executor.submit(() -> { // 任务代码,可能异常 throw new RuntimeException("任务执行异常"); }); try { future.get(); // 这里会出ExecutionException,其中包含原始异常 } catch (InterruptedException | ExecutionException e) { // 处理异常,通过e.getCause()获取原始异常 System.out.println(e.getCause().getMessage()); } ``` 2. 自定义线程池的`afterExecute`方法(适用于`ThreadPoolExecutor`): ```java class CustomThreadPoolExecutor extends ThreadPoolExecutor { // 构造方法省略 @Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); if (t == null && r instanceof Future<?>) { try { Future<?> future = (Future<?>) r; if (future.isDone()) { future.get(); // 如果任务有异常,这里会出 } } catch (CancellationException ce) { t = ce; } catch (ExecutionException ee) { t = ee.getCause(); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); // 重置中断状态 } } if (t != null) { // 处理异常,例如记录日志 System.out.println("捕获到异常: " + t.getMessage()); } } } ``` 3. 为任务设置未捕获异常处理器(但是注意:使用线程池时,任务中的未捕获异常不会触发默认的未捕获异常处理器): ```java Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> { // 处理未被捕获的异常 }); ``` 但是,线程池中的任务是通过`execute`提交的,任务中的未捕获异常会由线程池的`UncaughtExceptionHandler`处理(如果线程工厂设置了的话)。对于通过`submit`提交的任务,异常会被封装在`Future`中,因此不会触发未捕获异常处理器。 ### 总结 针对不同的场景,我们需要采用不同的策略来捕获异常信息: - **Spring Security**:自定义`AccessDeniedHandler`,在其中获取异常信息并写入响应。 - **拦截异常**:在拦截器内部直接处理,或通过自定义`HandlerExceptionResolver`处理。 - **线程池异常**:通过`Future`对象获取,或自定义线程池的`afterExecute`方法捕获。 下面生成相关问题: </think> ### 解决无法捕获异常信息的综合方案 #### 一、拦截器(Interceptor)异常捕获问题 **问题根源**:拦截器执行早于控制器方法,`@ControllerAdvice`无法捕获其异常[^1]。 **解决方案**: 1. **拦截器内部直接处理**(推荐) ```java public class SecurityInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { try { // 业务校验逻辑 if (!isValidRequest(request)) { throw new IllegalStateException("非法请求来源: " + request.getRemoteAddr()); } } catch (Exception ex) { // 直接处理异常 response.setStatus(403); response.getWriter().write("{\"error\":\"" + ex.getMessage() + "\"}"); return false; // 中断请求链 } return true; } } ``` 2. **异常转发至控制器** ```java // 拦截器内捕获异常后转发 request.setAttribute("exception", ex); request.getRequestDispatcher("/error-handler").forward(request, response); // 控制器建立专用入口 @RestController public class ErrorController { @RequestMapping("/error-handler") public ResponseEntity<?> handleError(HttpServletRequest request) { Exception ex = (Exception) request.getAttribute("exception"); return ResponseEntity.status(500).body(ex.getMessage()); } } ``` #### 二、Spring Security 异常捕获 **关键配置**:确保处理器直接获取异常对象 ```java @Configuration public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.exceptionHandling() .accessDeniedHandler((request, response, ex) -> { // 直接提取异常信息 response.setContentType("application/json"); response.getWriter().write("{\"code\":403,\"msg\":\"" + ex.getMessage() + "\"}"); }); return http.build(); } } ``` #### 三、线程池任务异常捕获 **问题根源**:线程池默认吞没用`submit()`提交的任务异常[^3]。 **解决方案**: 1. **使用Future获取异常** ```java ExecutorService executor = Executors.newCachedThreadPool(); Future<?> future = executor.submit(() -> { throw new RuntimeException("任务执行失败"); }); try { future.get(); // 显式触发异常捕获 } catch (ExecutionException e) { System.out.println("捕获到线程异常: " + e.getCause().getMessage()); } ``` 2. **自定义线程池处理器** ```java class CustomThreadPool extends ThreadPoolExecutor { @Override protected void afterExecute(Runnable r, Throwable t) { if (t == null && r instanceof Future<?>) { try { ((Future<?>) r).get(); } catch (ExecutionException ee) { t = ee.getCause(); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } } if (t != null) { System.err.println("线程异常: " + t.getMessage()); } } } ``` #### 四、Try-Catch失效场景处理 **常见陷阱**: 1. **异常被二次封装**: ```java try { return riskyOperation(); // 直接返回可能跳过catch块 } finally { cleanResources(); // 若此处异常会覆盖原始异常 } ``` 2. **正确做法**: ```java try { Object result = riskyOperation(); return result; } catch (SpecificException ex) { // 记录原始异常上下文 log.error("操作失败,参数: {}", params, ex); throw new BusinessException("业务处理失败", ex); // 保留原始异常 } ``` ### 调试技巧 1. **日志增强**: ```java private static final Logger logger = LoggerFactory.getLogger(YourClass.class); try { // ... } catch (Exception ex) { logger.error("异常上下文: [请求ID={}] [参数={}]", request.getId(), request.getParams(), ex); // 必须传递ex对象 } ``` 2. **全局兜底捕获**: ```java public static void main(String[] args) { Thread.setDefaultUncaughtExceptionHandler((thread, ex) -> { System.err.printf("全局捕获 %s: %s%n", thread.getName(), ex.getMessage()); }); } ``` --- ### 相关问题 1. **如何区分不同来源的异常进行差异化处理?** 可通过异常继承体系实现分层处理: ```java class NetworkException extends RuntimeException {} class BusinessException extends RuntimeException {} ``` 2. **微服务系统中如何实现跨服务的异常传递?** 建议使用错误码规范(如HTTP状态码+自定义错误码)和结构化错误响应体[^2]: ```json { "code": "AUTH-403", "message": "证书过期", "timestamp": "2023-11-05T12:34:56Z" } ``` 3. **为什么 finally 块中的异常会覆盖原始异常?** 当 try 和 finally 都异常时,JVM 会丢弃 try 块异常,只传播 finally 的异常。解决方案: ```java Exception primary = null; try { riskyOperation(); } catch (Exception ex) { primary = ex; } finally { try { cleanResources(); } catch (Exception ex) { if (primary != null) primary.addSuppressed(ex); } } ``` 4. **异步编程中如何避免异常丢失?** CompletableFuture 的异常处理建议: ```java CompletableFuture.supplyAsync(() -> 100/0) .exceptionally(ex -> { System.out.println("计算异常: " + ex.getMessage()); return 0; // 默认值 }); ``` 5. **如何监控生产环境的未捕获异常?** 集成 Sentry 或 ELK 方案: ```yml # Spring Boot配置示例 logging: level: root: WARN file: name: logs/app.log sentry: dsn: https://xxx@sentry.io/xxx ```
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值