張勝楠
(武漢光谷職業(yè)學院,武漢 430000)
Web系統(tǒng)應用中經(jīng)常會進行Excel表格的導出,傳統(tǒng)的導出方式在導出不同對象時需要配置表頭并且代碼頻繁復用,導致代碼冗余、工作量大,導出流程機械,并且對于大數(shù)據(jù)表操作效率較低。本文介紹一種基于Java反射和自定義注解,結合Fel計算表達式的方法,來實現(xiàn)Excel靈活動態(tài)的導出效果。
Java反射機制是在運行狀態(tài)中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態(tài)獲取的信息以及動態(tài)調用對象的方法的功能稱為Java語言的反射機制。
注解其實就是一種標記,可以在程序代碼中的關鍵節(jié)點(類、方法、變量、參數(shù)、包)上打上這些標記,然后程序在編譯時或運行時可以檢測到這些標記從而執(zhí)行一些特殊操作。
Fel是輕量級的高效的表達式計算引擎,F(xiàn)el源自于企業(yè)項目,設計目標是為了滿足不斷變化的功能需求和性能需求。Fel的執(zhí)行主要通過函數(shù)實現(xiàn),運算符(+、-等)都是Fel函數(shù),所有這些函數(shù)都是可以替換的,擴展函數(shù)也非常簡單,可以根據(jù)性能要求選擇執(zhí)行方式。編譯執(zhí)行就是將表達式編譯成字節(jié)碼(生成java代碼和編譯模塊都是可以擴展和替換的)。
構建ExportTblAnnotation.java類,自定義類注解:構建ExportAnnotation.java類,自定義方法注解,標記特定的類和方法,用于導出文件的數(shù)據(jù)填充。
//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"";
}
用于存儲被上述自定義注解所標記的類和方法的定義。一個BeanDefinition描述了一個Bean實例,實例包含屬性值、構造方法參數(shù)值以及更多實現(xiàn)信息。該BeanDefinition只是一個最小的接口,主要目的是能動態(tài)地調用實現(xiàn)方法,這里列出幾個核心方法。
//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;}
....
在程序啟動時,初始化階段掃描有上述自定義注解的類與方法,并將該方法的bean-Name、方法名稱methodName、參數(shù)類型parameterClass、參數(shù)名稱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);
...
(1)創(chuàng)建導出文件配置信息表(見圖1),用于存儲導出路徑、導出報表名稱,支持xls、xlsx和csv三種格式、導出報表編碼,該值和自定義注解的code值對應、導出方式,支持方法導出、SQL導出、直接導出三種方式。
圖1 導出文件配置信息表
①方法導出方式僅需在導出文件所需數(shù)據(jù)的查詢方法上面添加自定義注解并配置報表編碼,舉例如下:
//添加自定義注解
@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,配置查詢數(shù)據(jù)的SQL語句即可,無需添加任何代碼。
③直接導出用于導出已生成的文件。
(2)創(chuàng)建導出文件字段映射表(見圖3),用于存儲Excel表頭、標題、列寬、行高、填充值等屬性。顯示屬性SHOW_FLAG可以動態(tài)調整數(shù)據(jù)列的顯示狀態(tài),順序屬性DISPLAY_ORDER可以調整列的顯示順序。
圖3 導出文件字段映射
接上述例子,假設導出報表的表頭是序號、交易卡號、交易對方證件號等信息,表填充如圖4。
圖4 導出報表填充信息
以上兩個步驟即可完成動態(tài)導出文件的相關配置,相對于傳統(tǒng)實現(xiàn)方法的業(yè)務代碼和功能代碼耦合性高,且需求變化時會修改大量代碼,本方法僅通過配置的方式即可實現(xiàn),業(yè)務代碼和導出邏輯完全分離,移植性好。
用戶通過前端頁面?zhèn)魅氪龑С鑫募膱蟊砭幋a值及查詢條件json串,即可根據(jù)上述配置信息表中的配置信息以及反射機制靈活導出多種格式文件,公共導出接口方法:
@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(“導出類型配置錯誤”);
}
公共接口方法通過code值找到對應的beandefinition對象,通過反射進行查詢方法調用,獲取要導出的數(shù)據(jù)。
...
BeanDefinition beanDefinition=Constant.beanMap.get(code);
...
Object service=SpringContextUtils.getBean(beanDefinition.getBeanName());
Method method=beanDefinition.getMethod();
.....
method.invoke(service,args,parameterClass)
將上述獲取到的待導出數(shù)據(jù)進行處理,通過2.4中所配置的文件字段信息及Fel表達式計算,動態(tài)生成表頭、標題及填充數(shù)據(jù)內容。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());
}
本文實現(xiàn)了一種更加高效靈活的動態(tài)Excel文件導出方法。創(chuàng)新點有三處:①利用自定義注解及Java反射機制,使代碼更加簡潔,而且業(yè)務代碼和導出功能代碼是獨立的,減小耦合性;②導出功能還運用了Fel表達式計算引擎的特性,支持大數(shù)據(jù)高精度計算、千萬次每秒的執(zhí)行速度、而且輕量級易擴展,使Excel導出更具有靈活性、高效性和易擴展性;③導出方式有三種,支持方法導出、SQL導出、直接導出,導出報表支持xls、xlsx和csv三種格式。