Spring Security: ACL

ACL(Access Control List),中文翻译为访问控制列表,用以控制对象的访问权限。为什么要有ACL?在复杂的应用中,我们不仅要知道对谁进行授权(Authentication),还要知道在什么地方(MethodInvocation),以及对什么对象(SomeDomainObject)进行权限认证。

lookupStrategy

lookupStrategy的作用就是从数据库中读取信息,把这些信息提供给aclService使用,所以我们要为它配置一个dataSource,配置中还可以看到一个aclCache,这就是上面我们配置的缓存,它会把资源最大限度的利用起来。

中间一部分可能会让人感到困惑,为何一次定义了三个adminRole呢?这是因为一旦acl信息被保存到数据库中,无论是修改它的从属者,还是变更授权,抑或是修改其他的ace信息,都需要控制操作者的权限,这里配置的三个权限将对应于上述的三种修改操作,我们把它配置成,只有ROLE_ADMIN才能执行这三种修改操作。

PermissionEvaluator

Go-反射

什么是反射?

反射是程序在运行时检查其变量和值并找到其类型的能力。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package main

import (
"fmt"
"reflect"
)

type order struct {
ordId int
customerId int
}

type employee struct {
name string
id int
address string
salary int
country string
}

func createQuery(q interface{}) {

if reflect.ValueOf(q).Kind() == reflect.Struct {
tableName := reflect.TypeOf(q).Name()
query := fmt.Sprintf("insert into %s(", tableName)

t := reflect.TypeOf(q)
for i := 0; i < t.NumField(); i++ {
if i == 0 {
query = fmt.Sprintf("%s%s", query, t.Field(i).Name)
} else {
query = fmt.Sprintf("%s, %s", query, t.Field(i).Name)
}
}

query = fmt.Sprintf("%s%s", query, ")")
query = query + " values("

v := reflect.ValueOf(q)

for i := 0; i < v.NumField(); i++ {
switch v.Field(i).Kind() {
case reflect.Int:
if i == 0 {
query = fmt.Sprintf("%s%d", query, v.Field(i).Int())
} else {
query = fmt.Sprintf("%s, %d", query, v.Field(i).Int())
}
case reflect.String:
if i == 0 {
query = fmt.Sprintf("%s\"%s\"", query, v.Field(i).String())
} else {
query = fmt.Sprintf("%s, \"%s\"", query, v.Field(i).String())
}
default:
fmt.Println("Unsupported type")
return
}
}
query = fmt.Sprintf("%s)", query)
fmt.Println(query)
return

}
fmt.Println("unsupported type")

}

func main() {
e := employee{
name: "Naveen",
id: 565,
address: "Science Park Road, Singapore",
salary: 90000,
country: "Singapore",
}
createQuery(e)
}

Go-第一等公民function

什么是一等公民函数

一个语言如果支持将函数赋值给变量,作为参数传递,且能作为其他函数的返回值,我们就说函数在这个语言中是一等公民。Go语言支持一等公民函数。

匿名函数

在定义函数的时候不为其提供函数名,只是将其作为值赋值给变量或者作为参数或返回值直接返回,这类函数叫做匿名函数。匿名函数的使用方法和普通函数一样,在其后面加上括号和参数。

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func main() {
a := func() {
fmt.Println("Hello Anonymous FunctioN!")
}

a()
}

匿名函数也可以不通过赋值进行调用:

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
func() {
fmt.Println("Hello Anonymous FunctioN!")
}()
}

用户定义函数类型

就像我们可以自定义struct类型一样,我们也可以定义函数类型。

1
type add func(a int, b int) int

上面的代码定义了一个函数类型add,其接受两个int类型参数并返回int值。这样我们就可以定义add类型的变量了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"fmt"
)

type add func(a int, b int) int

func main() {
var a add = func(a int, b int) int {
return a + b
}
s := a(5, 6)
fmt.Println("Sum", s)
}

高阶函数

高阶函数需要满足一下两个条件之一:

  • 使用一个活或多个函数作为参数
  • 返回函数

将函数作为参数传递给其他函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
)

func simple(a func(a, b int) int) {
fmt.Println(a(60, 7))
}

