温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

怎么使用golang编写基于注解的静态代码增强器/生成器

发布时间:2021-10-13 10:59:29 来源:亿速云 阅读:104 作者:iii 栏目:编程语言

这篇文章主要介绍“怎么使用golang编写基于注解的静态代码增强器/生成器”,在日常操作中,相信很多人在怎么使用golang编写基于注解的静态代码增强器/生成器问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么使用golang编写基于注解的静态代码增强器/生成器”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

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

到此,关于“怎么使用golang编写基于注解的静态代码增强器/生成器”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI