基于Java反射和Fel计算引擎动态导出Excel的实现
2022-08-20张胜楠
张胜楠
(武汉光谷职业学院,武汉 430000)
0 引言
Web系统应用中经常会进行Excel表格的导出,传统的导出方式在导出不同对象时需要配置表头并且代码频繁复用,导致代码冗余、工作量大,导出流程机械,并且对于大数据表操作效率较低。本文介绍一种基于Java反射和自定义注解,结合Fel计算表达式的方法,来实现Excel灵活动态的导出效果。
1 相关概念介绍
1.1 Java反射
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
1.2 自定义注解
注解其实就是一种标记,可以在程序代码中的关键节点(类、方法、变量、参数、包)上打上这些标记,然后程序在编译时或运行时可以检测到这些标记从而执行一些特殊操作。
1.3 Fel计算
Fel是轻量级的高效的表达式计算引擎,Fel源自于企业项目,设计目标是为了满足不断变化的功能需求和性能需求。Fel的执行主要通过函数实现,运算符(+、-等)都是Fel函数,所有这些函数都是可以替换的,扩展函数也非常简单,可以根据性能要求选择执行方式。编译执行就是将表达式编译成字节码(生成java代码和编译模块都是可以扩展和替换的)。
2 实现步骤
2.1 自定义注解
构建ExportTblAnnotation.java类,自定义类注解:构建ExportAnnotation.java类,自定义方法注解,标记特定的类和方法,用于导出文件的数据填充。
//Excel导出注解
@Target({E1ementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public@interface ExportTblAnnotation{
}
//Excel导出注解
@Target({E1ementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public@interface ExportAnnotation{
Public String code()default"";
}
2.2 创建Bean Def i nat i on对象
用于存储被上述自定义注解所标记的类和方法的定义。一个BeanDefinition描述了一个Bean实例,实例包含属性值、构造方法参数值以及更多实现信息。该BeanDefinition只是一个最小的接口,主要目的是能动态地调用实现方法,这里列出几个核心方法。
//bean定义
public class BeanDefinition{
private String beanName;
private String methodName;
private Class<?>[]parameterClass;
private Class<?>returnType;
private String[]parameterNames;
public String[]getParameterNames(){return paraneterNames;}
public void setParameterNames(String[]parame ter-Names){this.parameterNames=parameterNames;}
public String getBeanName(){return beanName;}
public void setBeanName(String beanName){this.beanName=beanName;}
public String getMethodName(){return methodName;}
public Class<?>[]getParameterClass(){return parameterClass;}
public void setParameterClass(Class<?>[]parameter-Class){this.parameterClass=parameterClass;}
....
2.3 添加自定义注解扫描方法
在程序启动时,初始化阶段扫描有上述自定义注解的类与方法,并将该方法的bean-Name、方法名称methodName、参数类型parameterClass、参数名称parameterNames、返回类型returnType等属性存到BeanDefinition对象,与注解上code值一一映射,存入beanMap,核心代码如下:
@PostConstruct
Public void init(){
log.info(“开始初始化报表配置”);
Map<String,Object>beanDefinitions=SpringContextUtils.getApplicationContext().getBeansWithAnnotation(ExportTblAnnotion.class);
DefaultParameterNameDiscoverer dpnp=new DefaultParameterNameDiscoverer();
...
BeanDefinition definition=new BeanDefinition();
...
Constant.beanMap.put(code,definition);
...
2.4 创建动态导出文件配置表
(1)创建导出文件配置信息表(见图1),用于存储导出路径、导出报表名称,支持xls、xlsx和csv三种格式、导出报表编码,该值和自定义注解的code值对应、导出方式,支持方法导出、SQL导出、直接导出三种方式。
图1 导出文件配置信息表
①方法导出方式仅需在导出文件所需数据的查询方法上面添加自定义注解并配置报表编码,举例如下:
//添加自定义注解
@ExportAnnotation(code="ATLAS_TXN_DTL")
@0verride
public PageOutVo<BaseTxnV0> queryTxnDtl(AtlasTxnDtlInV0 inV0){
PageOutVo<BaseTxnV0>outV0=new PageOutVo<>();
If(StringUtils.isEmpty(inV0.getFrom())||String-Utils.isEmpty(inV0.getTarget())){
return outV0;
}
StringBuilder sb=new StringBuilder();
…
配置报表编码(图2)。
图2 配置报表编码
②SQL导出方式仅需配置exp_sql,配置查询数据的SQL语句即可,无需添加任何代码。
③直接导出用于导出已生成的文件。
(2)创建导出文件字段映射表(见图3),用于存储Excel表头、标题、列宽、行高、填充值等属性。显示属性SHOW_FLAG可以动态调整数据列的显示状态,顺序属性DISPLAY_ORDER可以调整列的显示顺序。
图3 导出文件字段映射
接上述例子,假设导出报表的表头是序号、交易卡号、交易对方证件号等信息,表填充如图4。
图4 导出报表填充信息
以上两个步骤即可完成动态导出文件的相关配置,相对于传统实现方法的业务代码和功能代码耦合性高,且需求变化时会修改大量代码,本方法仅通过配置的方式即可实现,业务代码和导出逻辑完全分离,移植性好。
2.5 添加公共导出接口
用户通过前端页面传入待导出文件对应的报表编码值及查询条件json串,即可根据上述配置信息表中的配置信息以及反射机制灵活导出多种格式文件,公共导出接口方法:
@GetMapping("/export/data")
@ApiOperation(“导出文件接口”)
public ResponseEntity<Resource>exportData(String code,String condStr){
log.info(“开始导出文件code="+code+";condStr=”+condStr);
Map<String.Object>parm=new HashMap();
parm.put(“code”,code);
List<EpTblCfg> tblCfgList= jdbcTemplate.qury
(“select*fromep_tbl_cfg where table_code=:code”,parm,new ObjRowMapper(EpTblCfg.class));
if(CollectionUtils.isEmpty(tblCfglist)){
throw new AmtRunExceptin(“未配置表格配置信息:”+code);
}
EpTblCfg tblCfg=tblCfgList.get(0);
String expTpCd=StringUtils.isEmpty(tblCfg.getExp-Type())?Constant.EXP_TPCD.METHOD:
tblCfg.getExpType();
If(Constant.EXP_TPCD.METHOD.equals(exp TpCd)){
Return reportByMethod(code,condStr);
}else if(Constant.EXP_TPCD.FILE.equals(exp TpCd)){
Return reportByFile(code,condStr);
}else if(Constant.EXP_TPCD.SQL.equals(exp TpCd)){
Return reportBySql(code,condStr);
}else{Throw new AmtRunException(“导出类型配置错误”);
}
2.6 通过反射机制实现方法调用
公共接口方法通过code值找到对应的beandefinition对象,通过反射进行查询方法调用,获取要导出的数据。
...
BeanDefinition beanDefinition=Constant.beanMap.get(code);
...
Object service=SpringContextUtils.getBean(beanDefinition.getBeanName());
Method method=beanDefinition.getMethod();
.....
method.invoke(service,args,parameterClass)
2.7 基于Fel动态导出实现
将上述获取到的待导出数据进行处理,通过2.4中所配置的文件字段信息及Fel表达式计算,动态生成表头、标题及填充数据内容。for(EpFieldcfg cfg:columnCfg){
Cell cell=bodyRow.createCell(bodyCellIdx++);
cell.setCellStyle(bodyStyle);
object result=null;
if(StringUtils.isNotEmpty(cfg.getFieldData())){
try{
result=FelUtils.getExpressionResult(cfg.get-FieldData(),mct);
}catch(Exception e){...}
if(result==null){
cell.setCellvalue(""′);
}else{
cell.setCellValue(result.toString());
}
3 结语
本文实现了一种更加高效灵活的动态Excel文件导出方法。创新点有三处:①利用自定义注解及Java反射机制,使代码更加简洁,而且业务代码和导出功能代码是独立的,减小耦合性;②导出功能还运用了Fel表达式计算引擎的特性,支持大数据高精度计算、千万次每秒的执行速度、而且轻量级易扩展,使Excel导出更具有灵活性、高效性和易扩展性;③导出方式有三种,支持方法导出、SQL导出、直接导出,导出报表支持xls、xlsx和csv三种格式。