0 概述
Java第三方库经验汇总,Java库的总体质量还是相当不错的。
1 jsoup
代码在这里
官网在这里
jsoup是一个以jQuery的语法,对于html文档获取对应节点的工具,是后端爬虫的基础套路了,这个没啥好说的。它的优点在于,成熟,bug少,对jQuery语法支持比较完备。这里实名举报js的cheerio库,同样的查询语法,在cherrio是跑不起来的,在jsoup轻松查找出来,js的库的总体质量真的比较随意。
package spring_test;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.TextNode;
import org.jsoup.select.Elements;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static org.junit.jupiter.api.Assertions.*;
public class JsoupTest {
@Test
public void testBasic(){
String xml = "<!doc html>" +
"<html>" +
"<head></head>" +
"<body>" +
"<h1>你好</h1>" +
"</body>" +
"</html>";
Document document = Jsoup.parse(xml);
String target = document.select("h1").text();
assertEquals(target,"你好");
}
@Test
public void testText(){
String xml = "<!doc html>" +
"<html>" +
"<head></head>" +
"<body>" +
"<h1>用户:<span>mk</span>你好</h1>" +
"</body>" +
"</html>";
Document document = Jsoup.parse(xml);
//text方法会取自身,和子级所有的textNode
String target = document.select("h1").text();
assertEquals(target,"用户:mk你好");
//textNodes方法只会取自身的textNode
List<TextNode> textNodes = document.select("h1").textNodes();
List<String> texts = textNodes.stream().map(single->{
return single.getWholeText();
}).collect(Collectors.toList());
assertIterableEquals(texts, Arrays.asList("用户:","你好"));
}
@Test
public void testSelect(){
String xml = "<!doc html>" +
"<html>" +
"<head></head>" +
"<body>" +
"<h1>" +
"<div class=\"u\">bb</div>"+
"</h1>" +
"<h1>" +
"<div class=\"u\">cc</div>"+
"</h1>" +
"</body>" +
"</html>";
Document document = Jsoup.parse(xml);
//select可以匹配多个
Elements elements = document.select("h1 .u");
List<String> nameList = elements.stream().map(single->{
return single.text();
}).collect(Collectors.toList());
assertIterableEquals(nameList,Arrays.asList("bb","cc"));
//匹配不到的时候,也是返回有对象的Elements,只是size为0而已,不会返回null
Elements elements2 = document.select("h1 .uk");
List<String> nameList2 = elements2.stream().map(single->{
return single.text();
}).collect(Collectors.toList());
assertIterableEquals(nameList2,Arrays.asList());
}
@Test
public void testSelectEq(){
String xml = "<!doc html>" +
"<html>" +
"<head></head>" +
"<body>" +
"<h1>" +
"<div class=\"u\">b1</div>"+
"<div class=\"u\">b2</div>"+
"<div class=\"u\">b3</div>"+
"</h1>" +
"<h1>" +
"<div class=\"u\">c1</div>"+
"<div class=\"u\">c2</div>"+
"</h1>" +
"<h1>" +
"<div class=\"u\">d1</div>"+
"<div>"+
"<div class=\"u\">d2</div>"+
"</div>"+
"</h1>" +
"</body>" +
"</html>";
Document document = Jsoup.parse(xml);
//eq代表.u同级里面的第几个,注意下标从0开始
Elements elements = document.select("h1 .u:eq(1)");
List<String> nameList = elements.stream().map(single->{
return single.text();
}).collect(Collectors.toList());
assertIterableEquals(nameList,Arrays.asList("b2","c2"));
//匹配不到的时候,也会只返回1个
Elements elements2 = document.select("h1 .u:eq(2)");
List<String> nameList2 = elements2.stream().map(single->{
return single.text();
}).collect(Collectors.toList());
assertIterableEquals(nameList2,Arrays.asList("b3"));
}
@Test
public void testSelectInner(){
String xml = "<!doc html>" +
"<html>" +
"<head></head>" +
"<body>" +
"<h1>" +
"<div class=\"u\">b1</div>"+
"<div class=\"u\">b2</div>"+
"<div class=\"u\">b3</div>"+
"</h1>" +
"<h1>" +
"<div class=\"u\">c1</div>"+
"<div class=\"u\">c2</div>"+
"</h1>" +
"<h1>" +
"<div class=\"u\">d1</div>"+
"<div>"+
"<div class=\"u\">d2</div>"+
"<div class=\"g\">d3</div>"+
"</div>"+
"</h1>" +
"</body>" +
"</html>";
Document document = Jsoup.parse(xml);
//嵌套查询,可以拿到顶数据以后,嵌套往下查询
Elements elements = document.select("h1:eq(2)");
//这里匹配了2个Element,转换为text的时候,中间直接加空格
String text1 = elements.select(".u").text();
assertEquals(text1,"d1 d2");
//匹配了1个Element
String text2 = elements.select(".g").text();
assertEquals(text2,"d3");
}
}
这代码真的没啥好说的
- 输入String或者InputStream,用Jsoup.parse生成Document对象。
- 使用Document对象,传入jQuery查询语法到select方法中,得到Elements对象。Elements对象可以用进一步使用select方法,得到子级的Elements对象。
- 对于每个Elements,可以用text(),textNode()的方法获取值
基本上就是和jQuery一样的语法
注意,爬虫拉到的数据都是不可靠的,最好用ValidatorUtil来校验一下比较稳妥
2 poi
代码在这里
官网在这里
poi是Java里面的读写MS Documents的工具,主要是针对xls,xlsx的工具。另外,国内有一家商业做excel读写,和展示编辑的工具,看这里,唯一的缺点是要花钱。
poi的接口本身就写得够简单的,而且Bug相对较少,不建议使用EasyPoi等的类似工具,无法获得第一手的poi更新,而且生态与成熟度都不高。
2.1 依赖
dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.3</version>
<dependency>
</dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
<dependency> </
ooxml是对excel 2007+以上的处理,可以单独引入。poi是excel 2003的处理。
加入以上的maven依赖即可
A part name shall not have a forward slash as the last character
在使用poi 5.1.0的版本的时候会有以上的报错,改为最新版本即可。
2.2 生成与导出excel
package spring_test;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Component;
@Component
public class ExportExcelService {
public void injectContent(Sheet sheet){
//创建行头
= sheet.createRow(0);
Row row = row.createCell(0);
Cell cell .setCellValue("名称");
cell= row.createCell(1);
cell .setCellValue("年龄");
cell
//创建数据行1
= sheet.createRow(1);
row = row.createCell(0);
cell .setCellValue("fish");
cell= row.createCell(1);
cell //通过写入的数据类型,来确定填充什么类型
.setCellValue(12);
cell
//创建数据行2
= sheet.createRow(2);
row = row.createCell(0);
cell .setCellValue("cat");
cell= row.createCell(1);
cell .setCellValue(89);
cell}
public Workbook createWorkbook2003(){
= new HSSFWorkbook();
Workbook w = w.createSheet("表格1");
Sheet sheet this.injectContent(sheet);
return w;
}
public Workbook createWorkbook2007(){
= new XSSFWorkbook();
Workbook w = w.createSheet("表格1");
Sheet sheet this.injectContent(sheet);
return w;
}
public Workbook createWorkbook2007_large(){
= new SXSSFWorkbook();
Workbook w = w.createSheet("表格1");
Sheet sheet this.injectContent(sheet);
return w;
}
}
生成Excel的步骤:
- 通过XSSFWorkbook来创建Workbook,HSSFWorkbook是2003版本,XSSFWorkbook是2007版本,SXSSFWorkbook是2007的优化版本,以流方式写入文档,减少内存使用,但是对随机访问的能力要差一点。具体性能比较看这里,原理看这里
- 通过Workboot的createSheet生成表格
- 通过Sheet的createRow与createCell创建单元格
总体来说,真的挺简单的。注意,单元格的数据类型,就是由传入setCellValue的数据类型来确定的,这点要小心。
package spring_test;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Workbook;
import org.h2.util.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
/**
* Created by fish on 2021/4/25.
*/
@RestController
@RequestMapping("/hello")
@Slf4j
public class MyController {
@GetMapping("/go")
public String go(){
return "Hello World";
}
@Autowired
private ExportExcelService excelService;
@GetMapping("get1")
public void get1(){
= (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = requestAttributes.getResponse();
HttpServletResponse response = requestAttributes.getRequest();
HttpServletRequest request
String filename = "地址列表.xls";
OutputStream out = null;
= null;
Workbook workbook try {
// 下面几行是为了解决文件名乱码的问题
.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename,"UTF-8"));
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
response= response.getOutputStream();
out
= excelService.createWorkbook2003();
workbook .write(out);
workbook}catch(IOException e){
throw new RuntimeException(e);
}finally {
try{
if( workbook != null ){
.close();
workbook}
if( out != null ){
.close();
out}
}catch(IOException e ){
.printStackTrace();
e}
}
}
}
生成workbook以后,直接写入到HttpServletResponse就可以了。注意要设置HttpServletResponse的header信息。另外,这里用到了URLEncoder,将返回数据使用URLEncode来编码先,以符合HTTP标准,也避免乱码。
2.3 导入与分析excel
package spring_test;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Workbook;
import org.h2.util.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
/**
* Created by fish on 2021/4/25.
*/
@RestController
@RequestMapping("/hello")
@Slf4j
public class MyController {
@Autowired
private ImportExcelSevice importExcelSevice;
//使用curl提交,curl http://localhost:8585/hello/post -X POST -F "data=@./excel.xlsx"
@PostMapping("post1")
public Object post1(@RequestParam(name="data") MultipartFile file,
@RequestParam(name="type") String type){
.info("type {}",type);
logInputStream is = null;
try{
= file.getInputStream();
is return importExcelSevice.read(is);
}catch(IOException e ){
throw new RuntimeException(e);
}finally {
if( is != null ){
try{
.close();
is}catch(IOException e){
.printStackTrace();
e}
}
}
}
}
在SpringBoot中使用@RequestParam注解,和MultipartFile参数来接收文件参数。
package spring_test;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Component
@Slf4j
public class ImportExcelSevice {
private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
public Optional<List<List<Object>>> read(InputStream is){
;
Workbook workbooktry{
= new XSSFWorkbook(is);
workbook }catch(IOException e){
return Optional.empty();
}
= workbook.getSheetAt(0);
Sheet sheet int firstRow = sheet.getFirstRowNum();
int lastRow = sheet.getLastRowNum();
List<List<Object>> result = new ArrayList<>();
//这里需要有等于号
for( int i = firstRow ;i <= lastRow;i++){
= sheet.getRow(i);
Row row if( row == null ){
continue;
}
int firstColumn = row.getFirstCellNum();
int lastColumn = row.getLastCellNum();
List<Object> rowData = new ArrayList<>();
//这里不能有等于号
for( int j = firstColumn ; j < lastColumn;j++ ){
= row.getCell(j);
Cell cell if( cell == null ){
.add("");
rowDatacontinue;
}
if( cell.getCellType() == CellType.STRING){
.add(cell.getStringCellValue());
rowData}else if(cell.getCellType() == CellType.NUMERIC){
if (DateUtil.isCellDateFormatted(cell)) {
.add(simpleDateFormat.format(cell.getDateCellValue()));
rowData}else{
.add(cell.getNumericCellValue());
rowData}
}
}
.add(rowData);
result}
return Optional.of(result);
}
}
最后,直接使用XSSFWorkbook来反序列化一个Workbook对象。然后慢慢地使用getRow与getCell来获取数据就可以了,接口也比较简单。注意Date的读取方式。
要注意的是,row与cell是可能为null的,需要判断一下
2.4 样式和基础工具
package spring_test;
import lombok.Data;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.function.Function;
public class ExcelUtil {
public enum TableColumnType{
STRING,
INTEGER,
BIG_DECIMAL,
BYTE,
}
public static class TableColumn{
private String title;
private Function getter;
private int width;
public TableColumn(String title,Function getter,int width){
this.title = title;
this.getter = getter;
this.width = width;
}
}
public static class Table<T>{
private List<TableColumn> data = new ArrayList<>();
public Table(){
}
public <R> void addColumn(String title,Function<T,R> getter,int width){
this.data.add(new TableColumn(title,getter,width));
}
public int getColumnSize(){
return data.size();
}
}
private static SimpleDateFormat ft = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static <T> void createTable(Sheet sheet,int beginRow,Table<T> table,List<T> data){
Row row = sheet.createRow(beginRow);
for( int i = 0 ;i != table.data.size();i++){
TableColumn column = table.data.get(i);
Cell cell = row.createCell(i);
cell.setCellValue(column.title);
//设置列宽度,建议设置为256的倍数,单位为字符宽度
sheet.setColumnWidth(i,column.width*256);
}
for( int i = 0 ;i != data.size() ;i ++){
T rowData = data.get(i);
beginRow++;
row = sheet.createRow(beginRow);
for( int j = 0 ; j != table.data.size() ;j ++){
TableColumn column = table.data.get(j);
Cell cell = row.createCell(j);
Object cellValue = column.getter.apply(rowData);
if( cellValue instanceof Long){
cell.setCellValue((Long)cellValue);
}else if( cellValue instanceof Integer){
cell.setCellValue((Integer)cellValue);
}else if( cellValue instanceof Byte){
cell.setCellValue((Byte)cellValue);
}else if( cellValue instanceof Date){
cell.setCellValue(ft.format((Date)cellValue));
}else if( cellValue instanceof String){
cell.setCellValue((String)cellValue);
}else if(cellValue instanceof BigDecimal){
cell.setCellValue(((BigDecimal) cellValue)..doubleValue());
}else{
throw new RuntimeException("unknown cellValue type "+cellValue.getClass().getName());
}
}
}
}
public static void exportToWriter(Workbook workbook,String filename){
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletResponse response = requestAttributes.getResponse();
HttpServletRequest request = requestAttributes.getRequest();
OutputStream out = null;
try {
// 下面几行是为了解决文件名乱码的问题
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename,"UTF-8"));
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
out = response.getOutputStream();
workbook.write(out);
}catch(IOException e){
throw new RuntimeException(e);
}finally {
try{
if( workbook != null ){
workbook.close();
}
if( out != null ){
out.close();
}
}catch(IOException e ){
e.printStackTrace();
}
}
}
}
大部分情况下,我们后端都是将List<T>转换为excel,以及将excel导出到HttpResponse里面,ExecelUtil做了以上工作,这点就不多说了。注意,设置列宽的代码。
package spring_test;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.*;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@Component
public class ExportExcelService2 {
@Data
@AllArgsConstructor
public static class Person {
private Long id;
private String name;
}
private List<Person> getData() {
ArrayList<Person> result = new ArrayList<>();
for (int i = 0; i != 100; i++) {
result.add(new Person(Long.valueOf(i + 10001), "fish"));
}
return result;
}
public XSSFCellStyle getTitleStyle(XSSFWorkbook wb){
XSSFFont font = wb.createFont();
font.setFontHeight(20);
font.setFontName("微软雅黑");
//font.setColor(Font.COLOR_RED);
font.setColor(new XSSFColor(java.awt.Color.decode("#F0C0E0"),new DefaultIndexedColorMap()));
XSSFCellStyle style = wb.createCellStyle();
//边框
style.setBorderBottom(BorderStyle.THIN);
style.setBorderTop(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
//字体
style.setFont(font);
//自动换行
style.setWrapText(false);
//水平对齐
style.setAlignment(HorizontalAlignment.CENTER);
//垂直对齐
style.setVerticalAlignment(VerticalAlignment.CENTER);
return style;
}
public void export(){
List<Person> person = this.getData();
XSSFWorkbook wb = new XSSFWorkbook();
Sheet sheet1 = wb.createSheet("Sheet1");
//创建标题
Row row = sheet1.createRow(0);
Cell cell = row.createCell(0);
cell.setCellValue("我是标题");
//合并单元格
//参数分别为firstRow,lastRow,firstCol和lastCol
sheet1.addMergedRegion(new CellRangeAddress(0,0,0,1));
//设置标题样式
XSSFCellStyle style = this.getTitleStyle(wb);
cell.setCellStyle(style);
//行高
//建议设置为20的倍数,单位为磅
row.setHeight((short)(60*20));
//创建表格
ExcelUtil.Table<Person> table = new ExcelUtil.Table<>();
table.addColumn("ID",Person::getId,20);
table.addColumn("名称",Person::getName,40);
ExcelUtil.createTable(sheet1,1,table,person);
//导出
ExcelUtil.exportToWriter(wb,"测试.xlsx");
}
}
在这个例子中,要点如下:
- 展示了如何使用ExportUtil将List数组转换为excel的行与列
- 展示了如何导出Excel到当前线程的HttpResponse里面
- 设置行高,row.setHeight
- 设置合并单元格,sheet.addMergedRegion
- 设置样式,setCellStyle,包括字体,换行,边框和对齐。
3 FreeMarker
FreeMarker是一个很成熟的Java模板工具了,现在前后端分离的开发环境下,已经很少用模板生成html了,更多是用模板来生成代码。
参考资料:
代码在这里
3.1 依赖
dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
<dependency> </
这个也是比较简单的
3.2 加载Configuration
package spring_test;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
@Component
public class FreeMarkerService {
private Configuration freeMarkConfiguration;
@PostConstruct
public void init(){
try{
= new Configuration(Configuration.VERSION_2_3_20);
freeMarkConfiguration //当前工作目录下的tpl文件夹
//freeMarkConfiguration.setDirectoryForTemplateLoading(new File("tpl"));
//使用类的资源方式加载
.setClassForTemplateLoading(this.getClass(),"/tpl");
freeMarkConfiguration.setDefaultEncoding("UTF-8");
freeMarkConfiguration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
freeMarkConfiguration}catch(Exception e){
throw new RuntimeException(e);
}
}
public String execute(String templateName,Object data){
try{
StringWriter stream = new StringWriter();
= freeMarkConfiguration.getTemplate(templateName);
Template tpl .process(data,stream);
tplreturn stream.toString();
}catch(IOException e){
throw new RuntimeException(e);
}catch(TemplateException e){
throw new RuntimeException(e);
}
}
}
我们一般都是直接按目录来加载模板的,注意模板文件的后缀名都必须是ftl。另外,Configuration类应该设计为单例,因为他自身包含了Cache的能力,重新创建Configuration的成本很高。最后,注意一下,加载本地文件夹,和加载类路径文件夹的不同写法。
3.3 模板语法
3.3.1 控制流
html>
<head>
<title>Welcome!</title>
<head>
</body>
<<!--if与else,注意用等号,而不是equals-->
<#if sex == 'mm'>
Welcome Lady ${user}<#else>
Welcome Man ${user}</#if>
<!--for,一个独特的as语法,并且支持sep指令来指定非末尾行的数,index从0开始-->
p>共有${cars?size}个车子</p>
<ul>
<<#list cars as car>
li>${car?index} name为${car.name}<#sep>,<#sep></li>
<</#list>
ul>
</
<!--for的另外一种写法,当cars为空的时候,连ul标签都不输出。我们还能额外用else标签来输出,index从1开始-->
<#list cars >
ul>
<<#items as car>
li>${car?counter} name2为${car.name}<#sep>,</#sep></li>
<</#items>
ul>
</<#else>
p>Nothing</p>
<</#list>
<!--for,访问哈希表-->
<#assign keys = brands?keys>
p>共有${keys?size}个种类的袋子</p>
<ul>
<<#list keys as key>
li>${key} => ${brands[key]}</li>
<</#list>
ul>
</
<#--注释标签,不会输出到前端-->
<#include "/footer.ftl">
body>
<html> </
这些控制流语法都是比较简单的,注意点都在注释上了,没啥好说的。注意访问Map的方法
package spring_test;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.util.*;
/**
* Created by fish on 2021/4/25.
*/
@RestController
@RequestMapping("/hello")
@Slf4j
public class MyController {
@Autowired
private FreeMarkerService freeMarkerService;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Product{
private String url;
private String name;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Car{
private String name;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class MainData{
private String user;
private String sex;
private List<Car> cars = new ArrayList<>();
private BigDecimal assets;
private Long assets2;
private Product latestProduct;
private Map<String,String> brands = new HashMap<>();
}
@GetMapping("/mainTpl")
public String mainTpl(){
= new Product();
Product product .setUrl("https://www.baidu.com");
product.setName("百度");
product= new MainData();
MainData data .setUser("fish");
data.setLatestProduct(product);
datareturn freeMarkerService.execute("main.ftl",data);
}
@GetMapping("/conditionTpl")
public String conditionTpl(){
= new Car();
Car car .setName("Toyota");
car= new Car();
Car car2 .setName("Mercedes");
car2= new MainData();
MainData data .setUser("fish");
data.setSex("Man");
data.setCars(Arrays.asList(car,car2));
datareturn freeMarkerService.execute("condition.ftl",data);
}
@GetMapping("/conditionTpl2")
public String conditionTpl2(){
= new MainData();
MainData data .setUser("Cat");
data.setSex("mm");
data.brands.put("LV","10个");
data.brands.put("Coach","20个");
datareturn freeMarkerService.execute("condition.ftl",data);
}
}
我们能够使用Map,或者Java Bean来传送数据,这个都是基本艺能,没啥好说的。
conditionTpl的结果
conditionTpl2的结果
3.3.2 外部导入
就是3.3.1节的#include指令
3.3.3 插值
html>
<head>
<title>Welcome!</title>
<head>
</body>
<<#--默认是没有转义的-->
p>${user}</p>
<<#--使用指定转义-->
p>${user?html}</p>
<<#--放入escape块的变量,指定默认使用x?html的语法来转义-->
<#escape x as x?html>
p>${user}</p>
<<#--我们用noescape来指定部分变量不转义-->
p><#noescape>${user}</#noescape></p>
<</#escape>
<#--默认数字输出有分割符-->
p>${assets}</p>
<p>${assets2}</p>
<
<#--默认数字输出有分割符,需要用?c来避免这个问题-->
p>${assets?c}</p>
<p>${assets2?c}</p>
<
<#--字符串组合-->
p>${"hello ${assets}"}</p>
<p>${"hello ${user[0..1]}"}</p>
<body>
</html> </
插值语法就比较多坑了,首先要注意数字输出默认就有分隔符,字符串输出默认没有escape,这些都需要根据环境来做好准备
package spring_test;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Created by fish on 2021/4/25.
*/
@RestController
@RequestMapping("/hello")
@Slf4j
public class MyController {
@Autowired
private FreeMarkerService freeMarkerService;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Product{
private String url;
private String name;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Car{
private String name;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class MainData{
private String user;
private String sex;
private List<Car> cars = new ArrayList<>();
private BigDecimal assets;
private Long assets2;
private Product latestProduct;
}
@GetMapping("/valueTpl")
public String valueTpl(){
= new MainData();
MainData data .setAssets(new BigDecimal("123456789.123"));
data.setAssets2(1234567890L);
data.setUser("<div style=\"color:red\">你好</div>");
datareturn freeMarkerService.execute("value.ftl",data);
}
}
导入数据
valueTpl的结果
4 Reflections
官网在这里,Reflections是一个比较简单的,扫描包的工具,输入一个包名,将它的所有类都抽取出来
4.1 依赖
dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.10.2</version>
<dependency> </
加入以上依赖即可
4.2 扫描
package spring_test;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.reflections.Reflections;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
import java.util.Set;
import java.util.stream.Collectors;
@Slf4j
@Component
public class MyReflections {
@Value("${reflections.packageName}")
private String scanPackageName;
@PostConstruct
public void init(){
if(Strings.isBlank(scanPackageName)){
throw new RuntimeException("reflections.packageName为空");
}
.info("scanPackageName {}",scanPackageName);
log= new Reflections(this.scanPackageName);
Reflections reflections
//获取所有枚举体
Set<Class<? extends Enum>> allEnums = reflections.getSubTypesOf(Enum.class);
.info("allEnums {}",allEnums.stream().map(single->single.getName()).collect(Collectors.toList()));
log
//获取所有注解的类
Set<Class<?>> allAnnotations = reflections.getTypesAnnotatedWith(RestController.class);
.info("allControllers {}",allAnnotations.stream().map(single->single.getName()).collect(Collectors.toList()));
log}
}
扫描包的代码,比较简单,没啥好说
/**
* Created by fish on 2021/3/15.
*/
server.port=8585
logging.level.root = INFO
spring.datasource.url = jdbc:h2:mem:testdb
reflections.packageName = spring_test
我们可以在properties中加入指定的扫描位置
- 本文作者: fishedee
- 版权声明: 本博客所有文章均采用 CC BY-NC-SA 3.0 CN 许可协议,转载必须注明出处!