本库是为了实现Golang的元编程(Metaprogramming)。
元编程是一种编程技术,使计算机程序能够将其他程序视为其数据。 这意味着一个程序可以被设计为读取、生成、分析或转换其他程序,甚至在运行时修改自身。 在某些情况下,这使程序员可以最大限度地减少表达解决方案的代码行数,从而减少开发时间。
- 消除样板代码(Boilerplate code)
- 可以面向切面编程(Aspect-Oriented Programming)
应用场景包括但不限于
很多编程语言都支持元编程,例如Java,主要通过反射加注解。
Golang通过Tag加反射也能进行元编程,但有以下缺点
- Tag仅支持结构成员
- 反射容易受语言本身限制
- 反射容易对性能产生负影响
- 反射只能在运行时才能发现问题
本库选择使用代码生成的方式
- 使用标准库
ast
与types
包解析代码及注释 - 使用标准库
text/template
包作为模版进行代码生成 - 封装常用代码解析函数作为模版的自定义函数,简化模版编写复杂度
- 解析Golang源码及注释
- 丰富的代码解析类模版函数
- 高度可定制,用户可以自定义模版函数及模版,用于生成自己想要的程序
本例子演示为注释有//+iface.Iface
的struct
生成接口
- 待成生接口的
struct
,struct.go
//SomeStruct
//+iface.Iface
type SomeStruct struct {
}
func (s *SomeStruct) PublicMethod(ctx context.Context, id int64) (string, error) {
return "nil", nil
}
func (s *SomeStruct) privateMethod(ctx context.Context, time time.Time) (int32, error) {
return 0, nil
}
//NoneMethodStruct
//+iface.Iface
type NoneMethodStruct struct {
}
- 代码生成程序,generator_tmpl_test.go
// define template
var tplText = `
{{range $struct := structs|filterByMeta "+iface.Iface"}}
{{$decorator := print $struct.Name "AOPIface"}}
type {{$decorator}} interface {
{{range $method := $struct|methods}}
{{if $method|exported}}
{{$method|declare}}
{{end}}
{{end}}
}
{{end}}
`
func Genearte() {
//scan current mod code
err := ScanCurrentMod().
// register template
TemplateText(tplText).
// the package path must regex match the rule
RegexOr("testdata").
// you can add more generator
And().
// generate code
Generate()
if err != nil {
fmt.Println(err.Error())
return
}
}
- 运行结果,生成代码文件,zz_testdata_gen.go
// Code generated by meta. DO NOT EDIT.
package testdata
import (
"context"
)
type NoneMethodStructAOPIface interface {
}
type SomeStructAOPIface interface {
PublicMethod(ctx context.Context, id int64) (string, error)
}
本例子演示为注释//+sqlmap.Mapper
的interface
生成数据库访问实现(类Spring Data
)
- 待生成实现的
interface
,user.go- 为方法
FindByBirthdayGTE
按方法签名生成数据库访问实现 - 为方法
FindByBirthdayGTE2
按方法上的+sqlmap.Select
的query
参数对应的sql生成数据库访问实现
- 为方法
//UserDao
//+sqlmap.Mapper Table=`user` Dialect="mysql"
type UserDao interface {
// FindByBirthdayGTE
FindByBirthdayGTE(ctx context.Context /*sql:param ctx*/, time time.Time) ([]*User, error)
//FindByBirthdayGTE2
/*+sqlmap.Select Query="select * from `user` where birthday >= :time"*/
FindByBirthdayGTE2(ctx context.Context /*sql:param ctx*/, time time.Time) ([]*User, error)
}
- 代码生成程序,genrator_test.go 较为复杂,此处略
- 运行结果,生成文件,zz_sql_dao_gen.go
package testdata
import (
"context"
"time"
"github.com/gomelon/melon/data"
)
var _ UserDao = &UserDaoSQLImpl{}
type UserDaoSQLImpl struct {
_tm *data.SQLTXManager
}
func NewUserDaoSQLImpl(_tm *data.SQLTXManager) *UserDaoSQLImpl {
return &UserDaoSQLImpl{
_tm: _tm,
}
}
func (_impl *UserDaoSQLImpl) FindByBirthdayGTE(ctx context.Context, time time.Time) ([]*User, error) {
_sql := "SELECT id, name, gender, birthday, created_at FROM `user` WHERE (`birthday` >= ?)"
_rows, _err := _impl._tm.OriginTXOrDB(ctx).
Query(_sql, time)
var _items []*User
if _err != nil {
return _items, _err
}
defer _rows.Close()
if !_rows.Next() {
return _items, _rows.Err()
}
for _rows.Next() {
_item := &User{}
_err = _rows.Scan(&_item.Id, &_item.Name, &_item.Gender, &_item.Birthday, &_item.CreatedAt)
if _err != nil {
return _items, _err
}
_items = append(_items, _item)
}
return _items, nil
}
func (_impl *UserDaoSQLImpl) FindByBirthdayGTE2(ctx context.Context, time time.Time) ([]*User, error) {
_sql := "select id, name, gender, birthday, created_at from `user` where birthday >= ?"
_rows, _err := _impl._tm.OriginTXOrDB(ctx).
Query(_sql, time)
var _items []*User
if _err != nil {
return _items, _err
}
defer _rows.Close()
if !_rows.Next() {
return _items, _rows.Err()
}
for _rows.Next() {
_item := &User{}
_err = _rows.Scan(&_item.Id, &_item.Name, &_item.Gender, &_item.Birthday, &_item.CreatedAt)
if _err != nil {
return _items, _err
}
_items = append(_items, _item)
}
return _items, nil
}