RESTFUL API开发常用接口以及java开发常用操作

目录

io.spring.platform和org.springframework.cloud

 

commons

java -jar 文件名

REST成熟等级

controller层中常用注解

@RequestParam

@PageableDefault

@PathVariable

@JsonView

@RequestBody

前后端分离中Date的使用

校验注解

@NotBlank和@Valid和BindingResult

大部分校验注解

自定义校验注解

后端错误请求抛出

浏览器错误抛出

自定义后端错误返回

RESTful  API的拦截

过滤器(Filter)

过滤器实现流程

添加第三方过滤器

拦截器(Interceptor)

拦截器实现流程

切片(Aspect)

文件的上传和下载

文件上传

文件下载

异步处理REST服务

使用Runnable异步处理Rest服务

使用DeferredResult异步处理Rest服务

异步处理配置

swagger自动生成文档

 WireMock快速伪造RESTful服务

引用properties中的属性

properties中自动注入properties文件中的属性

类属性注入properties文件中的属性

单个属性注入properties文件属性

利用ResourceBundle类获取properties中属性

properties乱码


io.spring.platform和org.springframework.cloud

主要是用来对项目中引入jar的管理,这样后续引入的jar就不需要引入版本了 

<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>io.spring.platform</groupId>
				<artifactId>platform-bom</artifactId>
				<version>Brussels-SR4</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>Dalston.SR2</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

 

但是很多项目会自动引入,所以上面那种方式基本上已经被淘汰了

<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.2.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

<module>

说明当前的maven项目是以下项目的父模块

<modules>
		<module>../security-app</module>
		<module>../security-browser</module>
		<module>../security-core</module>
		<module>../security-demo</module>
	</modules>

commons

常用的工具包

                <dependency>
			<groupId>commons-lang</groupId>
			<artifactId>commons-lang</artifactId>
		</dependency>
		<dependency>
			<groupId>commons-collections</groupId>
			<artifactId>commons-collections</artifactId>
		</dependency>
		<dependency>
			<groupId>commons-beanutils</groupId>
			<artifactId>commons-beanutils</artifactId>
		</dependency>

java -jar 文件名

直接命令行启动jar

java -jar 文件名

REST成熟等级

controller层中常用注解

@RequestParam

映射请求参数栋java方法的参数

@PageableDefault

指定分页参数默认值

@PathVariable

映射url片段到java方法的参数

//正则表达式,只能接收数字   {id:\\d+}
@RequestMapping(value = "user/{id:\\d+}")
	public String test(@PathVariable(name="id") String id){
		System.out.println(id);
		return "ok";
	}

@JsonView

控制json输出内容

public class User {
	
	public interface UserSimpleView {};
	public interface UserDetailView extends UserSimpleView {};
	
	private String id;
	
	private String username;
	
	private String password;
	
	private Date birthday;

	@JsonView(UserSimpleView.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;
	}

	@JsonView(UserSimpleView.class)
	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}
	
	@JsonView(UserSimpleView.class)
	public Date getBirthday() {
		return birthday;
	}

	public void setBirthday(Date birthday) {
		this.birthday = birthday;
	}
	
}

当方法上使用了 @JsonView之后,只返回user方法中注解了当前注解的属性

	@GetMapping("/{id:\\d+}")
	@JsonView(User.UserDetailView.class)
	public User getInfo(@ApiParam("用户id") @PathVariable String id) {
//		throw new RuntimeException("user not exist");
		System.out.println("进入getInfo服务");
		User user = new User();
		user.setUsername("tom");
		return user;
	}

@RequestBody

映射请求体到java 方法的参数,大部分用于处理post请求过来的请求体

        @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;
	}

前后端分离中Date的使用

在前后端分离的系统中,前端传给后端的时间为时间戳,后端传回给前端的也是时间戳,因为在前后端的思想中,后端对应着多个前端,对于日期的转换成如果由后台处理,其处理逻辑将非常麻烦。

        //传到后台的是时间戳,返回回去的时候不需要做任何修改,又直接返回回去
        @PostMapping
	public User create(@RequestBody User user) {

		System.out.println(user.getBirthday());

		user.setId("1");
		return user;
	}

