JAVA 基于注解的报表映射

  随着spring体系的逐渐火爆,“注解”的出镜率越来越高。尤其到了人人都大谈Restful的现在,面向注解的开发方式更是隐然成为了行业新标配。各大框架都争相推出自己的Restful注解(@POST,@GET.....)支持。那今天也来总结一下JAVA中注解的机制。
  "注解",其实说白了就是一种规范化的数据存储方式,能够将一部分的信息直接在代码级别存储。配合java反射机制,就能将这部分信息读取出来实现自己的功能。而注解,相比于另一种常用的XML配置方式,更好管理,更好维护。

下面先来聊一聊注解的体系,然后,我们来试试用注解完成的一个查询数据库快速生成报表功能。前面的基础部分是从别的地方转载的。所以,有兴趣的可以直接到后面的示例部分。
一、元注解:对注解的声明
1、@Target
使用方式:@Target(ElementType.CONSTRUCTOR)
@Target注解是比较重要的,Target的中文是“目标、位置”的意思,见名知意。@Target就用来声明我们创建的注解所放置的位置,也就是我们所创建的注解可以修饰什么样的元素。@Target的参数是一个ElementType的枚举,每个枚举项代表着一个位置。下方就是几个ElementType枚举比较常用的值:
● TYPE: 类,如果@Target的参数是TYPE,那么我们创建的这个注解只能修饰类、接口、枚举等这些类型上。
● FIELD: 字段修饰,如果我们的自定义注解是FIELD类型的话,那么我们的注解只能用来修饰类或者枚举的字段,也就是成员变量。
● CONSTRUCTOR:构造器类型,该类型的“注解”只能修饰构造器。
● METHOD:修饰“方法”的注解。
● PARAMETER:修饰“方法”中的参数的注解。
● LOCAL_VARIABLE: 修饰“局部变量”的注解。
下面截图是ElementType中所有的选项以及每个枚举值的作用。具体如下所示,下方两个是1.8后新加的枚举项,如下所示:
这里写图片描述

2、@Retention
使用方式:@Retention(RetentionPolicy.RUNTIME)
上面是@Retention的使用方式,Retention的中文意思是“保留”,也就是说该元注解给出了“注解”的保留周期。@Retention也是接收一个枚举类型的参数,下方就是该枚举所包含的类型。下方的英文注释已经具体的给出了每个枚举项所对应的意思。
● SOURCE:说明我们的注解只会留在我们的源码中,并不会被编译。
● CLASS: 说明我们的注解会被编译成字节码存储到.class文件中,但不会在虚拟机中链接运行。
● RUNTIME:这个就说明我们的注解会一直保留到程序的运行时,如果你想在运行时根据注解的信息通过反射机制做一些事情的话,那么必须得将我们的注解保留到这一阶段。
这里写图片描述
3、@Document与@Inherited
这两个注解就比较简单了,@Document说明将此注解包含在Javadoc中,而@Inherited则表示,该注解可以被子类继承。
上述的介绍可能会有些抽象,接下来我我们就根据实例,利用反射机制来操作相应类型的自定义注解。

二、类型注解:@Target(ElementType.TYPE)
接下来,我们来看一下类型注解的创建与使用。下方内容我们下创建一个修饰类型的注解,然后再相关类中添加上该注解的修饰,最后使用Java的反射机制来获取相应的注解信息。
1、创建注解
首先创建我们的注解,具体步骤如下所示,选择Annotation后键入注解名点击回车即可。
这里写图片描述
下方代码段就是所创建注解中的详细内容。我们可以看出@Target元注解的参数是ElementType.TYPE类型的。也就是说明我们创建的这个注解是修饰类型的注解,可以作用域类、接口、枚举等类型。然后我们还看到@Retention的参数是RetentionPolicy.RUNTIME类型的,说明该注解一直被保留到运行时。
注解是使用@Interface来声明的,这与接口的什么类似。@Interface后方跟着的就是注解的名称,本注解的名称为CETypeAnnotation。其中有一个公有的(public)整数(int)类型的id属性。该属性的默认值是0,具体如下所示。
这里写图片描述
2、注解的使用
下方代码段是对上述注解的使用。因为上述创建的注解是ElementType.TYPE类型的,所以我们就用该注解来修饰我们创建的一个类,也就是下方的TestClass。在注解修饰时,我们给id设置了一个值,也就是下方的id = 10。
这里写图片描述
四、其他类型的注解
上述我们详细的聊了ElementType.TYPE类型的注解,接下来我们来看一下其他类型的注解,以及这些注解的使用方式。
1、@Target(ElementType.CONSTRUCTOR)
接下来我们来创建一个修饰构造器的注解。下方的CEConstructorAnnotation就是我们创建的用来修饰类构造器的注解。其中的value字段的默认值是一个空字符串。
这里写图片描述
2、@Target(ElementType.FIELD)
接下来我们就来创建一个修饰字段的注解,我们将该字段命名为CEFieldAnnotation,具体代码如下所示:
这里写图片描述
3、@Target(ElementType.METHOD)
下方是我们创建的修饰方法的注解,我们将其命名为CEMethodAnnotation,具体代码如下所示。
这里写图片描述

