SpringMVC(二)

SpringMVC

1. RESTful风格

1.1 概念

RESTful :是网络应用程序的接口(url)设计风格,基于HTTP协议,采用了以下要素构建网络应用程序访问路径。

  • 设计要访问的资源【名词】路径,用唯一的 URI 表示

  • 选择资源的展现的方式(数据交互):一般是 json 格式,也可以是其它格式

例:

  • 查询编号为 10 的图书

  • 更新编号为 10 的图书

  • 删除编号为 10 的图书

  • 新增的图书,无编号

1.2 路径参数

要实现【设计要访问的资源【名词】,用统一的 URI 表示】这一特性,我们发现 RESTful 风格中,唯一标识 id,并不是像之前一样从请求参数(即 ? 后)传递过来,而是此 id 就是路径的组成部分,因此我们需要从路径中获取参数

  • Spring 提供了 @PathVariable 来解析资源路径中的参数信息

@PathVariable :路径变量,将路径后面占位符的值赋值给形参变量。如果占位符的名称和形参变量名不一样,需要在注解中指定占位符名称。

    @RequestMapping("/brand/{a}")
    public String selectById(@PathVariable("a") Integer id){
        log.info("selectById--->id:{}",id);
        return "selectById查询成功--->"+id;
    }

要实现【用 HTTP Method 【动词】来转换资源状态】按 **请求方法(GET POST 等)**来区分请求

  • 查询
    • method = RequestMethod.GET
  • 删除
    • method = RequestMethod.DELETE
  • 修改
    • method = RequestMethod.PUT
  • 新增
    • method = RequestMethod.POST

查询

@RequestMapping(value = "/brand/{id}", method = RequestMethod.GET)
public String selectById(@PathVariable Integer id) {
    log.info("selectById--->id:{}", id);
    return "selectById查询成功--->" + id;
}

修改

@RequestMapping(value = "/brand",method = RequestMethod.PUT)
public String update(@RequestBody Brand brand){
	log.info("update--->brand:{}",brand);
	return "update修改成功";
}

1.3 衍生注解

SpringMVC提供了**@GetMapping**、@PostMapping@PutMapping@DeleteMapping四种不同的注解定义不同请求方式映射路径

  • @GetMapping
@RestController
@Slf4j
@RequestMapping("/brand")
//根据id查询
public class BrandController {
	@GetMapping("/{id}")
    public String selectById(@PathVariable Integer id) {
        log.info("selectById--->id:{}", id);
        return "selectById查询成功--->" + id;
    }
}
  • @DeleteMapping
@RestController
@Slf4j
@RequestMapping("/brand")
public class BrandController {
	//根据id删除
	@DeleteMapping("/{id}")
    public String delete(@PathVariable Integer id) {
        log.info("delete--->id:{}", id);
        return "delete删除成功--->" + id;
    }
}
  • @PutMapping
@RestController
@Slf4j
@RequestMapping("/brand")
public class BrandController {
	//修改
	@PutMapping
    public String update(@RequestBody Brand brand){
        log.info("update--->brand:{}",brand);
        return "update修改成功";
    }
}
  • @PostMapping
@RestController
@Slf4j
@RequestMapping("/brand")
public class BrandController {
	//新增
    @PostMapping
    public String add(@RequestBody Brand brand) {
        log.info("add--->brand:{}", brand);
        return "add添加成功..";
    }
}
  • 注意:衍生注解只能写在方法上,不能写在类上。

2. 进阶

2.1 参数校验

数据不校验,就可能导致非法残缺数据入库,严重还可能引起系统漏洞

  • 添加依赖
<!--validation校验框架起步依赖-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
  • 添加校验规则
    • @NotNull:要求值不为null
    • @NotEmpty:要求值不为null和" "(空字符串),不能用于校验Integer类型的数据,只能用于校验字符串类型的数据
    • @NotBlank:要求值不为null、“”、" "(多个空字符串),不能用于校验Integer类型的数据,只能用于检验字符串类型的数据
    • @Min(18):设置最小值
    • @Min(80):设置最大值
