版权声明:本文由魏佳原创文章,转载请注明出处: 
文章原文链接:https://www.qcloud.com/community/article/173

来源:腾云阁 https://www.qcloud.com/community

使用go语言做后台服务已经有3年了,通过项目去检验一个又一个的想法,然后不断总结,优化,最终形成了自己的一整套体系,小到一个打印对象的方法,大到一个web后台项目最佳实践指导,这一点一滴都是在不断的实践中进化开来。以下内容将是一次整体的汇报,各位看官如有兴致,请移步GitHub 关注最新的代码变更。

wsp(go http webserver)

实现初衷

  • 简单可依赖,充分利用go已有的东西,不另外增加复杂、难以理解的东西,这样做的好处包括:更容易跟随go的升级而升级,降低使用者学习成本
  • yii提供的controller/action的路由方式比较常用,在wsp里实现一套
  • java annotation的功能挺方便,在wsp里,通过注释来实现过滤器方法的调用定义
  • 不能因为wsp的引入而降低原生go http webserver的性能

使用场景

  • 以http webserver方式对外提供服务
  • 后台接口服务

使用案例

大型互联网社交业务

实现方式

路由自动生成,按要求提供controller/action的实现代码,wsp执行后会分析项目代码,自动生成路由表并记录在文件demo/WSP.go里,controller/action定义代码必须符合函数定义:func(http.ResponseWriter, *http.Request),并且是带receiver的methoddemo_set.go

package controller

import (
"net/http" "github.com/simplejia/wsp/demo/service"
) // @prefilter("Login", {"Method":{"type":"get"}})
// @postfilter("Boss")
func (demo *Demo) Set(w http.ResponseWriter, r *http.Request) {
key := r.FormValue("key")
value := r.FormValue("value")
demoService := service.NewDemo()
demoService.Set(key, value) json.NewEncoder(w).Encode(map[string]interface{}{
"code": 0,
})
}

WSP.go

// generated by wsp, DO NOT EDIT.

package main

import "net/http"
import "time"
import "github.com/simplejia/wsp/demo/controller"
import "github.com/simplejia/wsp/demo/filter" func init() {
http.HandleFunc("/Demo/Get", func(w http.ResponseWriter, r *http.Request) {
t := time.Now()
_ = t
var e interface{}
c := new(controller.Demo)
defer func() {
e = recover()
if ok := filter.Boss(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e}); !ok {
return
}
}()
c.Get(w, r)
}) http.HandleFunc("/Demo/Set", func(w http.ResponseWriter, r *http.Request) {
t := time.Now()
_ = t
var e interface{}
c := new(controller.Demo)
defer func() {
e = recover()
if ok := filter.Boss(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e}); !ok {
return
}
}()
if ok := filter.Login(w, r, map[string]interface{}{"__T__": t, "__C__": c, "__E__": e}); !ok {
return
}
if ok := filter.Method(w, r, map[string]interface{}{"type": "get", "__T__": t, "__C__": c, "__E__": e}); !ok {
return
}
c.Set(w, r)
}) }
  • wsp分析项目代码,寻找符合要求的注释(见demo/controller/demo_set.go),自动生成过滤器调用代码在文件demo/WSP.go里,filter注解分为前置过滤器(prefilter)和后置过滤器(postfilter),格式如:@prefilter({json body}),{json body}代表传入参数,符合json array定义格式(去掉前后的中括号),可以包含string值或者object值,filter函数定义满足:func (http.ResponseWriter*http.Requestmap[string]interface{}) bool,过滤器函数如下: method.go
package filter
import (
"net/http"
"strings"
) func Method(w http.ResponseWriter, r *http.Request, p map[string]interface{}) bool {
method, ok := p["type"].(string)
if ok && strings.ToLower(r.Method) != strings.ToLower(method) {
http.Error(w, "405 Method Not Allowed", http.StatusMethodNotAllowed)
return false
}
return true
}

