* RESTfulAPI以及相关核心技术和工具 **

目录结构导航:

一. 项目初始化

1.1 简介:

  • 我会从SpringMVC开发RESTfulAPI入手,讲解工作中的关于RESTful 相关的东西

1.2 连接数据库的相关问题

  • 首先我们需要在xml中添加相关依赖:
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-jdbc</artifactId>
	</dependency>
  • 数据库连接:
	spring.datasource.driver-class-name=com.mysql.jdbc.Driver
	spring.datasource.url=jdbc:mysql://127.0.0.1:3306/...
	spring.datasource.username=root
	spring.datasource.password=root
~~~java
- 集群Session管理(后面讲)
~~~java
	<dependency>
		<groupId>org.springframework.session</groupId>
		<artifactId>spring-session</artifactId>
	</dependency>

刚开始的时候我们不一定需要使用,我们可以设置手动关闭:
在application.yml中进行设置:
spring.session.store-type:none //将Session关闭

配置端口号:server.prot=8060
当我们引入SpringSecurity框架的时候,会默认启动身份验证,如果我们不想启动身份验证我们可以先手动关闭:
security.basic.enabled=false //这样后访问系统内的接口就不会再自动身份验证啦~

后面我们会将这些全部打开,并进行相关的配置;

1.3 项目打包的问题

  • 打包我们可以使用Maven的Build进行打包;一般打包出来的文件是不包含第三方jar包的,我们如果想打包一个可运行的WEB项目,可以这样做:
    1. 在xml中添加依赖:
      <build>
      	<plugins>
      		<plugin>
      			<groupId>org.springframework.boot</groupId>
      			<artifactId>spring-boot-maven-plugin</artifactId>
      			<version>1.3.3.RELEASE</version>
      		</plugin>
      	</plugins>
      	<finalName>demo</finalName>				//打包出来的名字
      </build>
      

当有了这些配置后,我们在target中就可以看到打包有两个文件:
一个以jar.original结尾的是原始jar包,不包含第三方的;
一个是可执行jar包,名字为demo.jar 它是可以直接执行的;

  • 执行打包出来的jar包:
    • 启用cmd命令行,先cd到项目的target下输入:java -jar demo.jar
    • 输入后就会直接启动jar包,跟我们在项目中main函数启动的效果是一样的,我们可以直接在浏览器中请求接口;

二. 使用SpringMVC开发RESTful API : 查询

2.1 RESTful特点

  • 学习内容:

    • 使用SpringMVC 编写Restful API
    • 使用SpringMVC处理其他web应用常见的需求和场景
    • Restful API开发常用辅助框架
  • RESTful API特点:

    1. 用URL描述资源
    2. 使用HTTP方法描述行为。使用HTTP状态码来表示不同的结果
    3. 使用json交互数据
    4. RESTful只是一种风格,并不是强制的标准

在请求中只放资源,而不会表明做什么操作,通过HTTP状态进行对应

2.2 REST成熟度模型:

  • 等级划分:
    • 第0级: 使用HTTP作为传输方式
    • 第1级: 引入资源概念,每个资源都有对应的URL
    • 第2级: 使用HTTP方法进行不同的操作。使用HTTP状态码来表示不同的结果
    • 第3级: 使用超媒体,在资源的表达中包含了链接信息;

2.3 编写第一个Restful API 查询

  • 编写针对Restful API测试用例
    1. 首先添加依赖:
      <dependency>
      	<groupId>org.springframework.boot</groupId>
      	<artifactId>spring-boot-starter-test</artifactId>
      </denpendency>
      
      //添加这个依赖后会将Spring的测试框架引入
    2. 测试用例代码: //快速的判断程序是不是按照我们所期望的那样运行
      - @RunWith(SpringRunner.class)
      - @SpringBootTest
      - publci class UserControllerTest{
      - @Autowired
      - private WebApplicationContext wac;
      - private MockMvc mockMVC;
      - @Befroe
      - public void setup(){
      - mockMvc=MockMvcBuilders.webAppContextSetup(wac).build();
      - }
      - @Test
      - public void whenQuerySuccess() throws Exception{
      -   mockMvc.perform(MockMvcRequestBuilders.get("/user")
      -   . param("username","pojo")
      -   .contentType(MediaType.APPLICATION_JSON_UTF8))
      -   .addExpect(MockMvcResultMathchers.status().isOk())
      -   .andExpect(MockMvcResultMathers.jsonPath("$.length()").value(3));}}				//返回的结果json长度必须为3
      
  • 使用注解声明RestfulAPI
    • 常用注解:
      1. @RestController 标明此Controller提供RestAPI
      2. @RequestMapping及其变体。映射http请求url到java方法
      3. @RequestParam 映射请求参数到java方法的参数
      4. @PageableDefault指定分页参数默认值
    • 代码示例:
      @RestController
      public class UserController{
      	@RequestMapping(value="/user",method=RequestMethod.GET)
      	public List<User>query(@RequestParam String username){		//这里的@RequestParam 表示请求的参数必须带一个这样的参数username
      		List<User> users=new ArrayList<>();
      		users.add(new User());
      		users.add(new User());
      		users.add(new User());
      		return users;
      	}
      }
      
    • @@RequestParam的问题:
      1. 在使用@RequestParam()中,如果参数和请求的值不一样,我们可以这样设置:@RequestParam(name=“username” String nickname),这样前端把username的值传入的时候,也可以将值给到nickname了,解决了参数名不一致的问题;
      2. 我们也可以不设置不一定要携带这个参数,设置如下:
        @RequestParam(required=false,defaultVGFalue="tom") String nickname   这样设置的时候,即便没有给这个nickname值,也不会报错,同时还可以给到它一个默认的值
        
  • 在RestfulAPI中传递参数

