从lombok想到的行号问题


声明:本文转载自https://my.oschina.net/qixiaobo025/blog/1588584,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

背景

lombok 是近几年来声名鹊起的java效率提升利器,对于lombok一直只是在某些开源项目中可以看到。在自身的开发中并未使用。在github上确实使用者还比较可观

主要一直认为存在如下问题

  1. 自动生成不够直观
  2. 必须IDE支持
  3. 行号对不上

一直认为行号对不上是最大的问题===》but错误的认知【想要认识到自己的错误还是要花点时间的~】

国外也有小伙伴表达了同样的担忧 https://stackoverflow.com/questions/37908097/line-numbers-generation-with-lombok

解释

首先了解lombok的原理

原理

自从Java 6起,javac就支持“JSR 269 Pluggable Annotation Processing API”规范,只要程序实现了该API,就能在javac运行的时候得到调用。
举例来说,现在有一个实现了"JSR 269 API"的程序A,那么使用javac编译源码的时候具体流程如下:
1)javac对源代码进行分析,生成一棵抽象语法树(AST)
2)运行过程中调用实现了"JSR 269 API"的A程序
3)此时A程序就可以完成它自己的逻辑,包括修改第一步骤得到的抽象语法树(AST)
4)javac使用修改后的抽象语法树(AST)生成字节码文件
详细的流程图如下:
lombok本质上就是这样的一个实现了"JSR 269 API"的程序。在使用javac的过程中,它产生作用的具体流程如下:
1)javac对源代码进行分析,生成一棵抽象语法树(AST)
2)运行过程中调用实现了"JSR 269 API"的lombok程序
3)此时lombok就对第一步骤得到的AST进行处理,找到@Data注解所在类对应的语法树(AST),然后修改该语法树(AST),增加getter和setter方法定义的相应树节点
4)javac使用修改后的抽象语法树(AST)生成字节码文件

参考 http://blog.csdn.net/dslztx/article/details/46715803

语法糖之switch

首先针对该问题思考,这是在Javac做的事情 换言之 可以理解成javac的代码生成器 这个在每次java版本升级时都会有同样的changelog 通常我们称之为语法糖!

比如

  1. foreach迭代
  2. autoClose
  3. switch String

我们以switch举例

大家知道在java之前是不支持String的switch 但是支持int enum等。

那么设想我们使用String要如何处理呢?

我们看一下编译出来的class中如何描述

private String handlerObjectName(TsStockOut tsStockOut, String idOwnOrg) {     String var4 = tsStockOut.getBillType();     byte var5 = -1;     switch(var4.hashCode()) {     case 2269:         if (var4.equals("GD")) {             var5 = 3;         }         break;     case 66672:         if (var4.equals("CGT")) {             var5 = 0;         }         break;     case 67493:         if (var4.equals("DCD")) {             var5 = 7;         }         break;     case 69418:         if (var4.equals("FCG")) {             var5 = 6;         }         break;     case 75584:         if (var4.equals("LPD")) {             var5 = 4;         }         break;     case 75677:         if (var4.equals("LSD")) {             var5 = 5;         }         break;     case 79273:         if (var4.equals("PKD")) {             var5 = 1;         }         break;     case 79707:         if (var4.equals("PYD")) {             var5 = 2;         }     }       String ObjName;     switch(var5) {     case 0:         String idStockReturn = tsStockOut.getIdSourceBill();         TsStockReturn tsStockReturn = this.stockReturnService.getStockReturnById(idStockReturn, idOwnOrg);         ObjName = tsStockReturn.getSupplierName();         break;     case 1:         ObjName = "盘亏";         break;     case 2:         ObjName = "盘盈";         break;     case 3:     case 4:     case 5:         TsMaintainVO maintainVO = this.maintainService.selectMaintainById(tsStockOut.getIdSourceBill());         if (maintainVO == null) {             throw new BussinessException("源单已经不存在!" + tsStockOut.getIdSourceBill());         }           if (StringUtils.isBlank(maintainVO.getCarNoWhole())) {             ObjName = maintainVO.getNaCustomer();         } else {             ObjName = maintainVO.getNaCustomer() + "【" + maintainVO.getCarNoWhole() + "】";         }         break;     case 6:         String purchaseId = tsStockOut.getIdSourceBill();         TsPurchase tsPurchase = this.purchaseService.getPurchaseById(purchaseId, idOwnOrg);         if (tsPurchase == null) {             throw new BussinessException("源采购单已经不存在!" + tsStockOut.getIdSourceBill());         }           ObjName = tsPurchase.getSupplierName();         break;     case 7:         AllotVo allotVo = this.allotService.getAllotInfoIn(tsStockOut.getIdSourceBill());         CustomerCarVO customerCarVO = this.customerCarService.queryCustomerByIdOrgSource(allotVo.getIdOrgIn(), allotVo.getIdOrgOut());         CustomerCarVO customer = this.customerCarService.getCustomerById(customerCarVO.getIdCustomer(), allotVo.getIdOrgOut());         ObjName = customer.getName();         break;     default:         ObjName = StringUtils.isBlank(tsStockOut.getObjectName()) ? "手工" : tsStockOut.getObjectName();     }       return ObjName; }

 