filter输入参数map[string]interface{},会自动设置"T",time.Time类型,值为执行起始时间,可用于耗时统计,"C",{Controller}类型,值为{Controller}实例,可通过接口方式存取相关数据(这种方式存取数据较context方式更简单实用),"E",值为recover()返回值,用于检测错误并处理(后置过滤器必须recover())

package main

import (
"log" "github.com/simplejia/clog"
"github.com/simplejia/lc" "net/http" _ "github.com/simplejia/wsp/demo/clog"
_ "github.com/simplejia/wsp/demo/conf"
_ "github.com/simplejia/wsp/demo/mysql"
_ "github.com/simplejia/wsp/demo/redis"
) func init() {
lc.Init(1e5) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
})
} func main() {
clog.Info("main()") log.Panic(http.ListenAndServe(":8080", nil))
}

miscellaneous

  • 通过wrk压测工具在同样环境下(8核,8g),wsp空跑qps:9万,beego1.7.1空跑qps:5.5万
  • 更方便加入middleware(func(http.Handler) http.Handler),其实更推荐通过定义过滤器的方式支持类似功能
  • 更方便编写如下的测试用例:
    test (测试用例运行时需要用到项目配置文件,所以请在test目录生成../clog,../conf,../mysql,../redis的软链接)

demo

提供一个简单易扩展的项目stub

实现初衷

  • 简单可依赖,充分利用go已有的东西,不另外增加复杂、难以理解的东西,这样做的好处包括:更容易跟随go的升级而升级,降低使用者学习成本
  • 提供常用组件的简单包装,如下:
    • config,提供项目主配置文件自动解析,见conf
    • redis,使用(github.com/garyburd/redigo),提供配置文件自动解析,见redis
    • mysql,使用(database/sql),提供配置文件自动解析,见mysql,同时为了方便对象映射,提供了最常用的orm组件供选择使用,见orm

项目编写指导意见

  • 目录结构:
├── WSP.go
├── clog
│ └── clog.go
├── conf
│ ├── conf.go
│ └── conf.json
├── controller
│ ├── base.go
│ ├── demo.go
│ ├── demo_get.go
│ └── demo_set.go
├── demo
├── filter
│ ├── boss.go
│ ├── login.go
│ └── method.go
├── main.go
├── model
│ ├── demo.go
│ ├── demo_get.go
│ └── demo_set.go
├── mysql
│ ├── demo_db.json
│ └── mysql.go
├── redis
│ ├── demo.json
│ └── redis.go
├── service
│ ├── demo.go
│ ├── demo_get.go
│ └── demo_set.go
└── test
├── clog -> ../clog
├── conf -> ../conf
├── demo_get_test.go
├── demo_set_test.go
├── init_test.go
├── mysql -> ../mysql
└── redis -> ../redis
  • controller目录:负责request参数解析,service调用
  • service目录:负责逻辑处理,model调用
  • model目录:负责数据处理

接口实现上,建议一个接口对应一个文件,如controller/demo_get.go, service/demo_get.go, model/demo_get.go

lc (local cache)

实现初衷

  • 纯用redis做缓存,相比lc,redis有网络调用开销,反复调用多次,延时急剧增大,当网络偶尔出现故障时,我们的数据接口也就拿不到数据,但lc里的数据就算是超过了设置的过期时间,我们一样能拿到过期的数据做备用
  • 使用mysql,当缓存失效,有数据穿透的风险,lc自带并发控制,有且只允许同一时间同一个key的唯一一个client穿透到数据库,其它直接返回lc缓存数据

特性

  • 本地缓存
  • 支持Get,Set,Mget,Delete操作
  • 当缓存失效时,返回失效标志同时,还返回旧的数据,如:v, ok := lc.Get(key),当key已经过了失效时间了,并且key还没有被lru淘汰掉,v是之前存的值,ok返回false
  • 实现代码没有用到锁
  • 使用到lru,淘汰长期不用的key
  • 结合lm使用更简单快捷

demo

lc_test.go

package lc

import (
"testing"
"time"
) func init() {
Init(65536) // 使用lc之前必须要初始化
} func TestGetValid(t *testing.T) {
key := "k"
value := "v"
Set(key, value, time.Second)
time.Sleep(time.Millisecond * 10) // 给异步处理留点时间
v, ok := Get(key)
if !ok || v != value {
t.Fatal("")
}
}

lm (lc+redis+[mysql|http] glue)

实现初衷

写redis+mysql代码时(还可能加上lc),示意代码如下:

func orig(key string) (value string) {
value = redis.Get(key)
if value != "" {
return
}
value = mysql.Get(key)
redis.Set(key, value)
return
}
// 如果再加上lc的话
func orig(key string) (value string) {
value = lc.Get(key)
if value != "" {
return
}
value = redis.Get(key)
if value != "" {
lc.Set(key, value)
return
}
value = mysql.Get(key)
redis.Set(key, value)
lc.Set(key, value)
return
}

有了lm,再写上面的代码时,一切变的那么简单lm_test.go

func tGlue(key, value string) (err error) {
err = Glue(
key,
&value,
func(p, r interface{}) error {
_r := r.(*string)
*_r = "test value"
return nil
},
func(p interface{}) string {
return fmt.Sprintf("tGlue:%v", p)
},
&LcStru{
Expire: time.Millisecond * 500,
Safety: false,
},
&McStru{
Expire: time.Minute,
Pool: pool,
},
)
if err != nil {
return
}
return
}

功能

自动添加缓存代码,支持lc, redis,减轻你的心智负担,让你的代码更加简单可靠,少了大段的冗余代码,复杂的事全交给lm自动帮你做了
支持Glue[Lc|Mc]及相应批量操作Glues[Lc|Mc],详见lm_test.go示例代码

注意

lm.LcStru.Safety,当置为true时,对lc在并发状态下返回的nil值不接受,因为lc.Get在并发状态下,同一个key返回的value有可能是nil,并且ok状态为true,Safety置为true后,对以上情况不接受,会继续调用下一层逻辑

orm (配合sql.Rows使用的超简单数据到对象映射功能函数)

实现初衷

  • database/sql包,Db.Query返回的sql.Rows,通过Rows.Scan方式示例代码如下:
rows, err := db.Query("SELECT ...")
defer rows.Close()
for rows.Next() {
var id int
var name string
err = rows.Scan(&id, &name)
}
err = rows.Err()
...

但实际项目场景里,我们更想这样:

rows, err := db.Query("SELECT ...")
defer rows.Close()
var d []*stru
err = Rows2Strus(rows, &d)

这就是一种简单的对象映射,通过转为对象的方式,我们的代码更方便处理了

功能

一共提供四种场景的使用方法:

  • Rows2Strus, sql.Rows转为struct slice

  • sql.Rows转为struct,等同db.QueryRow

  • Rows2Cnts, sql.Rows转为int slice

  • Rows2Cnt, sql.Rows转为int,用于select count(1)操作

支持tag: orm,如下:

type Demo struct {
Id int
DemoName string `orm:"demo_name"` // 映射成demo_name字段
}

支持匿名成员,如下:

type C struct {
Id int
}
type P struct {
C // 映射成id字段
Name string
}

支持snakecase配置,通过设置orm.IsSnakeCase = true,如下:

type Demo struct {
Id int
DemoName string // 映射成demo_name字段
}

demo

orm_test.go

cmonitor

功能

用于进程监控,管理

实现

  • 被监控进程启动后,按每300ms执行一次状态检测(通过发signal0信号检测),每个被监控进程在一个独立的协程里被监测。
  • cmonitor启动后会监听一个http端口用于接收管理命令(start|stop|status|...)

使用方法

