《Spring实战》读书笔记-第7章 Spring MVC的高级技

return “redirect:/spitter/” + spitter.getUsername();

}

其中用到的SpitterForm类,如下所示:

package spittr.web;

import javax.validation.constraints.NotNull;

import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.Email;

import org.springframework.web.multipart.MultipartFile;

import spittr.model.Spitter;

public class SpitterForm {

@NotNull

@Size(min=5, max=16, message=“{username.size}”)

private String username;

@NotNull

@Size(min=5, max=25, message=“{password.size}”)

private String password;

@NotNull

@Size(min=2, max=30, message=“{firstName.size}”)

private String firstName;

@NotNull

@Size(min=2, max=30, message=“{lastName.size}”)

private String lastName;

@NotNull

@Email

private String email;

private MultipartFile profilePicture;

public String getUsername() {

return username;

}

public void setUsername(String username) {

this.username = username;

}

public String getPassword() {

return password;

}

public void setPassword(String password) {

this.password = password;

}

public String getFirstName() {

return firstName;

}

public void setFirstName(String firstName) {

this.firstName = firstName;

}

public String getLastName() {

return lastName;

}

public void setLastName(String lastName) {

this.lastName = lastName;

}

public String getEmail() {

return email;

}

public void setEmail(String email) {

this.email = email;

}

public MultipartFile getProfilePicture() {

return profilePicture;

}

public void setProfilePicture(MultipartFile profilePicture) {

this.profilePicture = profilePicture;

}

public Spitter toSpitter() {

return new Spitter(username, password, firstName, lastName, email);

}

}

如果需要将应用部署到Servlet 3.0的容器中,那么会有MultipartFile的一个替代方案。Spring MVC也能接受javax.servlet.http.Part作为控制器方法的参数。如果使用Part来替换MultipartFile的话,那么processRegistration()的方法签名将会变成如下的形式:

@RequestMappting(value=“/register”, method=POST)

public String processRegistration(

@RequestPart(“profilePicture”) Part profilePicture,

@Valid Spitter spitter,

Errors errors) {

}

那么将上传的文件写入文件系统中的代码为:

profilePicture.write(new File(“/tmp/spittr/” + profilePicture.getSubmittedFileName()));

值得一提的是,如果在编写控制器方法的时候,通过Part参数的形式接受文件上传,那么就没有必要配置MultipartResolver了。只有使用MultipartFile的时候,我们才需要MultipartResolver。

7.3 处理异常


不管发生什么事情,不管是好的还是坏的,Servlet请求的输出都是一个Servlet响应。如果在请求处理的时候,出现了异常,那它的输出依然会是Servlet响应。异常必须要以某种方式转换为响应。

Spring提供了多种方式将异常转换为响应:

  • 特定的Spring异常将会自动映射为指定的HTTP状态码;

  • 异常上可以添加@ResponseStatus注解,从而将其映射为某一个HTTP状态码;

  • 在方法上可以添加@ExceptionHandler注解,使其用来处理异常。

将异常映射为HTTP状态码

在默认情况下,Spring会将自身的一些异常自动转换为合适的状态码。下表列出了这些映射关系。

| Spring异常 | HTTP状态码 |

| — | — |

| BindException | 400 - Bad Request |

| ConversionNotSupportedException | 500 - Internal Server Error |

| HttpMediaTypeNotAcceptableException | 406 - Not Acceptable |

| HttpMediaTypeNotSupportedException | 415 - Unsupported Media Type |

| HttpMessageNotWritableException | 500 - Internal Server Error |

| HttpRequestMethodNotSupportedException | 405 - Method Not Allowed |

| MethodArgumentNotValidException | 400 - Bad Request |

| MissingServletRequestParameterException | 400 - Bad Request |

| MissingServletRequestPartException | 400 - Bad Request |

| NoSuchRequestHandlingMethodException | 404 - Not Found |

| TypeMismatchException | 400 - Bad Request |

以上的异常一般会由Spring自身抛出,作为DispatcherServlet处理过程中或执行校验时出现问题的结果。例如,如果DispatcherServlet无法找到适合处理请求的控制器方法,那么将会抛出NoSuchRequestHandlingMethodException异常,最终的结果就是产生404状态码的响应(Not Found)。