而在源码中如何书写的呢?

private String handlerObjectName(TsStockOut tsStockOut, String idOwnOrg) {    String ObjName;        switch (tsStockOut.getBillType()) {            case AppStatus.CGT:                String idStockReturn = tsStockOut.getIdSourceBill();                TsStockReturn tsStockReturn = this.stockReturnService.getStockReturnById(idStockReturn, idOwnOrg);                ObjName = tsStockReturn.getSupplierName();                break;            case AppStatus.PKD:                ObjName = "盘亏";                break;            case AppStatus.PYD:                ObjName = "盘盈";                break;            case AppStatus.GD:            case AppStatus.LPD:            case AppStatus.LSD:                TsMaintainVO maintainVO = this.maintainService.selectMaintainById(tsStockOut.getIdSourceBill());                if (maintainVO == null) {                    throw new BussinessException("源单已经不存在!" + tsStockOut.getIdSourceBill());                }                if(StringUtils.isBlank(maintainVO.getCarNoWhole())){                    ObjName = maintainVO.getNaCustomer();                }else{                    ObjName = maintainVO.getNaCustomer() + "【" + maintainVO.getCarNoWhole() + "】";                }                break;            case AppStatus.FCG:                String purchaseId = tsStockOut.getIdSourceBill();                TsPurchase tsPurchase = this.purchaseService.getPurchaseById(purchaseId, idOwnOrg);                if (tsPurchase == null) {                    throw new BussinessException("源采购单已经不存在!" + tsStockOut.getIdSourceBill());                }                ObjName = tsPurchase.getSupplierName();                break;            case AppStatus.DCD:                AllotVo allotVo = allotService.getAllotInfoIn(tsStockOut.getIdSourceBill());                CustomerCarVO customerCarVO = customerCarService.queryCustomerByIdOrgSource(allotVo.getIdOrgIn(), allotVo.getIdOrgOut());                CustomerCarVO customer = customerCarService.getCustomerById(customerCarVO.getIdCustomer(), allotVo.getIdOrgOut());                ObjName = customer.getName();                break;            default:                ObjName = StringUtils.isBlank(tsStockOut.getObjectName()) ? "手工" : tsStockOut.getObjectName();                break;        }    return ObjName; }

很明显其实仍然是使用int作为switch参数的

万幸hashCode方法返回是int类型!

为了防止出现碰巧出现同样hashcode的String javac还生成了equals方法 防止误判!

从上述可知 我们的代码已经发生了变化

那问题来了!行号咋办?

行号浅析

既然我们在日常工作中能够在异常中获得正常的行号!那么必然存在某个持久化文件用来存储行号!

那么首当其冲的就是class文件。我们知道实质上java运行的是class文件 因此考虑行号其实存储在class文件中

答案是肯定的!

LineNumberTale属性:用于描述Java源码的行号与字节码行号之间的对应关系,非运行时必需属性,会默认生成至Class文件中,可以使用Javac的-g:none或-g:lines关闭或要求生成该项属性信息,其结构如下:

类型

名称

数量

u2

attribute_name_index

1

u4

attribute_length

1

u2

line_number_table_length

1

line_number_info

line_number_table

line_number_table_length

line_number_table是一组line_number_info类型数据的集合,其所包含的line_number_info类型数据的数量为line_number_table_length,line_number_info结构如下:

类型

名称

数量

说明

u2

start_pc

1

字节码行号

u2

line_number

1

Java源码行号

不生成该属性的最大影响是:1,抛出异常时,堆栈将不会显示出错的行号;2,调试程序时无法按照源码设置断点

 

在class文件中有一块区域用来存储行号,默认情况下使用javac就可以生成 而我们使用maven插件编译

在maven插件中可以配置

/**     * Keyword list to be appended to the <code>-g</code> command-line switch. Legal values are none or a     * comma-separated list of the following keywords: <code>lines</code>, <code>vars</code>, and <code>source</code>.     * If debug level is not specified, by default, nothing will be appended to <code>-g</code>.     * If debug is not turned on, this attribute will be ignored.     *     * @since 2.1     */    @Parameter( property = "maven.compiler.debuglevel" )    private String debuglevel;

我们可以看到maven默认情况下使用 javac -g

-g  Generate all debugging information, including local variables. By default, only line number and source file information is generated.  -g:none  Do not generate any debugging information.  -g:{keyword list}  Generate only some kinds of debugging information, specified by a comma separated list of keywords. Valid keywords are:  sourceSource file debugging informationlinesLine number debugging informationvarsLocal variable debugging information

因此可以判断我们正常使用是行号会由javac写入到class文件中!

因此关于lombok的行号问题白担心那么久!!!

本文发表于2017年12月12日 18:33
(c)注:本文转载自https://my.oschina.net/qixiaobo025/blog/1588584,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 2268 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1