美文网首页GO与微服务
手撸golang 仿spring ioc/aop 之8 扫码3

手撸golang 仿spring ioc/aop 之8 扫码3

作者: 老罗话编程 | 来源:发表于2021-04-16 21:55 被阅读0次

    手撸golang 仿spring ioc/aop 之8 扫码3

    缘起

    最近阅读 [Spring Boot技术内幕: 架构设计与实现原理] (朱智胜 , 2020.6)
    本系列笔记拟采用golang练习之
    Talk is cheap, show me the code.

    Spring

    Spring的主要特性:
    1. 控制反转(Inversion of Control, IoC)
    2. 面向容器
    3. 面向切面(AspectOriented Programming, AOP)
    
    源码gitee地址:
    https://gitee.com/ioly/learning.gooop
    
    原文链接:
    https://my.oschina.net/ioly
    

    目标

    • 参考spring boot常用注解,使用golang编写“基于注解的静态代码增强器/生成器”

    子目标(Day 7)

    • 因为struct/field/method的扫描是关键,因此今天针对这块做了单元测试
      • common/Tokens.go:修复MatchBasicType方法的正则匹配bug。其实func类型的DataType也没考虑到,但现在暂时可以用type alias规避,先不追求完美吧。
      • scanner/IStructScanner.go: 修复若干细节, 并添加返回类型的扫描
      • scanner/IStructScanner_test.go:struct扫描器的单元测试

    common/Tokens.go

    修复MatchBasicType方法的正则匹配bug。其实func类型的DataType也没考虑到,但现在暂时可以用type alias规避,先不追求完美吧。

    func (me *tTokens) MatchBasicType(s string) (bool, string) {
        list := []string{
            "int",
            "string",
            "bool",
            "byte",
            "int32",
            "int64",
            "uint32",
            "uint64",
            "float32",
            "float64",
            "int8",
            "uint8",
            "int16",
            "uint16",
            `time\.Time`,
        }
    
        for _, it := range list {
            if ok, t := me.MatchRegexp(s, "^"+it+`(\s+|$)`); ok {
                return true, strings.TrimSpace(t)
            }
        }
    
        return false, ""
    }
    

    scanner/IStructScanner.go

    修复若干细节, 并添加返回类型的扫描

    package scanner
    
    import (
        "errors"
        "learning/gooop/spring/autogen/common"
        "learning/gooop/spring/autogen/domain"
        "regexp"
        "strings"
    )
    
    type IStructScanner interface {
        ScanStruct(file *domain.CodeFileInfo)
    }
    
    type tStructScanner int
    
    func (me *tStructScanner) ScanStruct(file *domain.CodeFileInfo) {
        bInStruct := false
        vStructs := []*domain.StructInfo{nil}
        for lineNO, line := range file.CleanLines {
            if bInStruct {
                // end?
                if gStructEndRegexp.MatchString(line) {
                    me.scanMethod(vStructs[0], lineNO+1)
                    file.AppendStruct(vStructs[0])
    
                    bInStruct = false
                    vStructs[0] = nil
                    continue
                }
    
                // in struct block
                ok, fname, ftype := me.scanField(line)
                if ok {
                    vStructs[0].AppendField(lineNO, fname, ftype)
                }
    
            } else {
                // not in struck block
                // matching start?
                if gStructStartRegexp.MatchString(line) {
                    bInStruct = true
                    ss := gStructStartRegexp.FindStringSubmatch(line)
    
                    vStructs[0] = domain.NewStructInfo()
                    vStructs[0].LineNO = lineNO
                    vStructs[0].CodeFile = file
                    vStructs[0].Name = ss[1]
                    continue
                }
            }
        }
    }
    
    func (me *tStructScanner) scanField(line string) (ok bool, fldName string, fldType string) {
        if !gFieldStartRegexp.MatchString(line) {
            return false, "", ""
        }
    
        t := line
        s1 := gFieldStartRegexp.FindString(t)
        fldName = strings.TrimSpace(s1)
    
        t = t[len(s1):]
        b2, s2 := common.Tokens.MatchDataType(t)
        if !b2 {
            return false, "", ""
        }
        fldType = strings.TrimSpace(s2)
    
        return true, fldName, fldType
    }
    
    func (me *tStructScanner) scanMethod(stru *domain.StructInfo, fromLineNO int) {
        for i, limit := fromLineNO, len(stru.CodeFile.CleanLines); i < limit; i++ {
            line := stru.CodeFile.CleanLines[i]
            if !gMethodStartRegex.MatchString(line) {
                continue
            }
    
            ss := gMethodStartRegex.FindStringSubmatch(line)
    
            // declare
            declare := ss[0]
            offset := len(declare)
    
            // receiver
            receiver := ss[1]
            if receiver != stru.Name {
                continue
            }
            method := domain.NewMethodInfo()
    
            // name
            method.Name = ss[2]
    
            // method input args
            e, args := me.scanMethodArgs(method, strings.TrimSpace(line[offset:]))
            if e != nil {
                panic(e)
            }
            offset += len(args)
    
            // method return args
            e = me.scanReturnArgs(method, strings.TrimSpace(line[offset:]))
            if e != nil {
                panic(e)
            }
    
            // end scan method
            stru.AppendMethod(method)
        }
    }
    
    func (me *tStructScanner) scanMethodArgs(method *domain.MethodInfo, s string) (error, string) {
        t := s
        offset := 0
        for {
            // name
            b1, s1 := common.Tokens.MatchRegexp(t, `^\w+(\s*,\s*\w+)?\s+`)
            if !b1 {
                break
            }
            argNames := strings.TrimSpace(s1)
            offset += len(s1)
            t = s[offset:]
    
            // data type
            b2, s2 := common.Tokens.MatchDataType(t)
            if !b2 {
                return gInvalidMethodArgs, ""
            }
            argDataType := s2
            offset += len(s2)
            t = s[offset:]
    
            for _, it := range strings.Split(argNames, ",") {
                method.AppendArgument(it, argDataType)
            }
    
            // ,\s+
            b3, s3 := common.Tokens.MatchRegexp(t, `\s*,\s*`)
            if !b3 {
                break
            }
            offset += len(s3)
            t = s[offset:]
        }
    
        b4, s4 := common.Tokens.MatchRegexp(t, `^\s*\)`)
        if !b4 {
            return errors.New("expecting right bracket"), ""
        }
        offset += len(s4)
    
        return nil, s[0:offset]
    }
    
    func (me *tStructScanner) scanReturnArgs(method *domain.MethodInfo, s string) error {
        // no args?
        if gMethodEndRegexp.MatchString(s) {
            return nil
        }
    
        // args start
        t := s
        b1, s1 := common.Tokens.MatchRegexp(t, `\s*\(\s*`)
        if !b1 {
            return errors.New("expecting left bracket")
        }
        t = t[len(s1):]
    
        // unnamed args?
        b2, s2 := common.Tokens.MatchDataType(t)
        if b2 {
            t = t[len(s2):]
            method.AppendUnnamedReturn(s2)
    
            // more unnamed args?
            for {
                b3, s3 := common.Tokens.MatchRegexp(t, `^\s*,\s*`)
                if !b3 {
                    break
                }
                t = t[len(s3):]
    
                b4, s4 := common.Tokens.MatchDataType(t)
                if !b4 {
                    return errors.New("expecting data type")
                }
                t = t[len(s4):]
                method.AppendUnnamedReturn(s4)
            }
        } else {
            // named args?
            for {
                // name
                b3, s3 := common.Tokens.MatchIdentifier(t)
                if !b3 {
                    return errors.New("expecting identifier")
                }
                t = t[len(s3):]
    
                // \s+
                b4, s4 := common.Tokens.MatchSpaces(t)
                if !b4 {
                    return errors.New("expecting spaces")
                }
                t = t[len(s4):]
    
                // type
                b5, s5 := common.Tokens.MatchDataType(t)
                if !b5 {
                    return errors.New("expecting data type")
                }
                t = t[len(s5):]
    
                // more?
                b6, s6 := common.Tokens.MatchRegexp(t, `^\s*,\s*`)
                if b6 {
                    // yes more
                    t = t[len(s6):]
                } else {
                    // no more
                    break
                }
            }
        }
    
        // arguments end
        b7, _ := common.Tokens.MatchRegexp(t, `^\s*\)\s*`)
        if !b7 {
            return errors.New("expecting end of arguments")
        }
    
        return nil
    }
    
    var gStructStartRegexp = regexp.MustCompile(`^\s*type\s+(\w+)\s+struct\s+\{`)
    var gStructEndRegexp = regexp.MustCompile(`^\s*}`)
    var gFieldStartRegexp = regexp.MustCompile(`^\s*\w+\s+`)
    var gMethodStartRegex = regexp.MustCompile(`^\s*func\s+\(\s*\w+\s+\*?(\w+)\s*\)\s+(\w+)\s*\(`)
    var gInvalidMethodArgs = errors.New("invalid method arguments")
    var gMethodEndRegexp = regexp.MustCompile(`^\s*\{`)
    
    var DefaultStructScanner IStructScanner = new(tStructScanner)
    

    scanner/IStructScanner_test.go

    struct扫描器的单元测试

    package scanner
    
    import (
        "encoding/json"
        "learning/gooop/spring/autogen/domain"
        "strings"
        "testing"
    )
    
    func Test_StructScan(t *testing.T) {
        s := `type _mystruct struct {`
        t.Log(gStructStartRegexp.MatchString(s))
    
        code := `
    type StructInfo struct {
        LineNO      int
        Name        string
        CodeFile    *CodeFileInfo
        Fields      []*FieldInfo
        Methods     []*MethodInfo
        Annotations []*AnnotationInfo
    }
    
    func NewStructInfo() *StructInfo {
        it := new(StructInfo)
        it.Fields = []*FieldInfo{}
        it.Methods = []*MethodInfo{}
        it.Annotations = []*AnnotationInfo{}
        return it
    }
    
    func (me *StructInfo) AppendField(lineNO int, name string, dataType string) {
        fld := NewFieldInfo()
        fld.Struct = me
        fld.LineNO = lineNO
        fld.Name = name
        fld.DataType = dataType
        me.Fields = append(me.Fields, fld)
    }
    
    func (me *StructInfo) AppendMethod(method *MethodInfo) {
        me.Methods = append(me.Methods, method)
    }
    
    func (me *StructInfo) AppendAnnotation(ant *AnnotationInfo) {
        me.Annotations = append(me.Annotations, ant)
    }`
        file := domain.NewCodeFileInfo()
        file.CleanLines = strings.Split(code, "\n")
    
        DefaultStructScanner.ScanStruct(file)
    
        file.CleanLines = nil
        j, e := json.MarshalIndent(file.Structs, "", "  ")
        if e != nil {
            panic(e)
        }
        t.Log(string(j))
    }
    

    测试输出

    API server listening at: [::]:36077
    === RUN   Test_StructScan
        IStructScanner_test.go:12: true
        IStructScanner_test.go:58: [
              {
                "LineNO": 1,
                "Name": "StructInfo",
                "Fields": [
                  {
                    "LineNO": 2,
                    "Name": "LineNO",
                    "DataType": "int",
                    "Annotations": []
                  },
                  {
                    "LineNO": 3,
                    "Name": "Name",
                    "DataType": "string",
                    "Annotations": []
                  },
                  {
                    "LineNO": 4,
                    "Name": "CodeFile",
                    "DataType": "*CodeFileInfo",
                    "Annotations": []
                  },
                  {
                    "LineNO": 5,
                    "Name": "Fields",
                    "DataType": "[]*FieldInfo",
                    "Annotations": []
                  },
                  {
                    "LineNO": 6,
                    "Name": "Methods",
                    "DataType": "[]*MethodInfo",
                    "Annotations": []
                  },
                  {
                    "LineNO": 7,
                    "Name": "Annotations",
                    "DataType": "[]*AnnotationInfo",
                    "Annotations": []
                  }
                ],
                "Methods": [
                  {
                    "LineNO": 0,
                    "Name": "AppendField",
                    "Arguments": [
                      {
                        "Name": "lineNO",
                        "DataType": "int"
                      },
                      {
                        "Name": "name",
                        "DataType": "string"
                      },
                      {
                        "Name": "dataType",
                        "DataType": "string"
                      }
                    ],
                    "Annotations": [],
                    "Returns": []
                  },
                  {
                    "LineNO": 0,
                    "Name": "AppendMethod",
                    "Arguments": [
                      {
                        "Name": "method",
                        "DataType": "*MethodInfo"
                      }
                    ],
                    "Annotations": [],
                    "Returns": []
                  },
                  {
                    "LineNO": 0,
                    "Name": "AppendAnnotation",
                    "Arguments": [
                      {
                        "Name": "ant",
                        "DataType": "*AnnotationInfo"
                      }
                    ],
                    "Annotations": [],
                    "Returns": []
                  }
                ],
                "Annotations": []
              }
            ]
    --- PASS: Test_StructScan (0.01s)
    PASS
    
    Debugger finished with exit code 0
    

    (未完待续)

    相关文章

      网友评论

        本文标题:手撸golang 仿spring ioc/aop 之8 扫码3

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