配置文件:conf.json (json格式,支持注释) conf.json

{
"env": "dev", // 配置运行环境
"envs": {
"dev": {
"port": 29118, // 配置监听端口
"rootpath": "/home/simplejia/tools/go/ws/src/github.com/simplejia",
"environ": "ulimit -n 65536", // 配置环境变量
"svrs": {
// demo
"demo": "wsp/demo/demo" // key: 名字 value: 将与rootpath拼接在一起运行
},
"log": {
"mode": 3, // 0: none, 1: localfile, 2: collector (数字代表bit位)
"level": 15 // 0: none, 1: debug, 2: warn 4: error 8: info (数字代表bit位)
}
},
"test": {
"port": 29118, // 配置监听端口
"rootpath": "/home/simplejia/tools/go/ws/src/github.com/simplejia",
"environ": "ulimit -n 65536", // 配置环境变量
"svrs": {
// demo
"demo": "wsp/demo/demo"
},
"log": {
"mode": 3, // 0: none, 1: localfile, 2: collector (数字代表bit位)
"level": 15 // 0: none, 1: debug, 2: warn 4: error 8: info (数字代表bit位)
}
},
"prod": {
"port": 29118, // 配置监听端口
"rootpath": "/home/simplejia/tools/go/ws/src/github.com/simplejia",
"environ": "ulimit -n 65536", // 配置环境变量
"svrs": {
// demo
"demo": "wsp/demo/demo"
},
"log": {
"mode": 2, // 0: none, 1: localfile, 2: collector (数字代表bit位)
"level": 14 // 0: none, 1: debug, 2: warn 4: error 8: info (数字代表bit位)
}
}
}
}
  • 运行方法:cmonitor.sh [start|stop|restart|status|check]
  • 进程管理:cmonitor -[h|status|start|stop|restart] [all|["svrname"]]

注意

  • cmonitor的运行日志通过clog上报,也可记录在本地cmonitor.log日志文件里,注意:此cmonitor.log日志文件不会被切分,所以尽量保持较少的日志输出,建议通过clog方式上报日志
  • cmonitor启动监控进程后,被监控进程控制台日志cmonitor.log会输出到相应进程目录,最多保存30天,历史日志以cmonitor.{day}.log方式备份
  • 当cmonitor启动时,会根据conf.json配置启动所有被监控进程,当被监控进程已经启动过,并且符合配置要求时,cmonitor会自动将其加入监控列表
  • cmonitor会定期检查进程运行状态,如果进程异常退出,cmonitor会反复重试拉起,并且记录日志
  • 当被监控进程为多进程运行模式,cmonitor只监控管理父进程(子进程应实现检测父进程运行状态,并随父进程退出而退出)
  • 被监控进程以nohup方式启动,所以你的程序就不要自己设定daemon运行了
  • 每分钟通过ps方式检测一次进程状态,如果出现任何异常,比如有多份进程启动等,记日志
  • 由于cmonitor会同时启动内部httpserver(绑内网ip),所以也支持远程管理,比如在浏览器里输入:http://xxx.xxx.xxx.xxx:29118/?command=status&service=all

demo

$ cmonitor -status all

*****STATUS OK SERVICE LIST*****
demo PID:13539 *****STATUS FAIL SERVICE LIST***** $ cmonitor -restart demo
SUCCESS

clog (集中式日志收集服务)

实现初衷

  • 实际项目中,服务会部署到多台服务器上去,机器本地日志不方便查看,通过集中收集日志到一台或两台机器上,日志以文件形式存在,按服务名,ip,日期,日志类型分别存储,这样查看日志时就方便多了
  • 我们做服务时,经常需要添加一些跟业务逻辑无关的功能,比如按错误日志报警,上报数据用于统计等等,这些功能和业务逻辑混在一起,实在没有必要,有了clog,我们只需要发送有效的数据,然后就可把数据处理的工作留给clog去做

    功能

  • 通过发送日志至本机agent,然后agent转发至远程master主机,api目前提供golang,c支持
  • 根据配置(master/conf/conf.json)运行相关日志分析程序,目前已实现:日志输出,报警
  • 输出日志文件按master/logs/{模块名}/log{dbg|err|info|war}/{day}/log{ip}{+}{sub}规则命名,最多保存30天日志

