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

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

緣起

最近閱讀 [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編寫“基于注解的靜態(tài)代碼增強(qiáng)器/生成器”
    • 配置: ComponentScan蔫慧,Configuration, Bean

    • Bean聲明:Component, Service, Controller

    • Bean注入:Autowried

    • AOP注解:Before, After, Around, PointCut

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

  • 昨天把思路擼清楚了滓走,今天動(dòng)手實(shí)現(xiàn)各種詞法元素的掃描
    • project.go: 掃描整個(gè)項(xiàng)目的所有代碼文件重挑。module名從go.mod文件里面取
    • packages.go: 遞歸掃描某個(gè)代碼目錄
    • files.go: 掃描某個(gè)go代碼文件嗓化,并解析import/struct/field/method等元素
    • imports: 掃描指定代碼文件的所有import
    • domain/*.go:詞法元素模型集,碼略

project.go

掃描整個(gè)項(xiàng)目的所有代碼文件谬哀。module名從go.mod文件里面取

package scanner

import (
    "errors"
    "io/ioutil"
    "learning/gooop/spring/autogen/common"
    "learning/gooop/spring/autogen/domain"
    "os"
    "path"
    "strings"
)

func ScanProject(name, dir string) (error, *domain.ProjectInfo) {
    e, module := parseModFileAndGetModuleName(dir)
    if e != nil {
        return e, nil
    }

    files, e := ioutil.ReadDir(dir)
    if e != nil {
        return e, nil
    }

    project := domain.NewProjectInfo()
    project.Name = name
    project.LocalDir = dir
    project.Module = module

    for _, file := range files {
        if !file.IsDir() {
            continue
        }

        e, pkg := ScanPackage(project, nil, dir+"/"+file.Name())
        if e != nil {
            return e, nil
        } else {
            project.AppendPackage(pkg)
        }
    }

    return nil, project
}

func parseModFileAndGetModuleName(dir string) (error, string) {
    modfile := path.Join(dir, gModuleFile)
    _, e := os.Stat(modfile)
    if e != nil {
        return gErrorModuleFileNotFound, ""
    }

    data, e := ioutil.ReadFile(modfile)
    if e != nil {
        return e, ""
    }

    text := string(data)
    for _, line := range strings.Split(text, "\n") {
        line := strings.TrimSpace(line)
        if !common.Tokens.MatchString(line, gModulePrefix) {
            continue
        }

        if ok, s := common.Tokens.MatchRegexp(line, gModulePattern); ok {
            return nil, strings.TrimSpace(s[len(gModulePrefix)+1:])
        }
    }

    return gErrorProjectModuleNotFound, ""
}

var gModuleFile = "go.mod"
var gModulePrefix = "module"
var gModulePattern = "^module\\s+\\w+(/\\w+)*"

var gErrorModuleFileNotFound = errors.New("module file not found: go.mod")
var gErrorProjectModuleNotFound = errors.New("project module not found in go.mod")

packages.go

遞歸掃描某個(gè)代碼目錄

package scanner

import (
    "io/ioutil"
    "learning/gooop/spring/autogen/domain"
    "path/filepath"
    "strings"
)

func ScanPackage(project *domain.ProjectInfo, parent *domain.PackageInfo, dir string) (error, *domain.PackageInfo) {
    pkg := domain.NewPackageInfo()
    pkg.Project = project
    pkg.Parent = parent
    pkg.LocalDir = dir

    _, f := filepath.Split(dir)
    pkg.Name = f

    files, e := ioutil.ReadDir(dir)
    if e != nil {
        return e, nil
    }

    for _, file := range files {
        if file.IsDir() {
            e, p := ScanPackage(project, pkg, dir+"/"+file.Name())

            if e != nil {
                return e, nil

            } else if p != nil {
                pkg.AppendPackage(p)
            }

        } else if strings.HasSuffix(file.Name(), ".go") {
            e, f := ScanCodeFile(pkg, dir+"/"+file.Name())

            if e != nil {
                return e, nil

            } else if f != nil {
                pkg.AppendFile(f)
            }
        }
    }

    return nil, pkg
}

files.go

讀入某個(gè)go代碼文件刺覆,清除注釋,然后解析import/struct/field/method等元素

package scanner

import (
    "io/ioutil"
    "learning/gooop/spring/autogen/common"
    "learning/gooop/spring/autogen/domain"
    "regexp"
    "strings"
    "unicode"
)

func ScanCodeFile(pkg *domain.PackageInfo, file string) (error, *domain.CodeFileInfo) {
    fbytes, e := ioutil.ReadFile(file)
    if e != nil {
        return e, nil
    }

    ftext := string(fbytes)
    lines := strings.Split(ftext, "\n")
    for i, it := range lines {
        lines[i] = strings.TrimRightFunc(it, unicode.IsSpace)
    }

    codeFile := domain.NewCodeFileInfo()
    codeFile.Package = pkg
    codeFile.RawLines = lines

    // clean comments
    bInParaComment := false
    cleanLines := make([]string, len(lines))
    for i, it := range lines {
        s := it

        if bInParaComment {
            // para comment end?
            i := strings.Index(it, gParaCommentEnd)
            if i >= 0 {
                bInParaComment = false
                s = s[i+1:]

            } else {
                cleanLines[i] = ""
                continue
            }
        }

        if common.Tokens.MatchString(it, gLineCommentPrefix) {
            cleanLines[i] = ""
            continue
        }

        s = removeParaCommentInLine(it)
        i1 := strings.Index(s, gParaCommentStart)
        if i1 >= 0 {
            s = s[:i1]
            bInParaComment = true
        }
        cleanLines[i] = s
    }

    // parse imports
    ScanImport(codeFile)

    // todo: parse struct declares/fields/methods

    return nil, nil
}

func removeParaCommentInLine(s string) string {
    arr := gParaCommentInLine.FindAllStringIndex(s, -1)
    if len(arr) > 0 {
        for i := len(arr) - 1; i >= 0; i-- {
            from := arr[i][0]
            to := arr[i][1]
            s = s[:from] + s[to+1:]
        }
    }

    return s
}

var gLineCommentPrefix = "^\\s*//"
var gParaCommentInLine = regexp.MustCompile("/\\*.*\\*/")
var gParaCommentStart = "/*"
var gParaCommentEnd = "*/"

imports.go

掃描指定代碼文件的所有import

package scanner

import (
    "learning/gooop/spring/autogen/domain"
    "regexp"
)

func ScanImport(file *domain.CodeFileInfo) {
    parseSingleImport(file)
    parseMultiImports(file)
}

func parseSingleImport(file *domain.CodeFileInfo) {
    for _, it := range file.CleanLines {
        if gSingleImportRegexp.MatchString(it) {
            ss := gSingleImportRegexp.FindAllStringSubmatch(it, -1)[0]
            imp := domain.NewImportInfo()
            imp.File = file

            if len(ss) == 3 {
                imp.Alias = ""
                imp.Package = ss[1]
            } else if len(ss) == 5 {
                imp.Alias = ss[1]
                imp.Package = ss[3]
            }

            file.AppendImport(imp)
        }
    }
}

func parseMultiImports(file *domain.CodeFileInfo) {
    bInBlock := false
    for _, it := range file.CleanLines {
        if bInBlock {
            if gMultiImportEnd.MatchString(it) {
                bInBlock = false
                continue
            }

            if gImportPackage.MatchString(it) {
                ss := gImportPackage.FindAllStringSubmatch(it, -1)[0]
                imp := domain.NewImportInfo()
                imp.File = file

                if len(ss) == 3 {
                    imp.Alias = ""
                    imp.Package = ss[1]
                } else if len(ss) == 5 {
                    imp.Alias = ss[2]
                    imp.Package = ss[3]
                }
            }
        }

        if gMultiImportStart.MatchString(it) {
            bInBlock = true
            continue
        }
    }
}

var gSingleImportRegexp = regexp.MustCompile(`\s*import\s+((\w+|\.)\s+)?("\w+(/\w+)*")`)
var gMultiImportStart = regexp.MustCompile(`^\s*import\s+\(`)
var gMultiImportEnd = regexp.MustCompile(`^\s*\)`)
var gImportPackage = regexp.MustCompile(`^\s*((\w+|\.)\s+)?("\w+(/\w+)*")`)

(未完待續(xù))

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末史煎,一起剝皮案震驚了整個(gè)濱河市谦屑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌篇梭,老刑警劉巖氢橙,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異恬偷,居然都是意外死亡悍手,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門袍患,熙熙樓的掌柜王于貴愁眉苦臉地迎上來坦康,“玉大人,你說我怎么就攤上這事诡延≈颓罚” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵肆良,是天一觀的道長(zhǎng)筛璧。 經(jīng)常有香客問我,道長(zhǎng)惹恃,這世上最難降的妖魔是什么隧哮? 我笑而不...
    開封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮座舍,結(jié)果婚禮上沮翔,老公的妹妹穿的比我還像新娘。我一直安慰自己曲秉,他們只是感情好采蚀,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著承二,像睡著了一般榆鼠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上亥鸠,一...
    開封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天妆够,我揣著相機(jī)與錄音识啦,去河邊找鬼。 笑死神妹,一個(gè)胖子當(dāng)著我的面吹牛颓哮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鸵荠,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼冕茅,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了蛹找?” 一聲冷哼從身側(cè)響起姨伤,我...
    開封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎庸疾,沒想到半個(gè)月后乍楚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡届慈,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年徒溪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拧篮。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖牵舱,靈堂內(nèi)的尸體忽然破棺而出串绩,到底是詐尸還是另有隱情,我是刑警寧澤芜壁,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布礁凡,位于F島的核電站,受9級(jí)特大地震影響慧妄,放射性物質(zhì)發(fā)生泄漏顷牌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一塞淹、第九天 我趴在偏房一處隱蔽的房頂上張望窟蓝。 院中可真熱鬧,春花似錦饱普、人聲如沸运挫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谁帕。三九已至,卻和暖如春冯袍,著一層夾襖步出監(jiān)牢的瞬間匈挖,已是汗流浹背碾牌。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留儡循,地道東北人舶吗。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像贮折,于是被迫代替她去往敵國(guó)和親裤翩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

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