func main() {
f := func(a, b int) int {
return a + b
}
simple(f)
}

从其他函数返回函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
)

func simple() func(a, b int) int {
f := func(a, b int) int {
return a + b
}
return f
}

func main() {
s := simple()
fmt.Println(s(60, 7))
}

闭包

闭包是一种特殊的匿名函数。闭包是能够访问定义在函数体外的变量的匿名函数。

1
2
3
4
5
6
7
8
9
10
11
12
package main

import (
"fmt"
)

func main() {
a := 5
func() {
fmt.Println("a =", a)
}()
}

如上面的例子,我们定义了一个匿名函数,它能够访问到其函数体外的变量a,因此这个匿名函数式闭包。每个闭包被绑定到它自己周围的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"fmt"
)

func appendStr() func(string) string {
t := "Hello"
c := func(b string) string {
t = t + " " + b
return t
}
return c
}

func main() {
a := appendStr()
b := appendStr()
fmt.Println(a("World"))
fmt.Println(b("Everyone"))

fmt.Println(a("Gopher"))
fmt.Println(b("!"))
}

在上面的例子中appendStr函数返回一个闭包。这个闭包绑定到变量t上。闭包a和b都绑定到t上,但他们有自己的t值。我们首先调用了a并给其传递参数World,那么a版本的t的值为Hello World。然后我们使用参数Everyone调用了闭包b,b有自己的变量t,b的t值的初始值是Hello,在调用结束后,b版本的t的职位Hello Everyone。函数最后执行结果为:

1
2
3
4
Hello World  
Hello Everyone
Hello World Gopher
Hello Everyone !

一等函数的实战

我们写一个程序基于一些条件过滤student slice。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package main

import "fmt"

type student struct {
firstName string
lastName string
grade string
country string
}

func filter(students []student, f func(s student) bool) []student {
var result []student
for _, s := range students {
if f(s) {
result = append(result, s)
}
}
return result
}

func main() {
s1 := student{
firstName: "Naveen",
lastName: "Ramanathan",
grade: "A",
country: "India",
}
s2 := student{
firstName: "Samuel",
lastName: "Johnson",
grade: "B",
country: "USA",
}
s := []student{s1, s2}
f := filter(s, func(s student) bool {
if s.grade == "B" {
return true
}
return false
})
fmt.Println(f)
}

Go-Panic和Recover

Go中处理异常的通用方式是使用error。Error对于程序中的大部分异常是足够用的,但是对于处理程序中出现导致其无法再继续执行的异常则是不够的。这种情景下我们使用panic终止程序。当函数碰到panic时,它将终止执行,任何延迟的函数将会执行,然后将控制权返回给它的调用者。程序会在当前goroutine的所有函数返回后打出panic信息和stack trace,然后终止程序。

可以使用recover重新获取到panic程序的控制权。panic和recover可以看做Java中的try-catch-finally。

什么时候使用panic?

尽可能的使用errors,而不是panic和recover。只有在程序无法继续运行的时候再使用panic和recover机制。

panic有两种使用场景:

  1. 当一个不可恢复的异常发生的时候,程序不能在继续执行。一个例子就是web服务器无法绑定端口。
  2. 程序员错误。比如一个方法接受指针参数,别人却使用nil作为参数,这种情况下,我们可以使用panic作为调用方法的错误。

Panic例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"fmt"
)

func fullName(firstName *string, lastName *string) {
if firstName == nil {
panic("runtime error: first name cannot be nil")
}
if lastName == nil {
panic("runtime error: last name cannot be nil")
}
fmt.Printf("%s %s\n", *firstName, *lastName)
fmt.Println("returned normally from fullName")
}

func main() {
firstName := "Elon"
fullName(&firstName, nil)
fmt.Println("returned normally from main")
}

panic会先打印出错误信息,然后将调用栈打出来。

1
2
3
4
5
6
7
panic: runtime error: last name cannot be nil

goroutine 1 [running]:
main.fullName(0xc000071f68, 0x0)
E:/git_projects/virtualworks/src/programming/panict/panict.go:12 +0x14c
main.main()
E:/git_projects/virtualworks/src/programming/panict/panict.go:20 +0x54

Defer while panicking