使用方法

  • agent机器

    布署本机agent服务:agent/agent,配置文件:agent/conf/conf.json

  • master机器

    布署master服务:master/master,配置文件:master/conf/conf.json

  • agent和master服务建议用cmonitor启动管理

注意

  • api.go文件里定义了agent服务端口(agent启动后会监听127.0.0.1:xxx),见clog.Port变量
  • master/conf/conf.json文件里,tpl定义模板,然后通过$xxx方式引用,目前支持的handler有:filehandler和alarmhandler,filehandler用来记录本地日志,alarmhandler用来发报警
  • 对于alarmhandler,相关参数配置见params,目前的报警只是打印日志,实际实用,应替换成自己的报警处理逻辑,重新赋值procs.AlarmFunc就可以了,可以在master/procs目录下新建一个go文件,如下示例:
package procs

import (
"encoding/json"
"os"
) func init() {
// 请替换成你自己的报警处理函数
AlarmFunc = func(sender string, receivers []string, text string) {
params := map[string]interface{}{
"Sender": sender,
"Receivers": receivers,
"Text": text,
}
json.NewEncoder(os.Stdout).Encode(params)
}
}
  • alarmhandler有防骚扰控制逻辑,相同内容,一分钟内不再报,两次报警不少于30秒,以上限制和日志文件一一对应
  • 如果想添加新的handler,只需在master/procs目录下新建一个go文件,如下示例:
package procs

func XxxHandler(cate, subcate string, content []byte, params map[string]interface{}) {
} func init() {
RegisterHandler("xxxhandler", XxxHandler)
}

demo

api_test.go
demo (demo项目里有clog的使用例子)

simplesvr (simple udp server)

功能:

  • 超简单c/c++服务,多进程,udp通信,没有高深复杂的事件驱动,没有多线程带来的数据共享问题(加锁对性能的影响),代码结构简单,直达业务
  • 适用场景:业务逻辑重,追求高吞吐量,容忍udp带来的不可靠。(已有c lib库,不方便采用golang包装时)
  • c开发新手也可以快速上手

特性

  • 代码结构简单,仅有一个.cpp文件:main/main.cpp,其它均是.h文件。
  • 调用协议简单,'\x00'分隔字段
  • 多进程,同时启动多个业务子进程,任何一个进程(包括父进程)退出,所有其它进程均退出。
  • 支持json格式配置文件
  • 可选通过clog方式记录日志并报警
  • 提供很多有用的小组件,包括: > 简单高效的http get及post操作组件 > 类似go lc的本地缓存组件(支持lru, 支持过期后还能返回旧数据,这个在获取新数据失败时尤其有用)
  • 提供些小的库函数,如:定时器,获取本机内网ip等

    注意

  • 加入新依赖库时,只需要在main/main.cpp里加入库头文件,修改Makefile文件
  • api目录提供api.go示例代码用于和simplesvr服务通信

gop (go REPL)

实现初衷

有时想快速验证go某个函数的使用,临时写个程序太低效,有了gop,立马开一个shell环境,边写边运行,自动为你保存上下文,还可随时导入导出snippet,另外还有代码自动补全等等特性