4、@Target(ElementType.PARAMETER)
下方是修饰方法中参数的注解,我们将其命名为,如下所示:
这里写图片描述
五、上述相关注解的使用
下方就是上述所定义的各种类型的注解的使用方式,各司其职。具体就不做过多赘述了。
这里写图片描述
六、使用反射机制获取不同类型的注解信息
之前我们已经聊了如何使用“Java”的反射机制来获取相关注解的信息,下方我们将会分别获取上述各种类型的注解的相关信息。下方代码主要是AnnotationTracker中的相关代码。
这里写图片描述

2、获取修饰方法和方法参数的注解信息
这里写图片描述
3、获取修饰字段的注解信息
这里写图片描述

基础部分讲完了, 再来做个具体的应用场景试试手。做一个数据查询的报表。下面的例子中主要关注的是注解的应用部分, jdbc封装,excel导出等基础工具的部分就都略过了。关键处是思考下怎么样能最快速的生成一个报表
报表功能很简单。
这里写图片描述
数据来源于一张表
这里写图片描述
然后,为了最快的定义出这个报表的结构。首先定义好业务的Bean.

public class DW_IDCODE_INVEST {
	@Column(PhoenixColumn="ROWKEY")
	private String rowkey;
	@Column(PhoenixColumn="YM",ExcelHeader="日期")
	private String ym;
	@Column(PhoenixColumn="PROVINCE",ExcelHeader="省份")
	private String province;
	@Column(PhoenixColumn="USER_CNT",ExcelHeader="投资人数")
	private String user_cnt;
	@Column(PhoenixColumn="AMT",ExcelHeader="投资额")
	private String amt;
	@Column(PhoenixColumn="CNT",ExcelHeader="投资笔数")
	private String cnt;

setter.....
getter.....
}

这个Bean一定义出来, 后面要干什么就很清楚了。这个Bean定义清楚了每个属性对应的数据库中的结果列,同时定义了对应的报表列头。下面处理好将这个Bean处理成一个完整的报表后,下次再要开发新的报表,只需要增加Bean就行。
为此, 首先需要定义注解

@Target(ElementType.FIELD)  
@Retention(RetentionPolicy.RUNTIME)  
public @interface Column {
	public String PhoenixColumn() default "";
	public String ExcelHeader() default "";
}

以下是Controller部分的例子。简单粗暴,相信大家一看就知道,重点只关注一下关于注解的部分。

@Controller
@RequestMapping(value = "PlatProDataAction")
public class PlatProDataAction {
	 @Autowired
	 private PlatProDataDao dao;//下面会有关键代码
	 @ResponseBody
	 @RequestMapping(value = "/getData.do",produces = "application/json;charset=UTF-8", method={RequestMethod.POST })  
	 public void getData(HttpServletRequest request,HttpServletResponse response,PageBean<DW_IDCODE_INVEST> pageBean) throws Exception{
		  //设置数据
		 String sql = "select * from DW_IDCODE_INVEST where YM =?";
		  //查询数据库,并根据PhoenixColumn属性,将结果映射成Bean
		  List<DW_IDCODE_INVEST> res4Report = dao.find(sql,new Object[]{pageBean.starttime});
    String[] columnNames = new String[fields.length];//报表的表投
		  List<String[]> infoList=new ArrayList<String[]>();//报表的数据
		  //字段名称
		  Field[] fields  = DW_IDCODE_INVEST.class.getDeclaredFields();
		  for(DW_IDCODE_INVEST dw:res4Report){
			  String[] info = new String[fields.length];	
			  for(int i = 0; i < fields.length; i ++){
			  		Field field = fields[i];
			  		//这里是关键 添加数据表头
			  		Column colAnno = field.getAnnotation(Column.class);
			  		if(colAnno != null){
			  			columnNames[i] = colAnno.ExcelHeader();
			  		}else{
						columnNames[i] = colAnno.ExcelHeader();
					}
			  		//调用getter
			  		String getterName = "get"+field.getName().substring(0, 1).toUpperCase()+field.getName().substring(1);
					//属性设置成了private,读不出来。需要调用getter
			  		//info[i] = field.get(dw).toString();
			  		info[i] = DW_IDCODE_INVEST.class.getMethod(getterName).invoke(dw).toString();
			  	}
			  infoList.add(info);
		  }
   Map<String,Object> res = new HashMap<String,Object>();
		 res.put("tableHeader",columnNames );
    res.put("tableData",infoList);
    return res;
	 }
}

然后,再看看查询数据的那一部分: dao.find(sql,new Object[] paras)方法
首先是继承自一个通用的父类。主要是可以用来指定Bean的类型。

@Repository
public class PlatProDataDao extends PhoenixTemplate<DW_IDCODE_INVEST>{

}

