【SSM项目】尚筹网(三)基于Servlet3.0项目搭建:异常映射和拦截器机制
阅读原文时间:2023年07月08日阅读:2

1 异常映射

1.1 目标

使用异常映射对项目的异常错误提示进行统一管理。

1.2 思路

对于普通的页面请求,异常映射机制捕获到handler方法抛出的异常后会响应为一个错误页面,对于处理ajax请求的handler方法抛出的异常则响应一个json。

SpringMVC提供了基于xml和注解方式的异常映射机制,而且通过xml视图控制器进行的请求映射的异常只能通过xml的异常映射机制获取而通过@requestMapping注解进行的请求映射的异常既能通过xml也能通过注解方式的异常映射机制获取

1.3 实现

1.3.1 基于xml的异常映射机制

SimpleMappingExceptionResolver

classpath:spring-webmvc.xml

    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>
                <!--       key指定异常的全类名         -->
                <!--       标签指定对应视图,会经过视图解析器添加前后缀         -->
                <prop key="java.lang.Exception">system-error</prop>
            </props>
        </property>
    </bean>
1.3.2 基于注解的异常映射机制

首先需要写一个判断请求类型的工具方法:

CrowdUtil.java

    public static boolean judgeRequestType(HttpServletRequest request) {
        String acceptHeader = request.getHeader("Accept");
        String xRequestHeader = request.getHeader("X-Requested-With");

        return acceptHeader.contains("application/json") || "XMLHttpRequest".equals(xRequestHeader);
    }

① 一个判断请求类型的空指针异常映射

@ControllerAdvice
public class CrowdExceptionResolve {
    // ExceptionHandler将一个异常类型和一个方法关联起来
    @ExceptionHandler(value = NullPointerException.class)
    public ModelAndView resolveNullsoftException(
            HttpServletRequest request,
            NullPointerException nullPointerException,
            HttpServletResponse response) throws IOException {
        // 判断是否为ajax请求
        if (CrowdUtil.judgeRequestType(request)) {
            String message = nullPointerException.getMessage();
            // 将异常信息写入json
            ResultEntity<Object> resultEntity = ResultEntity.failed(message);
            Gson gson = new Gson();
            String json = gson.toJson(resultEntity);
            // json写入响应体
            response.getWriter().write(json);
        }
        // 普通请求返回携带异常信息的视图
        ModelAndView modelAndView = new ModelAndView();
        String viewName = "system-error";
        modelAndView.addObject("exception", nullPointerException);
        modelAndView.setViewName(viewName);

        return modelAndView;
    }

@ControllerAdvice 声明类为一个异常处理类

@ExceptionHandler(value = 'Exception.class') 将异常处理方法和异常名绑定

② 优化

一方面需要书写大量的异常映射,重复性的书写太多另一方面前后端分离的页面不需要进行请求的判定更不需要返回modelandview了。

    public void commonExceptionResolve(Exception e,
                                       ServletRequest request,
                                       ServletResponse response) throws IOException {
        // 获取异常信息并写入json
        String message = e.getMessage();
        Gson gson = new Gson();
        String json = gson.toJson(R.failed(message));
        // 将json写入请求体
        response.getWriter().write(json);
    }

编写其他异常映射的时候只需要调用公共异常映射方法即可。

1.3.3 ☆创建自定义异常

① 以登录为例,创建一个自定义异常LoginFailedException

public class LoginFailedException extends RuntimeException{

    public LoginFailedException() {
        super();
    }

    public LoginFailedException(String message) {
        super(message);
    }

    public LoginFailedException(String message, Throwable cause) {
        super(message, cause);
    }

    public LoginFailedException(Throwable cause) {
        super(cause);
    }

    protected LoginFailedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

继承自RuntimeException或者CompilerException这里都可以

② 在登录业务service中抛出自定义异常

    /**
     * 验证用户账户及密码
     *
     * @param formLoginName, formPassWord
     * @return
     */
    @Override
    public void userVerification(String formLoginName, String formPassWord) {

        // 对密码进行md5加密
        formPassWord = CrowdUtil.md5(formPassWord);

        UserExample userExample = new UserExample();
        userExample.createCriteria().andLoginNameEqualTo(formLoginName);
        List<User> users = userMapper.selectByExample(userExample);
        // 用户名不存在
        if (users.size() <= 0) {
            throw new LoginFailedException(CrowdConstant.MASSAGE_LOGIN_FAILED);
        }

        String dbPassword = users.get(0).getPassWord();
        // 密码不正确
        if (!Objects.equals(formPassWord, dbPassword)) {
            throw new LoginFailedException(CrowdConstant.MASSAGE_LOGIN_FAILED);
        }
    }

③ 在异常处理类中捕获controller处理请求产生的异常

@Slf4j
@ControllerAdvice
public class CrowdExceptionResolve {

