Web开发学习13聊聊java反射
很喜欢一本叫《走出软件作坊》的书,其中有一句话让我较为深刻,“架构师,客户和程序员之间走钢丝的人”,一个优秀的程序员过单行线都会左右都看看,更何况一个架构师肯定是需要非常谨慎的。那怎么样才能做到谨慎呢?在我看来对于架构师的谨慎其实就是一句话:不要让程序员写原本不应该是他写的代码,这句话说的好像有点抽象,但其实很好理解,就是尽可能让程序员的代码变的少,这样不但开发起来效率快,而且review代码以及排查bug都是很方便的,那怎么样才能让程序员写的代码变少呢?今天我想借java的反射来举几个例子,通过反射封装让编码变的更简单,当然这肯定不是唯一的办法。
1.excel文件导出
这个封装是在我工作两年的时候自己想的,当时真的被逼的没办法,十几个模块都需要添加excel导出功能,当做出来以后确实被自己感动了,同时感觉封装的重要性。首先要创建一个注解类
/** * * @author sdh 2012-7-26 */ @Target(value={java.lang.annotation.ElementType.METHOD,java.lang.annotation.ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface Export{ /** * @return 表头名称 */ String header() default " "; /** * @return 排序 */ int sort(); /** * 设定列宽 * @return */ int columnWidth() default 100; /** * 状态转换 * @return */ String stateStr() default ""; }
header标志列头名称,sort标志排第几列,columnWidth标志列宽,stateStr是状态转换,当数据库是int类型转成中文时用,然后在需要导出的实体类字段中按需求添加注解配置
public class Coupon implements IBasicBean { private static final long serialVersionUID = -8233437005461628357L; private Long id; @Export(header="业务类型",sort=0,stateStr="1:印刷品,2:DM,3:视频") private Integer businessType; @Export(header="产品编号",sort=1) private Integer productId; @Export(header="优惠券编号",sort=2) private String couponNum; @Export(header="优惠券名称",sort=3) private String couponName; @Export(header="优惠券价格",sort=4) private Float couponPrice; }
看封装类投代码
public class ExcelExportUtils<T> { public ExcelExportUtils() { }
用泛型类来配合反射是常用的手段,只需在调用时传递操作类型即可在封装类中反射出来了,调用方传递操作类型和该操作类型的数据集,封装类通过反射获取字段上的注解后去数据集中获取该字段的值即可完成导出逻辑,篇幅关系这里不做详细代码描述了
2.hibernate基类
这个方法我是看开涛的《跟我学springmvc》看到的,感觉还有点用,在基类中定义好每个数据库访问类对应的基本查询语句,方便后续开发不用重复写,看代码
public class HibernateDaoImpl<T extends BaseEntity> extends HibernateDaoSupport implements HibernateDao<T>{ protected final String COUNT_HQL = "select count(id) "; protected final String LIST_HQL; protected final String TABLE_NAME; /** * 数据实体类型 */ protected Class<T> entityClass; /** * 默认构造函数 */ @SuppressWarnings("unchecked") public HibernateDaoImpl() { Type superClassType = getClass().getGenericSuperclass(); if (superClassType instanceof ParameterizedType) { Type[] paramTypes = ((ParameterizedType) superClassType) .getActualTypeArguments(); this.entityClass = (Class<T>) paramTypes[0]; } TABLE_NAME = this.entityClass.getSimpleName(); LIST_HQL = "from " + this.entityClass.getSimpleName() + " where 1=1"; }
通过getClass().getGenericSuperclass()反射在父类的构造中获取到了子类传递的泛型
3.springmvc全局参数验证
参数为空的情况可能导致程序奔溃所以需要在程序中强制验证,但是文本参数的长度如果校验就显得有点累赘了,所以我通过反射封装了一个全局参数长度校验,在拦截器中插入即可
//对请求参数做校验(根据自定义验证注解) public boolean paramsValidate(BaseControler handler,HttpServletRequest request) throws Exception{ //获取当前controll的所有方法 Method[] m = handler.getClass().getDeclaredMethods(); //遍历 for (int i = 0; i < m.length; i++) { //获取RequestMapping注解 RequestMapping an = m[i].getAnnotation(RequestMapping.class); //如果注解不是空的 if(an != null){ //把注解的值和当前请求的url做对比 String[] vs = an.value(); if(vs != null && vs.length > 0){ if(request.getRequestURI().endsWith(vs[0])){ //对比一致 获取当前方法的参数列表 @SuppressWarnings("rawtypes") Class[] c = m[i].getParameterTypes(); for (int j = 0; j < c.length; j++) { //如果当前参数是basebean的子类视为载体 if(c[j].getSuperclass() != null && c[j].getSuperclass().equals(BaseBean.class)){ Map<String, String[]> map = request.getParameterMap(); for(String key : map.keySet()){ //只对string类型的参数做校验 Field f = c[j].getDeclaredField(key); //获取到当前字段的validation注解 MyValidation valid = f.getAnnotation(MyValidation.class); String v1 = StringUtil.array2String(map.get(key)); if(StringUtil.isEmpty(v1)){ return true; } if(valid != null){ //根据配置来验证参数 int maxLen = valid.maxLen(); if(v1.length() > maxLen){ throw new Exception(f.getName() + " not accord with maxlenth"); } String pattern = valid.pattern(); if(!StringUtil.isEmpty(pattern)){ Pattern p = Pattern.compile(pattern); Matcher matcher = p.matcher(v1); if(!matcher.matches()){ throw new Exception(f.getName() + " not accord with pattern"); } } }else{ //按照默认的长度控制 if(v1.length() > IConstans.PARAM_MAX_LEN){ throw new Exception(f.getName() + " not accord with maxlenth"); } } } } } } } } } return true; }
这个封装的关键点就在拦截器中操作类没人传递无法反射,但是拦截器中有当前访问的controller类和访问地址,所以我的思路是通过访问的地址链接来反射到controller类对应的@RequestMapping注解的方法,然后获取到该方法参数列表中类型为baseEntity子类的参数即是操作类,然后通过操作类获取字段及注解验证
4.全局埋点
这个功能我在前面的博客比较详细的说明了,主要还是在拦截器中以注解反射的方式来定位到用户的本次的操作记录到日志
@Override protected String doIntercept(ActionInvocation invocation) throws Exception { String result = null; try { //先执行action方法 result = invocation.invoke(); String enterMethod = invocation.getProxy().getMethod(); //获取action方法上的WebOperateAnno 注解 WebOperateAnno webOperateAnno = invocation.getAction().getClass().getMethod(enterMethod, new Class[0]).getAnnotation(WebOperateAnno.class); Object returnData = invocation.getAction(); writeLog(webOperateAnno,returnData); } catch (Exception e) { sysLogger.error("操作日志记录失败",e); } return result; }
这种例子在程序中还有很多,目的就是做好封装减少程序员的编码量,如果以后面试的时候碰到面试官问你用java反射做了什么,或许你可以借鉴这些例子