然后看看这个模板类,通过注释,将数据库查出来的结果映射成Bean

public class PhoenixTemplate<T> {
 private Class<?> entityClass; //映射类T
	private Field[] fields; //保存映射类T的Fields
	
	public PhoenixTemplate(){
  //获取泛型T
		ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
  //封装泛型T的Class
		entityClass = (Class<T>) type.getActualTypeArguments()[0];
   //封装泛型T的属性
		fields = entityClass.getDeclaredFields();
	}

@Autowired
private PhoenixCommonDao pCommonDao;//封装标准的JDBC查询

public List<T> find(String sql,Object[] args) throws Exception{
  //保存查询的结果
		List<T> res = new ArrayList<T>();
		//标准JDBC查询
		List<Map<String, Object>> queryRes = pCommonDao.query(sql,args);
		//返回对象列表
		for(Map<String,Object> queryData : queryRes){
			T t=(T)entityClass.newInstance();
			for(Field field:fields){
   //循环获取各个field
           	 String fieldName=field.getName();
   //获取field上面的@Column注解
           	 Column annotation = field.getAnnotation(Column.class);
           	 if(annotation!=null){
     //获取@Column注解中的  PhonenixColumn 属性
           		 fieldName=annotation.PhoenixColumn();
           	 }
   //设置属性可见
           	 field.setAccessible(true);	
    //获取查询结果中 PhoenixColumn 属性对应列的值
           	 String value=queryData.containsKey(fieldName)?FilterUtil.nullFilter((String)queryData.get(fieldName)):"";
           	 field.set(t, value);
            }
			res.add(t);
		}
		return res;
	}
}

这样,就通过Controller完成了从数据库到报表的完整映射。剩下的事情,就是页面通过AJAX请求Controller,获取到报表的表头 tableHeader 以及报表的数据tableData,然后去进行报表展示了。
比如像这样,用AngularJS的一种实现
js代码:

var loadinfo=function(){
	//查询前情况page
	pageBean.page=null;
	//设置查询时间
	pageBean.starttime=$("#starttime").val();
	var config = {params:pageBean};
	//数据查询
	$http.get("PlatProDataAction/getData.do",config)
    .success(function(response) {
  	   $scope.tableHeader =response.tableHeader ;//数据集合
  	   $scope.tableData =response.tableData;//开始时间
     });
  }

页面处理

<div class="table-responsive">
	<table class="table table-striped">
		<thead>
			<tr>
				<th>#</th>
				<th ng-repeat="header in tableHeader track by $index" style="word-break: keep-all;white-space:nowrap;">{{ header }}</th>
			</tr>
		</thead>	
		<tbody>	
			<tr ng-repeat="rowdata in tableData">
			    <td>{{ $index + 1 }}</td>
			    <td>{{ rowdata .ym }}</td>
				<td>{{ rowdata .province }}</td>
				<td>{{ rowdata .user_cnt }}</td>
				<td>{{ rowdata .amt }}</td>
				<td>{{ rowdata .cnt }}</td>
			</tr>
		</tbody>
	</table>
</div> 

关键代码贴出来了, 然后就是功能的思考了。
核心是通过对我们定义的JavaBean DW_IDCODE_INVEST 添加注解,完成从数据库到报表的展现。
经过我们的封装之后,再做一个新的报表,定义一个新的JavaBean就行了,一个报表框架的雏形,是不是就出来了?
当然,这其中还有些要做的工作。
一、把SQL也通过注解配置到JavaBean中去。
@RenderSQL(sql=“select * from DW_IDCODE_INVEST where YM =?”,paras="{YM}")
public class DW_IDCODE_INVEST {
。。。。。
}
二、在页面和Controller端, 做一下处理,将对应的Bean作为参数来指定。
这样,再出一个新的报表,就不需要再复制 页面 和 Controller 了。

怎么样?有没有人愿意一起来封装一个框架?

后续补充:

这一设想目前已经实现在了自己的GenUI项目中,实现的效果是将这一段逻辑固定下来,只需要配置带注解的javaBean就能快速生成一个报表数据。而最终,这个javaBean改为通过jdbc读取表数据,由freemarker自动生成。
最终的效果是只要在数据库里建好表,然后配置一个生成计划,里面指定数据库里的表,就可以自动生成实现一个报表页面所需要的从前台页面到后台功能的所有代码。该页面还带有excel导出,以及完善的权限管理机制可以管理到页面控件级别。而生成的查询SQL,留有一定的扩展。excel导入功能,除了模版不太好生成,其他部分要生成也不是很难。而再加上GenUI中引入了图形化报表开发工具,反正最终目标就是为了偷懒,一分钟开发一个管理页面不再是个麻烦事。
在很多大型应用中,UI管理只是其中很不愿花功夫但又很重要的一小块,用GenUI快速搭建,还是能省不少事情的。有兴趣的可以到码云了解下。https://gitee.com/tearwind/GenUI

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

roykingw

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值