校验注解

@NotBlank和@Valid和BindingResult

当数据传入的时候,判断是否为空,两个注解要一起使用,否则不会生效。BindingResult类可以使得即使触发了@Valid也会进入到代码中执行(通过BindingResult获取错误信息),而不是直接给前端返回错误码。

        @NotBlank(message = "密码不能为空")
	private String password;
       public User update(@Valid @RequestBody User user, BindingResult errors) {

		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;
	}

大部分校验注解

Constraint 详细信息
@Valid 被注释的元素是一个对象,需要检查此对象的所有字段值
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse  被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min)    被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future     被注释的元素必须是一个将来的日期
@Pattern(value)  被注释的元素必须符合指定的正则表达式
@Email 被注释的元素必须是电子邮箱地址
@Length 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range  被注释的元素必须在合适的范围内
@NotBlank  被注释的字符串的必须非空
@URL(protocol=,host=, port=,regexp=, flags=) 被注释的字符串必须是一个有效的url
@CreditCardNumber      被注释的字符串必须通过Luhn校验算法,银行卡,信用卡等号码一般都用Luhn计算合法性

自定义校验注解

创建注解,该注解的处理逻辑主要在 MyConstraintValidator类中,里面的三个元素必须存在,message是出现错误之后返回给前端的信息

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MyConstraintValidator.class)
public @interface MyConstraint {
	
	String message();

	Class<?>[] groups() default { };

	Class<? extends Payload>[] payload() default { };

}

 MyConstraintValidator,校验逻辑主要在isvalid方法中

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.greeting("tom");
		System.out.println(value);
		return true;
	}

}

后端错误请求抛出

浏览器错误抛出

直接在resources文件夹下创建文件以及页面即可

自定义后端错误返回

创建异常类

public class UserNotExistException extends RuntimeException {

	/**
	 * 
	 */
	private static final long serialVersionUID = -6112780192479692859L;
	
	private String id;
	
	public UserNotExistException(String id) {
		super("user not exist");
		this.id = id;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

}

创建ControllerExceptionHandler类,当controller层返回异常时,自定义返回内容

@ControllerAdvice
public class ControllerExceptionHandler {

	@ExceptionHandler(UserNotExistException.class)
	@ResponseBody
	@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
	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;
	}

}

RESTful  API的拦截

过滤器(Filter)

@Component
public class TimeFilter implements Filter {

	/* (non-Javadoc)
	 * @see javax.servlet.Filter#destroy()
	 */
	@Override
	public void destroy() {
		System.out.println("time filter destroy");
	}

	/* (non-Javadoc)
	 * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
	 */
	@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);
		System.out.println("time filter 耗时:"+ (new Date().getTime() - start));
		System.out.println("time filter finish");
	}

	/* (non-Javadoc)
	 * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
	 */
	@Override
	public void init(FilterConfig arg0) throws ServletException {
		System.out.println("time filter init");
	}

}

过滤器实现流程

 

添加第三方过滤器

因为第三方过滤器中没有加component注解,所以需要我们写一个配置类来添加

@Configuration
public class WebConfig{
	
	@Bean
	public FilterRegistrationBean timeFilter() {
		
		FilterRegistrationBean registrationBean = new FilterRegistrationBean();
		
		TimeFilter timeFilter = new TimeFilter();
		registrationBean.setFilter(timeFilter);
		
		List<String> urls = new ArrayList<>();
		urls.add("/*");     //拦截所有的请求
		registrationBean.setUrlPatterns(urls);
		
		return registrationBean;
		
	}

}

过滤器拦截还是存在很多问题的,只能知道拦截下来的http请求和响应的内容,不知道具体是哪个方法去处理的。这个时候需要后面的拦截器来具体处理。

拦截器(Interceptor)

@Component
public class TimeInterceptor implements HandlerInterceptor {

