目錄
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");