当发生panic的时候,defer调用会怎样?正如之前所说,function遇到panic的时候,会将先将函数内的defer调用执行结束,然后将控制权返回给调用者,直到当前goroutine最顶层调用执返回之后,再打印出panic信息和调用堆栈。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"fmt"
)

func fullName(firstName *string, lastName *string) {
defer fmt.Println("deferred call in fullName")
if firstName == nil {
panic("runtime error: first name cannot be nil")
}
if lastName == nil {
panic("runtime error: last name cannot be nil")
}
fmt.Printf("%s %s\n", *firstName, *lastName)
fmt.Println("returned normally from fullName")
}

func main() {
defer fmt.Println("deferred call in main")
firstName := "Elon"
fullName(&firstName, nil)
fmt.Println("returned normally from main")
}

输出

1
2
3
4
5
6
7
8
9
deferred call in fullName  
deferred call in main
panic: runtime error: last name cannot be nil

goroutine 1 [running]:
main.fullName(0x1042bf90, 0x0)
/tmp/sandbox060731990/main.go:13 +0x280
main.main()
/tmp/sandbox060731990/main.go:22 +0xc0

Recover

recover是内置的函数用于重新获取出现panic的gorountine的控制权。

recover只有在defer函数的内部调用才有效。在一个defer函数内部调用recover将恢复执行并获取错误信息来会停止panic序列,如果在defer函数外调用recover,并不能停止panic序列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"fmt"
)

func recoverName() {
if r := recover(); r!= nil {
fmt.Println("recovered from ", r)
}
}

func fullName(firstName *string, lastName *string) {
defer recoverName()
if firstName == nil {
panic("runtime error: first name cannot be nil")
}
if lastName == nil {
panic("runtime error: last name cannot be nil")
}
fmt.Printf("%s %s\n", *firstName, *lastName)
fmt.Println("returned normally from fullName")
}

func main() {
defer fmt.Println("deferred call in main")
firstName := "Elon"
fullName(&firstName, nil)
fmt.Println("returned normally from main")
}

recoverName函数调用了recover,返回了传给panic的值。当fullName panic的时候,defer函数recoverName将被调用,其调用recover函数停止了panic队列。

程序输出为:

1
2
3
recovered from  runtime error: last name cannot be nil  
returned normally from main
deferred call in main

Panic, Recover 和 Goroutines

recover只有在相同的goroutine内部调用时才会起作用。无法从其他goroutine内发生的panic恢复。

运行时panic

panic也能会因为运行时错误导致,比如数组越界。运行时panic等同于使用runtime.Error类型的参数调用panic。runtime.Error是内置的类型,其实现了error接口。

1
2
3
4
5
6
7
8
type Error interface {  
error
// RuntimeError is a no-op function but
// serves to distinguish types that are run time
// errors from ordinary errors: a type is a
// run time error if it has a RuntimeError method.
RuntimeError()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"fmt"
)

func a() {
n := []int{5, 7, 4}
fmt.Println(n[3])
fmt.Println("normally returned from a")
}
func main() {
a()
fmt.Println("normally returned from main")
}

上面例子执行结果:

1
2
3
4
5
6
7
panic: runtime error: index out of range

goroutine 1 [running]:
main.a()
/tmp/sandbox780439659/main.go:9 +0x40
main.main()
/tmp/sandbox780439659/main.go:13 +0x20

和其他panic一样,运行时panic也能恢复。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
)

func r() {
if r := recover(); r != nil {
fmt.Println("Recovered", r)
}
}

func a() {
defer r()
n := []int{5, 7, 4}
fmt.Println(n[3])
fmt.Println("normally returned from a")
}

func main() {
a()
fmt.Println("normally returned from main")
}

恢复后如何获取调用栈

调用recover后,会失去panic调用栈,但可以使用Debug.PringStack函数获取调用栈。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"fmt"
"runtime/debug"
)

func r() {
if r := recover(); r != nil {
fmt.Println("Recovered", r)
debug.PrintStack()
}
}

func a() {
defer r()
n := []int{5, 7, 4}
fmt.Println(n[3])
fmt.Println("normally returned from a")
}

func main() {
a()
fmt.Println("normally returned from main")
}

Go: 错误处理

