缘起
最近阅读 [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 scannerimport ("errors""learning/gooop/spring/autogen/common""learning/gooop/spring/autogen/domain""regexp""strings")type IAnnotationScanner interface {ScanAnnotations(s *domain.StructInfo)}type tAnnotationScanner intfunc (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()// namedeclare := ss[0]a.Name = ss[1]// propertiest := line[len(declare):]for {// space*b1, s1 := common.Tokens.MatchSpaces(t)if b1 {t = t[len(s1):]}// keyb2, 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):]}// valueb4, 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 stringb4, 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 scannerimport ("encoding/json""learning/gooop/spring/autogen/domain""strings""testing")func Test_AnnotationScanner(t *testing.T) {code := `// @RestController path=/order scope=singletontype StructInfo struct {LineNO intName stringCodeFile *CodeFileInfoFields []*FieldInfoMethods []*MethodInfoAnnotations []*AnnotationInfo}func NewStructInfo() *StructInfo {it := new(StructInfo)it.Fields = []*FieldInfo{}it.Methods = []*MethodInfo{}it.Annotations = []*AnnotationInfo{}return it}// @GetMapping path=/AppendFieldfunc (me *StructInfo) AppendField(lineNO int, name string, dataType string) error {fld := NewFieldInfo()fld.Struct = mefld.LineNO = lineNOfld.Name = namefld.DataType = dataTypeme.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=/AppendAnnotationfunc (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.CleanLinesDefaultStructScanner.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_AnnotationScannerIAnnotationScanner_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)PASSDebugger finished with exit code 0
(未完待续)