@Data
public class User {
   //@NotNull //要求值不为null
    @NotEmpty(message = "不能为空!") //要求值不为null和" "(空字符串),不能用于校验Integer类型的数据,只能用于校验字符串类型的数据
    //@NotBlank //要求值不为null、""、"  "(多个空字符串),不能用于校验Integer类型的数据,只能用于检验字符串类型的数据
    private String name;
    @NotNull //不为null
    @Min(18)//最小值
    @Min(80)//设置最大值
    private Integer age;
}
  • 控制器代码
    /**
     * 保存学生信息
     * @Valid:表示开启校验
     * BindingResult result:封装了校验结果,只能放到被检验对象的参数后面
     * @param user
     * @return
     */
    @PostMapping
    public String save(@Valid @RequestBody User user, BindingResult result){
        //使用校验框架对user进行校验
        if (result.hasErrors()) {   //是否有错误
            //获取所以错误集合
            List<FieldError> fieldErrors = result.getFieldErrors();
            log.info("fieldErrors = {}", fieldErrors);
            //遍历所有错误的集合
            for (FieldError fieldError : fieldErrors) {
                //获取错误字段
                String field = fieldError.getField();
                //获取错误信息
                String defaultMessage = fieldError.getDefaultMessage();
                log.info("{}{}", field,defaultMessage);
            }
            return "参数不合法!";
        }
        log.info("save--->user:{}",user);
        //判断校验是否成功,如果有不合法数据,打印错误信息
        return "保存user成功";
    }

2.2 文件上传

  • 文件上传对我们的表单是由要求的,下面是我们平时用的普通表单,无法完成文件上传

  • 文件上传表单

单文件上传
  • controller代码
@RestController
@Slf4j
public class UploadController {
    //需要在配置文件配置路径
	@Value("${fileDir}")
    private String fileDir;   
	/** 
     * 单文件上传
     * @param name
     * @param multipartFile
     * @return
     * @throws IOException
     */
    @RequestMapping("/upload")
    public String upload(String name, @RequestParam("file") MultipartFile multipartFile) throws IOException {
        //1.判断是否有上传文件
        if (multipartFile == null || multipartFile.isEmpty()) {
            return "未上传文件!";
        }
        //2.获取文件名
        String fileName = multipartFile.getOriginalFilename();
        log.info("文件名:{}", fileName);
        //3.获取其他的文件信息
        log.info("文件名:{}", fileName);   //获取文件大小,单位字节
        log.info("文件大小:{}", multipartFile.getSize());//获取文件类型

        //4.将上传的文件保存到服务器硬盘中
        multipartFile.transferTo(new File(fileDir, fileName));
        return "上传成功!";
    }
}
  • yml文件
  servlet:
    multipart:
      max-file-size: 500MB #指定上传文件允许的最大大小。 默认值为1MB
      max-request-size: 1GB #指定multipart/form-data请求允许的最大大小。 默认值为10MB。
fileDir: D:\\yyds\\javacode\\javaEE-framework\\springmvc_01\\src\\main\\resources\\static\\img #文件保存路径
多文件上传
  • controller代码
@RestController
@Slf4j
public class UploadController {
    //需要在配置文件配置路径
	@Value("${fileDir}")
    private String fileDir;   
    /**
     * 多文件上传
     * @param name
     * @param multipartFiles
     * @return
     * @throws IOException
     */
    @RequestMapping("/uploads")
    public String upload2(String name, @RequestParam("file") MultipartFile[] multipartFiles) throws IOException {
        for (MultipartFile multipartFile : multipartFiles) {
            //1.判断是否有上传文件
            if (multipartFile == null || multipartFile.isEmpty()) {
                return "未上传文件!";
            }
            //2.获取文件名
            String fileName = multipartFile.getOriginalFilename();
            log.info("文件名:{}", fileName);
            //3.获取其他的文件信息
            log.info("文件名:{}", fileName);   //获取文件大小,单位字节
            log.info("文件大小:{}", multipartFile.getSize());//获取文件类型
            //4.将上传的文件保存到服务器硬盘中
            multipartFile.transferTo(new File(fileDir, fileName));
        }
        return "上传成功!";
    }
}

2.3 统一响应结果

设置响应状态码和响应头

可以用 @ResponseStatus 来指定控制器方法的状态码

@GetMapping("/s2")
@ResponseStatus(HttpStatus.BAD_REQUEST)//响应400状态码
public void s2() {}

可以控制器方法中使用用 ResponseEntity来封装响应头

