SpringBoot3之Web編程
一、簡(jiǎn)介
基于web包的依賴,SpringBoot可以快速啟動(dòng)一個(gè)web容器,簡(jiǎn)化項(xiàng)目的開(kāi)發(fā);
在web開(kāi)發(fā)中又涉及如下幾個(gè)功能點(diǎn):
攔截器:可以讓接口被訪問(wèn)之前,將請(qǐng)求攔截到,通過(guò)對(duì)請(qǐng)求的識(shí)別和校驗(yàn),判斷請(qǐng)求是否允許通過(guò);
頁(yè)面交互:對(duì)于服務(wù)端的開(kāi)發(fā)來(lái)說(shuō),需要具備簡(jiǎn)單的頁(yè)面開(kāi)發(fā)能力,解決部分場(chǎng)景的需求;
Swagger接口:通過(guò)簡(jiǎn)單的配置,快速生成接口的描述,并且提供對(duì)接口的測(cè)試能力;
Junit測(cè)試:通過(guò)編寫代碼的方式對(duì)接口進(jìn)行測(cè)試,從而完成對(duì)接口的檢查和驗(yàn)證,并且可以不入侵原代碼結(jié)構(gòu);
二、工程搭建
1、工程結(jié)構(gòu)
圖片
2、依賴管理
<!-- 基礎(chǔ)框架組件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- 接口文檔組件 -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<!-- 前端頁(yè)面組件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- 單元測(cè)試組件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring-boot.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
三、Web開(kāi)發(fā)
1、接口開(kāi)發(fā)
編寫四個(gè)簡(jiǎn)單常規(guī)的接口,從對(duì)資源操作的角度,也就是常說(shuō)的:增Post、刪Delete、改Put、查Get,并且使用了swagger注解,可以快速生成接口文檔;
@RestController
@Tag(name = "Rest接口")
public class RestWeb {
@Operation(summary = "Get接口")
@GetMapping("rest/get/{id}")
public String restGet(@PathVariable Integer id) {
return "OK:"+id;
}
@Operation(summary = "Post接口")
@PostMapping("/rest/post")
public String restPost(@RequestBody ParamBO param){
return "OK:"+param.getName();
}
@Operation(summary = "Put接口")
@PutMapping("/rest/put")
public String restPut(@RequestBody ParamBO param){
return "OK:"+param.getId();
}
@Operation(summary = "Delete接口")
@DeleteMapping("/rest/delete/{id}")
public String restDelete(@PathVariable Integer id){
return "OK:"+id;
}
}
2、頁(yè)面交互
對(duì)于服務(wù)端開(kāi)發(fā)來(lái)說(shuō),在部分場(chǎng)景下是需要進(jìn)行簡(jiǎn)單的頁(yè)面開(kāi)發(fā)的,比如通過(guò)頁(yè)面渲染再去生成文件,或者直接通過(guò)頁(yè)面填充郵件內(nèi)容等;
數(shù)據(jù)接口
@Controller
public class PageWeb {
@RequestMapping("/page/view")
public ModelAndView pageView (HttpServletRequest request){
ModelAndView modelAndView = new ModelAndView() ;
// 普通參數(shù)
modelAndView.addObject("name", "cicada");
modelAndView.addObject("time", "2023-07-12");
// 對(duì)象模型
modelAndView.addObject("page", new PageBO(7,"頁(yè)面數(shù)據(jù)模型"));
// List集合
List<PageBO> pageList = new ArrayList<>() ;
pageList.add(new PageBO(1,"第一頁(yè)"));
pageList.add(new PageBO(2,"第二頁(yè)"));
modelAndView.addObject("pageList", pageList);
// Array數(shù)組
PageBO[] pageArr = new PageBO[]{new PageBO(6,"第六頁(yè)"),new PageBO(7,"第七頁(yè)")} ;
modelAndView.addObject("pageArr", pageArr);
modelAndView.setViewName("/page-view");
return modelAndView ;
}
}
頁(yè)面解析:分別解析了普通參數(shù),實(shí)體對(duì)象,集合容器,數(shù)組容器等幾種數(shù)據(jù)模型;
<div style="text-align: center">
<hr/>
<h5>普通參數(shù)解析</h5>
姓名:<span th:text="${name}"></span>
時(shí)間:<span th:text="${time}"></span>
<hr/>
<h5>對(duì)象模型解析</h5>
整形:<span th:text="${page.getKey()}"></span>
字符:<span th:text="${page.getValue()}"></span>
<hr/>
<h5>集合容器解析</h5>
<table style="margin:0 auto;width: 200px">
<tr>
<th>Key</th>
<th>Value</th>
</tr>
<tr th:each="page:${pageList}">
<td th:text="${page.getKey()}"></td>
<td th:text="${page.getValue()}"></td>
</tr>
</table>
<hr/>
<h5>數(shù)組容器解析</h5>
<table style="margin:0 auto;width: 200px">
<tr>
<th>Key</th>
<th>Value</th>
</tr>
<tr th:each="page:${pageArr}">
<td th:text="${page.getKey()}"></td>
<td th:text="${page.getValue()}"></td>
</tr>
</table>
<hr/>
</div>
效果圖展示
圖片
四、攔截器
1、攔截器定義
通過(guò)實(shí)現(xiàn)HandlerInterceptor接口,完成對(duì)兩個(gè)攔截器的自定義,請(qǐng)求在訪問(wèn)服務(wù)時(shí),必須通過(guò)兩個(gè)攔截器的校驗(yàn);
/**
* 攔截器一
*/
public class HeadInterceptor implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(HeadInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
log.info("HeadInterceptor:preHandle");
Iterator<String> headNames = request.getHeaderNames().asIterator();
log.info("request-header");
while (headNames.hasNext()){
String headName = headNames.next();
String headValue = request.getHeader(headName);
System.out.println(headName+":"+headValue);
}
// 放開(kāi)攔截
return true;
}
@Override
public void postHandle(HttpServletRequest request,HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
log.info("HeadInterceptor:postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request,HttpServletResponse response,
Object handler, Exception e) throws Exception {
log.info("HeadInterceptor:afterCompletion");
}
}
/**
* 攔截器二
*/
public class BodyInterceptor implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(BodyInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request,HttpServletResponse response,
Object handler) throws Exception {
log.info("BodyInterceptor:preHandle");
Iterator<String> paramNames = request.getParameterNames().asIterator();
log.info("request-param");
while (paramNames.hasNext()){
String paramName = paramNames.next();
String paramValue = request.getParameter(paramName);
System.out.println(paramName+":"+paramValue);
}
// 放開(kāi)攔截
return true;
}
@Override
public void postHandle(HttpServletRequest request,HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
log.info("BodyInterceptor:postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request,HttpServletResponse response,
Object handler, Exception e) throws Exception {
log.info("BodyInterceptor:afterCompletion");
}
}
2、攔截器配置
自定義攔截器之后,還需要添加到web工程的配置文件中,可以通過(guò)實(shí)現(xiàn)WebMvcConfigurer接口,完成自定義的配置添加;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
/**
* 添加自定義攔截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HeadInterceptor()).addPathPatterns("/**");
registry.addInterceptor(new BodyInterceptor()).addPathPatterns("/**");
}
}
五、測(cè)試工具
1、Swagger接口
添加上述的springdoc依賴之后,還可以在配置文件中簡(jiǎn)單定義一些信息,訪問(wèn)IP:端口/swagger-ui/index.html即可;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
/**
* 接口文檔配置
*/
@Bean
public OpenAPI openAPI() {
return new OpenAPI()
.info(new Info().title("【boot-web】").description("Rest接口文檔-2023-07-11")
.version("1.0.0"));
}
}
圖片
2、Junit測(cè)試
在個(gè)人的習(xí)慣上,Swagger接口文檔更偏向在前后端對(duì)接的時(shí)候使用,而Junit單元測(cè)試更符合開(kāi)發(fā)的時(shí)候使用,這里是對(duì)RestWeb中的接口進(jìn)行測(cè)試;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class RestWebTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testGet () throws Exception {
// GET接口測(cè)試
MvcResult mvcResult = mockMvc
.perform(MockMvcRequestBuilders.get("/rest/get/1"))
.andReturn();
printMvcResult(mvcResult);
}
@Test
public void testPost () throws Exception {
// 參數(shù)模型
JsonMapper jsonMapper = new JsonMapper();
ParamBO param = new ParamBO(null,"單元測(cè)試",new Date()) ;
String paramJson = jsonMapper.writeValueAsString(param) ;
// Post接口測(cè)試
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/rest/post")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON).content(paramJson)).andReturn();
printMvcResult(mvcResult);
}
@Test
public void testPut () throws Exception {
// 參數(shù)模型
JsonMapper jsonMapper = new JsonMapper();
ParamBO param = new ParamBO(7,"Junit組件",new Date()) ;
String paramJson = jsonMapper.writeValueAsString(param) ;
// Put接口測(cè)試
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.put("/rest/put")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON).content(paramJson)).andReturn();
printMvcResult(mvcResult);
}
@Test
public void testDelete () throws Exception {
// Delete接口測(cè)試
MvcResult mvcResult = mockMvc
.perform(MockMvcRequestBuilders.delete("/rest/delete/2"))
.andReturn();
printMvcResult(mvcResult);
}
/**
* 打印【MvcResult】信息
*/
private void printMvcResult (MvcResult mvcResult) throws Exception {
System.out.println("請(qǐng)求-URI【"+mvcResult.getRequest().getRequestURI()+"】");
System.out.println("響應(yīng)-status【"+mvcResult.getResponse().getStatus()+"】");
System.out.println("響應(yīng)-content【"+mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8)+"】");
}
}
六、參考源碼
文檔倉(cāng)庫(kù):
https://gitee.com/cicadasmile/butte-java-note
源碼倉(cāng)庫(kù):
https://gitee.com/cicadasmile/butte-spring-parent