什么是错误?

错误表示程序中的异常情况。比如在打开文件的时候,如果文件不存在,这就是个异常情况,使用错误来表示。

Go中的错误是普通的旧值。使用内置的error类型表示。就像其他内置的类型一样,错误值可以存储在变量中,作为函数的返回值等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"fmt"
"os"
)

func main() {
f, err := os.Open("/test.txt")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(f.Name(), "opened successfully")
}

在上面的例子中,尝试打开文件/test.txt,os包中的Open函数的签名为:

1
func Open(name string) (file *File, err error)

如果文件成功打开,那么Open函数将返回文件句柄,error将为nil,如果打开文件是出错了,一个非nil的错误将被返回。如果函数或方法返回错误,那么按照惯例,错误将作为最后一个返回值返回。

Go中处理错误的管用方式是将返回的错误与nil比较。nil表示没有错误出现,非nil表示有错误出现。

错误类型的表示

error类型是一个接口类型,定义如下:

1
2
3
type error interface {  
Error() string
}

error接口只包含一个Erorr()string方法,任何实现了该接口的类型都可以作为错误使用。调用fmt.Println函数打印错误的时候,内部将调用Error()string方法获取错误的描述。

从error中提取更多信息的不同方式

我们既然知道error是个接口类型,那么如何从错误中提取更多信息。

1. 断言底层struct类型从struct字段中获取更多信息

Open函数返回的错误类型为*PathError,PathError是一个结构类型,定义如下:

1
2
3
4
5
6
7
type PathError struct {  
Op string
Path string
Err error
}

func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }

我们可以在代码中断言error类型是不是* os.PathError,以获取Path和Op字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"fmt"
"os"
)

func main() {
f, err := os.Open("/test.txt")
if err, ok := err.(*os.PathError); ok {
fmt.Println("File at path", err.Path, "failed to open")
return
}
fmt.Println(f.Name(), "opened successfully")
}

2. 断言底层struct类型通过方法中获取更多信息

第二个获取更多错误信息的方式是通过调用底层错误类型的方法。比如DNSError结构类型,它定义了TimeOut,Temporary等方法,可以调用这些方法获取更多的错误信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
"net"
)

func main() {
addr, err := net.LookupHost("golangbot123.com")
if err, ok := err.(*net.DNSError); ok {
if err.Timeout() {
fmt.Println("operation timed out")
} else if err.Temporary() {
fmt.Println("temporary error")
} else {
fmt.Println("generic error: ", err)
}
return
}
fmt.Println(addr)
}

3. 直接比较

第三个获取更多错误信息的方式是通过与一个error类型的变量直接比较。

filepath包中的Glob函数用于返回匹配模式的所有文件的名称。这个函数在模式异常时会返回一个ErrBadPattern错误。

ErrBadPattern定义在filepath中:

1
var ErrBadPattern = errors.New("syntax error in pattern")

ErrBadPattern在模式异常时被Glob函数返回。可以通过比较检查这个错误信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"fmt"
"path/filepath"
)

func main() {
files, error := filepath.Glob("[")
if error != nil && error == filepath.ErrBadPattern {
fmt.Println(error)
return
}
fmt.Println("matched files", files)
}

不要忽略错误

绝对不要忽略错误。忽略错误将引来麻烦。

Go: Defer

Defer是什么?

Defer语句用于在所在的函数执行return之前调用函数。类似于Java中finally的作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"fmt"
)

func finished() {
fmt.Println("Finished finding largest")
}

func largest(nums []int) {
defer finished()
fmt.Println("Started finding largest")
max := nums[0]
for _, v := range nums {
if v > max {
max = v
}
}
fmt.Println("Largest number in", nums, "is", max)
}

func main() {
nums := []int{78, 109, 2, 563, 300}
largest(nums)
}

上面的例子中largest函数的第一行包含了defer finished(),这条语句将在largest函数执行结束前调用。该例子的执行结果为:

1
2
3
Started finding largest
Largest number in [78 109 2 563 300] is 563
Finished finding largest

Defer 方法

Defer的作用不限于函数,对于方法调用同样适用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
)

type person struct {
firstName string
lastName string
}