特性

  • history record(gop启动后会在home目录下生成.gop文件夹, 输入历史会记录在此)
  • tab complete,可以补全package,补全库函数,需要系统安装有gocode
  • r|w两种模式切换,r是默认模式,对用户输入实时解析运行,执行w命令切换到w模式,w模式下,只有当执行run命令时,代码才会真正执行
  • 代码实时查看和编辑功能[!命令功能]
  • snippet,可以导入和导出模板[<,>命令功能]

    注意:

  • 输入代码时,支持续行
  • 对于如下代码,只会在执行结束后一并输出 > print(1);time.Sleep(time.Second);print(2)
  • 可以通过echo 123这种方式输出, echo是println的简写,你甚至可以重新定义println变量来使用自己的打印方法,比如像我这样定义(utils.IprintD的特点是可以打印出指针指向的实际内容,就算是嵌套的指针也可以,fmt.Printf做不到):
    import "github.com/simplejia/utils"
    var println = utils.IprintD
  • 导入项目package时,最好提前通过go install方式安装包文件到pkg目录,这样可以加快执行速度
  • 可以提前import包,后续使用时再自动引入
  • gop启动后会自动导入$PWD/gop.tmpl或者$HOME/.gop/gop.tmpl模板代码,可以把常用的代码保存到gop.tmpl里

demo