	/* (non-Javadoc)
	 * @see org.springframework.web.servlet.HandlerInterceptor#preHandle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object)
	 */
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		System.out.println("preHandle");
		
		System.out.println(((HandlerMethod)handler).getBean().getClass().getName());
		System.out.println(((HandlerMethod)handler).getMethod().getName());
		
		request.setAttribute("startTime", new Date().getTime());
		return true;
	}

	/* (non-Javadoc)
	 * @see org.springframework.web.servlet.HandlerInterceptor#postHandle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object, org.springframework.web.servlet.ModelAndView)
	 */
	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
		System.out.println("postHandle");
		Long start = (Long) request.getAttribute("startTime");
		System.out.println("time interceptor 耗时:"+ (new Date().getTime() - start));

	}

	/* (non-Javadoc)
	 * @see org.springframework.web.servlet.HandlerInterceptor#afterCompletion(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object, java.lang.Exception)
	 */
	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
		System.out.println("afterCompletion");
		Long start = (Long) request.getAttribute("startTime");
		System.out.println("time interceptor 耗时:"+ (new Date().getTime() - start));
		System.out.println("ex is "+ex);

	}

}
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
	@SuppressWarnings("unused")
	@Autowired
	private TimeInterceptor timeInterceptor;
	
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(timeInterceptor);
	}
	

}

拦截器实现流程

切片(Aspect)

切片也就是对spring aop的运用,我们由于拦截器不能指定requestmapping方法,不是非常灵活,而aop的话可以指定方法,并且对方法进行增强。

@Aspect
@Component
public class TimeAspect {
	
	@Around("execution(* com.imooc.web.controller.UserController.*(..))")
	public Object handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable {
		
		System.out.println("time aspect start");
		
		Object[] args = pjp.getArgs();
		for (Object arg : args) {
			System.out.println("arg is "+arg);
		}
		//args保持的是存入的参数
		long start = new Date().getTime();
		
		Object object = pjp.proceed();
		//object是调用方法返回的对象
		System.out.println("time aspect 耗时:"+ (new Date().getTime() - start));
		
		System.out.println("time aspect end");
		
		return object;
	}

}

 总结:过滤器无法拿到处理的方法的信息,拦截器无法拿到处理的方法传入的数据,切片可以完成前面两个的不足。

restful拦截 API的执行流程


   说明:

文件的上传和下载

文件上传

@RestController
@RequestMapping("/file")
public class FileController {

	private String folder = "/Users/java/workspace/src/main/java/com/web/controller";

	@PostMapping
	public FileInfo upload(MultipartFile file) throws Exception {

		System.out.println(file.getName());
		System.out.println(file.getOriginalFilename());
		System.out.println(file.getSize());

		File localFile = new File(folder, new Date().getTime() + ".txt");

		file.transferTo(localFile);

		return new FileInfo(localFile.getAbsolutePath());
	}

}
public class FileInfo {
	
	public FileInfo(String path){
		this.path = path;
	}
	
	private String path;

	public String getPath() {
		return path;
	}

	public void setPath(String path) {
		this.path = path;
	}
	
}

文件下载

@RestController
@RequestMapping("/file")
public class FileController {

	private String folder = "/Users/web";


	@GetMapping("/{id}")
	public void download(@PathVariable String id, HttpServletRequest request, HttpServletResponse response) throws Exception {

		try (InputStream inputStream = new FileInputStream(new File(folder, id + ".txt"));
				OutputStream outputStream = response.getOutputStream();) {
			
			response.setContentType("application/x-download");
			response.addHeader("Content-Disposition", "attachment;filename=test.txt");
			
			IOUtils.copy(inputStream, outputStream);
			outputStream.flush();
		} 

	}

}

异步处理REST服务

使用Runnable异步处理Rest服务

@RestController
public class AsyncController {

	
	
	private Logger logger = LoggerFactory.getLogger(getClass());
	
