通过前几篇Shiro学习已经可以开发基本的权限系统了,然而在Shiro的示例中并没有对因为权限访问而抛出的异常进行处理。本篇通过Spring MVC提供的异常处理机制来解决Shiro抛出的异常。
首先看一下效果,没有处理异常的情况
再看一下处理后的情况
Spring HandlerExceptionResolver implementations deal with unexpected exceptions that occur during controller execution. A HandlerExceptionResolver somewhat resembles the exception mappings you can define in the web application descriptor web.xml. However, they provide a more flexible way to do so. For example they provide information about which handler was executing when the exception was thrown. Furthermore, a programmatic way of handling exceptions gives you more options for responding appropriately before the request is forwarded to another URL (the same end result as when you use the Servlet specific exception mappings).
Besides implementing the HandlerExceptionResolver interface, which is only a matter of implementing the resolveException(Exception, Handler) method and returning a ModelAndView, you may also use the provided SimpleMappingExceptionResolver or create @ExceptionHandler methods. The SimpleMappingExceptionResolver enables you to take the class name of any exception that might be thrown and map it to a view name. This is functionally equivalent to the exception mapping feature from the Servlet API, but it is also possible to implement more finely grained mappings of exceptions from different handlers. The @ExceptionHandler annotation on the other hand can be used on methods that should be invoked to handle an exception. Such methods may be defined locally within an @Controller or may apply to many @Controller classes when defined within an @ControllerAdvice class. The following sections explain this in more detail.
在Controller执行过程中发生的异常可以通过Spring的HandlerExceptionResolver实现类来处理异常,它有点像web.xml中你所能定义的那种错误页面。然而,它提供的方式比web.xml更加灵活性。比如它能提供发生异常所在handler的信息,而且通过编程处理异常在转发请求的时候给开发者更多的选择。除了通过实现HandlerExceptionRresolver接口来处理异常之外(它通常是实现resolveException(Exception, Handler)方法并返回一个ModelAndView)开发人员还可以使用SimpleMappingExceptionResolver 或者@ExceptionHandler来处理异常。开发者可以通过SimpleMappingExceptionResolver将异常的类名和一个视图名映射起来,这等同于通过Servlet API的映射, 但是它可以实现更加细粒度的将异常映射到不同的handler上。 在Controller中,可以通过@ExceptionHandler注解来处理异常@ExceptionHandler注解在方法之上,将或者通过@ControllerAdvice定义在许多的Controller上。
接下来我们分别通过HandlerExceptionResolver,SimpleMappingExceptionResolver 和@ExceptionHandler
这三种方式来处理Shiro的权限异常。
public interface HandlerExceptionResolver {
ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);
}
HandlerExceptionResolver是一个接口,只有一个方法,我们只需要实现这个接口,并实现它的方法就可以了。
我的实现如下
MyExceptionResolver.java
package com.gwc.shirotest.exception;
import org.apache.log4j.Logger;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/** * Created by GWCheng on 2016/3/14. */
public class MyExceptionResolver implements HandlerExceptionResolver {
private static final Logger logger = Logger.getLogger(MyExceptionResolver.class);
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
Map<String, Object> model = new ConcurrentHashMap<String, Object>();
model.put("ex", ex);
// 可以细化异常信息,给出相应的提示
logger.info("==========发生了异常:");
logger.info("==========异常类型:"+ex.getClass().getSimpleName());
logger.info("==========异常描述:"+ex.getMessage());
logger.info("==========异常原因:"+ex.getCause());
return new ModelAndView("error/error",model);
}
}
具体的显示页面
页面位置
error.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page isELIgnored="false" %>
<html>
<head>
<title>Title</title>
<c:catch var="importError0">
<c:import url="../common/base.jsp" charEncoding="utf-8"></c:import>
</c:catch>
<c:out value="${importError0}"></c:out>
</head>
<body>
当前页面为${currentPage}
<p></p>
<h1>发生了异常</h1>
<h2>异常信息如下</h2>
${ex.message}<br>
${ex.cause}<br>
</body>
</html>
最后将我们的MyExceptionResolver配置到spring的mvc中
<!-- 异常处理-->
<bean id="exceptionResolver" class="com.gwc.shirotest.exception.MyExceptionResolver"/>
日志输出如下
Spring MVC may raise a number of exceptions while processing a request. The SimpleMappingExceptionResolver can easily map any exception to a default error view as needed. However, when working with clients that interpret responses in an automated way you will want to set specific status code on the response. Depending on the exception raised the status code may indicate a client error (4xx) or a server error (5xx).
The DefaultHandlerExceptionResolver translates Spring MVC exceptions to specific error status codes. It is registered by default with the MVC namespace, the MVC Java config, and also by the the DispatcherServlet (i.e. when not using the MVC namespace or Java config). Listed below are some of the exceptions handled by this resolver and the corresponding status codes:
在处理请求的时候Spring MVC可能抛出很多异常,SimpleMappingExceptionResolver能够轻松的将异常映射到对应的错误视图上。然而,给客户端返回信息的时候需要和相关的错误码匹配,根据错误给客户的返回相关的4xx或5xx。
DefaultHandlerExceptionResolver将SpringMVC的异常分发到不同的错误代码上。在开启mvc命名空间的时候已经默认开启了。下面是错误代码和异常的对应关系。
配置spring-mvc.xml
将刚才用HandlerExceptionResolver定义的异常注释掉,并开启SimpleMappingExceptionResolver
<!-- 异常处理-->
<!--<bean id="exceptionResolver" class="com.gwc.shirotest.exception.MyExceptionResolver"/>-->
<!-- 异常处理-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!-- 定义默认的异常处理页面,当该异常类型的未注册时使用 -->
<property name="defaultErrorView" value="error/error"></property>
<!-- 定义异常处理页面用来获取异常信息的变量名,默认名为exception -->
<property name="exceptionAttribute" value="ex"></property>
<!-- 定义需要特殊处理的异常,用类名或完全路径名作为key,异常以页名作为值 -->
<property name="exceptionMappings">
<props>
<!-- 创建自己所要自定义的异常类 -->
<!--<prop key="Exception1">error1</prop> <prop key="Exception2">error2</prop>-->
<!-- 还可以继续扩展对不同异常类型的处理 -->
</props>
</property>
</bean>
运行结果和上面的一样
The HandlerExceptionResolver interface and the SimpleMappingExceptionResolver implementations allow you to map Exceptions to specific views declaratively along with some optional Java logic before forwarding to those views. However, in some cases, especially when relying on @ResponseBody methods rather than on view resolution, it may be more convenient to directly set the status of the response and optionally write error content to the body of the response.
You can do that with @ExceptionHandler methods. When declared within a controller such methods apply to exceptions raised by @RequestMapping methods of that contoroller (or any of its sub-classes). You can also declare an @ExceptionHandler method within an @ControllerAdvice class in which case it handles exceptions from @RequestMapping methods from many controllers. Below is an example of a controller-local @ExceptionHandler method:
HandlerExceptionResolver接口和SimpleMappingExceptionResolver允许开发者将异常映射到特定的视图上。开发者还可以使用@ExceptionHandler方法,讲@ExceptionHandler标注在Controller的方法上,该方法将处理由@RequestMapping方法抛出的异常。
下面介绍一下使用
@ExceptionHandler对异常的处理
讲上面介绍的两个bean都注释掉
在需要权限时会抛出异常的Controller里添加如下方法
@ExceptionHandler({Exception.class})
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ModelAndView processUnauthenticatedException(NativeWebRequest request, UnauthorizedException ex) {
log.info("==========进入了异常处理方法,使用@ExceptionHandler处理异常");
ModelAndView mv = new ModelAndView();
mv.addObject("ex", ex);
// 为了区分,跳转掉另一个视图
mv.setViewName("error/unauthorized");
return mv;
}
完整的代码
AdminController.java
package com.gwc.shirotest.controller;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.servlet.ModelAndView;
/** * Created by GWCheng on 2016/3/8. */
@Controller
public class AdminController {
private static final Log log = LogFactory.getLog(AdminController.class);
@RequiresAuthentication
@RequestMapping(value = "/admin/home")
public String adminHomePage(){
return "admin/home";
}
@RequiresRoles("admin")
@RequestMapping(value = "/admin/role")
public String adminWithRole(){
return "admin/withrole";
}
@RequiresPermissions(value={"user:view","user:create"}, logical= Logical.OR)
@RequestMapping(value = "/admin/auth")
public String adminWithAuth(){
return "admin/withauth";
}
@ExceptionHandler({Exception.class})
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ModelAndView processUnauthenticatedException(NativeWebRequest request, UnauthorizedException ex) {
log.info("==========进入了异常处理方法,使用@ExceptionHandler处理异常");
ModelAndView mv = new ModelAndView();
mv.addObject("ex", ex);
// 为了区分,跳转掉另一个视图
mv.setViewName("error/unauthorized");
return mv;
}
}
unauthorized.jsp和error.jsp的内容一样,为了有区别的演示而加的。
日志如下
好了,通过Spring提供的异常处理机制已经可以对开发中抛出的异常信息进行友好的处理了。
http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/
http://www.open-open.com/lib/view/open1413639779887.html