手撸golang 仿spring ioc/aop 之8 扫码3
缘起
最近阅读 [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 7)
- 因为struct/field/method的扫描是关键,因此今天针对这块做了单元测试
- common/Tokens.go:修复MatchBasicType方法的正则匹配bug。其实func类型的DataType也没考虑到,但现在暂时可以用type alias规避,先不追求完美吧。
- scanner/IStructScanner.go: 修复若干细节, 并添加返回类型的扫描
- scanner/IStructScanner_test.go:struct扫描器的单元测试
common/Tokens.go
修复MatchBasicType方法的正则匹配bug。其实func类型的DataType也没考虑到,但现在暂时可以用type alias规避,先不追求完美吧。
func (me *tTokens) MatchBasicType(s string) (bool, string) {
list := []string{
"int",
"string",
"bool",
"byte",
"int32",
"int64",
"uint32",
"uint64",
"float32",
"float64",
"int8",
"uint8",
"int16",
"uint16",
`time\.Time`,
}
for _, it := range list {
if ok, t := me.MatchRegexp(s, "^"+it+`(\s+|$)`); ok {
return true, strings.TrimSpace(t)
}
}
return false, ""
}
scanner/IStructScanner.go
修复若干细节, 并添加返回类型的扫描
package scanner
import (
"errors"
"learning/gooop/spring/autogen/common"
"learning/gooop/spring/autogen/domain"
"regexp"
"strings"
)
type IStructScanner interface {
ScanStruct(file *domain.CodeFileInfo)
}
type tStructScanner int
func (me *tStructScanner) ScanStruct(file *domain.CodeFileInfo) {
bInStruct := false
vStructs := []*domain.StructInfo{nil}
for lineNO, line := range file.CleanLines {
if bInStruct {
// end?
if gStructEndRegexp.MatchString(line) {
me.scanMethod(vStructs[0], lineNO+1)
file.AppendStruct(vStructs[0])
bInStruct = false
vStructs[0] = nil
continue
}
// in struct block
ok, fname, ftype := me.scanField(line)
if ok {
vStructs[0].AppendField(lineNO, fname, ftype)
}
} else {
// not in struck block
// matching start?
if gStructStartRegexp.MatchString(line) {
bInStruct = true
ss := gStructStartRegexp.FindStringSubmatch(line)
vStructs[0] = domain.NewStructInfo()
vStructs[0].LineNO = lineNO
vStructs[0].CodeFile = file
vStructs[0].Name = ss[1]
continue
}
}
}
}
func (me *tStructScanner) scanField(line string) (ok bool, fldName string, fldType string) {
if !gFieldStartRegexp.MatchString(line) {
return false, "", ""
}
t := line
s1 := gFieldStartRegexp.FindString(t)
fldName = strings.TrimSpace(s1)
t = t[len(s1):]
b2, s2 := common.Tokens.MatchDataType(t)
if !b2 {
return false, "", ""
}
fldType = strings.TrimSpace(s2)
return true, fldName, fldType
}
func (me *tStructScanner) scanMethod(stru *domain.StructInfo, fromLineNO int) {
for i, limit := fromLineNO, len(stru.CodeFile.CleanLines); i < limit; i++ {
line := stru.CodeFile.CleanLines[i]
if !gMethodStartRegex.MatchString(line) {
continue
}
ss := gMethodStartRegex.FindStringSubmatch(line)
// declare
declare := ss[0]
offset := len(declare)
// receiver
receiver := ss[1]
if receiver != stru.Name {
continue
}
method := domain.NewMethodInfo()
// name
method.Name = ss[2]
// method input args
e, args := me.scanMethodArgs(method, strings.TrimSpace(line[offset:]))
if e != nil {
panic(e)
}
offset += len(args)
// method return args
e = me.scanReturnArgs(method, strings.TrimSpace(line[offset:]))
if e != nil {
panic(e)
}
// end scan method
stru.AppendMethod(method)
}
}
func (me *tStructScanner) scanMethodArgs(method *domain.MethodInfo, s string) (error, string) {
t := s
offset := 0
for {
// name
b1, s1 := common.Tokens.MatchRegexp(t, `^\w+(\s*,\s*\w+)?\s+`)
if !b1 {
break
}
argNames := strings.TrimSpace(s1)
offset += len(s1)
t = s[offset:]
// data type
b2, s2 := common.Tokens.MatchDataType(t)
if !b2 {
return gInvalidMethodArgs, ""
}
argDataType := s2
offset += len(s2)
t = s[offset:]
for _, it := range strings.Split(argNames, ",") {
method.AppendArgument(it, argDataType)
}
// ,\s+
b3, s3 := common.Tokens.MatchRegexp(t, `\s*,\s*`)
if !b3 {
break
}
offset += len(s3)
t = s[offset:]
}
b4, s4 := common.Tokens.MatchRegexp(t, `^\s*\)`)
if !b4 {
return errors.New("expecting right bracket"), ""
}
offset += len(s4)
return nil, s[0:offset]
}
func (me *tStructScanner) scanReturnArgs(method *domain.MethodInfo, s string) error {
// no args?
if gMethodEndRegexp.MatchString(s) {
return nil
}
// args start
t := s
b1, s1 := common.Tokens.MatchRegexp(t, `\s*\(\s*`)
if !b1 {
return errors.New("expecting left bracket")
}
t = t[len(s1):]
// unnamed args?
b2, s2 := common.Tokens.MatchDataType(t)
if b2 {
t = t[len(s2):]
method.AppendUnnamedReturn(s2)
// more unnamed args?
for {
b3, s3 := common.Tokens.MatchRegexp(t, `^\s*,\s*`)
if !b3 {
break
}
t = t[len(s3):]
b4, s4 := common.Tokens.MatchDataType(t)
if !b4 {
return errors.New("expecting data type")
}
t = t[len(s4):]
method.AppendUnnamedReturn(s4)
}
} else {
// named args?
for {
// name
b3, s3 := common.Tokens.MatchIdentifier(t)
if !b3 {
return errors.New("expecting identifier")
}
t = t[len(s3):]
// \s+
b4, s4 := common.Tokens.MatchSpaces(t)
if !b4 {
return errors.New("expecting spaces")
}
t = t[len(s4):]
// type
b5, s5 := common.Tokens.MatchDataType(t)
if !b5 {
return errors.New("expecting data type")
}
t = t[len(s5):]
// more?
b6, s6 := common.Tokens.MatchRegexp(t, `^\s*,\s*`)
if b6 {
// yes more
t = t[len(s6):]
} else {
// no more
break
}
}
}
// arguments end
b7, _ := common.Tokens.MatchRegexp(t, `^\s*\)\s*`)
if !b7 {
return errors.New("expecting end of arguments")
}
return nil
}
var gStructStartRegexp = regexp.MustCompile(`^\s*type\s+(\w+)\s+struct\s+\{`)
var gStructEndRegexp = regexp.MustCompile(`^\s*}`)
var gFieldStartRegexp = regexp.MustCompile(`^\s*\w+\s+`)
var gMethodStartRegex = regexp.MustCompile(`^\s*func\s+\(\s*\w+\s+\*?(\w+)\s*\)\s+(\w+)\s*\(`)
var gInvalidMethodArgs = errors.New("invalid method arguments")
var gMethodEndRegexp = regexp.MustCompile(`^\s*\{`)
var DefaultStructScanner IStructScanner = new(tStructScanner)
scanner/IStructScanner_test.go
struct扫描器的单元测试
package scanner
import (
"encoding/json"
"learning/gooop/spring/autogen/domain"
"strings"
"testing"
)
func Test_StructScan(t *testing.T) {
s := `type _mystruct struct {`
t.Log(gStructStartRegexp.MatchString(s))
code := `
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
}
func (me *StructInfo) AppendField(lineNO int, name string, dataType string) {
fld := NewFieldInfo()
fld.Struct = me
fld.LineNO = lineNO
fld.Name = name
fld.DataType = dataType
me.Fields = append(me.Fields, fld)
}
func (me *StructInfo) AppendMethod(method *MethodInfo) {
me.Methods = append(me.Methods, method)
}
func (me *StructInfo) AppendAnnotation(ant *AnnotationInfo) {
me.Annotations = append(me.Annotations, ant)
}`
file := domain.NewCodeFileInfo()
file.CleanLines = strings.Split(code, "\n")
DefaultStructScanner.ScanStruct(file)
file.CleanLines = nil
j, e := json.MarshalIndent(file.Structs, "", " ")
if e != nil {
panic(e)
}
t.Log(string(j))
}
测试输出
API server listening at: [::]:36077
=== RUN Test_StructScan
IStructScanner_test.go:12: true
IStructScanner_test.go:58: [
{
"LineNO": 1,
"Name": "StructInfo",
"Fields": [
{
"LineNO": 2,
"Name": "LineNO",
"DataType": "int",
"Annotations": []
},
{
"LineNO": 3,
"Name": "Name",
"DataType": "string",
"Annotations": []
},
{
"LineNO": 4,
"Name": "CodeFile",
"DataType": "*CodeFileInfo",
"Annotations": []
},
{
"LineNO": 5,
"Name": "Fields",
"DataType": "[]*FieldInfo",
"Annotations": []
},
{
"LineNO": 6,
"Name": "Methods",
"DataType": "[]*MethodInfo",
"Annotations": []
},
{
"LineNO": 7,
"Name": "Annotations",
"DataType": "[]*AnnotationInfo",
"Annotations": []
}
],
"Methods": [
{
"LineNO": 0,
"Name": "AppendField",
"Arguments": [
{
"Name": "lineNO",
"DataType": "int"
},
{
"Name": "name",
"DataType": "string"
},
{
"Name": "dataType",
"DataType": "string"
}
],
"Annotations": [],
"Returns": []
},
{
"LineNO": 0,
"Name": "AppendMethod",
"Arguments": [
{
"Name": "method",
"DataType": "*MethodInfo"
}
],
"Annotations": [],
"Returns": []
},
{
"LineNO": 0,
"Name": "AppendAnnotation",
"Arguments": [
{
"Name": "ant",
"DataType": "*AnnotationInfo"
}
],
"Annotations": [],
"Returns": []
}
],
"Annotations": []
}
]
--- PASS: Test_StructScan (0.01s)
PASS
Debugger finished with exit code 0
(未完待续)