	@RequestMapping("/order")
	public Callable<String> order() throws Exception {
		logger.info("主线程开始");
		Callable<String> result = new Callable<String>() {
			@Override
			public String call() throws Exception {
				logger.info("副线程开始");
				Thread.sleep(1000);
				logger.info("副线程返回");
				return "success";
			}
		};
              return result;
	}
}
}

使用DeferredResult异步处理Rest服务

类似于对于每一个应用创建一个线程,线程1发送处理请求,线程2监听接收请求,消息队列简单来说就是MQ,应用2线程就是具体处理请求的应用。

异步处理配置

swagger自动生成文档

这个是后端写好代码之后,给前端的接口文档

添加相关依赖

		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger2</artifactId>
			<version>2.7.0</version>
		</dependency>
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger-ui</artifactId>
			<version>2.7.0</version>
		</dependency>

添加注解

输入(注意大小写)

http://localhost:8081/swagger-ui.html

在方法上面添加ApiOperation注解,就会在swagger文档中对调用方法描述

	@PostMapping
	@ApiOperation(value = "创建用户")
	public User create(@Valid @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;
	}

也可以在属性上添加ApiModelProperty注解,就会在swagger文档中对属性的描述

@ApiModelProperty(value = "用户名")
	private String username;

对于传入参数的说明使用ApiParam注解

 

	@GetMapping("/{id:\\d+}")
	@JsonView(User.UserDetailView.class)
	public User getInfo(@ApiParam("用户id") @PathVariable String id) {
//		throw new RuntimeException("user not exist");
		System.out.println("进入getInfo服务");
		User user = new User();
		user.setUsername("tom");
		return user;
	}

 WireMock快速伪造RESTful服务

WireMock就是一个独立的服务器,前端可以先去WireMock中请求,当后端开发完毕之后,前端修改端口即可

进入官网:http://wiremock.org/

下载

执行命令:

$ java -jar wiremock-standalone-2.25.1.jar

在项目中添加依赖

                <dependency>
			<groupId>com.github.tomakehurst</groupId>
			<artifactId>wiremock</artifactId>
		</dependency>
/**
 * 
 */
package com.imooc.wiremock;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.configureFor;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.removeAllMappings;
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;

import java.io.IOException;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.core.io.ClassPathResource;


public class MockServer {

	/**
	 * @param args
	 * @throws IOException
	 */
	public static void main(String[] args) throws IOException {
		configureFor(8062);
		removeAllMappings();

		mock("/order/1", "01");
		mock("/order/2", "02");
	}

	private static void mock(String url, String file) throws IOException {
		ClassPathResource resource = new ClassPathResource("mock/response/" + file + ".txt");
		String content = StringUtils.join(FileUtils.readLines(resource.getFile(), "UTF-8").toArray(), "\n");
		stubFor(get(urlPathEqualTo(url)).willReturn(aResponse().withBody(content).withStatus(200)));
	}

}

 01.txt

{
	"id":1,
	"type":"C"
}

02.txt

{
	"id":2,
	"type":"B"
}

引用properties中的属性

properties中自动注入properties文件中的属性

在resourc文件下创建config.properties

# dcm文件存储路径
project.dcm_image=/dcm_image
# 标注等其余文件
project.other_file=${project.dcm_image}/other_file

类属性注入properties文件中的属性

创建config.java类,@Component将Config对象注入到Spring容器中,@PropertySource("classpath:config.properties")表示将config.properties注入容器,@ConfigurationProperties(prefix = "project")将config.properties前缀为project和类的变量一一对应
 

@Component
@PropertySource("classpath:config.properties")
@ConfigurationProperties(prefix = "project")
@Data
public class Config {
    private String dcm_image;
    private String other_file;
}

单个属性注入properties文件属性

在properties文件注入后,如果引用单个文件,可以用@{}

@Value("${project.dcm_image}")

利用ResourceBundle类获取properties中属性

ResourceBundle bundle = ResourceBundle.getBundle("config");
#config.properties 省略后缀名
String filePath = bundle.getString("project.dcm_image");

properties乱码

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章