    @ExceptionHandler(value = LoginFailedException.class)
    public void LoginFailedResolve(LoginFailedException e,
                                   ServletRequest request,
                                   ServletResponse response) throws IOException {
        commonExceptionResolve(e, request, response);
    }

异常处理类的功能总结来说就是捕获控制器产生异常时的异常信息以及请求,然后进行一些处理响应这些请求

如此一来,我们在控制器层面就不需要考虑各种情况直接返回成功的结果就可以了,因为不成功的请求都被异常处理器响应了,所以一般情况下除了查询的service其他都可以写成void的返回类型了

    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public R<Object> loginHandle(@RequestBody String requestBody) {
        JSONObject jsonObject = JSON.parseObject(requestBody);
        String loginName = (String) jsonObject.get("loginName");
        String passWord = (String) jsonObject.get("passWord");
        // 验证用户名和密码
        userService.userVerification(loginName, passWord);
        // 返回token
        Map<String, String> map = userService.getTokenByLoginName(loginName);

        return R.successWithData(map);
    }

2 拦截器

拦截器能够实现对请求的判断拦截,如为了实现登录检查,可以拦截所有的请求然后放行登录和退出的请求。注意放行退出是因为如果用户登录期间登录过期的话,这时候如果不放行用户需要登录之后才能退出,这显然是反人类的。

2.1 拦截器的创建 HandlerInterceptor接口

jdk8.0之后java不需要实现接口的全部方法,之前是需要实现接口HandlerInterceptorAdapter才能使用单个方法。

@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info(request.getRequestURI());
        String token = request.getHeader("X-Token");
        R<Object> resultEntity;
        // 请求头中不含token
        if(token == null) {
            resultEntity = new R<>(JWTConstant.JWT_ERRORCODE_EXPIRE, null, null);

            Gson gson = new Gson();
            String json = gson.toJson(resultEntity);

            response.getWriter().write(json);

            return false;
        }
        CheckResult checkResult = JWTUtil.validateJwt(token);
        // token过期或者不合法
        if(!checkResult.isSuccess()) {
            int errorCode = checkResult.getErrorCode();
            resultEntity = new R<>(errorCode, null, null);

            Gson gson = new Gson();
            String json = gson.toJson(resultEntity);

            response.getWriter().write(json);

            return false;
        }
        return true;
    }
}

2.2 拦截器的注册

在Servlet3.0中,Spring容器(这里则是AppConfig配置类)是通过实现WebMvcConfigurer接口来接管MVC的,可以在里面通过实现对应方法完成和web-mvc.xml相同的工作,如

  • 配置SpringMVC注解驱动(如@ControllerAdvice,@ResponseBody,@RequestBody),这个其实是通过使用注解@EnableWebMvc实现的(这个注解特别多的坑。。开启之后进行单元测试会报错)

  • 开启对静态资源的访问

  • 配置视图解析器

  • 等等

而在这里我们使用前后端分离的工作模式因此不需要其他的功能。

@Configuration
// 开启mvc注解驱动
@EnableWebMvc
@ComponentScan(value = "com.hikaru.crowd", includeFilters = {
        @ComponentScan.Filter(classes = {RestController.class})
}, useDefaultFilters = false)
public class AppConfig implements WebMvcConfigurer {
    /**
     * 拦截器注册
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**")
                .excludePathPatterns("/test")
                .excludePathPatterns("/user/logout")
                .excludePathPatterns("/user/login");

    }
}

注意:拦截器是SpringMVC自己的,只有使用了SPringMVC的工程才能使用拦截器,因此拦截器只会拦截对控制器的访问请求,如jsp、html、css、image、js是不会进行拦截的!这也是上面的请求路径只写controller的路径的原因。

2.3 区分过滤器和拦截器以及web容器

拦截器:如上面所说,是SpringMVC自己的,只有使用了SPringMVC的工程才能使用拦截器,因此拦截器只会拦截对控制器的访问请求,如jsp、html、css、image、js是不会进行拦截的

过滤器:是servlet规范的一部分,任何的javaweb都可以使用,在url-pattern配置之后对所有的资源都能进行拦截,其中/表示拦截除了jsp的资源,/*表示拦截所有的资源。

web三大器:web三大器指的是servletfilter以及listener

servlet不必多说,代表web应用的ServletContext在每一个web中包含一个,常用的即是DispatcherServlet前端拦截器。

filter上面也介绍过了,常用到的比如编码过滤器CharacterEncodingFilter。

listener比较少见,目前用到过的则是ContextLoaderListener,servelet2.0时代,配置web.xml的时候,为了实现构建spring容器(实际上也整合了mybatis)web容器之间的子父类关系,使用了这个监听器来实现监听web容器的加载来加载spring容器,从而实现spring容器接管mvc。

web三大器全部是配置在web容器当中的,对应着项目的实现接口AbstractAnnotationConfigDispatcherServletInitializer的web容器配置类WebApplicationInitializer,也起到了Servlet 2.0时代web.xml的作用

public class WebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        filter.setEncoding("utf-8");
        filter.setForceRequestEncoding(true);
        filter.setForceResponseEncoding(true);

        return new Filter[]{filter};
    }

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{RootConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{AppConfig.class};
    }

    //@Override
    //public void onStartup(ServletContext servletContext) throws ServletException {
    //    this.registerCharacterEncodingFilter(servletContext);
    //    super.onStartup(servletContext);
    //}

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    //public void registerCharacterEncodingFilter(ServletContext servletContext) {
    //    FilterRegistration ceFilter = servletContext.addFilter("ceFilter", CharacterEncodingFilter.class);
    //    ceFilter.setInitParameter("encoding", "UTF-8");
    //    ceFilter.setInitParameter("forceRequestEncoding", "true");
    //    ceFilter.setInitParameter("forceResponseEncoding", "true");
    //
    //    ceFilter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");
    //}
}

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章