手?jǐn)]golang 仿spring ioc/aop 之9 掃碼4

手?jǐn)]golang 仿spring ioc/aop 之9 掃碼4

緣起

最近閱讀 [Spring Boot技術(shù)內(nèi)幕: 架構(gòu)設(shè)計(jì)與實(shí)現(xiàn)原理] (朱智勝 , 2020.6)
本系列筆記擬采用golang練習(xí)之
Talk is cheap, show me the code.

Spring

Spring的主要特性:
1. 控制反轉(zhuǎn)(Inversion of Control, IoC)
2. 面向容器
3. 面向切面(AspectOriented Programming, AOP)

源碼gitee地址:
https://gitee.com/ioly/learning.gooop

原文鏈接:
https://my.oschina.net/ioly

目標(biāo)

  • 參考spring boot常用注解,使用golang編寫(xiě)“基于注解的靜態(tài)代碼增強(qiáng)器/生成器”

子目標(biāo)(Day 9)

  • struct解析清楚了写半,接著解析注解就比較容易了
    • scanner/IStructScanner.go:修復(fù)scanMethod()和scanAnnotation()的細(xì)節(jié)問(wèn)題
    • scanner/IAnnotationScanner.go:注解掃描接口及默認(rèn)實(shí)現(xiàn)讶迁。注解的屬性支持雙引號(hào)和重音號(hào)字符串忙干。
    • scanner/IAnnotationScanner_test.go:針對(duì)注解信息的單元測(cè)試

scanner/IAnnotationScanner.go

注解掃描接口及默認(rèn)實(shí)現(xiàn)。注解的屬性支持雙引號(hào)和重音號(hào)字符串奄毡。

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

針對(duì)注解信息的單元測(cè)試

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))
    }
}

測(cè)試輸出

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

(未完待續(xù))

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子患蹂,更是在濱河造成了極大的恐慌,老刑警劉巖砸紊,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件传于,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡醉顽,警方通過(guò)查閱死者的電腦和手機(jī)沼溜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)游添,“玉大人系草,你說(shuō)我怎么就攤上這事∷衾裕” “怎么了找都?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)廊酣。 經(jīng)常有香客問(wèn)我能耻,道長(zhǎng),這世上最難降的妖魔是什么亡驰? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任晓猛,我火速辦了婚禮,結(jié)果婚禮上凡辱,老公的妹妹穿的比我還像新娘戒职。我一直安慰自己,他們只是感情好透乾,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布洪燥。 她就那樣靜靜地躺著,像睡著了一般续徽。 火紅的嫁衣襯著肌膚如雪蚓曼。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天钦扭,我揣著相機(jī)與錄音纫版,去河邊找鬼。 笑死客情,一個(gè)胖子當(dāng)著我的面吹牛其弊,可吹牛的內(nèi)容都是我干的癞己。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼梭伐,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼痹雅!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起糊识,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤绩社,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后赂苗,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體愉耙,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年拌滋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了朴沿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡败砂,死狀恐怖赌渣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情昌犹,我是刑警寧澤坚芜,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站祭隔,受9級(jí)特大地震影響货岭,放射性物質(zhì)發(fā)生泄漏路操。R本人自食惡果不足惜疾渴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望屯仗。 院中可真熱鬧搞坝,春花似錦、人聲如沸魁袜。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)峰弹。三九已至店量,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鞠呈,已是汗流浹背融师。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蚁吝,地道東北人旱爆。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓舀射,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親怀伦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子脆烟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容