SpringMvc in Action——处理异常以及跨重定向

处理异常

当处理请求时,抛出异常应该怎么处理呢?如果发生了这种情况,客户端又怎么响应呢?
Spring提供了多种方式将异常转换为响应:

  • 特定的异常会自动映射为指定的HTTP状态码
  • 异常上可以添加@ResponseStatus注解,从而将其映射为某一个HTTP状态码
  • 在方法上可以添加@RxceptionHandler注解,使用其来处理异常。

将异常映射为HTTP状态码
一些异常会默认映射为HTTP状态码
在这里插入图片描述
表中的异常一般会由Spring自身抛出,作为DispatcherServlet处理过程中或执行校验时出现问题的结果。
尽管这些内置的映射是很有用的,但是对于应用所抛出的异常它们就无能为力了,幸好,Spring提供了一种机制,通过@ResponseStatus注解将异常映射为HTTP状态码。
我们在SpittleController中如下的请求处理方法:

  @RequestMapping(value="/{spittleId}", method=RequestMethod.GET)
    public String spittle(
            @PathVariable(value = "spittleId") long spittleId,Model model) {
        Spittle spittle=spittleRepository.findOne(spittleId);
        if (spittle==null){
         throw new SpittleNotFoundException();
        }
        model.addAttribute( spittle);
        return "spittle";
    }

如果调用spittle()方法处理请求,但给定的ID获取到的结果为空,那么会抛出异常。这种异常是一种请求资源没有找到的场景,应该返回404状态码。然而,默认会返回500状态码。所以,我们需要使用注解将其映射为404状态码。

package spittr.web;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(value = HttpStatus.NOT_FOUND,
    reason = "Spittle Not Found")
public class SpittleNotFoundException extends RuntimeException {
   
}

结果:
在这里插入图片描述
编写异常处理的方法
很多情况下,将异常映射为状态码是很简单的方案,并且就功能来说也足够了。但是如果我们想要在响应中,不仅要包括状态码,还要包含所产生的错误。
那么,我们就不能将异常视为HTTP错误了,而是要按照处理请求的方式来处理异常了。

 @RequestMapping(method = RequestMethod.POST)
    public String saveSpittle(SpittleForm form, Model model) {
        try {
            spittleRepository.save(new Spittle(null, form.getMessage(), new Date(),
                    form.getLongitude(), form.getLatitude()));
            return "redirect:/spittles";
        } catch (DuplicateSpittleException e) {
            return "error/duplicate";
        }
    }

这个方法没有任何问题,他有两个路径,每个路径有不同的输出。但是,如果能让saveSpittle()只关注正确的路径,而让其他方法处理异常的话,那么它就能简单一些。
首先,我们修改saveSpittle()方法:
删除try catch

 @RequestMapping(method = RequestMethod.POST)
    public String saveSpittle(SpittleForm form, Model model) {
            spittleRepository.save(new Spittle(null, form.getMessage(), new Date(),
                    form.getLongitude(), form.getLatitude()));
            return "redirect:/spittles";
    }

现在,我们为SpittleController添加一个新方法,它会处理抛出异常的情况:

 @ExceptionHandler(DuplicateSpittleException.class)
    public String handleDuplicateSpittle(){
        return "error/duplicate";
    }

所以它能处理所有抛出这个异常的情况。

跨重定向请求传递数据

在处理完POST请求后,通常来讲一个最佳实践就是执行一下重定向:防止用户刷新或回退后,客户端重新执行危险的POST请求。
redirect:让重定向变得很简单,你可能会想Spring很难再让重定向变得更简单了,但是,Spring为重定向提供了一些其他的辅助功能。
一般而言,使用了重定向后,原始的请求就结束了,原始请求所带的模型数据也就随着请求一起消亡了。
在这里插入图片描述
但是,有时候我们希望既能重定向,又能传递数据。我们有一些其他方案,能够从发起重定向的方法传递数据给处理重定向方法中:

  • 使用URL模版以路径变量和/或查询参数的形式传递数据
  • 通过flash属性发送数据

