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);
        }
    });
    
  • 相关阅读:
    SSL JudgeOnline 1194——最佳乘车
    SSL JudgeOnline 1457——翻币问题
    SSL JudgeOnlie 2324——细胞问题
    SSL JudgeOnline 1456——骑士旅行
    SSL JudgeOnline 1455——电子老鼠闯迷宫
    SSL JudgeOnline 2253——新型计算器
    SSL JudgeOnline 1198——求逆序对数
    SSL JudgeOnline 1099——USACO 1.4 母亲的牛奶
    SSL JudgeOnline 1668——小车载人问题
    SSL JudgeOnline 1089——USACO 1.2 方块转换
  • 原文地址:https://www.cnblogs.com/iandf/p/13955634.html
Copyright © 2011-2022 走看看