Spring MVC
1. 初识Spring MVC
1.1. spring mvc 的流程
在springboot中,我们可以很轻易地完成进行web开发,只需要添加@Controller等注解即可,但是springboot底层是使用spring mvc完成的我们的知道他的运行流程,初始化了什么组件,组件有什么功能,各组件之间的练习。spring mvc是以DispatcherServlet为核心的配合其他组件进行工作。spring mvc的工作流程如下图所示:
在web容器初始化的时候,@Controller和@RequstMapping等注解内容会被扫描到HandlerMapping机制中存储起来。用户在发起请求之后,被DispatcherServlet拦截下来,得到URL和其他条件,与HandlerMapping进行匹配,找到对应的处理器,并将处理器和拦截器保存到HandlerExecutionChain中,把他返回给DispatcherServlet。
1.2 定制MVC的初始化
想要自定义mvc 的初始化配置可以在配置文件中设置或者自定义一个配置类实现WebMvcConfigurer
接口,那在配置文件中和配置类中的内容是由配置类WebMvcAutoConfiguration
来定义的,它里面有一个内部类WebMvcAutoConfigurationAdapter
实现了WebMvcConfigurer
,通过它,springboot就能自动的配置spring mvc的初始化。
1.3 获取前端传递的参数
书中使用jsp文件做前端展示与后端交互的,所以看这篇博客在springboot中使用jsp
-
通过URL传递参数
@RequestParam它可以使前端URL传过来的参数和后端方法里的参数相对应。
public Map<String, Object> insertUser(@RequestParam("user_name") String userName, String note)
-
传递数组,前端传过来的数组元素之间必须以
,
隔开才能被正确接收如http://localhost:8080?init_val=1,2,3
public int test(@RequestParam("init_val") int[] initVal)
-
传递json
前端传过来的数据如果是Json格式的,要使用@ResponseBody来接收,spring mvc会自动将json里面的数据映射为@ResponseBody修饰的参数,要求Json里面的属性名称和User对象的属性名称是一致的
public int test(@RequestBody User user)
-
通过URL路径传递参数
使用@PathVariable配合@RequestMapping可以获取URL路径中的值
@RequestMapping("/insertUsers/{id}") @ResponseBody public int test(@PathVariable int id) {
-
获取格式化参数
在系统开发中,需要格式化数据,日期和金钱最为常见,如:系统中日期格式的约定是yyyy-MM-dd,金额约定以$和,分隔,日期约定可以使用@DataTimeFormat,金钱约定可以使用@NumberFormat
public Map<String,Object> commit( @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Date date, @NumberFormat(pattern = "#,###.##") Double money ){
2.深入学习SpirngMVC
2.1 参数转换流程
除了springMVC的参数转换规则外,我们还可以自定义转换规则
如果是简单的参数,springMVC可以通过已经定义好的简单转换器进行转换,如果是http请求体那么会通过HttpMessageConverter
来进行转换
public interface HttpMessageConverter<T> {
//是否可读,Class<?> var1:java类型, MediaType var2 :http请求类型
boolean canRead(Class<?> var1, @Nullable MediaType var2);
//是否可写,判断是否能将java类型转换为http响应类型,Class<?> var1:java类型, MediaType var2 :http响应类型
boolean canWrite(Class<?> var1, @Nullable MediaType var2);
//获取支持的媒体类型列表
List<MediaType> getSupportedMediaTypes();
//canRead为true后执行,将java类型读入HttpInputMessage
T read(Class<? extends T> var1, HttpInputMessage var2) throws IOException, HttpMessageNotReadableException;
//canWrite通过以后,将java类型写入响应
void write(T var1, @Nullable MediaType var2, HttpOutputMessage var3) throws IOException, HttpMessageNotWritableException;
}
例如上面的获取JSON类型,因为控制器方法的参数使用了@RequestBody,所以处理器会采用请求体的内容进行参数转换,先调用canRead判断请求体是否可读,然后调用read方法将请求体提交的JSON类型转换为控制器的参数User类型。
HttpMessageConverter
接口中的canRead和read方法就是把http的请求体转换为java类型的。但是还有其他的工作比如前端转换过来的是一个整数,但是想要将它转换成一个枚举类型,当然也可以使用VO对象,在处理器中处理,但是spring可以自定义参数转换规则,当然不用这么麻烦。
获取参数,转换参数和数据的验证的过程是由WebDataBinder
来完成的。先获取参数,然后通过三种接口GenericConverter
,Formatter
,Converter
来实现参数的转换,springMVC中,有这三个接口的实现类,都采用了 注册机机制。可以完成很多参数的转换(如 integer string。。。)。那么要自定义参数转换规则,只需要在注册机中注册自己的转换器即可。
springMVC处理HTTP请求体的流程图如下图所示
GenericConverter
,Formatter
,Converter
Converter
是一个简单的转换器,例如参数是Integer类型,参数是字符串,那么Converter
就会把字符串转成Integer
Formatter
:是一个格式转换器,例如类似日期字符串就是通过Formatter
按照约定的格式转成Date类型
GenericConverter
将http参数转换为数组???
数据类型转换,springMVC提供了服务机制ConversionService
去管理这些转换规则,默认通过DefaultFormattingConversionService
来管理这些转换类。
GenericConverter
,Formatter
,Converter
可以通过他们自己的注册机接口进行注册,这样处理器就能获取对应的转换类来进行参数转换。
SpringBoot中,通过WebMvcAutoConfigurationAdapter
类中的addFormatters方法来进行注册,源码如下
public void addFormatters(FormatterRegistry registry) {
ApplicationConversionService.addBeans(registry, this.beanFactory);
}
public static void addBeans(FormatterRegistry registry, ListableBeanFactory beanFactory) {
Set<Object> beans = new LinkedHashSet();
beans.addAll(beanFactory.getBeansOfType(GenericConverter.class).values());
beans.addAll(beanFactory.getBeansOfType(Converter.class).values());
beans.addAll(beanFactory.getBeansOfType(Printer.class).values());
beans.addAll(beanFactory.getBeansOfType(Parser.class).values());
Iterator var3 = beans.iterator();
while(var3.hasNext()) {
Object bean = var3.next();
if (bean instanceof GenericConverter) {
registry.addConverter((GenericConverter)bean);
} else if (bean instanceof Converter) {
registry.addConverter((Converter)bean);
} else if (bean instanceof Formatter) {
registry.addFormatter((Formatter)bean);
} else if (bean instanceof Printer) {
registry.addPrinter((Printer)bean);
} else if (bean instanceof Parser) {
registry.addParser((Parser)bean);
}
}
}
2.2 Converter接口,一对一转换器
public interface Converter<S, T> {
//S:源,T:目标类型
T convert(S source);
}
需求: 将格式为id-userName-note的字符串转换成user对象
@Component//boot会自动扫描,并注册到转换机制中
public class StringToUserConverter implements Converter<String, TUser> {
@Override
public TUser convert(String source) {
TUser tuser = new TUser();
String[] split = source.split("-");
tuser.setId(Long.parseLong(split[0]));
tuser.setUserName(split[1]);
tuser.setNote(split[2]);
return tuser;
}
}
前面讲过只要加上Component继承了Converter,springboot就能自动识别并注册该转换器到参数转换机制中。
但很遗憾虽然已经注册进去了,但不知道什么原因不起作用???测试链接为:http://localhost:8080/user/convert?user=11-iandf-student
是因为参数上没有加@RequestParam("user")注解,加上就成功了
2.3 验证机制
spring可以引入依赖,使用JSR-303的注解进行验证
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
public class ValidatorPojo {
// 非空判断
@NotNull(message = "id不能为空")
private Long id;
@Future(message = "需要一个将来日期") // 只能是将来的日期
// @Past //只能去过去的日期
@DateTimeFormat(pattern = "yyyy-MM-dd") // 日期格式化转换
@NotNull // 不能为空
private Date date;
@NotNull // 不能为空
@DecimalMin(value = "0.1") // 最小值0.1元
@DecimalMax(value = "10000.00") // 最大值10000元
private Double doubleValue = null;
@Min(value = 1, message = "最小值为1") // 最小值为1
@Max(value = 88, message = "最大值为88") // 最大值88
@NotNull // 不能为空
private Integer integer;
@Range(min = 1, max = 888, message = "范围为1至888") // 限定范围
private Long range;
// 邮箱验证
@Email(message = "邮箱格式错误")
private String email;
@Size(min = 20, max = 30, message = "字符串长度要求20到30之间。")
private String size;
/**** setter and getter ****/
}
当然有时候这些注解满足不了验证的规则,那么可以使用自定义验证规则,这需要使用到Validator
接口,然后将它注入到WebDateBinder中,让它工作。
需求:让用户名字不能为空
public class UserValidator implements Validator {
//如果是TUser.class则执行当前验证方法
@Override
public boolean supports(Class<?> clazz) {
return clazz.equals(TUser.class);
}
//当supports为true时执行
@Override
public void validate(Object target, Errors errors) {
// 强制转换
TUser user = (TUser) target;
// 用户名非空串
if (StringUtils.isEmpty(user.getUserName())) {
// 增加错误,可以进入控制器方法
errors.rejectValue("userName", "111", "用户名不能为空");
}
}
}
需要使用@InitBinder注解的作用是在控制器方法执行前,先执行带有@InitBinder注解的方法,这时可以将WebDateBinder作为参数传递进去,不仅仅可以注册自定义的Validator还可以自定义Date等类型的格式,这样就可以替代@DateTimeFormat
@InitBinder//
public void initBinder(WebDataBinder binder){
binder.addValidators(new UserValidator());
//false表示不允许为空
binder.registerCustomEditor(Date.class,new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"),false));
}
测试
@GetMapping("/validator")
@ResponseBody
public Map<String, Object> validator(@Valid TUser user, Errors Errors) {
Map<String, Object> map = new HashMap<>();
map.put("user", user);
// 判断是否存在错误
if (Errors.hasErrors()) {
// 获取全部错误
List<ObjectError> oes = Errors.getAllErrors();
for (ObjectError oe : oes) {
// 判定是否字段错误
if (oe instanceof FieldError) {
// 字段错误
FieldError fe = (FieldError) oe;
map.put(fe.getField(), fe.getDefaultMessage());
} else {
// 对象错误
map.put(oe.getObjectName(), oe.getDefaultMessage());
}
}
}
return map;
}
@Valid注解,当spring mvc遇到带有@Valid注解的参数时,回去便利验证器,对User对象进行验证。
测试成功
2.4 数据模型
数据模型的作用是绑定数据,为后面的视图渲染做准备的。Spring使用的数据模型接口和类的继承关系如下
只要在控制器的方法参数中使用Model
ModelMap
ModelAndView
spring mvc都会为其创建数据模型。
测试
@RequestMapping("/model")
public String model(Long id, Model model){
TUser tUser = userMapper.selectByPrimaryKey(id);
model.addAttribute("user",tUser);
return "/data/user";
}
@RequestMapping("/modelMap")
public ModelAndView modelMap(Long id, ModelMap modelMap){
TUser tUser = userMapper.selectByPrimaryKey(id);
ModelAndView mv = new ModelAndView();
mv.setViewName("/data/user");
modelMap.addAttribute("user",tUser);
return mv;
}
@RequestMapping("/modelAndView")
public ModelAndView modelMap(Long id, ModelAndView mv){
TUser tUser = userMapper.selectByPrimaryKey(id);
mv.setViewName("/data/user");
mv.addObject("user",tUser);
return mv;
}
这三种都能成功完成试图渲染,尽管第二种的modelMap没有和ModelAndView绑定。spring mvc会自动的绑定它。
2.5 视图设计
spring mvc提供了很多视图,但这些视图都继承了View接口
public interface View {
String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
String PATH_VARIABLES = View.class.getName() + ".pathVariables";
String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";
@Nullable
default String getContentType() {
return null;
}
void render(@Nullable Map<String, ?> var1, HttpServletRequest var2, HttpServletResponse var3) throws Exception;
}
getContentType得到Http的响应类型,类型可以使Json,文件等。render方法则是将数据模型渲染到视图的。
spring mvc提供的视图接口和类如下:
只有逻辑视图才用到视图解析器。
需求:将所有用户使用pdf文档导出并展示。
PDF视图不是逻辑视图,有上表所知,需要实现AbstratctPdfView。需要实现一个方法
protected abstract void buildPdfDocument(Map<String, Object> model, Document document, PdfWriter writer,
HttpServletRequest request, HttpServletResponse response)
所以自定义PdfView实现这个类,由于每个控制器想要导出的Pdf格式不一致,所以还有创建一个导出服务接口,让各个控制器自己写导出的Pdf的逻辑(标题,字体颜色等等)
//这个接口由控制器实现,然后把它注入到PdfView中。
public interface PdfExportService {
void buildPdfDocument(Map<String, Object> map, Document document, PdfWriter pdfWriter, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse);
}
public class PdfView extends AbstractPdfView {
PdfExportService exportService;
public PdfView(PdfExportService exportService) {
this.exportService = exportService;
}
@Override
protected void buildPdfDocument(Map<String, Object> map, Document document, PdfWriter pdfWriter, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
//让各个控制器自定义导出Pdf的格式等等
exportService.buildPdfDocument(map,document,pdfWriter,httpServletRequest,httpServletResponse);
}
}
测试
@RequestMapping("/export/pdf")
public ModelAndView exportPdf(ModelAndView mv){
List<TUser> userList = userMapper.selectAll();
//准备视图
PdfView view = new PdfView(exportService());
mv.addObject("userList",userList);
mv.setView(view);
return mv;
}
private PdfExportService exportService() {
// 使用Lambda表达式定义自定义导出
return (model, document, writer, request, response) -> {
try {
// A4纸张
document.setPageSize(PageSize.A4);
// 标题
document.addTitle("用户信息");
// 换行
document.add(new Chunk("
"));
// 表格,3列
PdfPTable table = new PdfPTable(3);
// 单元格
PdfPCell cell = null;
// 字体,定义为蓝色加粗
Font f8 = new Font();
f8.setColor(Color.BLUE);
f8.setStyle(Font.BOLD);
// 标题
cell = new PdfPCell(new Paragraph("id", f8));
// 居中对齐
cell.setHorizontalAlignment(1);
// 将单元格加入表格
table.addCell(cell);
cell = new PdfPCell(new Paragraph("user_name", f8));
// 居中对齐
cell.setHorizontalAlignment(1);
table.addCell(cell);
cell = new PdfPCell(new Paragraph("note", f8));
cell.setHorizontalAlignment(1);
table.addCell(cell);
// 获取数据模型中的用户列表
List<TUser> userList = (List<TUser>) model.get("userList");
for (TUser user : userList) {
document.add(new Chunk("
"));
cell = new PdfPCell(new Paragraph(user.getId() + ""));
table.addCell(cell);
cell = new PdfPCell(new Paragraph(user.getUserName()));
table.addCell(cell);
String note = user.getNote() == null ? "" : user.getNote();
cell = new PdfPCell(new Paragraph(note));
table.addCell(cell);
}
// 在文档中加入表格
document.add(table);
} catch (DocumentException e) {
e.printStackTrace();
}
};
}
pdf视图就被成功渲染出来了
2.6 文件上传
DispartcherServlet会使用适配器模式将HttpServeletRequest转换为MultipartHttpServletRequest。
HttpServeletRequest与MultipartHttpServletRequest关系图如下:
MultipartHttpServletRequest同时继承了HttpServeletRequest和MultipartRequest(里面包含了操作文件的方法)。
如何判断是否将HttpServeletRequest转换为MultipartHttpServletRequest还需要通过MultipartResolver,推荐使用StandardServletMultipartResolver
MultipartResolver接口和类图如下:
StandardServletMultipartResolver部分源码如下:
public boolean isMultipart(HttpServletRequest request) {
return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");
}
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
}
StandardServletMultipartResolver会被默认创建,为了更加灵活还可以在配置文件中自定义配置,具体配置看MultipartProperties类。
servlet:
multipart:
enabled: true #Whether to enable support of multipart uploads.
location: e:/springboot/upload #Intermediate location of uploaded files.
max-file-size: 5MB #maxFileSize
max-request-size: 20MB #上传所有文件最大的容量
需求,上传一个word文档到e:/springboot/upload目录下
<%--这边的enctype必须指定为multipart/,否则MultipartResolver不会转换HttpServletRequest--%>
<form method="post"
action="./request" enctype="multipart/form-data">
<input type="file" name="file" value="请选择上传的文件" />
<input type="submit" value="提交" />
</form>
@PostMapping("/upload/request")
@ResponseBody
public Map<String,Object> uploadRequest(HttpServletRequest request){
MultipartHttpServletRequest multipartHttpServletRequest = null;
HashMap<String, Object> resultMap = new HashMap<>();
if (request instanceof MultipartHttpServletRequest) {
multipartHttpServletRequest = (MultipartHttpServletRequest) request;
}else {
return dealResultMap(false,"上传失败");
}
//获取MultipartFile对象
MultipartFile multipartFile = multipartHttpServletRequest.getFile("file");
String filename = multipartFile.getOriginalFilename();
//创建新文件,指定文件名
File file = new File(filename);
try {
//将文件保存至配置的地点,e:/springboot/upload
multipartFile.transferTo(file);
} catch (IOException e) {
e.printStackTrace();
return dealResultMap(false,"上传失败");
}
return dealResultMap(true,"上传成功");
}
结果
对于文件的上传可以使用 Servlet API提供的Part接口或者 Spring MVC提供的Multipartfile接口作为参数。其实无论使用哪个类都是允许的,只是我更加推荐使用的是Part,因为毕竟 Multipartfile是 Spring MVC提供的第三方包才能进行支持的,后续版本变化的概率略大一些。
@PostMapping("/upload/multi")
@ResponseBody
public Map<String,Object> uploadMulti(@RequestParam("file") MultipartFile multipartFile){
String filename = multipartFile.getOriginalFilename();
//创建新文件,指定文件名
File file = new File(filename);
try {
//将文件保存至配置的地点,e:/springboot/upload
multipartFile.transferTo(file);
} catch (IOException e) {
e.printStackTrace();
return dealResultMap(false,"上传失败");
}
return dealResultMap(true,"上传成功");
}
@PostMapping("/upload/part")
@ResponseBody
public Map<String,Object> uploadPart(@RequestParam("file") Part sourceFile){
String filename = sourceFile.getSubmittedFileName();
try {
//将文件保存至配置的地点,e:/springboot/upload
sourceFile.write(filename);
} catch (IOException e) {
e.printStackTrace();
return dealResultMap(false,"上传失败");
}
return dealResultMap(true,"上传成功");
}
2.7 拦截器
2.7.1 拦截器简介
所有的拦截器都要实现HandlerInterceptor
接口
public interface HandlerInterceptor {
//preHandle方法在处理器执行前调用
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
//postHandle在处理器处理后,视图渲染前调用
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
//afterCompletion在视图渲染后调用
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}
拦截器的执行流程如下:
处理器的逻辑包含了控制器功能。
2.7.2 单个拦截器
验证处理器执行流程
@Component
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle...处理器处理前");
//返回true不会拦截后续的处理
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle...处理器处理后,视图渲染前");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("postHandle...视图渲染后");
}
}
拦截器不会自动的加载到spring mvc的机制中,需要在配置类中指定
@Configuration
public class MyWebConfig implements WebMvcConfigurer {
private MyInterceptor myInterceptor;
public MyWebConfig(MyInterceptor myInterceptor) {
this.myInterceptor = myInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
//将拦截器添加到spring mvc机制,返回一个拦截器注册
InterceptorRegistration interceptorRegistration = registry.addInterceptor(myInterceptor);
//配置拦截匹配模式,拦截指定的URL
interceptorRegistration.addPathPatterns("/interceptor/*");
}
}
测试
@Controller
@RequestMapping("/interceptor")
public class InterceptorController {
@GetMapping("/start")
public String start(){
System.out.println("执行处理器逻辑");
return "/welcome";
}
}
<body>
<h1><%
System.out.println("视图渲染");
out.print("欢迎学习Spring Boot MVC章节
");
%></h1>
</body>
preHandle返回值改为false之后,执行完preHandle就直接是完成请求的状态,状态码是200
2.7.3 多个拦截器
写拦截器MultiInterceptor1,MultiInterceptor2,MultiInterceptor3
@Component
public class MultiInterceptor1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("["+this.getClass().getSimpleName()+"]"+"preHandle...处理器处理前");
//返回true不会拦截后续的处理
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("["+this.getClass().getSimpleName()+"]"+"postHandle...处理器处理后,视图渲染前");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("["+this.getClass().getSimpleName()+"]"+"postHandle...视图渲染后");
}
}
注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MultiInterceptor1()).addPathPatterns("/interceptor/*");
registry.addInterceptor(new MultiInterceptor2()).addPathPatterns("/interceptor/*");
registry.addInterceptor(new MultiInterceptor3()).addPathPatterns("/interceptor/*");
}
测试
preHandle方法时先注册限制行,postHandle和afterCompletion是先注册后执行,遵循责任链模式。
那么多个拦截器,把MultiInterceptor2的preHandle方法改为false之后执行流程是这样的
从上面的日志可以看出,处理器前( preHandle)方法会执行,但是一旦返回 false,则后续的拦截器、处理器和所有拦截器的处理器后( postHandle)方法都不会被执行。完成方法 afterCompletion则不一样,它只会执行返回true的拦截器的完成方法,而且顺序是先注册后执行
3. spring mvc的其他知识
3.1 @ReponseBody转换Json的秘密
在进入控制器方法前,当遇到标注的@Responsebody后,处理器就会记录这个方法的响应类型为JSON数据集。当执行完控制器返回后,处理器会启用结果解析器( Resultresolver)去解析这个结果,它会去轮询注册给 Spring MVC的 Httpmessageconverter接口的实现类。因为 Mapping Jackson2HttpmessageConverter这个实现类已经被 Spring MVC所注册,加上 Spring MVC将控制器的结果类型标明为JSON,所以就匹配上了,于是通过它就在处理器内部把结果转换为了JSON。当然有时候会轮询不到匹配的HttpmessageConverter,那么它就会交由 Spring MVC后续流程去处理。如果控制器返回结果MappingJackson2HttpmessageConverter进行了转换,那么后续的模型和视图( ModelAndview)就返null,这样视图解析器和视图渲染就不会工作了
@ReponseBody准换Json的流程图
3.2 重定向
重定向使用“redirect”开头的字符串,后面跟URL。
在重定向中如果想要保存前一条请求带来的数据要使用RedirectAttributes
redirectAttributes.addFlashAttribute("name","value");
Model是不能携带数据到下一个请求的。RedirectAttributes是先把数据放在Session中,在重定向请求之后,从Session中取出数据,填充到重定向的参数和数据模型中,再删除session中的相关数据,流程图如下:
3.3 给控制器添加通知
之前可以通过AOP给bean类添加通知,spring mvc也提供了注解给控制器添加通知,主要是通过以下四个注解完成
@ControllerAdvice//定义一个控制器通知类,用来增强控制器的功能
@InitBinder//用来给WebDataBinder绑定参数准换规则,定义参数的格式和数据验证等。
@ModelAttribute//控制器方法执行前,往数据模型中添加信息
@ExceptionHandler(value = Exception.class)//控制器执行过程中出了异常就由这个方法处理
@ControllerAdvice(
basePackages = "com.yogurt.chapter9.controller.advice",
annotations = Controller.class
)//定义一个控制器通知类,用来增强控制器的功能
public class MyControllerAdvice {
@InitBinder//用来给WebDataBinder绑定参数准换规则,定义参数的格式和数据验证等。
public void initBinder(WebDataBinder dataBinder){
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
dataBinder.registerCustomEditor(Date.class,new CustomDateEditor(simpleDateFormat,false));
}
//控制器方法执行前,往数据模型中添加信息
@ModelAttribute
public void modelAttribute(Model model){
model.addAttribute("project_name","chapter9");
}
@ExceptionHandler(value = Exception.class)//控制器执行过程中出了异常就由这个方法处理
public String exceptionHandler(Model model,Exception exception){
//给异常模型添加信息
model.addAttribute("exception_message",exception.getMessage());
//返回异常视图
return "/exception";
}
}
测试
@Controller
@RequestMapping("/advice")
public class AdviceController {
@GetMapping("/test")
// 因为日期格式被控制器通知限定,所以无法再给出
public String test(@RequestParam("date") Date date, ModelMap modelMap) {
// 从数据模型中获取数据
System.out.println(modelMap.get("project_name"));
// 打印日期参数
System.out.println(date.toString());
// 抛出异常,这样流转到控制器异常通知
throw new RuntimeException("异常了,跳转到控制器通知的异常信息里");
}
}
测试无误
3.4 获取请求头参数
使用@RequestHeader注解获取请求头参数
测试
@GetMapping("/header/page")
public String headerPage() {
return "header";
}
@PostMapping("/header/user")
@ResponseBody
// 通过@RequestHeader接收请求头参数
public TUser headerUser(@RequestHeader("id") Long id) {
TUser user = userMapper.selectByPrimaryKey(id);
return user;
}
$.post({
url : "./user",
// 设置请求头参数
headers : {id : '77'},
// 成功后的方法
success : function(user) {
if (user == null || user.id == null) {
alert("获取失败");
return;
}
// 弹出请求返回的用户信息
alert("id=" + user.id +", user_name="
+user.userName+", note="+ user.note);
}
});