** 通过URL模版进行重定向**
通过路径变量和查询参数进行重定向很简单,比如username的值是直接连在重定向的URL的String上面的:return "redirect:/spitter/{username}"。这能够正常运行。但当构建URL或SQL查询语句的时候,使用String连接还是很危险的。
除了连接String的方式来构建重定向URL,Spring还提供了模版的方式来定义重定向URL。例如:
修改前:

    @RequestMapping(value = "/register", method = RequestMethod.POST)
    public String processRegistration(@Valid Spitter spitter, Errors errors){
        if (errors.hasErrors())
            return "registerForm";//重新来
        spitterRepository.save(spitter);//保存注册信息
        return "redirect:/spitter/"+spitter.getUsername();
    }

修改后:

    @RequestMapping(value = "/register", method = RequestMethod.POST)
    public String processRegistration(@Valid Spitter spitter, Errors errors,Model model){
        if (errors.hasErrors())
            return "registerForm";//重新来
        spitterRepository.save(spitter);//保存注册信息
        model.addAttribute("username",spitter.getUsername());
        return "redirect:/spitter/{username}";
    }

由于使用redirect:/spitter/{username}替换了return "redirect:/spitter/"+spitter.getUsername();。现在username作为占位符填充到了URL模版中,而不是直接连接到重定向的URL中,所以username中所有的不安全字符都会进行转移,这样会更加安全。
除此之外,模型中所有的其他的原始类型值都可以添加到URL中,作为查询参数。假设除了Username外,模型中还有包含创建Spitter对象的id属性,那么,processRegistration()方法可以修改为:

    @RequestMapping(value = "/register", method = RequestMethod.POST)
    public String processRegistration(@Valid Spitter spitter, Errors errors,Model model){
        if (errors.hasErrors())
            return "registerForm";//重新来
        spitterRepository.save(spitter);//保存注册信息
        model.addAttribute("username",spitter.getUsername());
        model.addAttribute("spitterId",spitter.getId());
        return "redirect:/spitter/{username}";
    }

虽然,我们return返回没有太大的变化,但是因为Model中的spitterId属性没有匹配重定向URL的任何占位符,所以它会以查询参数的形式附加到重定向的URL上:比如,username的值是habuma,并且spitterId的值是42,那么url将会是"/spitter/habuma?spitterId=42"

使用flash属性
假设我们不想在重定向中发送username或者ID了,而我们想发送一个实际的Spitter对象。如果我们只发送ID的话,那么处理重定向的方法还需要在数据库里查找一遍,事实上我们手里本来就有这个对象了,为什么不直接拿到呢?
因为这种复杂对象不能使用路径变量或查询参数那么容易,因此,我们需要将Spitter对象放在一个位置,以便它能够在重定向的过程中存活下来
实际上,Spring认为将跨重定向存活的数据放到会话中是一个很不错的方式。但是,Spring认为我们并不需要管理这些数据,相反它停工了将数据发送为flash attribute的功能,按照定义flash属性会一直携带这些数据直到下一次请求,然后才会小时。

Spring提供了通过RedirectAttributes设置flash属性的方法,这是Model的一个子接口,RedirectAttributes提供了Model的所有功能,除此之外,还有方法是用来设置flash属性的

    @RequestMapping(value = "/register", method = RequestMethod.POST)
    public String processRegistration(@Valid Spitter spitter, Errors errors,RedirectAttributes model){
        if (errors.hasErrors())
            return "registerForm";//重新来
        spitterRepository.save(spitter);//保存注册信息
        model.addAttribute("username",spitter.getUsername());
        model.addFlashAttribute("spitter",spitter);
        return "redirect:/spitter/{username}";
    }

这样我们就达成了目的:
在这里插入图片描述
为了再得到flash属性,我们首先从模型中检查Spitter对象是否存在,如果不存在,再在数据库里查找:

 @RequestMapping(value="/{username}", method=RequestMethod.GET)
    public String showSpitterProfile(
            @PathVariable String username, Model model) {
        if (!model.containsAttribute("spitter")) {
            model.addAttribute(
                    spitterRepository.findByUsername(username));
        }
        return "profile";
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值