手撸golang 仿spring ioc/aop 之9 扫码4

手撸golang 仿spring ioc/aop 之9 扫码4

缘起

最近阅读 [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 9)

  • struct解析清楚了,接着解析注解就比较容易了
    • scanner/IStructScanner.go:修复scanMethod()和scanAnnotation()的细节问题
    • scanner/IAnnotationScanner.go:注解扫描接口及默认实现。注解的属性支持双引号和重音号字符串。
    • scanner/IAnnotationScanner_test.go:针对注解信息的单元测试

scanner/IAnnotationScanner.go

注解扫描接口及默认实现。注解的属性支持双引号和重音号字符串。

package scanner

import (
    "errors"
    "learning/gooop/spring/autogen/common"
    "learning/gooop/spring/autogen/domain"
    "regexp"
    "strings"
)

type IAnnotationScanner interface {
    ScanAnnotations(s *domain.StructInfo)
}

type tAnnotationScanner int

func (me *tAnnotationScanner) ScanAnnotations(s *domain.StructInfo) {
    me.scanStructAnnotation(s)
    me.scanFieldAnnotation(s)
    me.scanMethodAnnotation(s)
}

func (me *tAnnotationScanner) scanStructAnnotation(s *domain.StructInfo) {
    for i := s.LineNO - 1; i >= 0; i-- {
        if !me.matchAnnotation(s, i) {
            break
        }

        code := s.CodeFile.RawLines[i]
        e, a := me.parseAnnotation(code)
        if e != nil {
            panic(e)
        }
        s.AppendAnnotation(a)
    }
}

func (me *tAnnotationScanner) scanFieldAnnotation(s *domain.StructInfo) {
    for _, fld := range s.Fields {
        for i := fld.LineNO - 1; i >= 0; i-- {
            if !me.matchAnnotation(s, i) {
                break
            }

            code := s.CodeFile.RawLines[i]
            e, a := me.parseAnnotation(code)
            if e != nil {
                panic(e)
            }
            fld.AppendAnnotation(a)
        }
    }
}

func (me *tAnnotationScanner) scanMethodAnnotation(s *domain.StructInfo) {
    for _, method := range s.Methods {
        for i := method.LineNO - 1; i >= 0; i-- {
            if !me.matchAnnotation(s, i) {
                break
            }

            code := s.CodeFile.RawLines[i]
            e, a := me.parseAnnotation(code)
            if e != nil {
                panic(e)
            }
            method.AppendAnnotation(a)
        }
    }
}

func (me *tAnnotationScanner) matchAnnotation(s *domain.StructInfo, lineNO int) bool {
    line := s.CodeFile.RawLines[lineNO]
    return gAnnotationStartRegexp.MatchString(line)
}

func (me *tAnnotationScanner) parseAnnotation(line string) (error, *domain.AnnotationInfo) {
    ss := gAnnotationStartRegexp.FindStringSubmatch(line)
    if len(ss) <= 0 {
        return nil, nil
    }
    a := domain.NewAnnotationInfo()

    // name
    declare := ss[0]
    a.Name = ss[1]

    // properties
    t := line[len(declare):]
    for {
        // space*
        b1, s1 := common.Tokens.MatchSpaces(t)
        if b1 {
            t = t[len(s1):]
        }

        // key
        b2, s2 := common.Tokens.MatchIdentifier(t)
        if !b2 {
            break
        }
        t = t[len(s2):]

        // =
        b31, s31 := common.Tokens.MatchSpaces(t)
        if b31 {
            t = t[len(s31):]
        }
        b32 := common.Tokens.MatchString(t, "=")
        if !b32 {
            return errors.New("expecting ="), nil
        } else {
            t = t[1:]
        }
        b33, s33 := common.Tokens.MatchSpaces(t)
        if b33 {
            t = t[len(s33):]
        }

        // value
        b4, s4, i4 := me.parsePropertyValue(t)
        if !b4 {
            return errors.New("expecting attribute value"), nil
        } else {
            t = t[i4:]
            a.AppendAttribute(s2, s4)
        }
    }

    return nil, a
}

func (me *tAnnotationScanner) parsePropertyValue(s string) (bool, string, int) {
    // quoted string by ""
    b2, s2 := common.Tokens.MatchRegexp(s, `^"((\\")|[^"])*"`)
    if b2 {
        return true, me.removeDoubleQuote(s2), len(s2)
    }

    // quoted string by ``
    b3, s3 := common.Tokens.MatchRegexp(s, "^`[^`]+`")
    if b3 {
        return true, s3[1 : len(s3)-1], len(s3)
    }

    // simple string
    b4, s4 := common.Tokens.MatchRegexp(s, `^\S+`)
    if b4 {
        return true, s4, len(s4)
    }

    return false, "", 0
}

func (me *tAnnotationScanner) removeDoubleQuote(s string) string {
    s = s[1 : len(s)-1]
    arrSpecialChars := [][]string{
        {`\r`, "\r"},
        {`\n`, "\n"},
        {`\t`, "\t"},
        {`\"`, "\""},
        {`\\`, "\\"},
        {`\v`, "\v"},
    }

    for _, it := range arrSpecialChars {
        s = strings.ReplaceAll(s, it[0], it[1])
    }
    return s
}