2.4 编写用户详情服务

  • 概述:
    • @PathVariable 映射url片段到java方法的参数
    • 在url在声明中声依永正则表达式
    • @JsonView控制json输出内容
  • 测试用例代码:
    @Test
    public void whenGenInfoSuccess() throws Exception{
    	mockMvc.perform(get("/user/1")
    	.contentType(MediaType.APPLICATION_JSON_UTF8))
    	.andExpect(status().isOk())
    	.andExpect(jaonPath("$.username").value("tom"))
    }
    

这里测试用例,限制了这个请求接口必须为GET请求,然后犯规的username是tom,用户id值为1的用户详细信息,这里将MockMvcRequestBuilders以及另外一个提取出来做为了一个静态方法,所以可以省略直接调用;

  • 接口方法代码:

    @RequestMapping(value="/user/{id}",method=RequestMethod.GET)
    public User getInfo(@PathVariable(name="id") String id){		//指定了name=id  那么后面的String类型的变量名可以随便取名,不一定为id
    	User user=new User();
    	user.setUsername("tom");
    	return user;
    }
    
  • 我们还可以进行限制,id值只能传入数字,使用正则表达式

    @RequestMapping(value="/user/{id:\\d+}",method=RequestMethod.GET)
    	public User getInfo(@PathVariable(name="id") String id){		//指定了name=id  那么后面的String类型的变量名可以随便取名,不一定为id
    		User user=new User();
    		user.setUsername("tom");
    		return user;
    	}
    
  • 当id值为字母的时候,肯定是错误的,那么我们可以做一个错误测试用例,检测上面的正则表达式是否有效果;

    @Test
    public void whenGetInfoFail() throws Exception{
    	mockMvc.perform(get("/user/a")
    	.contentType(MediaType.APPLICATION_JSON_UTF8))
    	.andExpect(status().is4xxClientError());		//服务器返回4..的状态比如404,400等都是在内
    }
    

