《Spring Boot 实战派》--02 Spring Boot 应用

Spring Boot 应用:

5. 分层开发web应用程序

本章首先介绍 Web开发中最常用的分层开发的模式 MVC ( Model View Cont olle 「),然后讲解视图技术 Thymeleaf的语法和实用技术,
并用实例讲解如何将 MVC 者联系起来构建 Web用程序;最后通过实例讲解如何用 Validator实现数据验证,以及如何自定义数据验证;
 
 
5.1 应用程序分层开发模式--MVC
如果不想使用 MVC 开发模式也是可以的, MVC 只是一个非常合理的规范 MVC 的关系:

  如果读者对MVC 开发模式理解得不深入,那么往往会以为用户通过浏览器访问 MVC 模型的页面就是访问视图 ( View ) 。实际上, 它并不是直接访问视图,

而是访问 DispatcherServlet处理映射和调用视图渲染,然后返回给用户的数据;

  在整个 Spring MVC 框架中, DispatcherServlet 处于核心位置,继承自 HttpServlet。它负责协调和组织不同组件,以完成请求处理并返回响应果工作;
整个工程流程如下:
( 1 )客户端(用户)发出的请求由 Tomcat (服务器)接收,然后Tomcat 将请求转交给DispatcherServlet 处理
( 2 ) DispatcherServlet 匹配控制器中配置的映射路径,进行下一步处理
( 3 ) ViewResolver将ModelAndView 或Exception 解析成 View。 然后 View会调用 render方法,并根据 ModelAndView 中的数据渲染出页面;
在MVC 开发模式中,容易混淆的还有 Model, 它往往会被认为是业务逻辑层或 DAO。这种理解并不能说是错误的, 但并不是严格意义上的 MVC 模式;
 
 
三层架构,就是将整个应用程序划分为表现层( UI )、业务逻辑层( Service) 、数据访问层( DAO/Repository)
  RepositoryDAO 层一样,都可以进行数据的增加、删除、修改和查询。它们相当于仓库管理员,执行进/出货操作。
  DAO 层的工作是存取对象 Repository 层的工作是存取和管理对象。
  简单理解就是:Repository =管理对象(对象缓存和在 Repository 的状态)+ DAO
 
控制器Controller中常用的注解:
@RequestMapping
它用来处理请求地址映射的注解,  可用在类或方法上。 如果用在类上,则表示类中的所有响应请求的方法都以该地址作为父路径;
RequestMapping 注解有6个属性:
  • value :指定请求的地址
  • method :指定请求的 method 类型-GET 、HEAD、 POST、 PUT、 PATCH、 DELETE、 OPTIONS、 TRACE
  • consumes :消费消息,指定处理请求的提交内容类型( Content-Type ),例如 application/json、text/html
  • produces :生产消息,指定返回的内容类型。 仅当request 请求头中的 Accept 类型中包含该指定类型时才返回
  • params :指定request 中必须包含某些参数值才让该方法处理请求。
  • headers :指定request 中必须包含某些指定的 header  值才能让该方法处理请求
@PathVariable
将请求 URL 中的模板变量映射到功能处理 法的参数上,即获取 URI 的变量作为参数下代码先通过获取路径中的 值,
再根据获取的id 值来获取数据库中产晶的对象
@RequestMapping(value = "/product/{id}" method = RequestMethod.GET) 
public String getProduct(@PathVaribale("id") String id){ 
    Product product = productRepository.findById(id);
    System.out.println("产品id:"+ product.getld());
    System.out.printin("产品名称:"+product.getTitle());
    return "product/show";
}

  

5.3.2 URL 映射到方法
  URL (统一资源定位符)映射到方法,是通过注解@ RequestMapping 来处理的;
URL映射真实就是用控制器定义访问的 URL 路径,  用户通过输入路径来访问某个方法

 

Spring Boot 还提供了更简洁的编写 URL 映射的方法,如@GetMapping(“/”),它等价于@ RequestMapping(value =”/”,method= RequestMethod.GET),  除此之外还有下面的写法。
• @GetMapping :处理 GET 请求
• @PostMapping : 处理 POST 请求
• @DeleteMapping :  处理删除请求
• @PutMapping :处理修改请求。
 