@GetMapping("/s2")
public ResponseEntity s2() {
    log.info("s2...");
    //1.准备响应头
    LinkedMultiValueMap header = new LinkedMultiValueMap();
    header.add("header1", "a");
    //2.准备响应体
    Brand body = new Brand();
    //3.创建ResponseEntity对象,封装响应信息(状态码、响应头、响应体)
    ResponseEntity entity = new ResponseEntity(body, header, HttpStatus.OK);
    //返回ResponseEntity对象
    return entity;
}
自定义Result封装响应结果

响应状态码的不足

  • 可以用状态码较为精确地描述此操作是成功失败、是客户端的问题、还是服务端的问题,是最为通用的描述,但状态码毕竟有限。
  • 返回的响应体中,可能会表示正常的数据,也有可能包含错误提示,若不统一,就增加了前端解析成本
  • 因此一般应用开发时,会定义一个 Result统一响应格式
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Result {
    //扩展的状态码,和具体业务有关
    private Integer code;
    //响应消息,可以保存成功的消息,也可以保存失败的消息
    private String msg;
    //响应结果,适用于查询
    private Object data;
}

自定义应用(业务)状态码举例:在企业中由公司团队技术骨干制定:

  • Controller
//统一响应结果Result类
@GetMapping("/{id}")
public Result selectByIds(@PathVariable Integer id) {
	log.info("selectById-->id : {}", id);
	return new Result(2001, "查询一个品牌成功", new Brand());
}

3. 高级

3.1 异常处理

全局异常处理

Spring MVC 提供了一种处理异常的方式,定义全局异常处理通知类来处理

  • 定义一个异常通知类
//@ControllerAdvice //表示该类是一个异常处理类,类中的方法用来处理各种异常
@RestControllerAdvice //等价于@ControllerAdvice + @RequestBody
@Slf4j
public class GlobalExceptionHandler {
     /**
     * 处理服务端异常
     * @ExceptionHandler(Exception.class): 该方法是一个处理异常的方法
     *      注解参数表示该方法要处理的异常类型,可以省略不写,如果不写就表示处理方法参数对应的异常
     * @param e 要处理的异常对象,也就是接收到的异常对象
     * @return
     */
    //    @ExceptionHandler(Exception.class)
    @ExceptionHandler //处理和方法形参同类型的异常
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)//响应状态码500
    public Result doException(Exception e) {
        log.info("接收到的异常是:{}", e.getMessage());
        //向客户端返回结果
        return new Result(500, "服务器正在维护,请耐心等待。", null);
    }
}
  • controller
 @GetMapping("/{id}")
    public String selectById(@PathVariable Integer id) {
        log.info("selectById--->id:{}", id);
        int i = 1 / 0;
        return "selectById查询成功--->" + id;
    }
  • 结果
自定义异常

如果用户传入的参数不正确,我们通过全局异常处理器告知了前端,但是状态码是一个500的状态码是不合适的。

  • 封装客户异常:只要是客户端导致的异常,统统抛出ClientException
/**
 * 封装客户异常:只要是客户端导致的异常,统统抛出ClientException
 */
public class ClientException extends RuntimeException {
    public ClientException(String message) {
        super(message);
    }
}
  • 在GlobalExceptionHandler添加处理客户端异常方法
 /**
     * 处理客户端异常
     * @param e
     * @return
     */
    @ExceptionHandler   //处理和方法形参同类型的异常
    @ResponseStatus(HttpStatus.BAD_REQUEST)//响应状态码400
    public Result doClientException(ClientException e) {
        log.info("接收到的异常是: {}", e.getMessage());
        //向客户端返回结果
        return new Result(400, e.getMessage(), null);
    }
  • controller
 @PostMapping
    public String save(@Valid @RequestBody User user, BindingResult result){
        //使用校验框架对user进行校验
        if (result.hasErrors()) {   //是否有错误
            //获取所以错误集合
            List<FieldError> fieldErrors = result.getFieldErrors();
            log.info("fieldErrors = {}", fieldErrors);
            //遍历所有错误的集合
            for (FieldError fieldError : fieldErrors) {
                //获取错误字段
                String field = fieldError.getField();
                //获取错误信息
                String defaultMessage = fieldError.getDefaultMessage();
                log.info("{}{}", field,defaultMessage);
                //校验不合法就抛出异常到处理器中
//                throw new RuntimeException(field + defaultMessage);
                throw new ClientException(field + defaultMessage);
            }
//            return "参数不合法!";
        }
        log.info("save--->user:{}",user);
        //判断校验是否成功,如果有不合法数据,打印错误信息
        return "保存user成功";
    }