对于应用所抛出的异常,这些内置的映射就无能为力了。幸好,Spring提供了一种机制,能够通过@RequestStatus注解将异常映射为HTTP状态码。

为了阐述这项功能,请参考SpittleController中如下的请求处理方法,它可能会产生HTTP 404状态(但目前还没有实现):

@RequestMapping(value=“/{spittleId}” , method=RequestMethod.GET)

public String spittle(

@PathVariable(“spittleId”) long spittleId,

Model model) {

Spittle spittle = spittleRepository.findOne(spittleId);

if (spittle == null) {

throw new SpittleNotFoundException();

}

model.addAttribute(spittle);

return “spittle”;

}

现在SpittleNotFoundException就是一个简单的非检查型异常,如下所示:

package spittr.web;

public class SpittleNotFoundException extends RuntimeException {

}

SpittleNotFoundException默认会产生500状态码(Internal Server Error)的响应。实际上,如果出现任何没有映射的异常,响应都会带有500状态码,但是,我们可以通过映射SpittleNotFoundException对这种默认行为进行变更。

当抛出SpittleNotFoundException异常时,这是一种请求资源没有找到的场景。我们要使用@ResponseStatus注解将SpittleNotFoundException映射为HTTP状态码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 {

}

编写异常处理的方法

作为样例,假设用户试图创建的Spittle与已创建的Spittle文本完全相同,那么SpittleRepository的save()方法将会抛出DuplicateSpittleException异常。这意味着SpittleController的saveSpittle()方法可能需要处理这个异常。如下面的程序所示,saveSpittle()方法可以只关注成功保存Spittle的情况。

@RequestMapping(method=RequestMethod.POST)

public String saveSpittle(SpittleForm form, Model model) throws Exception {

spittleRepository.save(new Spittle(null, form.getMessage(), new Date(),

form.getLongitude(), form.getLatitude()));

return “redirect:/spittles”;

}

然后,我们为SpittleController添加一个新的方法,它会处理抛出DuplicateSpittleException的情况:

@ExceptionHandler(DuplicateSpittleException.class)

public String handleDuplicateSpittle(){

return “error/duplicate”;

}

对于@ExceptionHandler注解标注的方法来说,比较有意思的一点在于它能处理同一个控制器中所有处理器方法所抛出的异常。所有,我们不用在每一个可能抛出DuplicateSpittleException的方法中添加异常处理代码,这一个方法就涵盖了所有的功能。

7.4 为控制器添加通知


如果控制器类的特定切面能够运用到整个应用程序的所有控制器中,那么这将会便利很多。举例说明,如果要在多个控制器中处理异常,那@ExceptionHandler注解所标注的方法是很有用的。不过,如果多个控制器类中都会抛出某个特定的异常,那么你可能会发现要在所有的控制器方法中重复相同的@ExceptionHandler方法。或者,为了避免重复,我们会创建一个基础的控制器类,所有控制器类要扩展这个类,从而继承通用的@ExceptionHandler方法。

Spring 3.2为这类问题引入了一个新的解决方案:控制器通知。

控制器通知(controller advice)是任意带有@ControllerAdvice注解的类,这个类会包含一个或多个如下类型的方法:

  • @ExceptionHandler注解标注的方法;

  • @InitBinder注解标注的方法;

  • @ModelAttribute注解标注的方法;

在带有@ControllerAdvice注解的类中,以上所述的这些方法会运用到整个应用程序所有控制器中带有@RequestMapping注解的方法上。

@ControllerAdvice注解本身已经使用@Component,因此@ControllerAdvice注解所标注的类将会自动被组件扫描获取到,就像带有@Component注解的类一样。

@ControllerAdvice最为实用的一个场景就是将所有的@ExceptionHandler方法收集到一个类中,这样所有控制器的异常就能在一个地方进行一致的处理。例如,我们想将DuplicateSpittleException的处理方法用到整个应用程序的所有控制器上。

package spittr.web;

import org.springframework.web.bind.annotation.ControllerAdvice;

import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice

public class AppWideExceptionHandler {

@ExceptionHandler(DuplicateSpittleException.class)

public String duplicateSpittleHandler(){

return “error/duplicate”;

}

}

现在,如果任意的控制器方法抛出了DuplicateSpittleException,不管这个方法位于哪个控制器中,都会调用这个duplicateSpittleHandler()方法来处理异常。

7.5 跨重定向请求传递数据


在处理完POST请求后,通常来讲一个最佳实践就是执行一下重定向。除了其他的一些因素外,这样做能够防止用户点击浏览器的刷新按钮或后退箭头时,客户端重新执行危险的POST请求。

“redirect:”前缀能够让重定向功能变得非常简单。Spring为重定向功能还提供了一些其他的辅助功能。

一般来讲,当一个处理器方法完成之后,该方法所指定的模型数据将会复制到请求中,并作为请求中的属性,请求会转发(forward)到视图上进行渲染。因为控制器方法和视图所处理的是同一个请求,所以在转发的过程中,请求属性能够得以保存。

但是,当控制器的结果是重定向的话,原始的请求就结束了,并且会发起一个新的GET请求。原始请求中所带有的模型数据也就随着请求一起消亡了。在新的请求属性中,没有任何的模型数据,这个请求必须要自己计算数据。

显然,对于重定向来说,模型并不能用来传递数据。但是我们也有一些其他的方案,能够从发起重定向的方法传递数据给处理重定向方法中:

  • 使用URL模板以路径变量和/或查询参数的形式传递数据;

  • 通过flash属性发送数据。

通过URL模板进行重定向

通过路径变量和查询参数传递数据看起来非常简单。例如

return “redirect:/spitter/” + spitter.getUsername();

这能够正常运行,但是还远远不能说没有问题。当构建URL或SQL查询语句的时候,使用String连接是很危险的。

除了连接String的方式来构建重定向URL,Spring还提供了使用模板的方式来定义重定向URL。例如

@RequestMapping(value=“/register”, method=POST)

public String processRegistration(

Spitter spitter, Model model) {

spitterRepository.save(spitter);

model.addAttribute(“username”, spitter.getUsername());

return “redirect:/spitter/{username}”;

}

现在,username作为占位符填充到了URL模板中,而不是直接连接到重定向String中,所以username中所有的不安全字符都会进行转义。这样会更加安全,这里允许用户输入任何想要的内容作为username,并会将其附加在路径上。

除此之外,模型中所有其他的原始类型值都可以添加到URL中作为查询参数。作为样例,假设除了username以外,模型中还要包含新创建Spitter对象的id属性,那processRegistration()方法可以改为如下写法:

@RequestMapping(value=“/register”, method=POST)

public String processRegistration(

Spitter spitter, Model model) {

spitterRepository.save(spitter);

model.addAttribute(“username”, spitter.getUsername());

model.addAttribute(“spitterId”, spitter.getId());

return “redirect:/spitter/{username}”;

}

所返回的重定向String并没有太大的变化。但是,因为模型中的spitterId属性没有匹配重定向URL中的任何占位符,所以它会自动以查询参数的形式附加到重定向URL上。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

最后

现在其实从大厂招聘需求可见,在招聘要求上有高并发经验优先,包括很多朋友之前都是做传统行业或者外包项目,一直在小公司,技术搞的比较简单,没有怎么搞过分布式系统,但是现在互联网公司一般都是做分布式系统。

所以说,如果你想进大厂,想脱离传统行业,这些技术知识都是你必备的,下面自己手打了一份Java并发体系思维导图,希望对你有所帮助。

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

视频,并且后续会持续更新**

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-KYxUxKgZ-1712460696403)]

最后

现在其实从大厂招聘需求可见,在招聘要求上有高并发经验优先,包括很多朋友之前都是做传统行业或者外包项目,一直在小公司,技术搞的比较简单,没有怎么搞过分布式系统,但是现在互联网公司一般都是做分布式系统。

所以说,如果你想进大厂,想脱离传统行业,这些技术知识都是你必备的,下面自己手打了一份Java并发体系思维导图,希望对你有所帮助。

[外链图片转存中…(img-oo8y5Q9C-1712460696403)]

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值