第1步:定义收纳HttpHeaders和HttpStatus的 线程容器
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
public class ResponseContext {
private static ThreadLocal<HttpHeaders> ThreadLocalHeaders = new InheritableThreadLocal<>();
private static ThreadLocal<HttpStatus> ThreadLocalStatus = new InheritableThreadLocal<>();
public static void addHeaders(String key, String value) {
if (ThreadLocalHeaders.get() == null) {
ThreadLocalHeaders.set(new HttpHeaders());
}
ThreadLocalHeaders.get().add(key, value);
}
public static void setResponseCode(HttpStatus httpStatus) {
ThreadLocalStatus.set(httpStatus);
}
public static HttpHeaders getHeaders() {
return ThreadLocalHeaders.get();
}
public static HttpStatus getResponseCode() {
return ThreadLocalStatus.get();
}
public static void clear() {
if (ThreadLocalHeaders.get() != null) {
ThreadLocalHeaders.remove();
}
if (ThreadLocalStatus.get() != null) {
ThreadLocalStatus.remove();
}
}
}
第2步:封装 把分页信息转储到线程容器 的方法
import com.github.pagehelper.Page;
import com.github.pagehelper.PageInfo;
import com.zhangziwa.practisesvr.utils.response.ResponseContext;
public class PageHeaderUtils {
private static final String PAGE_NUM = "page_num"; // 当前第几页
private static final String PAGE_SIZE = "page_size"; // 每页显示的条数
private static final String PREV_PAGE = "prev_page"; // 上一页页码
private static final String NEXT_PAGE = "next_page"; // 下一页页码
private static final String TOTAL_COUNT = "total_count"; // 总条数
private static final String TOTAL_PAGE = "total_page"; // 总页数
public static <E> void setPageHeader(Page<E> page) {
if (page == null) {
return;
}
ResponseContext.addHeaders(PAGE_NUM, String.valueOf(page.getPageNum()));
ResponseContext.addHeaders(PAGE_SIZE, String.valueOf(page.getPageSize()));
ResponseContext.addHeaders(TOTAL_COUNT, String.valueOf(page.getTotal()));
ResponseContext.addHeaders(TOTAL_PAGE, String.valueOf(page.getPages() == 0 ? 1 : page.getPages()));
if (page.getPages() == 0 || page.getPages() == 1) {
ResponseContext.addHeaders(PREV_PAGE, "");
ResponseContext.addHeaders(NEXT_PAGE, "");
} else if (page.getPageNum() == 1) {
ResponseContext.addHeaders(PREV_PAGE, "");
ResponseContext.addHeaders(NEXT_PAGE, String.valueOf(page.getPages() + 1));
} else if (page.getPageNum() == page.getPages()) {
ResponseContext.addHeaders(PREV_PAGE, String.valueOf(page.getPages() - 1));
ResponseContext.addHeaders(NEXT_PAGE, "");
} else {
ResponseContext.addHeaders(PREV_PAGE, String.valueOf(page.getPages() - 1));
ResponseContext.addHeaders(NEXT_PAGE, String.valueOf(page.getPages() + 1));
}
}
/**
* 设置分页头信息
* @param page 分页对象
* @param <E> 分页对象的数据类型
*/
public static <E> void setPageHeader(PageInfo<E> page) {
if (page == null) {
return;
}
ResponseContext.addHeaders(PAGE_NUM, String.valueOf(page.getPageNum())); // 设置当前页码
ResponseContext.addHeaders(PAGE_SIZE, String.valueOf(page.getPageSize())); // 设置每页显示数量
ResponseContext.addHeaders(TOTAL_COUNT, String.valueOf(page.getTotal())); // 设置总记录数
ResponseContext.addHeaders(TOTAL_PAGE, String.valueOf(page.getPages() == 0 ? 1 : page.getPages())); // 设置总页数
// page.getPages()=1表示就1页,前后页都不存在,故也算特殊场景.也为了page.getPages()+1和page.getPages()-1不会对[1,page.getPages()]越界
if (page.getPages() == 0 || page.getPages() == 1) {
ResponseContext.addHeaders(PREV_PAGE, ""); // 上一页
ResponseContext.addHeaders(NEXT_PAGE, ""); // 下一页
} else if (page.getPageNum() == 1) {
ResponseContext.addHeaders(PREV_PAGE, ""); // 上一页
ResponseContext.addHeaders(NEXT_PAGE, String.valueOf(page.getPages() + 1)); // 下一页
} else if (page.getPageNum() == page.getPages()) {
ResponseContext.addHeaders(PREV_PAGE, String.valueOf(page.getPages() - 1)); // 上一页
ResponseContext.addHeaders(NEXT_PAGE, ""); // 下一页
} else {
ResponseContext.addHeaders(PREV_PAGE, String.valueOf(page.getPages() - 1)); // 上一页
ResponseContext.addHeaders(NEXT_PAGE, String.valueOf(page.getPages() + 1)); // 下一页
}
}
}
第3步:分页查询后 把HttpHeaders和HttpStatus收集到线程容器
@Service
@Slf4j
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
# PageInfo方式
@Override
public List<Student> listStudents(Integer pageNum, Integer PageSize) {
PageHelper.startPage(PageUtils.getPageNum(pageNum), PageUtils.getPageSize(PageSize), PageUtils.isQueryTotalCount());
PageHelper.orderBy("age asc");
List<Student> students = userMapper.listStudents();
PageInfo<Student> studentPageInfo = PageInfo.of(students);
// 收集分页信息到 ThreadLocal
PageHeaderUtils.setPageHeader(studentPageInfo);
// 收集HttpStatus到 ThreadLocal
// ResponseContext.setResponseCode(num2HttpStatus("200")); // 为了使用一下num2HttpStatus方法
ResponseContext.setResponseCode(HttpStatus.OK);
return students;
}
# Page 方式
@Override
public List<Student> listStudents2(Integer pageNum, Integer PageSize) {
PageHelper.startPage(PageUtils.getPageNum(pageNum), PageUtils.getPageSize(PageSize), PageUtils.isQueryTotalCount());
PageHelper.orderBy("age asc");
Page<Student> students = (Page<Student>) userMapper.listStudents();
PageHeaderUtils.setPageHeader(students);
ResponseContext.setResponseCode(HttpStatus.OK);
return students;
}
# 多数据源聚合时的手工分页
@Override
public List<Student> listStudents3(Integer pageNum, Integer pageSize) {
// 模拟数据聚合
List<Student> students3 = new ArrayList<>();
List<Student> students1 = userMapper.listStudents();
List<Student> students2 = userMapper.listStudents();
students3.addAll(students1);
students3.addAll(students2);
Page<Student> studentsPage = PageHeaderUtils.manualPage(students3, pageNum, pageSize);
PageHeaderUtils.setPageHeader(studentsPage);
ResponseContext.setResponseCode(HttpStatus.OK);
return studentsPage;
}
}
public class HttpStatusUtils {
public static HttpStatus num2HttpStatus(String num) {
HttpStatus httpStatus = HttpStatus.NOT_FOUND;
for (HttpStatus status : HttpStatus.values()) {
if (Integer.parseInt(num) == status.value()) {
return status;
}
}
return httpStatus;
}
}
总结就是:单次分页查询可以使用PageHelper
,但是如果多数据源数据聚合时PageHelper
不再适用,可以使用手工分页
的方式进行分页。
# 使用PageHelper进行分页时
PageHelper.startPage(PageUtils.getPageNum(pageNum), PageUtils.getPageSize(PageSize), PageUtils.isQueryTotalCount());
# 数据聚合场景下的手工分页
public class PageHeaderUtils {
// 也适合EmptyList场景 Page{count=true, pageNum=1, pageSize=10, startRow=0, endRow=0, total=0, pages=0, reasonable=null, pageSizeZero=null}
public static <E> Page<E> manualPage(List<E> res, Integer pageNum, Integer pageSize) {
if (res == null) {
res = new ArrayList<>();
}
int pageNumber = PageUtils.getPageNum(pageNum);
int sizePerPage = PageUtils.getPageSize(pageSize);
int totalSize = res.size();
List<E> pageElements = res.stream().skip((long) (pageNumber - 1) * sizePerPage).limit(sizePerPage).toList(); // 分页
Page<E> page = new Page<>();
page.setPageNum(pageNumber);
page.setPageSize(sizePerPage);
page.setTotal(totalSize);
page.setPages((totalSize / pageSize + ((totalSize % pageSize == 0) ? 0 : 1)));
page.addAll(pageElements);
return page;
}
}
第4步:检查controller层, @Controller → @RestController
@RestController
@Slf4j
public class SearchController {
@Autowired
UserService userService;
@RequestMapping(value = "/getAllStudents", method = RequestMethod.GET)
public List<Student> getAllStudents() {
List<Student> students = userService.listStudents(1, 10);
students.forEach(System.out::println);
return students;
}
@RequestMapping(value = "/getAllStudents2", method = RequestMethod.GET)
public List<Student> getAllStudents2() {
List<Student> students = userService.listStudents2(1, 10);
students.forEach(System.out::println);
return students;
}
@RequestMapping(value = "/getAllStudents3", method = RequestMethod.GET)
public List<Student> getAllStudents3() {
List<Student> students = userService.listStudents3(1, 5);
students.forEach(System.out::println);
return students;
}
}
第5步:线程容器收纳的 HttpHeaders和HttpStatus 添加到ServerHttpResponse
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import static java.util.Objects.nonNull;
@ControllerAdvice
public class ResponsePostAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class clazz,
ServerHttpRequest request, ServerHttpResponse response) {
HttpHeaders headers = response.getHeaders();
// 分页信息添加到ServerHttpResponse
HttpHeaders headersContext = ResponseContext.getHeaders();
if (nonNull(headersContext) && !headersContext.isEmpty()) {
headers.addAll(headersContext);
}
// 状态码添加到ServerHttpResponse
if (nonNull(ResponseContext.getResponseCode())) {
response.setStatusCode(ResponseContext.getResponseCode());
}
return body;
}
}
第6步:清理 线程容器
定义拦截器执行ResponseContext.clear();
,用于资源清理
import com.zhangziwa.practisesvr.utils.response.ResponseContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
@Component
public class ResponsePostInterceptor implements HandlerInterceptor {
//在Controller执行之前调用,如果返回false,controller不执行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.err.println("***ResponsePostInterceptor.preHandle***");
return true;
}
//controller执行之后,且页面渲染之前调用
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.err.println("***ResponsePostInterceptor.postHandle***");
}
//页面渲染之后调用,一般用于资源清理操作
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.err.println("***ResponsePostInterceptor.afterCompletion***");
ResponseContext.clear(); // 清除业务层分页信息上下文
PageMethod.clearPage(); // 清除PageHelper的分页信息上下文
}
}
注册拦截器
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private ResponsePostInterceptor responsePostInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(responsePostInterceptor);
}
}
第7步:测试结果