5.3.3 处理 HTTP 请求的方法
  RequestMapping 的method 类型有 GE HEAD POST PUT PATCH DELETE OPTIONS TRACE
可以通过这些 method 来处理前端用不同方法提交的数据。
1. GET
  GET 方法是最常用的万法 GET 方法可以获取资源,  比如,以下代码用 GET万法根据 id来获取文章对象。
@GetMapping(”/{id}”) 
public ModelAndView getArticle(@PathVariable("id") Integer id) throws Exception { 
    Article ticles = articleRepository.findById(id);
    ModelAndView mav = new ModelAndView("article/show”);
    mav.addObject("article", articles);
    return mav;
}

2.  DELETE

  如果需要删除一个数据,根据 Restful 风格则需要使用 DELETE方法。在使用 Delete方法
删除资源时 要注意判断是否成功,因为返回的是 VOID 类型,一般有以下三个方法进行判断。
  • 使用 try  catch  exception :如果不发生异常,则默认为成功,但是这样并不好。
  • 通过存储过程的返回值来判断是否正确执行:如果执行成功,则返回 1或大于0 的值;如果执行失败,则返回0
  • 在执行 DELETE 方法前先查询是否有数据:在执行 DELETE 方法后返回值是 0,所以,一般先查询 一下是否有数据。
3. POST
如果需要添加对象,那一般使用 POST 方法传递一个 Model 对象
在求职面试时,有时面试官会询问GET 和 POST 的区别,一般而言, GET 和 POST有如下区别:
•GET在浏览器中可以回退,而POST 访问同一个地址时也是再次提交请求
•GET请求会被浏览器主动缓存,而 POST 则不会
•GET中的参数会被完整地保留在浏览器历史记录里,而 POST 中的参数则不会t1呆留
•GET只能进行 URL 编码,而 POST 支持多种编码方式
•GET只接收 ASCII字符,而 POST 没有限制
•GET的安全性相比POST 低,因为参数直接暴露在 URL上,所以不能用它传遂敏感信息
•GET 的参数是通过URL 传递的,而 POST 的参数是放在request body 中的
但是,以上这些都不是绝对的,比如 POST 也可以通过 URL 路径提交参数

  

4. PUT

如果对象需要更新, 则用 PUT方法发送请求。
 
5. PATCH
PATCH 是一个新引入的方法,是对 PUT方法的补充,用来对已知资源进行局部更新。
很多人对这个方法不太理解 ,因为使用 PUT和 PATCH 方法都能成功,导致不太理解什么叫局部更新。
下面以更新User对象来理解它们的区别。
User对象有 id、name、 password、 sex 等属性。如果只需要修改 name 的值,则此时的更新操作就可以用 patch方法。
但是在大多数的应用程序中 ,很多人都会使用 PUT 方法提交完整的 User对象给后端。
这种做法虽然在功能是可以实现的,但对资源是一种浪费一一提交的数据过多了。如果遇到文章类型的对象,为了改一个标题,而要同时提交很多的内容实在是不划算(一篇文章一般由标题和内容等构成,内容往往很多)

 

5.1处理内容类型

1. 认识Http中的媒体类型content-Type

在HTTP 协议消息头中,用 Content-Type 来表示具体请求中的媒体类型信息。PC 端网页常用的是“text/html 格式,手机 APP 常用的是 JSON 恪式
(I) 常见的媒体格式如下
• text/html: HTML 格式。
• text/plain :纯文本恪式
• text/xml :XML 格式。
• image/gif: GIF 图片格式
• image/jpeg: JPG图片格式
• image/png: PNG图片恪式
( 2 ) 以 application 开头的媒体格式如下:
• application/xhtm +xml :XHTML+XML 格式
• application/xm :XML 数据格式。
• application/atom+xml:  Atom XML 聚合格式
• application/json : JSON 数据格式
• application/pdf : PDF格式
• application/msword : Word文档格式
• application/octet-strearn :二进制流数据(常用于文件下载)
• application/x-www-form-urlencoded :表单数据编码方式,<form encType=" " >中默认的 encType, Form (表单)数据被默认编码为key/value格式发送给服务器
• multipart/form-data :如果在表单中进行文件上传要使用该恪式
 
2. 用Produces 和 Consumes 处理内容类型
(1) Produces 的例子
  下面是一个返回 JSON 格式的数据的例子, 代码中可以省略 Produces 属性,因为己经使用了注解@RestController,它的返回值就是 JSON 格式的数据
@RestController
@RequestMapping(value = "/{id}”, method=RequestMethod.GET,produces="application/json")
public Model getModel (@PathVariable String id, Model model) { 
  //
}

 如果要强制返回编码,则加上编码类型,如以下代码:

produces ="MediaType.APPLICATION_JSON_VALUE"+";charset=utf-8")

  

( 2 ) Consumes 的例子
在以下例子中, Consumes 是消费者,用于指定获取消费者的数据类型
@RestController
@RequestMapping(value = "/{id}”, method=RequestMethod.GET,consumes="application/json")
public void getModel (@RequestBody Model model) { 
  //
}

 

5.2 在方法中使用参数

1. 获取路径中的值

/** 
 * Description: 根据 id 获取文章对象
 *
 */
@GetMapping("article/{id}")
public ModelAndView getArticle(@PathVariable("id") Integer id) { 
    Article articles = articleRepositor.findByld(id);
    ModelAndView mav = new ModelA idView("article/show")
    mav.addObject("article’’, articles);
    reture mav;
}

  

在访问“http//localhost/article/123”时,程序会自动将URL 中的模板变量{ id }绑定到通过@PathVariable 的同名参数上,即 “程序获取路径中123的值“。
 

2.  获取路径中的参数

对于路径中的参数获取, 可以写入方法的形参中,下面代码是获取参数 username 的值;

@RequestMapping("/addUser")
public String addUser(String username){
}

  这里的参数和上面所讲的获取路径值是不一样的, 比如:http://localhost/user/?username=longzhiran, 它是由"="隔开的

3. 通过Bean接收HTTP提价的对象

  可以通过Bean 获取HTTP提交的对象, 如以下代码:

public String addUser(UserModel user){
}

 

4. 通过HttpServletRequest接收的参数

  可以通过HttpServletRequest接收参数, 如下代码:

@RequestMapping("/addUser")
public String addUser(HttpServletRequest request){
    Syso("name:"+request.getParameter("username"))  
    return "/index";
}

 

6. 用@RequestParam绑定入参

@RequestParam(value="username", required=false)

当请求参数不存在时会有异常发生, 可以通过设置属性“required=false”来解决

 

7. 用@RequestBody接收JSON数据

  可以通过@RequestBody 注解来接收JSON数据, 如下代码:

@RequestMapping("/addUser", method={RequestMethod.POST})
@ResponseBody
public void saveUser(@RequestBody List<User> users){
    userService.saveUser(users);
}

8. 上传文件 MultipartFile

  通过@RequestParam 获取文件, 如下代码:

public String singleFileUpload(@RequestParam("file")MultipartFile file,  RedirectAttributes redirectAttributes) { 
    if (file.isEmpty()) { 
        edirectAttributes.addFlashAttribute("message",“请选择文件");
        return "redirect:uploadStatus";
    }
    try{
      byte[] bytes= file.getBytes(); 
      Path path= Paths.get(UPLOADED_FOLDER + file.getOriginalFilename());
      Files.write(path, bytes); 
      edirectAttributes.addFlashAttribute("message", "成功上传"+ file.getOriginalFilename() +"");
    }catch (IOException e) { 
        e.printStackTrace();
    }
    return "redirect:/uploadStatus";
}

  处于安全考虑, 在生产环境中需要判断文件的类型, 一般不允许上传“.exe”等格式的可执行文件

9. 上传图片

很多人在整合富文本编辑器时不容易成功,特别是在不同版本要求返回的数据类型不一样时,而网络上的资料很多是不带版本号或是过时的。
这里以常用的富文本编辑器 CKEditor 4.10.1 为例,实现上传图片功能。 Spring Boot后的版本只有返回的是 JSON 格式的数据才能成功,如:
[{ ”uploaded": 1,  ”fileName":  ”fileName”,  "url" ="","message": "上传成功" }]
上传图片的代码如下:
long i = System.currentTimeMillis();
//新建日期对象
Data date = new Date();
//转换日期输出格式
SimpleDateFormat format = new SimpleDataFormat("yyyyMMdd");
String nyr = format.format(date);

private static String UPLOADED_FOLDER = "/UPLOAD/img/”;
@PostMapping("/upload") @ResponseBody //注意, ckeditor 上传的是 upload 字段 public Map<String, Object> singleFileUpload(@RequestParam(”upload”) MultipartFile file, RedirectAttributes redirectAttributes) {   Map<St ing, Object> map= new HashMap<St ing, Object>();   if(file.getOriginalFilename().endsWith{”.jpg”) || file.getOriginalFilename().endsWith(”.png”) || file.getOriginalFilename().endsWith(”.gif")) {     try{       byteD bytes = file.getBytes();       String s = nyr + Math.random() + file.getOriginalFilename();       Path path = Paths.get(UPLOADED_FOLDER + s);       Files.write(path, bytes);       map.put(”uploaded”, 1);       map.put(”fileName”, s);       map.put(”url”,”/UPLOAD/img/”+ s);       map.put(”message”,”上传成功”);       reture map;       //return "({”uploaded": "","fileName":"fileName","message":"上传成功" }];     } catch (IOException e) {       e.p intStackTrace();   }else{     map.put(”uploaded”, O);     map.put(”fileName”, file.getOriginalFilename());     map.put("url","/img/"+file.getOriginalFilename());     map.put("message",”图片后缀只支持 png,jpg,gif,请检查!");     return map;
  }   return map;
}

  

5.3 验证数据

1、认识内置的验证器Hibernate-validator

Hibernate-validator 可实现数据的验证,它是对 JSR (Java Specification Requests )标准 的实现。在Web开发中,不需要额外为验证再导入其他依赖,只需要添加Web依赖即可。

Web 依赖不只集成了 Hibernate-validator,还有如下的子依赖:

  • spring-boot-starter
  • spring-boot-starter-json。
  • spring-boot-starter-tomcat
  • hibernate-validator
  • spring-web
  • spring-webmvc

由此可见,Web依赖集成了服务器环境TomcatJSONMVCValidator在开发Web 时,只需要关注业务逻辑即可

Spring Boot 2.1.3Validator版本是6.0.16是依据JSR-380标准实现的,其常用注解见表

 

表 Validator验证的常用注解:

注 解

作用类型

说 明

@NotBlank(message =)

字符串

验证字符串非null,且长度必须大于0

@Email

字符串

被注释的元素必须是电子邮箱地址

@Length(min=,max=)

字符串

被注释的字符串的大小必须在指定的范围内,min代表最小,

max代表最大

@NotEmpty

字符串

被注释的字符串必须非空

@NotEmptyPattem

字符串

在字符串不为空的情况下,是否匹配正则表达式

@DateValidator

字符串

验证日期格式是否满足正则表达式,Local为英语

@DateFormatCheckPattern

字符串

验证日期格式是否满足正则表达式,Local是自己手动指定的

@CreditCardNumber

字符串

验证信用卡号码

@Range(min=,max=,message=)

数值类型、字

符串、字节等

被注释的元素必须在合适的范围内

@Null

任意

被注释的元素必须为null

@NotNull

任意

被注释的元素必须不为null

@AssertTrue

布尔偵

被注释的元素必须为true

@AssertFalse

布尔值

被注释的元素必须为false

@Min(value)

数字

被注释的元素必须是一个数字,且大于或等于指定的最小值

@Max(value)

数字

被注释的元素必须是一个数字,且小于或等于指定的最大值

@DecimalMin(value)

数字

被注释的元素必须是一个数字,且大于或等于指定的最小值

@DecimalMax(value)

数字

被注释的元素必须是一个数字,且小于或等于指定的最大值

@Size(max=,min=)

数字

被注释的元素的大小必须在指定的范围内,min代表最小,

max代表最大

@Digits (integer, fraction)

数字

被注释的元素必须是一个数字,且在可接收的范围内

@Past

日期

被注释的元素必须是一个过去的日期

 

作用类型

©Future

日期

被注释的元素必须是一个将来的日期

@Pattern(regex=,flag=

正则表达式

被注释的元素必须符合指定的正则表达式

@ListStringPattem

List<String>

验证集合中的字符串是否满足正则表达式

 
 

 

 

 

 

 

 

 

 

 

2、自定义验证功能

自定义验证需要提供两个类:①自定义注解类;②自定义验证业务逻辑类。

1. 自定义注解类

如果要自定义验证功能,则需要先自定义注解,以便在实体Bean中使用它,见以下代码:

 

package com.example.demo;
import com.example.MyCustomConstraintValidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//限定使用范围一一只能在字段上使用
@Target((ElementType.FIELD))
//表明注解的生命周期,它在代码运行时可以通过反射获取到注解
@Retention(RetentionPolicy.RUNTIME)
//©Constraint注解,里面传入了一个validatedBy字段,以指定该注解的校验逻辑
@Constraint(validatedBy = MyCustomConstraintValidator.class)
public ©interface MyCustomConstraint {

    /**
     * ©Description:错误提示
     */
    String message() default "请输入中国政治或经济中心的城市名";
    Class<?>[] groups default {};
    Class<? extends Payload>[] payload() default{};
}

  

 

2. 自定义验证业务逻辑类

在自定义验证实现类中需要两个方法(initialize和isValid ) ----始化验证消息的方法和执行验证的方法。

在初始化验证消息的方法中,可以得到配置的注解内容;而验证方法则是用来验证业务逻辑的, 它需要继承 ConstraintValidator接口。

package com.example;
import com.example.demo.MyCustomConstraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class MyCustomConstraintValidator implements ConstraintValidator<MyCustomConstraint, String> { //String为校验的类型 @Override public void initialize(MyCustomConstraint myConstraint) ( //在启动时执行 } /** * @Description:自定义校验逻辑 */ @Override public boolean isValid(String s, ConstraintValidatorContext validatorContext) ( if (!(s.equals(”北京”) || s.equals("上海"))){   return false;   }   return true; } }

  注解@MyCustomConstraint已被成功定义,可以在其他类中调用它来实现验证功能。

 

3、验证表单数据并实现数据的自定义验证

 

a.创建实体

  创建实体Bean,用于表单验证。这里要特别注意的是,定义的所有字段都需要被验证,否则会出错;

package com.example.demo.entity;
import com.example.demo.MyCustomConstraint;
import lombok.Data;
import org.hibernate.validator.constraints. Length;
import javax.validation.constraints.*;
import java.io.Serializable;
@Data public class User implements Serializable (   /**   * 主键id   */   private Long id;
  @NotBlank(message = ”用户名不能为空”)   @Length(min = 5, max=20, message="用户名长度为 5-20 个字符")
  private String name;
  @NotNull(message ="年龄不能为空")   @Min(value = 18, message ="最小18岁")   @Max(value = 60, message ="最大60岁”)   private Integer age;
  @Email(message ="请输入邮箱")   @NotBlank(message = ”邮箱不能为空”)   private String email;
  @MyCustomConstraint   private String answer;
}

  

b.编写验证控制器

编写控制器,用于绑定数据验证。

package com.example.demo.controller;
import com.example.demo.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes; 
import javax.validation.Valid;
©Controller public class TestValidator (
  @GetMapping("/test")   public String showForm(User user) (     return "form";   }
  @GetMapping("/results")   public String results() (     return "results";   }
  @PostMapping("/test")   public String checkUser(@Valid User user, BindingResult bindingResult, RedirectAttributes attr) {     //特别注意:实体中的属性都必须被验证过,否则不会成功     if (bindingResult.hasErrors()) (       return "form";     )     attr.addFlashAttribute("user", user);     return "redirect:/results";   } )

  

代码解释如下。

  • addAttribute 方法:用 RedirectAttributes addAttribute 方法传递参数,参数应跟在 URL 后面。
  • addFlashAttribute方法:参数不会跟在URL后面,会被暂存在session中。

redirect方法:必须是方法的映射路径。redirect后的值只会岀现一次,刷新后不会出现。 可以使用redirect方法来防止重复提交。

 

 

 

posted @ 2022-04-22 11:31  IT6889  阅读(136)  评论(0编辑  收藏  举报