var gAnnotationStartRegexp = regexp.MustCompile(`^//\s*@(\w+)\s*`)

var DefaultAnnotationScanner = new(tAnnotationScanner)

scanner/IAnnotationScanner_test.go

针对注解信息的单元测试

package scanner

import (
    "encoding/json"
    "learning/gooop/spring/autogen/domain"
    "strings"
    "testing"
)

func Test_AnnotationScanner(t *testing.T) {
    code := `
// @RestController path=/order scope=singleton
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
}

// @GetMapping path=/AppendField
func (me *StructInfo) AppendField(lineNO int, name string, dataType string) error {
    fld := NewFieldInfo()
    fld.Struct = me
    fld.LineNO = lineNO
    fld.Name = name
    fld.DataType = dataType
    me.Fields = append(me.Fields, fld)
    return nil
}

// @GetMapping path="/AppendMethod"
func (me *StructInfo) AppendMethod(method *MethodInfo) (error, string) {
    me.Methods = append(me.Methods, method)
    return nil, ""
}

// @PostMapping path=/AppendAnnotation
func (me *StructInfo) AppendAnnotation(ant *AnnotationInfo) (e error, s string) {
    me.Annotations = append(me.Annotations, ant)
    return nil, ""
}`
    file := domain.NewCodeFileInfo()
    file.CleanLines = strings.Split(code, "\n")
    file.RawLines = file.CleanLines

    DefaultStructScanner.ScanStruct(file)
    for _, it := range file.Structs {
        DefaultAnnotationScanner.ScanAnnotations(it)
        j, e := json.MarshalIndent(it, "", "  ")
        if e != nil {
            t.Fatal(e)
        }
        t.Log(string(j))
    }
}

测试输出

API server listening at: [::]:41281
=== RUN   Test_AnnotationScanner
    IAnnotationScanner_test.go:63: {
          "LineNO": 2,
          "Name": "StructInfo",
          "Fields": [
            {
              "LineNO": 3,
              "Name": "LineNO",
              "DataType": "int",
              "Annotations": []
            },
            {
              "LineNO": 4,
              "Name": "Name",
              "DataType": "string",
              "Annotations": []
            },
            {
              "LineNO": 5,
              "Name": "CodeFile",
              "DataType": "*CodeFileInfo",
              "Annotations": []
            },
            {
              "LineNO": 6,
              "Name": "Fields",
              "DataType": "[]*FieldInfo",
              "Annotations": []
            },
            {
              "LineNO": 7,
              "Name": "Methods",
              "DataType": "[]*MethodInfo",
              "Annotations": []
            },
            {
              "LineNO": 8,
              "Name": "Annotations",
              "DataType": "[]*AnnotationInfo",
              "Annotations": []
            }
          ],
          "Methods": [
            {
              "LineNO": 20,
              "Name": "AppendField",
              "Arguments": [
                {
                  "Name": "lineNO",
                  "DataType": "int"
                },
                {
                  "Name": "name",
                  "DataType": "string"
                },
                {
                  "Name": "dataType",
                  "DataType": "string"
                }
              ],
              "Annotations": [
                {
                  "Name": "GetMapping",
                  "Attributes": [
                    {
                      "Key": "path",
                      "Value": "/AppendField"
                    }
                  ]
                }
              ],
              "Returns": [
                {
                  "Name": "",
                  "DataType": "error"
                }
              ]
            },
            {
              "LineNO": 31,
              "Name": "AppendMethod",
              "Arguments": [
                {
                  "Name": "method",
                  "DataType": "*MethodInfo"
                }
              ],
              "Annotations": [
                {
                  "Name": "GetMapping",
                  "Attributes": [
                    {
                      "Key": "path",
                      "Value": "/AppendMethod"
                    }
                  ]
                }
              ],
              "Returns": [
                {
                  "Name": "",
                  "DataType": "error"
                },
                {
                  "Name": "",
                  "DataType": "string"
                }
              ]
            },
            {
              "LineNO": 37,
              "Name": "AppendAnnotation",
              "Arguments": [
                {
                  "Name": "ant",
                  "DataType": "*AnnotationInfo"
                }
              ],
              "Annotations": [
                {
                  "Name": "PostMapping",
                  "Attributes": [
                    {
                      "Key": "path",
                      "Value": "/AppendAnnotation"
                    }
                  ]
                }
              ],
              "Returns": [
                {
                  "Name": "e",
                  "DataType": "error"
                }
              ]
            }
          ],
          "Annotations": [
            {
              "Name": "RestController",
              "Attributes": [
                {
                  "Key": "path",
                  "Value": "/order"
                },
                {
                  "Key": "scope",
                  "Value": "singleton"
                }
              ]
            }
          ]
        }
--- PASS: Test_AnnotationScanner (0.01s)
PASS

Debugger finished with exit code 0

(未完待续)

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 230,622评论 6 544
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,716评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,746评论 0 383
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,991评论 1 318
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,706评论 6 413
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 56,036评论 1 329
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 44,029评论 3 450
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 43,203评论 0 290
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,725评论 1 336
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,451评论 3 361
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,677评论 1 374
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,161评论 5 365
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,857评论 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,266评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,606评论 1 295
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,407评论 3 400
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,643评论 2 380

推荐阅读更多精彩内容