3.2 定义拦截器

拦截器:是一种动态拦截处理器方法的机制,可以在处理器方法执行前后执行,底层采用的是AOP。

定义拦截器并使用
  • 编写拦截器
  1. 创建拦截器类实现HandlerInterceptor接口
  2. 重写preHandle方法
@Slf4j
@Component//添加到spring容器
public class LoginInterceptor implements HandlerInterceptor {
    /**
     * 前处理,在处理器方法执行前执行
     * @param request  请求对象
     * @param response 响应对象
     * @param handler  当前方法对象,类似于aop中的连接点对象,封装了要执行的方法Method
     * @return 返回true表示放行,返回false表示拦截,不继续执行
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("preHandle...前处理");
        return true; //表示是否放行
    }
  • 配置拦截器

​ 在启动类下进行配置或者自己新建一个类

  @Autowired //注入拦截对象
    private LoginInterceptor loginInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //addPathPatterns:添加拦截路径
        registry.addInterceptor(loginInterceptor).addPathPatterns("/brand/**");//拦截路径
    }
拦截器实现登录验证

需求:如果用户登录成功,就运行访问。如果没有登录,对客户端做出响应并阻止运行

  • 在Controller中定义了login方法,访问之后模拟用户登录。
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
    @PostMapping("/login")
    public String login(HttpSession httpSession){
        httpSession.setAttribute("user","user");
        return "登录成功!";
    }
}
  • 修改拦截器preHandle方法。
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("preHandle...");
        //1.从session域中获取user信息
        Object user = request.getSession().getAttribute("user");
        //2.如果没有获取到,就表示用户没有登录,return false阻止访问
        if (user == null) {
            //抛出客户端异常,告诉客户端没有登录
            throw new ClientException("你还没有登录,无权访问!");
        }
        return true; //返回true表示放行,返回false表示拦截,不继续执行
    }
拦截器和过滤器的区别
  • 归属不同: Filter属于Servlet技术,Interceptor属于SpringMVC技术
  • 拦截内容不同: Filter是拦截请求和响应的,Interceptor拦截访问处理器中的方法,也就是拦截Controller中的方法调用。
  • 执行顺序不同:先执行过滤器,后执行拦截器

3.3 SpringMVC的执行流程

前端控制器(DispatcherServlet):中间调度的作用,调度HandlerMapping、HandlerAdapter、ViewResolver工作。

处理器映射器(HandlerMapping):解析Controller中的路径,和我们客户端的路径进行匹配,找到要访问的Controller中的方法

处理器适配器(HandlerAdapter):负责去调用Controller中的对应的方法,执行该方法,将结果封装成ModelAndView对象。

试图解析器(ViewResolver):负责解析ModelAndView对象中的数据和页面,最终告诉前端控制器去响应。

处理器(Handler):也就是我们自己写的Controller对象。

  • 执行步骤:
  1. 用户发送请求到前端控制器(DispatcherServlet)。

  2. 前端控制器 ( DispatcherServlet ) 收到请求调用处理器映射器 (HandlerMapping),去查找处理器(Handler)。

  3. 处理器映射器(HandlerMapping)找到具体的处理器(可以根据 xml 配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给 DispatcherServlet。

  4. 前端控制器(DispatcherServlet)调用处理器适配器(HandlerAdapter)。

  5. 处理器适配器(HandlerAdapter)去调用自定义的处理器类(Controller)。

  6. 自定义的处理器类(Controller)将得到的参数进行处理并返回结果给处理器适配器(HandlerAdapter)。

  7. 处理器适配器 ( HandlerAdapter )将得到的结果返回给前端控制器 (DispatcherServlet),页面跳转返回ModelAndView,响应数据返回null给前端控制器 (DispatcherServlet)

  8. 前端控制器(DispatcherServlet )将 ModelAndView 传给视图解析器 (ViewReslover),如果是响应数据就没有这一步

  9. 视图解析器(ViewReslover)将得到的参数从逻辑视图转换为物理视图并返回给前端控制器(DispatcherServlet) ,如果是响应数据就没有这一步

  10. 前端控制器(DispatcherServlet)调用物理视图进行渲染并返回,如果是响应数据就没有这一步

  11. 前端控制器(DispatcherServlet)将渲染后的结果返回,如果是响应数据直接通过response响应体传输

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr.han、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值