美文网首页程序员
从lombok想到的行号问题

从lombok想到的行号问题

作者: 3c69b7c624d9 | 来源:发表于2017-12-12 22:41 被阅读62次

    背景

    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)生成字节码文件
    详细的流程图如下:

    163305_2hLK_871390.png163305_2hLK_871390.png

    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关闭或要求生成该项属性信息,其结构如下:

    <table>
    <tbody>
    <tr>
    <td> <p>类型</p> </td>
    <td> <p>名称</p> </td>
    <td> <p>数量</p> </td>
    </tr>
    <tr>
    <td> <p>u2</p> </td>
    <td> <p>attribute_name_index</p> </td>
    <td> <p>1</p> </td>
    </tr>
    <tr>
    <td> <p>u4</p> </td>
    <td> <p>attribute_length</p> </td>
    <td> <p>1</p> </td>
    </tr>
    <tr>
    <td> <p>u2</p> </td>
    <td> <p>line_number_table_length</p> </td>
    <td> <p>1</p> </td>
    </tr>
    <tr>
    <td> <p>line_number_info</p> </td>
    <td> <p>line_number_table</p> </td>
    <td> <p>line_number_table_length</p> </td>
    </tr>
    </tbody>
    </table>

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

    <table>
    <tbody>
    <tr>
    <td> <p>类型</p> </td>
    <td> <p>名称</p> </td>
    <td> <p>数量</p> </td>
    <td> <p>说明</p> </td>
    </tr>
    <tr>
    <td> <p>u2</p> </td>
    <td> <p>start_pc</p> </td>
    <td> <p>1</p> </td>
    <td> <p>字节码行号</p> </td>
    </tr>
    <tr>
    <td> <p>u2</p> </td>
    <td> <p>line_number</p> </td>
    <td> <p>1</p> </td>
    <td> <p>Java源码行号</p> </td>
    </tr>
    </tbody>
    </table>

    不生成该属性的最大影响是: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的行号问题白担心那么久!!!

    相关文章

      网友评论

        本文标题:从lombok想到的行号问题

        本文链接:https://www.haomeiwen.com/subject/hssoixtx.html