$ gop
Welcome to the Go Partner! [[version: 1.7, created by simplejia]
Enter '?' for a list of commands.
[r]$ ?
Commands:
?|help help menu
-[dpc][#],[#]-[#],... pop last/specific (declaration|package|code)
![!] inspect source [with linenum]
<tmpl source tmpl
>tmpl write tmpl
[#](...) add def or code
run run source
compile compile source
w write source mode on
r write source mode off
reset reset
list tmpl list
[r]$ for i:=1; i<3; i++ {
..... print(i)
..... time.Sleep(time.Millisecond)
.....}
1
2
[r]$ import _ "github.com/simplejia/wsp/demo/mysql"
[r]$ import _ "github.com/simplejia/wsp/demo/redis"
[r]$ import _ "github.com/simplejia/wsp/demo/conf"
[r]$ import "github.com/simplejia/lc"
[r]$ import "github.com/simplejia/wsp/demo/service"
[r]$ lc.Init(1024)
[r]$ demoService := service.NewDemo()
[r]$ demoService.Set("123", "456")
[r]$ time.Sleep(time.Millisecond)
[r]$ echo demoService.Get("123")
456
[r]$ >gop
[r]$ <gop
[r]$ !
package main p0: import _ "github.com/simplejia/wsp/demo/mysql"
p1: import _ "github.com/simplejia/wsp/demo/redis"
p2: import _ "github.com/simplejia/wsp/demo/conf"
p3: import "github.com/simplejia/lc"
p4: import "github.com/simplejia/wsp/demo/service"
p5: import "fmt" // imported and not used
p6: import "strconv" // imported and not used
p7: import "strings" // imported and not used
p8: import "time" // imported and not used
p9: import "encoding/json" // imported and not used
p10: import "bytes" // imported and not used func main() {
c0: lc.Init(1024)
c1: demoService := service.NewDemo()
c2: _ = demoService
c3: demoService.Set("123", "456")
c4: time.Sleep(time.Millisecond)
} [r]$

go语言实战向导的更多相关文章

  1. R入门<三>-R语言实战第4章基本数据管理摘要

    入门书籍:R语言实战 进度:1-4章 摘要: 1)实用的包 forecast:用于做时间序列预测的,有auto.arima函数 RODBC:可以用来读取excel文件.但据说R对csv格式适应更加良好 ...

  2. R语言实战(三)基本图形与基本统计分析

    本文对应<R语言实战>第6章:基本图形:第7章:基本统计分析 =============================================================== ...

  3. R语言实战(二)数据管理

    本文对应<R语言实战>第4章:基本数据管理:第5章:高级数据管理 创建新变量 #建议采用transform()函数 mydata <- transform(mydata, sumx ...

  4. R语言实战(一)介绍、数据集与图形初阶

    本文对应<R语言实战>前3章,因为里面大部分内容已经比较熟悉,所以在这里只是起一个索引的作用. 第1章       R语言介绍 获取帮助函数 help(), ? 查看函数帮助 exampl ...

  5. Swift语言实战晋级

    Swift语言实战晋级基本信息作者: 老镇 丛书名: 爱上Swift出版社:人民邮电出版社ISBN:9787115378804上架时间:2014-12-26出版日期:2015 年1月开本:16开页码: ...

  6. swift语言实战晋级-第9章 游戏实战-跑酷熊猫-9-10 移除平台与视差滚动

    9.9 移除场景之外的平台 用为平台是源源不断的产生的,如果不注意销毁,平台就将越积越多,虽然在游戏场景中看不到.几十个还看不出问题,那几万个呢?几百万个呢? 所以我们来看看怎么移除平台,那什么样的平 ...

  7. swift语言实战晋级-第9章 游戏实战-跑酷熊猫-7-8 移动平台的算法

    在上个小节,我们完成了平台的产生.那么我们来实现一下让平台移动.平台的移动,我们只需要在平台工厂类中写好移动的方法,然后在GameScene类中统一控制就行了. 在GameScene类中,有个upda ...

  8. Swift语言实战晋级-第9章 游戏实战-跑酷熊猫-5-6 踩踏平台是怎么炼成的

    在游戏中,有很多分来飞去的平台,这个平台长短不一.如果每种长度都去创建一张图片那是比较繁琐的事情.实际上,我们只用到3张图.分别是平台的,平台的中间部分,平台的右边.关键是平台的中间部分,两张中间部分 ...

  9. Swift语言实战晋级-第9章 游戏实战-跑酷熊猫-4 熊猫的跳和打滚

    之前我们学会了跑的动作,现在我们可以利用同样的方法来实现了跳和打滚的动画. …… class Panda : SKSpriteNode { …… //跳的纹理集合 let jumpAtlas = SK ...

随机推荐

  1. 2016年12月11日 星期日 --出埃及记 Exodus 21:6

    2016年12月11日 星期日 --出埃及记 Exodus 21:6 then his master must take him before the judges. He shall take hi ...

  2. 完成Adventure中的主方法

    package a; public interface CanSwim { void swim(); } package a; public interface CanFly { void fly() ...

  3. (1)建立一个名叫Cat的类: 属性:姓名、毛色、年龄 行为:显示姓名、喊叫 (2)编写主类: 创建一个对象猫,姓名为“妮妮”,毛色为“灰色”,年龄为2岁,在屏幕上输 出该对象的毛色和年龄,让该对象调用显示姓名和喊叫两个方法。

    package lianxi; public class Cat { String Name, Color; int Age; void getName() { System.out.println( ...

  4. C# 返回Foreach集合

    IEnumerable<DataRow> DetailRows()       {           foreach (DataRow dr in EditData.Tables[tb_ ...

  5. 【转载】COM 连接点

    原文:COM 连接点 CLR 完全介绍 COM 连接点 Thottam R. Sriram 来自:http://msdn.microsoft.com/zh-cn/magazine/cc163361.a ...

  6. Spring的IoC容器注入的类型

    Spring除了可以注入Bean实例外,还可以注入其他数据类型. 注入基本数据类型 xml配置文件中的init-method="init"属性是取得Bean实例之后,输入属性值后自 ...

  7. [SAP ABAP开发技术总结]消息处理Messages

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  8. JavaScript 拼接JSON

    <script language="javascript" type="text/javascript"> var json="" ...

  9. iOS添加广告的主要方法

    //用户取消正在执行的广告时 调用 - (void)cancelBannerViewAction { NSLog(@"Banner was cancelled!"); self.a ...

  10. 学习Berkeley DB- 入门

    1 导言 首先,我们要了解Berkeley DB的一些基本特性,在IBM的开发网站上有篇文章对其有比较清晰的介绍: 这篇文章讲到了BDB的设计思想和核心数据结构.以及数据访问算法:并有常用函数使用范例 ...