func (p person) fullName() {
fmt.Printf("%s %s",p.firstName,p.lastName)
}

func main() {
p := person {
firstName: "John",
lastName: "Smith",
}
defer p.fullName()
fmt.Printf("Welcome ")
}

参数计算

defer函数的参数是在defer语句执行的时候计算,而不是在实际函数调用结束时计算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
)

func printA(a int) {
fmt.Println("value of a in deferred function", a)
}
func main() {
a := 5
defer printA(a)
a = 10
fmt.Println("value of a before deferred function call", a)

}

上面的例子的输出结果为:

1
2
value of a before deferred function call 10
value of a in deferred function 5

可以看到printA调用输出并不是10。尽管a的值在执行完defer语句后变成了10,延迟调用printA函数的输出仍然为5。

defer栈

当一个函数有多个defer调用时,它们会被添加到一个栈中,按照后进先出(LIFO)的顺序执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"fmt"
)

func main() {
name := "Naveen"
fmt.Printf("Orignal String: %s\n", string(name))
fmt.Printf("Reversed String: ")
for _, v := range []rune(name) {
defer fmt.Printf("%c", v)
}
}

执行结果:

1
2
Orignal String: Naveen  
Reversed String: neevaN

defer的实际运用

defer用于那些无论代码流如何执行都需要执行的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package main

import (
"fmt"
"sync"
)

type rect struct {
length int
width int
}

func (r rect) area(wg *sync.WaitGroup) {
defer wg.Done()
if r.length < 0 {
fmt.Printf("rect %v's length should be greater than zero\n", r)
return
}
if r.width < 0 {
fmt.Printf("rect %v's width should be greater than zero\n", r)
return
}
area := r.length * r.width
fmt.Printf("rect %v's area %d\n", r, area)
}

func main() {
var wg sync.WaitGroup
r1 := rect{-67, 89}
r2 := rect{5, -67}
r3 := rect{8, 9}
rects := []rect{r1, r2, r3}
for _, v := range rects {
wg.Add(1)
go v.area(&wg)
}
wg.Wait()
fmt.Println("All go routines finished executing")
}

上面的例子中area函数无论走那个代码分支都需要执行wg.Done()函数,将其通过defer延迟调用,即使以后添加新的判断分支,也不会影响到wg.Done()函数的执行。

Go: 多态

Go语言借助接口实现多态。Go语言中接口可以隐式实现,只要实现了接口中所有声明的方法即可。

使用接口实现多态

一个接口类型的变量,可以持有任何实现了接口的值。接口的这个特性用来实现多态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

package main

import "fmt"

type Income interface {
calculate() int
source() string
}

type FixedBilling struct {
projectName string
biddedAmount int
}

type TimeAndMaterial struct {
projectName string
noOfHours int
hourlyRate int
}

func (fb FixedBilling) calculate() int {
return fb.biddedAmount
}

func (fb FixedBilling) source() string {
return fb.projectName
}

func (tm TimeAndMaterial) calculate() int {
return tm.noOfHours * tm.hourlyRate
}

func (tm TimeAndMaterial) source() string {
return tm.projectName
}

type Advertisement struct {
adName string
CPC int
noOfClicks int
}

func (a Advertisement) calculate() int {
return a.CPC * a.noOfClicks
}

func (a Advertisement) source() string {
return a.adName
}


func calculateNetIncome(ic []Income) {
var netincome int = 0
for _, income := range ic {
fmt.Printf("Income From %s = $%d\n", income.source(), income.calculate())
netincome += income.calculate()
}
fmt.Printf("Net income of organisation = $%d", netincome)
}

func main() {
project1 := FixedBilling{projectName: "Project 1", biddedAmount: 5000}
project2 := FixedBilling{projectName: "Project 2", biddedAmount: 10000}
project3 := TimeAndMaterial{projectName: "Project 3", noOfHours: 160, hourlyRate: 25}
bannerAd := Advertisement{adName: "Banner Ad", CPC: 2, noOfClicks: 500}
popupAd := Advertisement{adName: "Popup Ad", CPC: 5, noOfClicks: 750}
incomeStreams := []Income{project1, project2, project3, bannerAd, popupAd}
calculateNetIncome(incomeStreams)
}
|