学习过一个项目,记录一些基本知识点。
程序仓库:https://gitee.com/juncaoit/basic-springboot
目录:
1.springboot 的官网
2.@Controller与@RestController的区别
3.springboot的目录结构
4.同个⽂件的加载顺序
5.解压后的jar包的目录
6.对外接口使用统一的json格式
7.JackSon处理字段
8.添加配置文件
9.单元测试
10.MockMvc
11.全局异常处理
12.拦截器
13.监听器
14.拦截器
15.FreeMarker
1.springboot 的官网
https://spring.io/projects/spring-boot#overview
2.@Controller与@RestController的区别
@Controller 作⽤:⽤于标记这个类是⼀个控制器,返回⻚⾯的时候使⽤;如果要返回JSON,则需要在接⼝上使⽤@ResponseBody才可以
@RestController 作⽤:⽤于标记这个类是⼀个控制器,返回JSON数据的时候使⽤,如果使⽤这个注解,则接⼝返回数据会被序列化为JSON
所以:@RestController = @Controller+@ResponseBody
3.springboot的目录结构
src/main/java:存放代码
src/main/resources
static: 存放静态⽂件,⽐如 css、js、image, (访问⽅式 http://localhost:8080/js/main.js)
templates:存放静态⻚⾯jsp,html,tpl
config:存放配置⽂件,application.properties
resources:
4.同个⽂件的加载顺序
静态资源⽂件 Spring Boot 默认会挨个从:
META/resources > resources > static > public ⾥⾯找是否存在相应的资源
默认配置 spring.resources.static-locations = classpath:/METAINF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/
如何添加新的目录,则修改appliction.properties
#文件加载内容
spring.resources.static-locations=classpath:/META-INF/resources/, classpath:/resources/, classpath:/static/, classpath:/public/, classpath:/templates/
启动之后进行访问页面:
http://localhost:8080/success.html
5.解压后的jar包的目录
org:spring使用的
META-INF:指定main函数的位置,告诉虚拟机入口位置
BOOT-INF:自己的项目下的class与lib
6.对外接口使用统一的json格式
对外转换:
package com.jun.xiaod.utils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class JsonData<T> {
private int code;
private T data;
private String msg;
public JsonData(int code, T data) {
this.code = code;
this.data = data;
}
public static <T> JsonData<T> buildSuccess(T data) {
return new JsonData<T>(0, data, "");
}
public static <T> JsonData<T> buildFail(String msg) {
return new JsonData<T>(-1, null, msg);
}
public static <T> JsonData<T> buildFail(int code, String msg) {
return new JsonData<T>(code, null, msg);
}
}
同时,序列化与反序列化工具:
package com.jun.xiaod.utils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonUtils {
/**
* 序列化
*/
public static <T> String toJsonString(T t) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(t);
}
/**
* 反序列化
*/
public static <T> T read(String jsonStr) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(jsonStr, new TypeReference<T>(){});
}
}
7.JackSon处理字段
在返回的VO中添加注解:
jackson处理相关⾃动:
指定字段不返回:@JsonIgnore
指定⽇期格式:@JsonFormat(pattern="yyyy-MM-dd hh:mm:ss",locale="zh",timezone="GMT+8")
空字段不返回:@JsonInclude(Include.NON_NULL)
指定别名:@JsonProperty
举例:
package com.jun.xiaod.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Video {
private int id;
/**
* 视频标题
*/
private String title;
/**
* 概述
*/
private String summary;
/**
* 封面图
*/
private String coverImg;
/**
* 价格,分
*/
private int price;
/**
* 创建时间
*/
@JsonFormat(pattern="yyyy-MM-dd hh:mm:ss",locale="zh",timezone="GMT+8")
@JsonProperty("create_time")
private Date createTime;
/**
* 默认8.7,最高10分
*/
private Double point;
}
效果:
其中,video.setCreateTime(new Date());
{
"code": 0,
"data": [
{
"id": 1,
"title": "java",
"summary": null,
"coverImg": null,
"price": 0,
"point": null,
"create_time": "2022-05-06 09:57:54"
}
],
"msg": ""
}
8.添加配置文件
其中,配置文件如下:
#微信支付配置
wxpay.appid=w123456
wxpay.secret=safemn
wx.mechid=121212
其他地方引用使用:
@Resource
private WxConfig wxConfig;
9.单元测试
<!-- spring test框架 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
使用:
在controller的测试类中进行继承。
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {VideoApplication.class})
public class BaseTest {
}
10.MockMvc
增加类注解 @AutoConfigureMockMvc
注⼊⼀个MockMvc类
相关API :
perform执⾏⼀个RequestBuilder请求
andExpect:添加ResultMatcher->MockMvcResultMatchers验证规则
andReturn:最后返回相应的MvcResult->Response
package com.jun.xioad.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.jun.xiaod.controller.VideoController;
import com.jun.xiaod.domain.Video;
import com.jun.xiaod.utils.JsonData;
import com.jun.xiaod.utils.JsonUtils;
import com.jun.xioad.BaseTest;
import junit.framework.TestCase;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import javax.annotation.Resource;
import java.sql.*;
import java.util.List;
@Slf4j
@AutoConfigureMockMvc
public class VideoControllerTest extends BaseTest {
@Resource
private VideoController videoController;
@Resource
private MockMvc mockMvc;
@Test
public void testList() throws JsonProcessingException {
JsonData<List<Video>> videoList = videoController.getVideoList();
log.info("videoList=={}", JsonUtils.toJsonString(videoList));
TestCase.assertEquals(5, videoList.getData().size());
TestCase.assertTrue(videoList.getData().size() > 0);
}
/**
* mockMvc的使用
*/
@Test
public void testListMvc() throws Exception {
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/app/v1/test/get/config"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn();
int status = mvcResult.getResponse().getStatus();
log.info("ststua={}", status);
}
}
11.全局异常处理
统⼀的错误⻚⾯或者错误码
对⽤户更友好
package com.jun.xiaod.handler;
import com.jun.xiaod.utils.JsonData;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
@RestControllerAdvice
public class CustomExtHandler {
@ExceptionHandler(value = Exception.class)
JsonData<String> handle(Exception e, HttpServletRequest request) {
return JsonData.buildFail(-2, "网络正忙,请稍后重试");
}
}
思路:
类添加注解
@ControllerAdvice,如果需要返回json数据,则⽅法需要加@ResponseBody
@RestControllerAdvice, 默认返回json数据,⽅法不需要加@ResponseBody
⽅法添加处理器 捕获全局异常,处理所有不可知的异常
@ExceptionHandler(value=Exception.class)
12.拦截器
package com.jun.xiaod.filter;
import com.jun.xiaod.domain.User;
import com.jun.xiaod.service.impl.UserServiceImpl;
import com.jun.xiaod.utils.JsonData;
import com.jun.xiaod.utils.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Objects;
@Slf4j
@WebFilter(urlPatterns = "/api/v1/pri/*", filterName = "loginFilter")
public class LoginFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("doFilter");
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
// 先从请求头中进行获取,再从参数中进行获取
String token = request.getHeader("token");
if (StringUtils.isEmpty(token)) {
token = request.getParameter("token");
}
// 存在token
if (!StringUtils.isEmpty(token)) {
// 为了验证这里,需要先进行登陆,然后使用登陆之后的token获取user
User user = UserServiceImpl.sessionMap.get(token);
if (Objects.nonNull(user)) {
filterChain.doFilter(servletRequest, servletResponse);
} else {
renderJson(response, JsonUtils.toJsonString(JsonData.buildFail(-2, "登陆失败,没有登陆过")));
}
} else {
renderJson(response, JsonUtils.toJsonString(JsonData.buildFail(-3, "登陆失败,无token")));
}
}
/**
* 输出流输出
*/
private void renderJson(HttpServletResponse response, String json) {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
try (PrintWriter writer = response.getWriter()) {
writer.println(json);
} catch (Exception e) {
e.printStackTrace();
}
}
}
使用地方:
@Slf4j
@RestController
@RequestMapping("/api/v1/pri/order")
public class VideoOrderController {
@Resource
private VideoServiceImpl videoService;
@PostMapping("/save")
public JsonData<String> saveVideo() {
log.info("/save");
return JsonData.buildSuccess("下单成功");
}
}
思路:
启动类⾥⾯增加 @ServletComponentScan,进⾏扫描
新建⼀个Filter类,implements Filter,并实现对应的接⼝
@WebFilter 标记⼀个类为filter,被spring进⾏扫描。urlPatterns:拦截规则,⽀持正则,控制chain.doFilter的⽅法的调⽤,来实现是否通过放⾏。不放⾏,web应⽤resp.sendRedirect("/index.html") 或者 返回json字符串
效果:
13.监听器
常见的监听器
ServletContextListener 应⽤启动监听
HttpSessionLisener 会话监听
ServletRequestListener 请求监听
/**
* 应用启动监听器
*/
@Slf4j
@WebListener
public class ApplicationListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
log.info("ApplicationListener init");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
log.info("ApplicationListener destory");
}
}
14.拦截器
注册拦截器
package com.jun.xiaod.config;
import com.jun.xiaod.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 拦截器啥的
* 不依赖容器
*/
@Configuration
public class CustomerWebMvcConfigurer implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getLoginInterceptor()).addPathPatterns("/api/v1/**");
WebMvcConfigurer.super.addInterceptors(registry);
}
public LoginInterceptor getLoginInterceptor() {
return new LoginInterceptor();
}
}
定义拦截器:
package com.jun.xiaod.interceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("LoginInterceptor preHandle ");
return HandlerInterceptor.super.preHandle(request, response, handler);
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("LoginInterceptor postHandle ");
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("LoginInterceptor afterCompletion ");
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
15.FreeMarker
pom
<!--freemarker-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
配置文件
#freemarker
spring.freemarker.cache=false
spring.freemarker.charset=utf-8
spring.freemarker.allow-request-override=false
spring.freemarker.check-template-location=true
spring.freemarker.content-type=text/html
spring.freemarker.expose-request-attributes=true
spring.freemarker.expose-session-attributes=true
spring.freemarker.suffix=.ftl
spring.freemarker.template-loader-path=classpath:/templates/
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>payId:${setting.payId}</h2>
</body>
</html>
后端
@Controller
@RequestMapping("/api/v1/freemarker")
public class FreemarkerController {
@Resource
private WxConfig wxConfig;
@GetMapping("/get/config")
public String getConfig(ModelMap modelMap) {
modelMap.addAttribute("setting", wxConfig);
// 不用添加后缀
return "fm/user";
}
}