缘起
最近阅读 [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地址:
原文链接:
目标
- 参考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
(未完待续)