zoukankan      html  css  js  c++  java
  • spring mvc

    Spring MVC

    1. 初识Spring MVC

    1.1. spring mvc 的流程

    在springboot中,我们可以很轻易地完成进行web开发,只需要添加@Controller等注解即可,但是springboot底层是使用spring mvc完成的我们的知道他的运行流程,初始化了什么组件,组件有什么功能,各组件之间的练习。spring mvc是以DispatcherServlet为核心的配合其他组件进行工作。spring mvc的工作流程如下图所示:

    image-20201106150249276

    在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

    1. 通过URL传递参数

      @RequestParam它可以使前端URL传过来的参数和后端方法里的参数相对应。

      public Map<String, Object> insertUser(@RequestParam("user_name") String userName, String note) 
      
    2. 传递数组,前端传过来的数组元素之间必须以,隔开才能被正确接收

      如http://localhost:8080?init_val=1,2,3

      public int test(@RequestParam("init_val") int[] initVal)
      
    3. 传递json

      前端传过来的数据如果是Json格式的,要使用@ResponseBody来接收,spring mvc会自动将json里面的数据映射为@ResponseBody修饰的参数,要求Json里面的属性名称和User对象的属性名称是一致的

      public int test(@RequestBody User user)
      
    4. 通过URL路径传递参数

      使用@PathVariable配合@RequestMapping可以获取URL路径中的值

      @RequestMapping("/insertUsers/{id}")
      @ResponseBody
      public int test(@PathVariable int id) {
      
    5. 获取格式化参数

      在系统开发中,需要格式化数据,日期和金钱最为常见,如:系统中日期格式的约定是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请求体的流程图如下图所示

    image-2020110911032926

    GenericConverter,Formatter,Converter

    Converter是一个简单的转换器,例如参数是Integer类型,参数是字符串,那么Converter就会把字符串转成Integer

    Formatter:是一个格式转换器,例如类似日期字符串就是通过Formatter按照约定的格式转成Date类型

    GenericConverter将http参数转换为数组???

    数据类型转换,springMVC提供了服务机制ConversionService去管理这些转换规则,默认通过DefaultFormattingConversionService来管理这些转换类。

    image-20201109111259113

    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使用的数据模型接口和类的继承关系如下

    image-20201110090538811

    只要在控制器的方法参数中使用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提供的视图接口和类如下:

    image-20201110092624752

    只有逻辑视图才用到视图解析器。

    需求:将所有用户使用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();
            }
        };
    }
    
    image-2020111009473404

    pdf视图就被成功渲染出来了

    2.6 文件上传

    DispartcherServlet会使用适配器模式将HttpServeletRequest转换为MultipartHttpServletRequest。

    HttpServeletRequest与MultipartHttpServletRequest关系图如下:

    image-20201110103216891

    MultipartHttpServletRequest同时继承了HttpServeletRequest和MultipartRequest(里面包含了操作文件的方法)。

    如何判断是否将HttpServeletRequest转换为MultipartHttpServletRequest还需要通过MultipartResolver,推荐使用StandardServletMultipartResolver

    MultipartResolver接口和类图如下:

    image-20201110103646423

    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,"上传成功");
    }
    

    结果

    image-20201110110107922

    对于文件的上传可以使用 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 {
    	}
    }
    

    拦截器的执行流程如下:

    image-20201110161211225

    处理器的逻辑包含了控制器功能。

    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>
    

    image-20201110164107109

    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/*");
    }
    

    测试

    image-2020111016400901

    preHandle方法时先注册限制行,postHandle和afterCompletion是先注册后执行,遵循责任链模式。

    那么多个拦截器,把MultiInterceptor2的preHandle方法改为false之后执行流程是这样的

    image-20201110163822017

    从上面的日志可以看出,处理器前( 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的流程图

    image-20201110165339531

    3.2 重定向

    重定向使用“redirect”开头的字符串,后面跟URL。

    在重定向中如果想要保存前一条请求带来的数据要使用RedirectAttributes

    redirectAttributes.addFlashAttribute("name","value");
    

    Model是不能携带数据到下一个请求的。RedirectAttributes是先把数据放在Session中,在重定向请求之后,从Session中取出数据,填充到重定向的参数和数据模型中,再删除session中的相关数据,流程图如下:

    image-20201110170114658

    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);
        }
    });
    
  • 相关阅读:
    jekyll+github搭建个人博客总结
    ES6-let命令
    Ajax-快速上手前后端交互
    第一次项目总结——校园博览会
    Python获取exe文件版本
    @JsonFormat与@DateTimeFormat注解的使用
    前后端时间转化
    左右flex布局
    fastjson将json字符串转化成map的五种方法
    RestTemplate 发送post请求
  • 原文地址:https://www.cnblogs.com/iandf/p/13955634.html
Copyright © 2011-2022 走看看