2.5 JosnView注解

  • 使用步骤:

    • 使用接口来声明多个视图
    • 在值对象的get方法上指定视图
    • 在Controller方法上指定视图
  • 概述: JsonView 是什么,为什么要使用它?

    • @JsonView是Spring中的一个注解,用于在不同的请求中返回不同的视图。例如,在请求/users中会返回一个包含基本用户信息的List,此时不应该把所有的用户密码等详情返回出来。但是,当请求某一个用户的详情/user/{id:\+d}时候,则需要返回该用户的所有信息。此时,可以通过JsonView注解来表明如何在不同的请求中返回什么样的视图。
  • 代码示例:

    1. 使用接口声明多个视图,并在对象属性的get方法上指定视图
      import org.hibernate.validator.constraints.NotBlank;
      import com.fasterxml.jackson.annotation.JsonView;
      
      public class User {
      	
      	//声明一般视图接口 只允许这个视图返回用户名属性
      	public interface UserSimpView{};
      	//声明完整视图接口 允许返回用户名密码属性 由于集成了一般视图接口  含义是拥有了一般视图所具有的返回属性
      	public interface UserDetailView extends UserSimpView{};
      	
      	private Integer Id;
      	
      	private String userName;
      	
      	private String passWord;
      
      	public User() {
      	}
      
      	public User(Integer Id, String userName, String passWord) {
      		this.Id = Id;
      		this.userName = userName;
      		this.passWord = passWord;
      	}
      	
      	@JsonView(UserSimpView.class)
      	public Integer getId() {
      		return Id;
      	}
      
      	public void setId(Integer id) {
      		Id = id;
      	}
      
      	@JsonView(UserSimpView.class)
      	public String getUserName() {
      		return userName;
      	}
      
      	public void setUserName(String userName) {
      		this.userName = userName;
      	}
      	@JsonView(UserDetailView.class)
      	public String getPassWord() {
      		return passWord;
      	}
      
      	public void setPassWord(String passWord) {
      		this.passWord = passWord;
      	}
      
      	@Override
      	public String toString() {
      		return "User [Id=" + Id + ", userName=" + userName + ", passWord=" + passWord + "]";
      	}
      }
      
    2. 在Controller上指定视图:
      @RestController
      @RequestMapping("/user")
      public class UserController {
      	
      	@GetMapping(produces="application/json;charset=UTF-8")
      	@JsonView(UserDetailView.class)
      	public List getUserAll(@RequestParam("token") String token){
      
      		List<User> userList = new ArrayList<>();
      		userList.add(new User(1,"二十岁以后特殊视图0","123456特殊视图"));
      		userList.add(new User(2,"二十岁以后特殊视图1","qweqwe特殊视图"));
      		userList.add(new User(3,"二十岁以后特殊视图2","asdgdd特殊视图"));
      
      		return userList;
      	}
      
    3. 编写测试类:
      1. 使用完整视图的代码如下:
        @RestController
        @RequestMapping("/user")
        public class UserController {
        	
        	@GetMapping(produces="application/json;charset=UTF-8")
        	@JsonView(UserDetailView.class)				//完整视图,返回username,password
        	public List getUserAll(@RequestParam("token") String token){
        
        		List<User> userList = new ArrayList<>();
        		userList.add(new User(1,"二十岁以后特殊视图0","123456完整视图"));
        		userList.add(new User(2,"二十岁以后特殊视图1","qweqwe完整视图"));
        		userList.add(new User(3,"二十岁以后特殊视图2","asdgdd完整视图"));
        
        		return userList;
        	}   
        
      2. 使用特殊视图的代码如下:
        @RestController
        @RequestMapping("/user")
        public class UserController {
        	
        	@GetMapping(produces="application/json;charset=UTF-8")
        	@JsonView(UserSimpView.class)
        	public List getUserAll(@RequestParam("token") String token){
        
        		List<User> userList = new ArrayList<>();
        		userList.add(new User(1,"二十岁以后特殊视图0","123456完整视图"));
        		userList.add(new User(2,"二十岁以后特殊视图1","qweqwe完整视图"));
        		userList.add(new User(3,"二十岁以后特殊视图2","asdgdd完整视图"));
        
        		return userList;
        	}       
        

2.5 简化RequestMapping

  • 我们可以使用变体简化:

    • GetMapping("/user") 与 RequestMapping(method=RequestMethod.GET) //他们的含义是一样的
    • 其他的也是同理,比如PostMapping() DeleteMapping等。。。
  • 有测试用例的时候,我们在重构代码就能按着正确的路线走;

三. 使用SpringMVC开发RESTful API : 创建

3.1 处理创建请求的要点:

  • @ReqeustBody映射请求到java方法的参数
  • 日期类型参数的处理
  • @Valid注解和BindingResult验证请求的合法性并处理校验结果

3.2 创建请求的测试用例代码

@Test
public void whenCreateSuccess() throws Exception{
	String content="{\"username\":\"tom\",\"password\":null}";
	mockMvc.perform(post("/user").contentType(MediaType.APPLICATION_JSON_UTF8)
	.content(content))		//这里是给接口发送请求的时候携带的参数,表示只给了username和password,而username有值,password没有值
	.andExpect(status().isOk())
	.andExpect(jsonPath("$.id").value("1"));
}}

3.3 Controller中创建的接口

@PostMapping
public User create(@RequestBody() User user){		//使用@RequestBody后才能将请求的参数映射到对象里面,它是将Post的请求体Body里面的值映射
	user.setId("id");
	return user;
}

3.4 日期参数类型的处理

  • 在类中有生日Birthday的日期类型,我们应该如何去操作呢?

    • 在前后端分离,不同渠道,用指定的返回格式很麻烦,我们不使用带格式的时间转换器,我们可以使用时间戳,前端来决定如何展示;
    • 在我们的实体类中,比如生日: private Date birthday 是这样定义的,当前端传入的数据可以直接为时间戳,经过@ReqeustBody的映射自动转换后,就会变成时间格式了;当我们要返回给前端的时候,我们也可以继续以时间戳的形式返回;因为类上面有@RestController注解,那么返回给前端的就自动变成Json格式了,前端接收到的就自动变成时间戳;
  • 代码改造如下:

    1. 接口:
      @PostMapping
      public User create(@RequestBody User user){
      	System.out.println(user.getId());
      	System.out.println(user.getUsername());
      	System.out.println(user.getPassword());
      	System.out.println(user.getBirthday());		//返回标准日期格式
      	user.setId("1");
      	return user;
      }
      
    2. 测试方法
      @Test
      public void whenCreateSuccess() throws Exception{
      	Date date=new Date();
      	String content ="{\"username\":\"tom\",\"password\":null,\"birthday\":"+date.getTime};			//将时间戳信息给content
      	String result=mockMvc.perform(post("/user").contentType(MediaType.APPLICATION_JSON_UTF8)
      	.content(content))
      	.andExpect(status().isOk())
      	.andExpect(jsonPath("$.id").value("1"))
      	.andReturn().getResponse().getContentAsString();
      }
      

3.5 @Valid注解和BindingResult 验证请求参数的合法性并处理校验结果

  • 介绍:
    • 一定要验证用户传入的数据是否是有效的,有效的时候再执行逻辑,而每次这样写都会很繁琐,我们可以使用一些简便的方法进行限制,比如我们可以在实体类中的一个属性上加一个注解:@NotBlank 不为空
      @NotBlank
      private String password;
      
    • 我们还需要添加一个@Valid注解,在将请求体的数据转为对象的时候,就会判断类中的@NotBlank不为空;如果为空的话就会直接将请求打回,不会再进入请求体了;
      @PostMapping
      public User create(@Valid @RequestBody User user){
      	user.setId("1");
      	return user;
      }            
      
    • 有的时候,我们需要将错误信息记录下来,那么是要进入请求体的;比如当谁调用了这个接口,虽然格式不对,我们也要将用户请求的操作记录到日志里面;我们可以使用BindingResult erros ,它会带着错误信息进入请求;
      //返回的结果大致为:
      //显示内容:may not be empty      
      //具体方法:error.getDefaultMessage()
      
    • 接口改造:
      @PostMapping
      public User create(@Valid @RequestBody User user,BindingResult errors){
      if(errors.hasErrors()){
      	errors.getAllErrors().stream().forEach(errorm -> System.out.println(error.getDefaultMessage()));
      }
      user.setId("1");
      return user;
      }  
      
      
      
      

四. 使用SpringMVC开发RESTful API : 修改和删除

4.1 讲解点:

  • 常用的验证注解
  • 自定义消息
  • 自定义校验注解

4.2 常用验证注解

  • 图示1:
    [外链图片转存失败(img-MNxuvF3S-1563862092757)(https://i.imgur.com/n2v52DS.jpg)]

  • 图示2:
    [外链图片转存失败(img-XByUolvq-1563862092758)(https://i.imgur.com/fHrZxhi.jpg)]

4.3 代码

  • 一年以后的时间(这是未来的时间):
    Date date=new Date(LocalDateTime.now().plusYears(1).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli())
    
  • 概述: 当我们在实体类User上的Date类型的字段birthday上添加注解@Past的时候,并且从@PutMapping接口中传入的值加了@Valid和@ReqeustBody注解后,它会进行判断birthday字段传入的时间是否为当前时间的过去,如果不为则会报错,如果加了BindingResult类,则会返回:must be in the past //必须为过去
  • 说明:系统返回的是英文,我们可以自定义返回错误信息,比如:
    @Past(message="生日必须是过去的时间")
    private Date birthday;
    

4.4 自定义注解验证

  • 概述:很多时候,我们用不到这里面的注解,我们可以自定义注解,根据业务需要对传入的值进行判断;
  • 操作: //3.5 修改和删除请求这里
    1. 先创建一个注解Annotation: MyConstraint
      @Target({ElementType.METHOD,ElementType.FIELD})
      @Retention(RetentionPolicy.RUNTIME)
      @Constraint(validateBy=MyConstrainvalidator.class)		//锁定这个校验接口实现类
      public @interface MyConstraint{
      	String message();
      	Class<?>[] groups[] default{};
      	Class<? extends Payload>[] payload() default{};
      }		
      
    2. 实现一个接口ConstraintValidator,并重写它的两个方法,一个是初始化方法,这个随意,可以不写,一个是校验方法isValid ,这里面可以写校验逻辑,如果校验通过了,则可以直接返回true;
      //这里不用加@Component等注解,继承了这个接口就会自动加入Bean
      public class MyConstraintValidator implements ConstraintValidator<MyConstraint,Object>{
      	@Autowired
      	private HelloService helloService;
      	@Override
      	public void initialize(MyConstraint constraintAnnotation){
      		System.out.println("My validator init");
      	}
      	@Override
      	public boolean isValid(Object value,ConstraintValidatorContext context){
      		helloService.geeting("tom");		//这里可以忽略,执行业务逻辑,我们平时可以在这里随便写,可以注入Service
      		//这里的value是传入的值;
      		return true;
      	}
      }
      
    3. 使用
      1. 在实体类字段上添加注解:
        @MyConstraint(message="这是一个测试")
        private String username;
        
      2. 直接测试即可

校验的时候,我们如果需要根据业务来校验,就可以使用这种方式,自定义了业务逻辑,可以从传入的值进行判断,减少了很多不必要的操作;

4.5 修改update

PutMapping("/{id:\\d+}")
public User update(@Valid @RequestBody User user,BindingResult errors){
	if(errors.hasErrors()){
		errors.getAllErrors().stream().forEach(err0r->{System.out.println(error.getDefaultMessage());});
	}
}

4.6 修改-- 测试用例

@Test
public void whenUpdateSuccess() throws Exception{
Date date=new Date(LocalDateTime.now().plusYears(1).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
String content ="{\"username\":\"tom\",\"password\":null,\"birthday\":"+date.getTime};			//将时间戳信息给content
			String result=mockMvc.perform(post("/user/1").contentType(MediaType.APPLICATION_JSON_UTF8)
			.content(content))
			.andExpect(status().isOk())
			.andExpect(jsonPath("$.id").value("1"))
			.andReturn().getResponse().getContentAsString();
			System.out.println(result);
}

4.7 测试用例

@Test
public void whenDeleteSuccess() throws Exception{
	mockMvc.perform(delete("/user/1")
	.contentType(MediaType.APPLICATION_JSON_UTF8))
	.andExpect(status().isOk());
}

4.8 delete接口

@DeleteMapping("/{id:\\d+}")
public void delete(@PathVariable String id){
	System.out.println(id);
}

五. 服务异常处理

5.1 概述:

  • RESTful API错误处理机制
    • SpringBoot中默认的错误处理机制

      • SpringBoot中的错误处理会进行判断是浏览器发出来的还是客户端发出来的,
        • 如果是浏览器,会返回一个错误的HTML提示
        • 如果是客户端,会返回一个错误的JSON
      • 在BasicErrorController类它继承了AbstractErrorController,里面有两个方法,errorHtml和error, 前者是对浏览器的错误处理,后者是客户端的错误处理,当请求头中没有text/html时,就会返回json结果,如果有的话则返回HTML页面
      • 在浏览器发送请求中我们可以看到:Accept:text/html 所以才会有这样的两种不同结果 而在客户端中一般为: Accept: / 两种请求方式进入的方法不一样,所以返回的结果也不同;
    • 自定义异常处理

      • 自定义HTML异常
      • 自定义JSON格式异常

5.2 格式不正确的情况

  • 当我们请求后端时,传入的值不正确,而后端又使用了@NotBlank,@Valid,BindingResult等方式的时候,SpringBoot会自动收集错误,然后将错误信息和错误状态码等融合,然后一起返回给前端;
  • 图示:
    [外链图片转存失败(img-XYGvw6Oe-1563862092758)(https://i.imgur.com/cMAe2PU.jpg)]

5.3 自定义HTML异常

  • 位置:src/main/resources/resources/error/404.html
  • 输入:
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    </head>
    	您所访问的页面不存在
    </body>
    </html>
    
  • 当放在这个路径下,访问的为404的时候,会自动显示这个页面;
  • 我们比如也可以定义500…等等

5.4 自定义JSON异常(这里可以包含一些其他的信息)

@ControllerAdvice			//管辖所有的Controller类下的异常,本身不处理HTTP请求
public class ControllerExceptionHandler{
	@ExceptionHandler(UserNotExistException.class)		//加了这个注解后,其他Controller抛出的这个类型的异常就会转入到这个方法进行处理;0.
	@ResponseBody
	@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)		//500状态码
	public Map<String,Object>handleUserNotExistException(UserNotExistException ex){
		Map<String,Object> result =new HashMap<>();
		result.put("id",ex.getId());
		result.put("message",ex.getMessage());
		return result;			//返回错误后的Result
	}
}

六. RESTful API的拦截

6.1 概述:

  • 过滤器(Filter)
  • 拦截器(Interceptor)
  • 切片(Aspect)

6.2 自定义过滤器

  • 创建类:src/main/java/filter/TimeFilter.java:
    @Component
    public class TimeFilter implements Filter{
    	@Override
    	public void destroy(){
    	//销毁时执行的方法
    	}
    	@Override
    	public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain) throws IOException,ServletException{
    		System.out.println("time filter start");
    		long start =new Date().getTime();
    		chain.doFilter(request,response);			//这个表示用户执行的方法,因为过滤器有两部分,第一部分是用户请求前会进入一次过滤器,当用户执行完方法之后也会执行一次过滤器,而包裹我们的doFilter方法则是执行方法前,这里的chain.doFilter是执行方法时,当执行完后又会进入doFIlter里面,继续未完成的代码,通过这里我们可以实现计算一个方法的执行时间需要多久;
    		System.out.println("time filter 耗时:"+(new Date().getTime()-start));
    		System.out.println("time filter finish");
    	}
    	@Override
    	public void init(FilterConfig arg0) throws ServletException{
    		System.out.println("time filter init");
    	}
    }
    

6.3 如何将第三方过滤器加入到我们的项目

  • 概述: 在传统的SSM项目中我们可以使用web.xml将第三方的过滤器配置进去,而在SpringBoot没有配置文件不能以这种配置的方式,又不能改代码不能加入Bean,第三方过滤器的代码一般是不容易改的,如何做呢?
  • 解决方案:
    • 思路描述: 我们可以通过创建一个配置类,然后将写好的过滤器类new一个对象出来然后加入到注册Filter中,配置好路径,就可以使用啦~
  • 步骤如下:
    1. 第三方的Filter是没有@Component注解的,我们可以直接使用上面的Filter代码,只需要将@Component注释掉就可以当做一个第三方的过滤器了;
    2. 新建一个配置类 src/main/java/web/config/WebConfig.java:
      @Configuration
      public class WebConfig{
      	@Bean
      	public FilterRegistrationBean timeFilter(){
      		FilterRegistrationBean registrationBean =new FilterRegistrationBean();		//创建一个注册过滤器
      		TimeFilter timeFilter=new TimeFilter();			//创建一个自定义过滤器对象
      		registrationBean.setFiter(timeFilter);		//将自定义过滤器注册到注册过滤器中;
      		List<String> urls=new ArrayList<>();
      		ursls.add("/*");			
      		registrationBean.setUrlPatterns(urls);			//给过滤器设置拦截范围
      		return registrationBean
      	}
      	~~~
      
    3. 注意: 我们平时加了@Component注解的过滤器范围是所有的路径,而这里的自定义过滤器通过设置urls集合,可以自定义设置拦截路径;

6.4 拦截器

  • 为什么要使用拦截器呢?
    • 在过滤器Filter中,我们拦截的是Request请求,所以我们不知道具体执行的方法等,如果需要在这些方面进行我们可以使用拦截器(Interceptor),它是Spring提供的一套拦截机制,更贴合;
  • 操作:新建一个类:src/main/java/web/filter/Interceptor/TimeIntercetor.java
    • 它需要实现HandlerInterceptor并实现三个方法:preHandle(方法执行前,它是一个Boolean值,如果返回false,则不会执行postHandle方法),postHandle(方法执行后执行,如果方法抛出异常则不执行),afterCompletion(无论如何都会在方法之后且不管是否有异常都会执行,前面两个拦截器方法之后执行,类似于trycath中的finaly代码块)
    • 拦截器如果创建好了,加了@Component注解也需要
  • 代码:
    1. 自定义拦截器代码:TimeInterceptor.java:
      @Component		//这里只声明注解是不行的,我们必须添加到Interceptor中去
      public TimeInterceptor implements HandlerInterceptor{
      	@Override
      	public boolean preHandle(HttpServletRequest request ,HttpServletResponse response,Object handler) throws Exceptoon{
      		System.out.println(((HanderMethod)handler).getBean().getClass().getName());			//打印执行方法的类名
      		System.out.println(((HandleMethod)handler).getMethod().getName());					//打印执行方法的方法名
      		request.setAttribute("startTime",new Date().getTime());
      		return true;			//这里只有返回ture才会执行后面的postHandle方法 
      	}
      	@Override
      	public void postHandle(HttpServletRequest request ,HttpServletResponse response,Object handler,ModelAndView modelAndView ) throws Exception{
      		Long start=(Long) request.getAttribute("startTime");		//从上一个拦截器方法中找到这个start时间,然后通过执行方法后调用的这个PostHandle方法的时间相减,就可以得到方法的调用花费时间了;
      		System.out.println("time interceptor 耗时:"+ (new Date().getTime()-start))
      	}
      	@Override
      	public void afterCompletion(HttpServletRequest request,HttpServletResponse response,Object handler,Exceptiopn ex) throws Exception{
      		System.out.println("ex is "+ex);		//这里打印异常,如果异常被统一异常处理所捕捉了,那么即便发生了异常,这里打印还是会为null,需要注意;
      	}
      }
      
    2. 在配置类中将拦截器加入;
      @Configuration 
      public class WebConfig extends WebMvcConfigurerAdpter{
      	@Autowired
      	private TimeInterceptor timeInterceptor;
      	@Override
      	public void addInterceptors(InterceptorRegistry registry){
      		registry.addInterceptor(timeInterceptor);		//通过引入已经加入@Compnonet注解的拦截器,然后将其挂载到Interceptor上,即可实现了自定义拦截器
      	}		
      }
      

拦截器不仅能拦击我们的东西,也可以拦截Spring相关的Controller的方法;
拦截器虽然能处理方法,但是它不能知道方法中参数具体的类型,参数中具体的值,它的局限性我们可以通过切片解决;

6.5 切片Aspect

  • Spring AOP 简介:

    • 切片它本身是一个类
    • 切入点(注解)
      • 在哪些方法上起作用
      • 在什么时候起作用
    • 增强(方法)
      • 起作用时执行的业务逻辑
  • 操作:

    1. 添加依赖:
      <dependency>
      	<groupId>org.springframework.boot</groupId>
      	<artifactId>Spring-boot-starter-aop</artifactId>
      </dependency>
      
    2. 创建切片类: src/main/java/web/aspect/TimeAspect.java:
      @Aspect
      @Component
      public class TimeAspect{
      	@Around("execution(* com.migu.web.controller.UserController.*(...))")				//这里是围绕,我们还可以使用@After,@Before等
      	public Object handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable{
      		Object[] args= pjp.getArgs();			//获取到所有的参数
      		for(Object arg: args){
      			System.out.println("arg is "+arg);
      		}
      		long start =new Date().getTime();	
      		Object object=pjp.proceed();		//拿到返回值
      		return 	object;	
      	}
      }
      
      
      

6.6 拦截请求的顺序

  • 图示,当用户请求的时候,先经过Filter->Controller,由外向内,当请求返回的时候,则由内向外,比如抛出异常的时候,如果Aspect切片有设置相关的,则它是最先捕获到的;如果不放出异常,则其他的捕获则会null

过滤器只能拿到原始的Request,而拦截器能拿到方法但是不能拿到具体的值,而切片则可以拿到具体的参数和具体的返回值等。

七. 文件的上传和下载

7.1 文件上传的测试用例

@Test
public void whenUploadSuccess() throws Exception{
	String result=mockMvc.perform(fileUpload("/file")
	.file(new MockMultipartFile("file","text.txt","multipart/form-data","hello upload".getBytes("UTF-8")))
	.andExpect(status().isOk)
	.andReturn().getResponse().getContentAsString();
	System.out.println(result);
	)
}

7.2 文件上传服务

@RestController
@RequestMapping("/file")
public class FileController{
	@PostMapping
	public FileInfo uplaod(MultipartFile file) throws Exception{
	System.out.println(file.getName()+"-"+file.getOriginalFilename()+"-"+file.getSize());
	String folder="/..."		//这里是定义文件上传的地址,我们这里是存入本地,如果是上传到图片服务器之类的,则添加相应的东西即可;
	File localFile=new File(folder,new Date().getTime()+".txt");		//将新文件改名
	file.transferTo(localFile);			//执行上传操作
	return new FileInfo(localFile.getAbsolutePath());
}}

7.3 文件下载服务

  1. 添加依赖:
    <dependency>
    	<groupId>commons-io</groupId>
    	<artifactId>commons-io</artifactId>
    	<version>2.5</version>
    </dependency>
    
  2. 文件下载代码:
    @GetMapping("/{id}")
    public void download(@Pathvaariable String id,HttpServletRequest request,HttpResponse response){
    	try(
    	InputStream inputStream =new FileInputStream(new File(folder,id+".txt"));
    	OutputStream outputStream =response.getOutpustStream();	){
    		response.setContentType("application/x-download");
    		response.addHeader("Content-Disposition","attachment;filename=test.txt");
    		IOUtils.copy(inputStream,outputStream);
    	}}
    	~~~
    

这里是提供一个id值,然后以这个id值命名将文件下载下来;先InputStream读取文件信息,然后再使用outputStream输出,通过工具类IOUtils.copy完成;在这里面我们将try()中声明了inputStream 和outStream 流,这是JDK7以上的新特性,我们就可以不必手动关闭流了;

八. 异步处理REST服务

8.1 概述:

  • 使用Runnable异步处理Rest服务
    • 由主线程调用副线程进行处理,性能上有损失
  • 使用DeferredResult异步处理Rest服务
    • 它是相当于Controller中应用一的两个线程是分开的,一个负责接收请求并将请求内容发送到消息队列中,而另外一个应用二则一直监听,当接收到处理请求后它就开始处理并将处理结果发送到消息队列;应用一的另外一个线程则监听到处理结果后将结果响应给前端;
    • 这里面有三个职能划分,一个负责接收请求并转发到消息队列,一个负责监听请求并处理请求将结果给消息队列,一个负责接收处理结果并响应给前端; 这里面消息队列则是负责通信的核心;
  • 异步处理配置

为什么要使用异步处理?在高并发环境下有较好的吞吐量,比同步处理能同时处理更多的请求;
注意:如果想处理异步的请求有上面两种方式,但是在拦截方面我们也需要一个@Configuration注解所修饰的类实现WebMavConfigurerAdapter并重写configureAsyncSupport方法,在重写方法中configurer.registerCollable…或configurer.registerDeferredResult…要注册这两个异步的,因为同步和异步的拦截是不一样的;

相关的东西涉及的方面很多,难度也较大,这里只大概提一下,有兴趣的朋友可以单独找这方面的了解一下

九. 与前端开发并行工作

9.1 概述:

- 使用swagger自动生成html文档
	- 根据写的代码自动生成文档
- 使用WireMock快速伪造RESTful服务

9.2 操作:

  1. 添加依赖:
    <dependency>
    	<groupId>io.springfox</groupId>
    	<artifactId>springfox-swagger2</artifactId>
    	<2.7.0>
    </dependency>
    <dependency>
    	<groupId>io.springfox</groupId>
    	<artifactId>springfox-swagger-ui</artifactId>
    	<2.7.0>
    </dependency>
    

9.2 添加注解

  • 在一个需要生成文档的类上添加注解: @EnableSwagger2
  • 针对一个方法的描述注解,加在方法上面: @ApiOperation(value=“用户查询服务”)
  • 针对参数的属性,比如针对一个User用户的具体属性描述,我们可以加这个注解在它的实体类的具体属性值上,如:
    @ApiModelProperty(value="用户年龄起始值")
    private int age;
    

这几个是比较常用的,其他的注解需要了解的可以网上找相关文章;

9.3 伪造服务

  • 我们后端如果做伪造服务,那么前端就可以不用造假数据了。因为如果前端造假数据的话,如果是APP(安卓,苹果),浏览器等众多前端组每个都自己造假数据的话,对工作效率有影响,同时质量也残差不齐,我们后端可以做这方面的事情;

  • WireMock:它是一个独立的服务器,可以定义当它收到了什么请求后,返回什么样的响应;

9.4 操作

  1. 下载WireMock
  2. 在下载位置上,通过命令启动:java -jar wiremock-standalone-2.7.1.jar --port 8062
  3. 添加依赖:
    <dependency>
    	<groupId>com.github.tomakehurst</groupId>
    	<artifactId>wiremock</artifactId>
    	<version>2.5.1</version>
    </dependency>
    //如果不要版本号可以省略
    
  4. 创建一个类MockServer.java:
    public class MockServer{
    	public static void main(String[] args){
    		WireMock.configureFor(8062);		//因为是在本机上测试,所以可以不用指定ip
    		WireMock.removeAllMappings();		//每次开服务清除以前的配置,重新加配置更新;
    		WireMock.stubFor(geturlPathEqualTo("/order/1")).willReturn(aResponse().withBody("{\"id\":1}").withStatus(200)));		//这里的链式编程还可以继续加东西;而且相关的Json我们可以单独写,然后读取出来;
    	}}
    	//浏览器访问: localhost:8062/order/1
    

9.5 局部改造:

  1. 新建一个文件:src/main/resources/mock/response/01.txt:
    {
    	"id": 1
    	"type": "A"
    }
    
  2. 对类进行改造:
    public class MockServer{
    	public static void main(String[] args){
    		WireMock.configureFor(8062);		//因为是在本机上测试,所以可以不用指定ip
    		WireMock.removeAllMappings();		//每次开服务清除以前的配置,重新加配置更新;
    		ClassPathResource resource=new ClassPathResource("mock/response/01.txt");
    		String content =StringUtils.join(FileUtils.readLines(resource.getFile(),"UTF-8").toArray(),"\n")
    		WireMock.stubFor(geturlPathEqualTo("/order/1")).willReturn(aResponse().withBody("{\"id\":1}").withStatus(200)));		//这里的链式编程还可以继续加东西;而且相关的Json我们可以单独写,然后读取出来;
    	}}
    	//浏览器访问: localhost:8062/order/1
    

WireMock可以做各种请求,以及其他很多的高级特性,与前端的联合是很方便的;如果有深入了解的朋友可以详细查看官方文档;

restful restful所需要的jar包 ========================================= Restlet, a RESTful Web framework for Java ========================================= http://www.restlet.org ----------------------------------------- Native REST support * Core REST concepts have equivalent Java classes (UniformInterface, Resource, Representation, Connector for example). * Suitable for both client-side and server-side web applications. The innovation is that that it uses the same API, reducing the learning curve and the software footprint. * Restlet-GWT module available, letting you leverage the Restlet API from within any Web browser, without plugins. * Concept of "URIs as UI" supported based on the URI Templates standard. This results in a very flexible yet simple routing with automatic extraction of URI variables into request attributes. * Tunneling service lets browsers issue any HTTP method (PUT, DELETE, MOVE, etc.) through a simple HTTP POST. This service is transparent for Restlet applications. Complete Web Server * Static file serving similar to Apache HTTP Server, with metadata association based on file extensions. * Transparent content negotiation based on client preferences. * Conditional requests automatically supported for resources. * Remote edition of files based on PUT and DELETE methods (aka mini-WebDAV mode). * Decoder service transparently decodes compressed or encoded input representations. This service is transparent for Restlet applications. * Log service writes all accesses to your applications in a standard Web log file. The log format follows the W3C Extended Log File Format and is fully customizable. * Powerful URI based redirection support similar to Apache Rewrite module. Available Connectors * Multiple server HTTP connectors available, based on either Mortbay's Jetty or the Simple framework or Grizzly NIO framework. * AJP server connector available to let you plug behind an Apache HTT
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

暗余

码字来之不易,您的鼓励我的动力

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

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

打赏作者

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

抵扣说明:

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

余额充值