`

Java自定义注解以及在POI导出EXCEL中的一个应用

阅读更多

      本文简介Java自定义注解的使用,并且结合在使用POI导出excel表格中的一个应用来加深对annotation的理解。预备知识:Java基础、反射机制、略微了解POI或JXL等读写EXCEL的工具。

    

Annontation(注解是Java5开始引入的新特征。它用来将一些元数据/元信息(metadata)与程序元素(类、方法、成员变量等)进行关联,为程序的元素(类、方法、成员变量)加上更直观更明了的说明,并且供指定的工具或框架使用,起到说明、配置的功能。常用的注解如@Override,@Controller、@Repository等各种框架的注解等,Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。Annotation相关API包含在 java.lang.annotation 包中,使用@interface关键字来声明一个自己的注解类型,并配合一系列的元注解(@Retention @Target @Document @Inherited)来说明你的注解的信息
  1、生成文档。这是java 最早提供的注解。常用的有@param @return 等
   2、实现替代配置文件功能。通常用于Java Config的配置方式中,用来替代XML(最常用功能)
    3、在编译时进行格式检查。如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。
 
注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。而我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象$Proxy1。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池
 
自定义注解类编写的一些规则:
  1. Annotation型定义为@interface, 所有的Annotation会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口.
  2. 参数成员只能用public或默认(default)这两个访问权修饰
  3. 参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和String、Enum、Class、annotation(可组合别的注解,如@SpringBootApplication)等数据类型,以及这一些类型的数组.
  4. 要获取类方法和字段的注解信息,必须通过Java的反射技术来获取 Annotation对象,因为你除此之外没有别的获取注解对象的方法
1)isAnnotationPresent
2)getAnnotation
——————————————————————————————————————————
下面通过一个简单的例子来演示annotation的基本使用。该例子有以下三个组件:
1.组件1--水果名注解@FruitName。只有一个value属性,用于接收外界传入的水果名
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 水果名称注解
*/
@Target({ElementType.FIELD})//用于属性上注解
@Retention(RetentionPolicy.RUNTIME)//运行期也能起作用
@Documented//允许加入到javadoc中
public @interface FruitName {
String value() default "";
}
2. 组件2--一个实体类Apple,使用@FruitName注解来表示当前水果的名字
public class Apple {
@FruitName(value="China.Apple")//相当于为fruitname的value属性赋值
private String appleName;

public String getAppleName() {
return appleName;
}

public void setAppleName(String appleName) {
this.appleName = appleName;
}
}
3.组件3--一个注解使用类,演示怎么利用反射来使用注解
import java.lang.reflect.Field;
public class FruitNameUtil {
public static void main(String[] args) {
Apple apple = new Apple();
Field[] fields = apple.getClass().getDeclaredFields();

for (Field field : fields) {
if (field.isAnnotationPresent(FruitName.class)) {
//获取FruitName注解
FruitName fruitName = (FruitName) field.getAnnotation(FruitName.class);
String strFruitName = fruitName.value();
System.out.println("当前水果为:"+strFruitName);
}
}
}
}
大家可以看到,注解接口是一种特殊的接口,在业务类上使用特定注解标注,相当于创建了注解接口的一个实例,并且可以选择为该实例的某些属性赋值,比如FruitName注解的value属性。在具体使用注解时,通常要使用反射提取类、属性或方法上的注解(往往还会访问属性值),用于一些全局性的判断,比如spring mvc的@Controller注解,spring框架在反射时就知道该类是一个web控制器类,如果读到@requestmapping注解,就知道这个方法上标注了请求的URI,并且通过其value属性值提取对应的URI。这里的核心API是isAnnotationPresent(注解接口对应的class实例)和getAnnotation(注解接口对应的class实例)两个方法,前者用于判断指定注解是否存在,后者用于返回注解实例,用于进一步操作。
——————————————————————————————————————————
POI导出Excel基本操作
Apache POI是Apache软件基金会的一个开源框架,提供API给Java程序对Microsoft Office格式文件读和写的功能。下面的例子用于展示POI读写EXCEL的基本操作,4个Junit测试方法分别用于向EXCEL写常量数据、写实体对象数据、增加标题以及从EXCEL中读取数据。
public class PoiDemoApplicationTests2 {

/*@Test
public void contextLoads() {
}*/

// 简单写常量数据
/**
* HSSFWorkbook,针对是 EXCEL2003 版本,扩展名为 .xls
* XSSFWorkbook,针对是Excel2007版本,扩展名为 .xlsx
* @throws Exception
*/
@Test
public void insertExcel1() throws Exception {
// 创建工作簿对象(Excel文件)
HSSFWorkbook workbook = new HSSFWorkbook();
// (new FileInputStream(new File("E:/temp/t1.xls")));
// 创建表格中一个sheet对象
HSSFSheet sheet = workbook.createSheet("mysheet1");
// int i = workbook.getSheetIndex("xt"); // sheet表名
// sheet = workbook.getSheetAt(i);
// 创建行对象,映射到表格中某一行
HSSFRow row = sheet.getRow(0);
if (row == null) {
row = sheet.createRow(0); // 该行无数据,创建行对象
}

// 创建一个单元格对象,参数为该单元格列数(从0开始)
Cell cell = row.createCell(0); // 创建指定单元格对象。如本身有数据会替换掉
cell.setCellValue("test data"); // 设置内容

FileOutputStream fo = new FileOutputStream(new File("E:/temp/t1.xls")); // 输出到文件
workbook.write(fo);
fo.close();
}

// 写入对象数据
@Test
public void insertExcel2() throws Exception {
// 创建工作簿对象(Excel文件)
HSSFWorkbook workbook = new HSSFWorkbook();

HSSFSheet sheet = workbook.createSheet("mysheet2");


Memeber m1 = new Memeber();
Memeber m2 = new Memeber();
m1.setId("101");
m1.setName("zhangsan");

m2.setId("102");
m2.setName("lisi");
List<Memeber> objs = new ArrayList<>();
objs.add(m1);
objs.add(m2);
// 写入会员数据
for (int i = 0; i < objs.size(); i++) {
Row r = sheet.createRow(i + 1);
Memeber obj = objs.get(i);
System.out.println("当前数据对象为:" + obj);
r.createCell(0).setCellValue(obj.getId());
r.createCell(1).setCellValue(obj.getName());
r.createCell(2).setCellValue(new SimpleDateFormat("yyyy-MM-dd").format(obj.getRegistDate()));

}

FileOutputStream fo = new FileOutputStream(new File("E:/temp/t2.xls")); // 输出到文件
workbook.write(fo);
fo.close();
}

// 写入对象数据,同时加上标题
@Test
public void insertExcel3() throws Exception {
// 创建工作簿对象(Excel文件)
HSSFWorkbook workbook = new HSSFWorkbook();
// (new FileInputStream(new File("E:/temp/t1.xls")));
// 创建表格中一个sheet对象
HSSFSheet sheet = workbook.createSheet("mysheet2");


// 标头行,代表第一行
HSSFRow header = sheet.createRow(0);
// 创建单元格,0代表第一行第一列
header.createCell(0).setCellValue("会员编号");
header.createCell(1).setCellValue("会员姓名");
header.createCell(2).setCellValue("注册时间");

Memeber m1 = new Memeber();
Memeber m2 = new Memeber();
m1.setId("101");
m1.setName("lisi");

m2.setId("102");
m2.setName("wang5");
List<Memeber> objs = new ArrayList<>();
objs.add(m1);
objs.add(m2);
// 写入会员数据
for (int i = 0; i < objs.size(); i++) {
Row r = sheet.createRow(i + 1);
Memeber obj = objs.get(i);
System.out.println("当前数据对象为:" + obj);
r.createCell(0).setCellValue(obj.getId());
r.createCell(1).setCellValue(obj.getName());
r.createCell(2).setCellValue(new SimpleDateFormat("yyyy-MM-dd").format(obj.getRegistDate()));

}

FileOutputStream fo = new FileOutputStream(new File("E:/temp/t3.xls")); // 输出到文件
workbook.write(fo);
fo.close();
}

// 读取,全部sheet表及数据
@Test
public void readExcel1() throws Exception {
//绑定输入流生成工作簿对象
HSSFWorkbook workbook = new HSSFWorkbook(new FileInputStream(new File("E:/temp/t3.xls")));
HSSFSheet sheet = null;
//双重循环读取二维表格
for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
sheet = workbook.getSheetAt(i);// 获取Excel中每个Sheet
for (int j = 0; j < sheet.getLastRowNum() + 1; j++) {
HSSFRow row = sheet.getRow(j);// 获取sheet中每一行
if (row != null) {
for (int k = 0; k < row.getLastCellNum(); k++) {// getLastCellNum,是获取最后一个不为空的列是第几个
if (row.getCell(k) != null) { // getCell 获取每一个单元格数据
System.out.print(row.getCell(k) + "\t");
} else {
System.out.print("\t");
}
}
}
System.out.println(""); // 读完一行后换行
}
System.out.println("读取sheet表:" + workbook.getSheetName(i) + " 完成");
}
}
}
这里我们看到操作中存在一些可以封装的变化点,主要是用于输出EXCEL的实体对象有关,这些对象实际上是我们的数据源,我们归纳一下这些变化点及解决方法:
1.领域对象类型不定--Class 反射
2.领域对象的属性集不定--反射+集合+泛型
3.输出的Excel表头的标题不定--自定义注解+反射
4.输出的Excel表头(字段)的顺序不定--自定义注解+反射+自定义排序规则
可以看到,封装变化点的主要途径就是结合反射和自定义注解,其中领域对象类型不定和属性集不定属于我们解决方案的基础,可以单纯使用反射解决,后面两个跟业务有一定关联,需要结合自定义注解解决。
——————————————————————————————————————————
根据之前的分析,我们创建一个自定义注解用于封装表格字段标题和字段输出顺序,使这两个变化点可以通用设置值,这就是我们的组件1
1.组件1--自定义注解@ExcelResources 用于接收字段标题和字段输出顺序两个属性值,使用自定义注解的目的是可以
跨实体类通用
/**
* 用来在对象的get方法上加入的annotation,通过该annotation说明某个属性所对应的标题
*
* @author
* @date 2013-7-18
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.FIELD})
public @interface ExcelResources {
/**
* 属性的标题名称
*/
String title();
/**
* 在Excel中的顺序
*/
int order() default 9999;
}
为了实现自定义比较规则,我们创建一个ExcelHeader类实现Comparable接口,将标题、标题顺序、应用自定义注解的方法名抽象出来,将来用于确定字段输出顺序和反射,这就是组件2
2.组件2--ExcelHeader 类,用于设置表头字段的标题和顺序
/**
* 用来存储Excel标题的对象,通过该对象可以获取标题和方法的对应关系
* @author
*
*/
public class ExcelHeader implements Comparable<ExcelHeader>{
/**
* 标题的名称
*/
private String title;
/**
* 每一个标题的顺序
*/
private int order;
/**
* 所对应的的方法名称
*/
private String methodName;

public ExcelHeader(String title, int order, String methodName) {
this.title = title;
this.order = order;
this.methodName = methodName;
}

public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getOrder() {
return order;
}
public void setOrder(int order) {
this.order = order;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}

/**
* 自定义排序规则
*/
public int compareTo(ExcelHeader o) {
return order>o.order?1:(order<o.order?-1:0);
}

public String toString() {
return "ExcelHeader [title=" + title + ", order=" + order + ", methodName=" + methodName + "]";
}

}
接下来以一个员工实体类作为例子,包含员工的姓名、性别等属性(字段)
3.组件3--员工实体类Employee
public class Employee {
private String empName;//员工姓名
private Integer sex; //1-男 2-女 3-未知
private String idCardNo; //身份证号
private Date regTime;//注册时间
/*构造器、toString、getters和setters略*/
4.组件4--用于综合使用POI和自定义注解以及测试的类ExcelOperUtil,包含的行为如下
1)Workbook exportObj2Excel(List<?> objs, Class<?> clz, boolean isXssf) 用于创建EXCEL工作簿并返回,objs用于接收实体类集合,作为数据源,clz用于指定实体类类型,作为反射的起点,isXssf用于判断excel文件的版本,具体代码如下:
public Workbook exportObj2Excel(List<?> objs, Class<?> clz
//, boolean isXssf
) throws SecurityException, IllegalArgumentException, NoSuchMethodException, ClassNotFoundException, IllegalAccessException, InvocationTargetException, IOException{
Workbook wb = new XSSFWorkbook();
/*if(isXssf){
wb = new XSSFWorkbook();
}else{
wb = new HSSFWorkbook();
}*/
Sheet sheet = wb.createSheet();
//第一行为表头
Row r = sheet.createRow(0);
//通过传入的实体对象反射出所有属性
List<ExcelHeader> headers = getHeaderList(clz);
//根据实体类各属性的自定义注解值来确定在表格中的序号
Collections.sort(headers);
//写标题
for (int i = 0; i<headers.size(); i++) {
r.createCell(i).setCellValue(headers.get(i).getTitle());
}
//写数据
Object obj = null;
for (int i = 0; i<objs.size(); i++) {
r = sheet.createRow(i+1);
obj = objs.get(i);
System.out.println("当前数据对象为:"+obj);
for (int j = 0; j<headers.size(); j++) {
r.createCell(j).setCellValue(BeanUtils.getProperty(obj, getMethodName(headers.get(j))));
}
}

FileOutputStream fo = new FileOutputStream(new File("E:/temp/t3.xlsx")); // 输出到文件
wb.write(fo);
return wb;
}
 
2)List<ExcelHeader> getHeaderList(Class<?> clz)
私有方法,通过反射得到指定类的ExcelHeader集合,供exportObj2Excel方法调用,完整代码如下:
private List<ExcelHeader> getHeaderList(Class<?> clz){
List<ExcelHeader> headers = new ArrayList<ExcelHeader>();
for(Method m: clz.getDeclaredMethods()){
if(m.getName().startsWith("get")){
if(m.isAnnotationPresent(ExcelResources.class)){
//获取ExcelResources注解对象
ExcelResources er = m.getAnnotation(ExcelResources.class);
//通过实体对象的get方法(间接取得属性名),设置excel表头的列名和列的排列顺序
headers.add(new ExcelHeader(er.title(), er.order(), m.getName()));
}
}
}
return headers;
}
可以看到,这里通过Method类的isAnnotationPresent方法判断业务方法上是否加了ExcelResources自定义注解,在我们的场景中,主要用于业务实体需要导出属性的get方法上。比如一个员工类中相应的属性getters上
@ExcelResources(title = "序号",order = 1)
public Integer getSerialNo() {
return serialNo;
}
3)String getMethodName(ExcelHeader eh)
私有方法,根据标题获取相应的方法名称,供exportObj2Excel方法调用,完整代码为:
private String getMethodName(ExcelHeader eh){
String mn = eh.getMethodName().substring(3);
mn = mn.substring(0,1).toLowerCase()+mn.substring(1);
return mn;
}
4)最后造一个员工类封装成List进行输出EXCEL测试,当然你也可以用其他业务实体输出,只要封装成List传入exportObj2Excel方法即可,灵活通用,核心代码如下:
Workbook wb = util.exportObj2Excel(employeeList, Employee.class);
System.out.println("生成的工作簿:"+wb);
 
总结:本文组合使用了POI,自定义注解,Java反射等知识,通过一个实例,展示了如何实现不同业务对象输出EXCEL的通用解决方案。并且从设计的角度,发现问题、分析问题、解决问题,逐步演化我们的设计,所有源码完整上传到附件中,供大家参考。
分享到:
评论

相关推荐

    excel导入动态校验,自定义注解动态校验

    此外,还可以利用Apache POI库来读取和操作Excel文件,以及Hibernate Validator库来处理自定义注解的校验逻辑。 总之,"Excel导入动态校验,自定义注解动态校验"是一种高效且灵活的数据验证方法,它使得在导入大量...

    导出excel注解方式

    在Java编程中,导出Excel是一项常见的数据处理任务,尤其在数据分析、报表生成等领域中广泛应用。本教程将探讨如何利用注解方式实现简单的Excel表格导出功能。这种方法通常结合Java库,如Apache POI,来简化操作流程...

    通用excel导入/导出 (poi)

    总结来说,Apache POI提供了一个强大且灵活的工具集,使得在Java应用程序中处理Excel文件变得非常方便。通过注解和反射,我们可以轻松地实现Excel数据的导入和导出,这在处理大量结构化数据时特别有用。在实际开发中...

    注解反射导出Excel自定义中文表头

    在Java编程中,导出Excel数据是一项常见的任务,特别是在数据处理和报表生成的场景下。本文将深入探讨如何使用注解反射技术来实现Excel自定义中文表头的导出,结合数据库查询的数据,以实现更加灵活和人性化的数据...

    基于注解导出excel

    在Java编程中,导出Excel是一项常见的需求,特别是在数据分析、报表生成或数据交换等场景下。基于注解的导出方式可以提供更加简洁、灵活的代码实现,减少重复工作,并提高代码可读性。本篇将详细介绍如何使用注解与...

    基于Java反射机制的POI实现Excel数据导入_导出.pdf

    POI组件是Apache软件基金会提供的一个开源的Java库,用于操作Microsoft Office文件,包括Excel、Word、PowerPoint等。POI组件提供了一个统一的API,用于读取和写入各种Office文件。 3. 自定义注解简介 自定义注解...

    基于poi的excel导入导出封装

    1. **ExcelExport类**: 这是一个自定义的类,通过注解的方式,将Java对象列表转换为Excel文件。首先,你需要定义一个Excel模板,然后使用`@ExcelColumn`注解来标记需要导出的字段。在ExcelExport类中,会遍历列表中...

    Java 使用poi导入excel 并使用xml做数据验证

    在Java开发中,处理Excel数据是一项常见的任务,尤其是在数据导入导出、数据分析或者报表生成等场景。Apache POI是一个流行的库,它允许开发者使用Java来读取、写入和修改Microsoft Office格式的文件,包括Excel(....

    基于类注解将对象数据导出到excel

    在这个场景下,可能有一个自定义注解,比如`@ExportColumn`,它用于标记对象的属性,表示这些属性应被导出到Excel文件的相应列中。 描述中提到的博文链接指向了iteye.com的一个博客文章,虽然具体内容未提供,但...

    Excel导出数据(根据Excel模板定义)

    这通常需要一个数据结构来关联数据字段和模板中的位置。 - **数据填充**:利用POI API,将数据写入Cell对象,然后添加到Row和Sheet中。 - **样式应用**:在写入数据的同时,可以设置单元格的样式,使其符合模板...

    Java中利用POI优雅的导出Excel文件详解

    本文介绍了如何使用 POI 库在 Java 中优雅的导出 Excel 文件,并提供了一个简单的示例代码,演示了如何使用 POI 库将数据写入到 Excel 文件中。POI 库提供了一个灵活的 API,允许开发者自定义 Excel 文件的格式和...

    java excel生成二维码(可以添加图标和文字).zip

    通常,二维码生成库会返回一个黑白的像素矩阵,`MatrixToImageWriter`可能负责将这个矩阵转换成PNG或JPEG等图像格式,以便于插入到Excel中。这个过程可能涉及到像素处理、颜色转换等图像处理技术。 5. **自定义配置...

    easyuiPoi导出Excel工具类封装(支持合并单元格)

    在EasyUIPoI中,导出Excel的过程被封装成了一套简单的API。首先,你需要创建一个模板文件,定义好Excel的结构和样式,例如单元格的合并、字体、颜色等。模板文件可以使用Microsoft Excel来创建,保存为`.xlsx`格式,...

    excelpoi导入导出功能

    在Java开发中,Excel的导入导出功能是一个常见的需求,特别是在数据处理、报表生成和数据分析等领域。Apache POI是一个强大的库,它允许开发者使用Java来读取、写入和修改Microsoft Office格式的文件,包括Excel(....

    POI和JXL两种方式导出EXCEL

    在Java开发中,导出Excel文件是常见的需求,主要用于数据报表、数据分析等场景。本话题将探讨使用Apache POI和JExcelApi(JXL)两个库来实现这一功能。这两种库都是Java中广泛使用的处理Microsoft Office文件格式的...

    java导入导出通用模板

    总结起来,Java导入导出通用模板是利用自定义注解提供元数据,通过反射实现动态操作,结合Java POI库处理Excel文件,以实现数据的灵活导入和导出。这种模板化的设计模式在Java开发中具有广泛的应用价值,尤其是在...

    easypoi导入导出excel表格.pdf

    easypoi是一个基于Apache POI的简单易用的Java Excel操作工具库。它通过注解的方式简化了对Excel文件的操作,使得开发者可以更加便捷地实现数据的导入导出。尽管其功能强大,但在实际使用过程中,开发者也需要了解...

    Java导出Excel详细示例.pdf

    标题“Java导出Excel详细示例.pdf”表明,文档内容将围绕Java语言中的Excel文件操作进行详细说明。它将通过实例展示如何使用Java代码导出数据到Excel文件中,使用的是Apache POI库。文档中提到的“Java基础学习免费...

    利用easyExcel导出上万条数据,自定义策略合并单元格

    在导出过程中,我们可以定义一个`HeadListener`来处理表头的合并,以及一个`DataListener`来处理数据行的合并。例如,如果我们想根据某些字段值来决定是否合并单元格,可以在`DataListener`的`invoke`方法中实现逻辑...

Global site tag (gtag.js) - Google Analytics