目录
io.spring.platform和org.springframework.cloud
@NotBlank和@Valid和BindingResult
properties中自动注入properties文件中的属性
利用ResourceBundle类获取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) | 被注释的元素必须符合指定的正则表达式 |
被注释的元素必须是电子邮箱地址 | |
@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中请求,当后端开发完毕之后,前端修改端口即可
下载
执行命令:
$ 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");