大狗哥传奇

  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

annotation

发表于 2019-10-19 更新于 2020-12-02 分类于 java

元注解

java.lang.annotation提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解):

Retention 定义该注解的生命周期

  1. RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。
  2. RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式
  3. RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。

Target 表示该注解用于什么地方。默认值为任何元素,表示该注解用于什么地方。可用的ElementType参数包括

  1. ElementType.CONSTRUCTOR:用于描述构造器
  2. ElementType.FIELD:成员变量、对象、属性(包括enum实例)​​
  3. ElementType.LOCAL_VARIABLE:用于描述局部变量
  4. ElementType.METHOD:用于描述方法
  5. ElementType.PACKAGE:用于描述包
  6. ElementType.PARAMETER:用于描述参数
  7. ElementType.TYPE:用于描述类、接口(包括注解类型) 或enum声明

Documented 一个简单的Annotations标记注解,表示是否将注解信息添加在java文档中

Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类

自定义注解

自定义注解类编写的一些规则: Annotation型定义为@interface, 所有的Annotation会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口. 参数成员只能用public或默认(default)这两个访问权修饰 参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这一些类型的数组. 要获取类方法和字段的注解信息,必须通过Java的反射技术来获取 Annotation对象,因为你除此之外没有别的获取注解对象的方法 注解也可以没有定义成员, 不过这样注解就没啥用了 PS:自定义注解需要使用到元注解

1
2
3
4
5
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.TYPE})
public @interface NotNull {
}

注解的原理

注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。而我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象$Proxy1。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。

通过示例我们可以看出代理类,我们可以使用annotationType()方法来获取实际的注解类

1
2
3
4
5
FunctionalInterface functionalInterface = Function.class.getAnnotation(FunctionalInterface.class);
Class<? extends FunctionalInterface> proxy=functionalInterface.getClass();
Class<? extends Annotation> annotationType= functionalInterface.annotationType();
System.out.println("proxy = " + proxy);
System.out.println("annotationType = " + annotationType);

proxy = class com.sun.proxy.$Proxy1 annotationType = interface java.lang.FunctionalInterface

所有 class 的 class,即 Class 类中,有关于 annotation 的属性annotationData,annotationType,因所有 class 都是Class的实例,所以所有 class 都会包含这些有关 annotaion 的属性。这就是为什么所有的 class 都可以使用getAnnotations()等方法

annotation_Class.png
annotation_Class.png

mac

发表于 2019-10-17 更新于 2020-12-02 分类于 tips

常见问题

  1. mac 无法直接输入汉字,杀进程就行了

    safari连接不上网络,sudo killall -9 networkd

  2. terminal 切换到 root 提示

    sh: autojump_add_to_database: command not found

    切换 root 使用su - root即可解决这个问题

  3. command + tab 切换应用无反应 Command+tab 键,选中到那个应用之后,再松开 tab 键,按住 option 键,然后再松开 Command 键就可以显示窗口了

命令

more 1.txt|pbcopy
将文件内容复制到剪切板
pbpaste >> 1.txt
将剪切板内容复制到文件
pandoc
markdown 转换为 doc,pdf 等
ncdu
查看当前目录文件大小
mdfind
spotlight 搜索
fzf
模糊搜索工具,进入 fzf 模式
enca
字符集转码
bwm-ng
实时网速显示
ag
文本搜索工具

合并 PDF

1
2
3
"/System/Library/Automator/Combine PDF Pages.action/Contents/Resources/join.py" -o  ~/Downloads/1.pdf    ~/*.pdf

"/System/Library/Automator/Combine PDF Pages.action/Contents/Resources/join.py" -o ~/Downloads/1.pdf ~/1.pdf ~/2.pdf

打开class文件

1
2
vim -b XXX.class
:%!xxd

快速搜索文本

使用 grep 命令搜索文件时输出行号(grep -n),在 iTerm2 中打开可以直接定位到行

虚拟机 ip 固定

在虚拟机/etc/hosts 配置 ip

alfred

模糊搜索时使用空格

workflow 插件地址

自定义插件

file types 指的是文件的 metadata 信息中的 kMDItemContentTypeTree

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
$ mdls vim.md
_kMDItemOwnerUserID = 501
kMDItemContentCreationDate = 2020-09-16 07:06:53 +0000
kMDItemContentModificationDate = 2020-09-16 07:06:53 +0000
kMDItemContentType = "net.daringfireball.markdown"
kMDItemContentTypeTree = (
"net.daringfireball.markdown",
"public.plain-text",
"public.text",
"public.data",
"public.item",
"public.content"
)
kMDItemDateAdded = 2020-09-16 07:06:53 +0000
kMDItemDisplayName = "vim.md"
kMDItemFSContentChangeDate = 2020-09-16 07:06:53 +0000
kMDItemFSCreationDate = 2020-09-16 07:06:53 +0000
kMDItemFSCreatorCode = ""
kMDItemFSFinderFlags = 0
kMDItemFSHasCustomIcon = (null)
kMDItemFSInvisible = 0
kMDItemFSIsExtensionHidden = 0
kMDItemFSIsStationery = (null)
kMDItemFSLabel = 0
kMDItemFSName = "vim.md"
kMDItemFSNodeCount = (null)
kMDItemFSOwnerGroupID = 20
kMDItemFSOwnerUserID = 501
kMDItemFSSize = 6093
kMDItemFSTypeCode = ""
kMDItemKind = "Visual Studio Code document"
kMDItemLastUsedDate = 2020-11-03 01:50:05 +0000
kMDItemLogicalSize = 6093
kMDItemPhysicalSize = 8192
kMDItemUseCount = 6
kMDItemUsedDates = (
"2020-09-17 16:00:00 +0000",
"2020-09-20 16:00:00 +0000",
"2020-10-26 16:00:00 +0000",
"2020-10-28 16:00:00 +0000",
"2020-10-29 16:00:00 +0000",
"2020-11-02 16:00:00 +0000"
)

命令行连接 wifi 脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
wifi(){
networksetup -setairportpower en0 off
networksetup -setairportpower en0 on
status=`networksetup -setairportnetwork en0 semaphore asdfv123456`

if [ ! -n "$status" ];then
eixt
fi

}

for (( a=1;a<4;a++))
do
wifi
sleep 1s
done

bash.md

发表于 2019-10-15 更新于 2020-12-02 分类于 linux

概述

每条shell命令执行都会有个状态码0表示成功,1表示失败。可以使用$?得到上一条命令的执行结果来决定是否执行后续命令,快速的用法是使用&&,||

重定向符

重定向符有四个

  • > 将输出保持到文件中,会覆盖已存在文件,可以使用set -C禁止覆盖
  • >> 将输出追加到文件中
  • < 将文件内容作为输入源
  • << here 文档

linux 中默认输入输出是指向文件描述符0,1,2的,当我们需要将 所以2>&1 的意思就是将标准错误也输出到标准输出当中。

默认情况下输出的源文件描述符为0,

1
2
3
4
5
6
7
8
$ ls
1.txt
# 1和>没有空格
# 完整的表达式为 cat 1.txt 2.txt 1> 3.txt
$ cat 1.txt 2.txt > 3.txt
cat: 2.txt: No such file or directory
$ more 3.txt
1.txt

shell中可能经常能看到:echo log > /dev/null 2>&1,命令的结果可以通过>的形式来定义输出,/dev/null :代表空设备文件

  • 1 > /dev/null 2>&1 语句含义

    1. 1 > /dev/null : 首先表示标准输出重定向到空设备文件,也就是不输出任何信息到终端,说白了就是不显示任何信息。
    2. 2>&1 :接着,标准错误输出重定向(等同于)标准输出,因为之前标准输出已经重定向到了空设备文件,所以标准错误输出也重定向到空设备文件。
  • cmd >a 2>a 和 cmd >a 2>&1 为什么不同?

    1. cmd >a 2>a :stdout和stderr都直接送往文件 a ,a文件会被打开两遍,由此导致stdout和stderr互相覆盖。cmd >a 2>a 相当于使用了FD1、FD2两个互相竞争使用文件 a 的管道;
    2. cmd >a 2>&1 :stdout直接送往文件a,stderr是继承了FD1的管道之后,再被送往文件a 。a文件只被打开一遍,就是FD1将其打开。cmd >a 2>&1 只使用了一个管道FD1,但已经包括了stdout和stderr。从IO效率上来讲,cmd >a 2>&1的效率更高。

使错误日志重定向到正常输出

1
2
3
sh error.sh > messge.log 2>&1
#也可以这样写
sh error.sh 1>$messge.log

当前脚本永久重定向

1
2
3
4
5
exec 1>1.log
exec 2>1.log

#关闭某个文件描述符
exec 1>$-

here 文档

又称作 heredoc、hereis、here-字串或 here-脚本,here 文档最通用的语法是<<紧跟一个标识符,从下一行开始是想要引用的文字,然后再在单独的一行用相同的标识符关闭。在 Unix shell 里,here 文档通常用于给命令提供输入内容。

在以下几个例子中,文字用 here 文档传递给 tr 命令。

1
2
3
4
5
6
$ tr a-z A-Z <<END_TEXT
> one two three
> uno dos tres
> END_TEXT
ONE TWO THREE
UNO DOS TRES

默认地,会进行变量替换和命令替换:

1
2
3
4
$ cat << EOF
> Working dir $PWD
> EOF
Working dir /home/user

管道符

使用管道符,可以将一个命令的输出重定向到另一个命令的输入,默认情况下只会讲标准输出(fd 为 1)重定向到另一个命令

1
command1|command2

组合命令

可通过&&,让多个命令顺序执行,也可以通过;,不同的地方为&&,当前一个命令的返回码为 0 时,才会执行后一个命令 例如

1
cd ~/Downloads/ && rm -rf temp`

||,与&&相反,当前一个命令的返回码大于 0 才执行第二条

[]也可以组合使用

1
2
[ condition1 ] && [ condition2 ]
[ condition1 ] || [ condition2 ]

[ 和[[

[是 shell 的一个内置命令(和命令 test 是一样的),[到]之间都被视为[的参数 [[是一个关键字,它的参数会根据一定规则进行处理,其他的和[是一样的

所以下述用法就是不对的

1
2
3
4
5
6
7
8
9
10
11
12
$ name="here and there"
$ [ -n $name ] && echo not empty
bash: [: too many arguments

#正确的用法
$ [ -n "$name" ] && echo not empty
not empty

# test

$ test -n "$name" && echo not empty
not empty

[的常用语法有

  1. 判断文件属性

    操作符 含义
    -a 检查文件是否存在
    -b 检查是否为块特殊文件[1]
    -c 检查是否为字符特殊文件[2]
    -d 检查是否为文件夹
    -e 检查文件是否存在
    -f 检查是否为常规文件[3]
    -g 检查 gid[4]是否被置位
    -G 检查是否有相同的组 ID
    -k 检查防删除位是否被置位
    -L 检查是否为符号链接[5]
    -n 判断字符串长度是否不为 0
    -O 检查文件是否被当前进程的 user ID 拥有
    -p 检查文件是否为 FIFO[6]特殊文件或命名管道[7]
    -r 检查文件是否可读
    -s 检查文件大小是否大于 0
    -S 检查文件是否为 socket 文件
    -t 检查文件描述符是否打开
    -u 检查 uid[8]是否被置位
    -w 检查文件是否可写
    -x 检查文件是否可执行
    -z 判断字符串长度是否为 0

    示例

    1
    2
    3
    if [  -e "$myPath"]; then
    echo 'ok'
    fi
  2. 逻辑判断

    在 linux 中 命令执行状态:0 为真,其他为假

    操作符 解释
    -eq 等于
    -ne 不等于
    -gt 大于 (greater )
    -lt 小于 (less)
    -ge 大于等于
    -le 小于等于

[[的用法示例

1
2
3
4
5
#比较两个字符串

if [[ "$a" > "$b" ]];then
echo ''
fi

数值运算

在 bool 运算中使用((expression))

1
2
3
4
5
6
7
8
9
10
var=10
if (( $var ** 2 > 90))
then
echo ok
fi


num=$(( 1 + 1))
# 等效
num=$[ 1 + 1 ]

case

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
case 值 in
模式1)
command1
command2
command3
;;
模式2|模式3)
command1
command2
command3
;;
*)
command1
command2
command3
;;
esac

变量

变量引用

一般情况下使用 $variable来引用变量值,它是${variable}的一种缩写

  • ${VALUE:-WORD}:当变量未定义或者值为空时,返回值为 WORD 的内容,否则返回变量的值。
  • ${VALUE:=WORD}:当变量未定义或者值为空时,返回 WORD 的值的同时并将 WORD 赋值给 VALUE,否则返回变量的值。
  • ${VALUE:+WORD}:当变量已赋值时,其值才用 WORD 替换,否则不进行任何替换。
  • ${VALUE:?MESSAGE}:当变量已赋值时,正常替换。否则将消息 MESSAGE 送到标准错误输出(若此替换出现在 SHELL 程序中,那么该程序将终止运行)

    1
    2
    3
    4
    5
    6
    7
    8
    #当变量为空时,name 就为 hello
    name=${variable:-hello}
    #当变量为空时,variable被赋为hello,且返回hello
    name=${variable:=hello}
    #当变量为不为空时,返回hello
    name=${variable:+hello}
    #当变量为不为空时,variable被赋为hello,否则输出错误信息
    name=${variable:+hello}

基于位置的参数

命令可以作为参数传入 shell 脚本中

1
2
3
echo $1
echo $2
$1 $2

shell 脚本可以读取执行时传入的参数

  • $0 表示shell脚本本身
  • $[n] 表示shell第几个参数,$10 不能获取第十个参数,获取第十个参数需要${10}。当 n>=10 时,需要使用${n}来获取参数。
  • $* 表示所有参数,将所有参数视为一个单词
  • $@ 表示所有参数,将所有参数视为一个数组
  • ${@:n} 表示除 n 前的所有参数,例如${@:2}表示除了第一个参数之外的所有参数
  • $# 获取变量个数
  • $? 显示最后命令的退出状态。0 表示没有错误,其他任何值表明有错误 |code|描述| |:-|:-| |0|成功执行| |1|常规错误| |2|shell 错误使用| |126|无法执行| |127|找不到命令| |128|错误的退出参数| |128+|linux 信号错误| |130+|命令被 Ctl-C 终止| |255|退出状态溢出|
  • $$ 脚本运行的当前进程 ID 号
  • $! 后台运行的最后一个进程的 ID 号
  • * 表示当前目录所有文件,相当于 ls
  • shift n向左移动 n 个参数

    1
    2
    3
    4
    5
    6
    7
    ~$ cat 1.sh
    echo $_
    shift
    echo \$_
    ~$ sh 1.sh 1 2 3
    1 2 3
    2 3

getopts 获取参数

getopts(shell 内置命令),不能直接处理长命令(如:--prefix=/home 等,如有需要可以使用 getopt)

  • getopts 有两个参数,第一个参数是一个字符串,包括字符和:,每一个字符都是一个有效的选项,getopts 从命令中获取这些参数,并且删去了 -
  • 字符后面带有:,表示这个字符有自己的参数(有自己参数的选项,不能和别的选项写在一起)。
  • $OPTARG 总是存储有参数的选项的参数值
  • $OPTIND总是存储原始参数$*下一个要处理的元素位置
  • 第一个冒号表示忽略错误
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
#!/bin/bash

echo original parameters=[$*]
echo original OPTIND=[$OPTIND]
while getopts ":a:bc" opt
do
case $opt in
a)
echo "this is -a option. OPTARG=[$OPTARG] OPTIND=[$OPTIND]"
;;
b)
echo "this is -b option. OPTARG=[$OPTARG] OPTIND=[$OPTIND]"
;;
c)
echo "this is -c option. OPTARG=[$OPTARG] OPTIND=[$OPTIND]"
;;
?)
echo "there is unrecognized parameter."
exit 1
;;
esac
done
#通过shift $(($OPTIND - 1))的处理,$*中就只保留了除去选项内容的参数,
#可以在后面的shell程序中进行处理
shift $(($OPTIND - 1))

echo remaining parameters=[$*]
echo \$1=[$1]
echo \$2=[$2]

# 执行脚本
$ bash getopts.sh -a 12 -b -c file1 file2
original parameters=[-a 12 -b -c file1 file2]
original OPTIND=[1]
this is -a option. OPTARG=[12] OPTIND=[3]
this is -b option. OPTARG=[] OPTIND=[4]
this is -c option. OPTARG=[] OPTIND=[5]
remaining parameters=[file1 file2]
$1=[file1]
$2=[file2]


#可以z这样缩写
$ bash getopts.sh -bca 12 file1 file2
original parameters=[-bca 12 file1 file2]
original OPTIND=[1]
this is -b option. OPTARG=[] OPTIND=[1]
this is -c option. OPTARG=[] OPTIND=[1]
this is -a option. OPTARG=[12] OPTIND=[3]
remaining parameters=[file1 file2]

#如果这样写就不对
$ bash getopts.sh -abc 12 file1 file2
original parameters=[-abc 12 file1 file2]
original OPTIND=[1]
this is -a option. OPTARG=[bc] OPTIND=[2]
remaining parameters=[12 file1 file2]
$1=[12]
$2=[file1]

标准的参数选项

linux 有一些标准的参数选项,通过该选项我们大概可以知道该参数的含义

option description
-a 显示所有
-c 计数
-d 指定一个目录
-e 扩展
-f 读取一个指定的文件
-h 帮助信息
-i 忽略大小写
-l 展示一个长格式的输出信息
-n 使用非交互模式
-o 指定一个输出文件
-q 不打印输出
-r 递归
-s 不打印输入
-v 输出详细信息
-x 包含某个对象

数组 arrays

1
2
3
4
5
6
7
8
9
10
11
12
13
14
a=(1 2 3 4)
echo $a #取第一个元素
echo ${a[1]} #取角标为1的元素
a[1]=100 #赋值
echo ${a[*]} #取所有元素
unset a[1] #移除角标1的元素
echo ${a[*]}
unset a #移除整个array
echo ${a[*]}
# 1
# 2
# 1 100 3 4
# 1 3 4
#
1
2
3
4
5
6
7
8
9
10
#!/bin/bash

string="hello,shell,split,test"
array=(${string//,/ })

# 迭代数组
for var in ${array[@]}
do
echo $var
done

字符串变量相关

  • 字符串长度

    1
    len=${#variable}
  • ${variable#*string}从左往右,删除最短的一个以 string 结尾的子串,即截取第一个 string 子串之后的字符串
  • ${variable##*string}从左往右,删除最长的一个以 string 结尾的子串,即截取最后一个 string 子串之后的字符串
  • ${variable%string*}从右往左,删除最短的一个以 string 开头的子串,即截取最后一个 string 子串之前的字符串
  • ${variable%%string*}从右往左,删除最长的一个以 string 开头的子串,即截取第一个 string 子串之前的字符串
  • ${variable/OLD/NEW}或${variable//OLD/NEW}:用 NEW 子串替换 VALUE 字符串中匹配的 OLD 子串。

1
2
3
4
5
TEST=123abc456abc789
echo ${TEST#*abc} #删掉 123abc 剩下 456abc789
echo ${TEST##*abc} #删掉 123abc456abc 剩下"789
echo ${TEST%abc*} #删掉 abc789 剩下 123abc456
echo ${TEST%%abc*} #删掉 abc456abc789 剩下 123

固定位置截取

${varible:start:len}:截取变量 varible 从位置 start 开始长度为 len 的子串。第一个字符的位置为 0。

1
2
3
TEST=123abc456abc789
echo ${TEST:0:3} #123
echo ${TEST:3:3} #abc"

引号

  1. 单引号'',被称作弱引用,在'内的字符串会被直接使用,不会被替换。

    1
    2
    3
    echo ''\'''
    #输出结果
    # '

    在单引号中转义单引号

    1
    2
    3
    alias rxvt='urxvt -fg '"'"'#111111'"'"' -bg '"'"'#111111'"'"
    # ^^^^^ ^^^^^ ^^^^^ ^^^^
    # 12345 12345 12345 1234

    上述转义是如何被解析的

    1. ' 第一个引用的结束,使用单引号
    2. " 第二个引用的开始,使用双引号
    3. ' 被引用的字符
    4. " 第二个引用的结束,使用双引号
    5. ' 第三个引用的开始
  2. 双引号"",被称做强引用,在"的字符串的变量引用会被直接替换为实际的值

  3. 反引号``, 反引号括起来的字串被 Shell 解释为命令,在执行时,Shell 首先执行该命令,并以它的标准输出结果取代整个反引号(包括两个反引号)部分,也可以使用$()达到同样的效果,shell 会以子进程的方式去调用被替换的命令,其替换后的值为子进程命令的 stdout 输出,其文件描述符为1,

容错断言

如果一个或多个必要的环境变量没被设置的话, 就打印错误信息.下面是两种方式

1
2
${HOSTNAME?}  #正常
${HOSTNAME1?} #HOSTNAME1: parameter null or not set

设置静态变量

1
readonly MY_PATH=/usr/bin

变量使用*

在编写 shell 脚本的过程中,有的时候难免会用到一些变量值被定义为(*)的变量,但是当我们试图引用这个变量的时候 bash 有默认会把(*)替换成当前目录下的所有文件名的列表,如下:

1
2
3
4
5
[root@vm_102 ~]# a=*
[root@vm_102 ~]# echo $a
anaconda-ks.cfg install.log install.log.syslog
[root@vm_102 ~]# ls
anaconda-ks.cfg install.log install.log.syslog

这个时候我们可以考虑一个问题:这里的(*)是在哪一步被替换成当前目录下面的文件列表的呢:是在第一步,变量赋值的时候就被替换的呢还是说,在 echo 变量值的时候被替换的呢? 事实是这样子的: 1、当变量复制的时候,bash 会直接将(*)赋值给变量 a; 2、但是在第二步引用变量的时候,bash 默认会把(*)替换成当前目录下的所有文件的列表,大家可以这么实验一下:

1
2
[root@vm_102 ~]# echo *
anaconda-ks.cfg install.log install.log.syslog

捕获信号

1
2
3
4
5
6
#!/bin/bash
#捕获EXIT信号
trap "echo byebye" EXIT
sleep 100;
#移除捕获EXIT信号
trap - EXIT
1
2
3
4
# 列出所有信号,trap可以使用信号的编号
$ trap -l
# 向某个进程发出指定信号
$ kill -s <signal> <pid>

当使用 ctl-c 中断时,会执行 trap 里的命令

忽略信号:

1
2
#如果陷阱列出的命令是空的,指定的信号接收时,将被忽略。例如,下面的命令:
$ trap '' 2

函数

shell 中函数的有两种方式

1
2
3
4
5
6
7
8

function name{
commands
}

name(){
commands
}
  1. 返回值(仅允许为 0~255 的数值),可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。
  2. 函数返回值在调用该函数后通过 $? 来获得。
  3. 调用函数时可以向其传递参数。在函数体内部,通过 $n 的形式来获取参数的值,例如,$1 表示第一个参数,$2 表示第二个参数
  4. 函数的输出可以使用替换命令来得到,即使用`command`或$(command)(不是所有 linux 都支持$())
  5. 可以使用local声明函数内部的变量,使其作用域仅限于函数内

示例

1
2
3
4
5
6
7
fun(){
echo $1
return 1
}

fun 'hello'
echo $?

引入外部脚本

格式 . filename 注意点号.和文件名中间有一空格 或 source filename

脚本运行时参数

在写脚本的一开始加上set -xeuo pipefail,一般用于调试脚本

示例

1
2
3
#!/bin/bash
set -x
...

-x: 在执行每一个命令之前把经过变量展开之后的命令打印出来 -e: 在遇到一个命令失败时,立即退出 -u: 试图使用未经定义的变量,立即退出 -o pipefail: 只要管道中的一个子命令失败,整个管道命令就失败。

循环

c 风格的 for 循环

1
2
3
4
for (( a=1;a<10;a++))
do
echo $a;
done

break 和 continue

break n可以跳出 n 层循环,continue n的用法类似

1
2
3
4
5
6
7
8
9
10
11
for (( a=1;a<5;a++))
do
for (( b=1;b<5;b++))
do
if [ $a -gt 2 ] && [ $b -gt 3 ]
then
break 2
# continue 2
fi
done
done

done

可以在 done 处使用重定向符,管道符,类似的用法可以在 if 的结束 fi 上使用,case 的 esac 处使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for (( a=1;a<100;a++))
do
echo $a;
done |grep 5

for (( a=1;a<100;a++))
do
echo $a;
done > 1.txt


if [ -n "$1" ]
then
echo $1
fi > 1.txt

数据库相关

查询数据库字段将其赋值给变量

1
2
3
4
5
6
7
db2 "connect to ${1} user ${2} using ${3}"
db2 "set schema ${4}"

db2 -x "select status,timestamp from results where id = 1" | while read status timestamp ; do
echo $status
echo $timestamp
done

图形化

菜单

select 命令只需要一条命令就可以创建出菜单,然后获取输入的答案并自动处理。 命令格式如下:

1
2
3
4
select variable in list
do
commands
done

list 参数是由空格分隔的文本选项列表,这些列表构成了整个菜单。select 命令会将每个列表项显示成一个带编号的选项,然后为选项显示一个由 PS3 环境变量定义的特殊提示符。

例如:

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
#!/bin/bash
# using select in the menu

function diskspace {
clear
df -k
}

function whoseon {
clear
who
}

function memusage {
clear
cat /proc/meminfo
}

PS3="Enter an option: "
select option in "Display disk space" "Display logged on users" "Display memory usage" "Exit program"
do
case $option in
"Exit program")
break ;;
"Display disk space")
diskspace ;;
"Display logged on users")
memusage ;;
"Display memory usage")
memusage ;;
*)
clear
echo "Sorry, wrong selection";;
esac
done
clear

wsx@wsx:~/tmp$ ./smenu1

  1. Display disk space 3) Display memory usage
  2. Display logged on users 4) Exit program

免密

sudo 执行不输密码

在/etc/sudoers最后一行新增规则,即可达到免密执行

1
2
# li 为登陆用户
li ALL=(root) NOPASSWD: /root/dhclient.sh

错误问题

执行 sh 脚本时报错

':command not found

这是因为在 win 上的格式有问题,使用 dos2unix 命令转换一下脚本即可

python入门

发表于 2019-10-10 更新于 2020-06-30 分类于 python

class 实例的方法,第一个参数自动转换为实例的索引

第一个参数不一定非要是self

类方法

直接使用Class.method(instance),参数需要显式的传递实例对象

python 对重载运算符

什么是运算符重载 让自定义的类生成的对象(实例)能够使用运算符进行操作 作用: 让自定义的实例像内建对象一样进行运算符操作 让程序简洁易读 对自定义对象将运算符赋予新的规则

算术运算符的重载

方法名                   运算符和表达式       说明

1
2
3
4
5
6
7
8
9
__add__(self,rhs)        self + rhs        加法
__sub__(self,rhs)        self - rhs         减法
__mul__(self,rhs)        self * rhs         乘法
__truediv__(self,rhs)   self / rhs          除法
__floordiv__(self,rhs)  self //rhs          地板除
__mod__(self,rhs)       self % rhs       取模(求余)
__pow__(self,rhs)       self **rhs         幂运算
__or__(self, other) self | other 或运算
__and__(self, other) self & other 与运算

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Mynumber:
def __init__(self,v):
self.data = v
def __repr__(self): #消除两边的尖括号
return "Mynumber(%d)"%self.data

def __add__(self,other):
'''此方法用来制定self + other的规则'''

v = self.data + other.data
return Mynumber(v) #用v创建一个新的对象返回给调用者

def __sub__(self,other):
'''此方法用来制定self - other的规则'''
v = self.data - other.data
return Mynumber(v)

n1 = Mynumber(100)
n2 = Mynumber(200)
# n3 = n1 + n2
n3 = n1+n2 # n3 = n1.__add__(n2)
print(n3) #Mynumber(300)
n4 = n3 - n2 #等同于n4 = n3.__sub__(n2)

反向运算符的重载

当运算符的左侧为内建类型时,右侧为自定义类型进行算术匀算符运算时会出现 TypeError 错误,因为无法修改内建类型的代码          实现运算符重载,此时需要使用反向运算符的重载

方法名                   运算符和表达式       说明

1
2
3
4
5
6
7
**radd**(self,lhs)       lhs + self       加法
**rsub**(self,lhs)       lhs - self       减法
**rmul**(self,lhs)       lhs \* self       乘法
**rtruediv**(self,lhs)   lhs / self       除法
**rfloordiv**(self,lhs)  lhs // self       地板除
**rmod**(self,lhs)       lhs % self       取模(求余)
**rpow**(self,lhs)       lhs \*\* self       幂运算

比较算术运算符的重载

方法名                   运算符和表达式       说明

1
2
3
4
5
6
__lt__(self,rhs)       self < rhs        小于
__le__(self,rhs)       self <= rhs       小于等于
__gt__(self,rhs)       self > rhs        大于
__ge__(self,rhs)       self >= rhs       大于等于
__eq__(self,rhs)       self == rhs       等于
__ne__(self,rhs)       self != rhs       不等于

位运算符重载

方法名               运算符和表达式         说明

1
2
3
4
5
__and__(self,rhs)       self & rhs           位与
__or__(self,rhs)        self | rhs              位或
__xor__(self,rhs)       self ^ rhs             位异或
__lshift__(self,rhs)    self <<rhs            左移
__rshift__(self,rhs)    self >>rhs            右移

其他运算符重载

in/not in 运算符重载 注: in / not in 返回布尔值 True / False 当重载了__contains__后,in 和 not in 运算符都可用 not in 运算符的返回值与 in 相反

1
__contains__(self,e):

父类构造器

python不会自动调用父类构造器,需要显式的调用

1
2
3
4
5
6
7
8
9
10
11
class SongBird(Bird):

def __init__(self):

Bird.__init__(self)

self.sound = 'Squawk'

def sing(self):

print self.sound

class 属性

定义在class方法外的属性,method本身也属于class属性

断言

断言自定义提示信息

1
assert x >= 0, 'x is less than zero'

更新PIP

python -m pip install -U pip

XML解析

1
2
3
4
5
6
7
8
9
10
11
12
import xml.etree.ElementTree as ET

tree = ET.parse("country.xml")
root = tree.getroot()
root.tag
root.attrlib

find(match) # 查找第一个匹配的子元素, match可以时tag或是xpaht路径
findall(match # 返回所有匹配的子元素列表
findtext(match, default=None)
iter(tag=None) # 以当前元素为根节点 创建树迭代器,如果tag不为None,则以tag进行过滤
iterfind(match)

调用其他 py 文件方法

1
2
3
import other

other.m()

动态调用方法

在py文件中,可以使用

1
2
3
4
def func(arg1,arg2):
pass

globals()['func'](1,2)

调用class方法,可以使用

1
2
3
4
5
6
7
class Data:
def func(self,arg1,arg2):
pass

data = Data()
func = getattr(data,'func')
func(1,2)

字符串格式化

python可以使用''' str ''',来进行纯字符串赋值,而不需要考虑转译字符。 python字符串可定义占位符,通过format函数进行格式化

1
2
print('{}1{}'.format(0,3))
print('{a}1{b}'.format(** {"a":1,"b":3,}))

json格式化输出

1
2
3
4
import json
str = '{"foo":"bar","name":"he"}'
parsed = json.loads(str)
print(json.dumps(parsed,indent=4,sort_keys=True))

常用函数

zip将多个数组打包成元组

1
2
3
4
a = [1,2,3]
b = [4,5,6,7,8]
zipped =zip(a,b) # 元素个数与最短的列表一致
zip(*zipped) # 与 zip 相反,*zipped 可理解为解压,返回二维矩阵式

list转换为数组

很多常用函数函数的不是直接的数组,比如map,filter等,需要在使用list直接转换为数组

enumerate函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标

1
2
3
4
5
enumerate(sequence, [start=0])

seasons = ['Spring', 'Summer', 'Fall', 'Winter']
list(enumerate(seasons))
#[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]

返回多个值

1
2
3
4
def multi():
return 1,2

x,y = multi()

实际上 python 返回的是一个tulpe,在语法上,返回一个 tuple 可以省略括号,而多个变量可以同时接收一个 tuple,按位置赋给对应的值,所以,Python 的函数返回多值其实就是返回一个 tuple,但写起来更方便.

获取方法文档注释

1
my_func.__doc__

获取python版本

1
2
3
import sys
if sys.version_info[0] < 3:
raise Exception("Must be using Python 3")

枚举

python3.4版本支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from enum import Enum


class Direction(Enum):
LEFT = "left"
RIGHT = "right"
UP = "up"
DOWN = "down"


def move(direction):

# Type checking
if not isinstance(direction, Direction):
raise TypeError('direction must be an instance of Direction Enum')

print(direction.value)

move(Direction.LEFT)#left
move("right")#TypeError: direction must be an instance of Direction Enum
print({d.name: d.value for d in Direction})
print(Direction('up').name)#UP

获取所有方法

1
print([func for func in dir(Func) if callable(getattr(Func,func))])

打印方法的所有参数

1
2
import inspect
print(inspect.getfullargspec(a_method))

参数

可变参数

  1. 当我们声明一个星号的参数,如*param,那么从这一点开始到结束的所有位置的参数都被收集到一个叫param的元组中。
  2. 同样,当我们声明一个双星参数,如**param,那么从那一点开始到结束的所有关键字参数都被收集到一个叫param的字典中。
  3. 当我们调用方法时显示的使用*,即表示将当前数组展开为多个参数。**同理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

a = [1, 2, 3]


def m1(*arg):
print(arg)
pass


m1(1, 2)
m1(a)
m1(*a)


def m2(arg):
print(arg)
pass


m2(a)
m2(*a)

执行结果

(1, 2) ([1, 2, 3],) (1, 2, 3) [1, 2, 3] Traceback (most recent call last): File "/Users/li/Downloads/test.py", line 21, in <module> m2(*a)

调用方法显示使用**

1
2
3
4
5
6
7
8
9
10
11
12
para = {'a': 1, 'b': 2}


def m(** para):

print(para)
pass


m(**para)
print('-----------------')
m(para)

{'a': 1, 'b': 2} '----------------- Traceback (most recent call last): File "/Users/li/Downloads/test.py", line 12, in <module> m(para)

默认参数

1
2
3
4
5
6
7
def log(level='debug'):

print(level)

log()
log('hello')
log(level='info')

debug hello info

全局变量

全局变量需要在外部声明,在方法内部使用时需要在方法内部使用 global 申声明

1
2
3
4
5
6
7
8
9
10
11
12
name = None
def foo():
global name
print(name)

def bar():
global name
name ='bar'

foo()
bar()
foo()

None bar

开启一个简单的 http 服务

python2 或者低版本,直接敲

1
python -m SimpleHTTPServer <port>

python3

1
python -m http.server <port>

jquery相关

发表于 2019-09-10 更新于 2020-06-30 分类于 前端

引入公共页面

1
<div id="header"></div>
1
$("#header").load("header.html");

aop概念

发表于 2019-09-04 更新于 2020-12-02 分类于 java

术语

  1. 连接点 Join Point java 运行过程中的位置,对于Spring来说,表示允许使用切面的位置,它一般是指方法运行的过程,比如运行前,运行后,抛出异常等。
  2. 通知 Advice 在某连接点上执行的特殊操作
  3. 切入点 PointCut 切入点通过表达式来连接通知和连接点,确定连接点是否符合切入点表达式
  4. 目标类 Target 被切面切入的类
  5. 代理类 AOP proxy 实现切面的方式即实现目标类的代理类对象,对于Spring来说,通过JDK动态代理和CGLIB字节码代理
  6. 织入 Weaving 把切面应用到目标对象来创建新的代理对象的过程

JDK动态代理

JDK动态代理仅支持代理接口(因为代理类继承Proxy,而java不支持多继承)。 由JDK动态生成继承接口的子类,子类的所有方法调用都由创建代理类时使用的参数java.lang.reflect.InvocationHandler的invoke方法去完成。

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
package com.li.springboot.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.function.Supplier;

public class DJKProxy<T> implements InvocationHandler {

private T t;

public DJKProxy(T t) {
this.t = t;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("method = " + method);
return method.invoke(t, args);
}

public static void main(String[] args) {
//设置为true时,将会在项目跟目录生成代理类的源码
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
Supplier supplier = () -> 1;
DJKProxy<Supplier> proxy = new DJKProxy<>(supplier);
Supplier proxyInstance = (Supplier) Proxy.newProxyInstance(DJKProxy.class.getClassLoader(), supplier.getClass().getInterfaces(), proxy);
Object result = proxyInstance.get();
System.out.println("proxyInstance = " + proxyInstance);
System.out.println("result = " + result);
}
}

下面是自动生成的代理类反编译的源码

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
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.sun.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.function.Supplier;

public final class $Proxy0 extends Proxy implements Supplier {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;

public $Proxy0(InvocationHandler var1) throws {
super(var1);
}

public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

public final Object get() throws {
try {
return (Object)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("java.util.function.Supplier").getMethod("get");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}

我们可以看到实际所有方法的调用都由InvocationHandler去调用,我们查看下Proxy.newProxyInstance的细节

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
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);

final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}

/*
* 获取代理类class类型,也就是动态生成代理类的过程
*/
Class<?> cl = getProxyClass0(loader, intfs);

/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
/**
* 获取使用InvocationHandler作为参数的构造器,并且实例化
*/
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
1
2
3
4
5
6
7
8
9
10
11
 private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
//根据接口返回代理类,当前classloader已经存在则直接返回缓存
return proxyClassCache.get(loader, interfaces);
}
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
public V get(K key, P parameter) {
Objects.requireNonNull(parameter);

expungeStaleEntries();

Object cacheKey = CacheKey.valueOf(key, refQueue);

ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
if (valuesMap == null) {
ConcurrentMap<Object, Supplier<V>> oldValuesMap
= map.putIfAbsent(cacheKey,
valuesMap = new ConcurrentHashMap<>());
if (oldValuesMap != null) {
valuesMap = oldValuesMap;
}
}

Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
Supplier<V> supplier = valuesMap.get(subKey);
Factory factory = null;

while (true) {
if (supplier != null) {
V value = supplier.get();
if (value != null) {
return value;
}
}
if (factory == null) {
factory = new Factory(key, parameter, subKey, valuesMap);
}

if (supplier == null) {
supplier = valuesMap.putIfAbsent(subKey, factory);
if (supplier == null) {
supplier = factory;
}
} else {
if (valuesMap.replace(subKey, supplier, factory)) {
supplier = factory;
} else {
supplier = valuesMap.get(subKey);
}
}
}
}

通过断点debug,我们可以得出supplier的类为class java.lang.reflect.WeakCache$Factory,查看其get方法,

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
public synchronized V get() { // serialize access
// re-check
Supplier<V> supplier = valuesMap.get(subKey);
if (supplier != this) {
// the loop
return null;
}
V value = null;
try {
//根据上面的代码可知valueFactory为ProxyClassFactory
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
} finally {
if (value == null) { // remove us on failure
valuesMap.remove(subKey, this);
}
}
assert value != null;

CacheValue<V> cacheValue = new CacheValue<>(value);

if (valuesMap.replace(subKey, this, cacheValue)) {
reverseMap.put(cacheValue, Boolean.TRUE);
} else {
throw new AssertionError("Should not reach here");
}
return value;
}

查看ProxyClassFactory的apply方法,

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
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}

String proxyPkg = null; // package to define proxy class in
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}

if (proxyPkg == null) {
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}

long num = nextUniqueNumber.getAndIncrement();
//代理类的类名 com.sun.proxy. + $Proxy + 1
String proxyName = proxyPkg + proxyClassNamePrefix + num;

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
//加载类
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
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
 private final static boolean saveGeneratedFiles =java.security.AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles")).booleanValue();
public static byte[] generateProxyClass(final String name, Class<?>[] interfaces,int accessFlags){
ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
//生成类
final byte[] classFile = gen.generateClassFile();

if (saveGeneratedFiles) {//根据sun.misc.ProxyGenerator.saveGeneratedFiles决定是否输出代理类的文件
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
try {
int i = name.lastIndexOf('.');
Path path;
if (i > 0) {
Path dir = Paths.get(name.substring(0, i).replace('.', File.separatorChar));
Files.createDirectories(dir);
path = dir.resolve(name.substring(i+1, name.length()) + ".class");
} else {
path = Paths.get(name + ".class");
}
Files.write(path, classFile);
return null;
} catch (IOException e) {
throw new InternalError(
"I/O exception saving generated file: " + e);
}
}
});
}

return classFile;
}
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
private byte[] generateClassFile() {

/*
* 注册Object方法的代理方法
*/
addProxyMethod(hashCodeMethod, Object.class);
addProxyMethod(equalsMethod, Object.class);
addProxyMethod(toStringMethod, Object.class);

/*
* 注解接口方法的代理方法
*/
for (Class<?> intf : interfaces) {
for (Method m : intf.getMethods()) {
addProxyMethod(m, intf);
}
}

for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
checkReturnTypes(sigmethods);
}

try {
// 生成默认构造器
methods.add(generateConstructor());

for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
for (ProxyMethod pm : sigmethods) {

// 生成静态变量target方法
fields.add(new FieldInfo(pm.methodFieldName,
"Ljava/lang/reflect/Method;",
ACC_PRIVATE | ACC_STATIC));

// 生成代理方法细节,即交由InvocationHandler调用,参数使用了静态属性target方法
methods.add(pm.generateMethod());
}
}

methods.add(generateStaticInitializer());

} catch (IOException e) {
throw new InternalError("unexpected I/O Exception", e);
}

if (methods.size() > 65535) {
throw new IllegalArgumentException("method limit exceeded");
}
if (fields.size() > 65535) {
throw new IllegalArgumentException("field limit exceeded");
}

cp.getClass(dotToSlash(className));
cp.getClass(superclassName);
for (Class<?> intf: interfaces) {
cp.getClass(dotToSlash(intf.getName()));
}
cp.setReadOnly();
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);

try {
// u4 magic;
dout.writeInt(0xCAFEBABE);
// u2 minor_version;
dout.writeShort(CLASSFILE_MINOR_VERSION);
// u2 major_version;
dout.writeShort(CLASSFILE_MAJOR_VERSION);

cp.write(dout); // (write constant pool)

// u2 access_flags;
dout.writeShort(accessFlags);
// u2 this_class;
dout.writeShort(cp.getClass(dotToSlash(className)));
// u2 super_class;
dout.writeShort(cp.getClass(superclassName));

// u2 interfaces_count;
dout.writeShort(interfaces.length);
// u2 interfaces[interfaces_count];
for (Class<?> intf : interfaces) {
dout.writeShort(cp.getClass(
dotToSlash(intf.getName())));
}

// u2 fields_count;
dout.writeShort(fields.size());
// field_info fields[fields_count];
for (FieldInfo f : fields) {
f.write(dout);
}

// u2 methods_count;
dout.writeShort(methods.size());
// method_info methods[methods_count];
for (MethodInfo m : methods) {
m.write(dout);
}

// u2 attributes_count;
dout.writeShort(0); // (no ClassFile attributes for proxy classes)

} catch (IOException e) {
throw new InternalError("unexpected I/O Exception", e);
}

return bout.toByteArray();
}
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
/**
* 根据方法名,方法参数类型,方法返回类型,抛出异常来给fromClass增加方法
*/
private void addProxyMethod(Method m, Class<?> fromClass) {
String name = m.getName();
Class<?>[] parameterTypes = m.getParameterTypes();
Class<?> returnType = m.getReturnType();
Class<?>[] exceptionTypes = m.getExceptionTypes();

String sig = name + getParameterDescriptors(parameterTypes);
List<ProxyMethod> sigmethods = proxyMethods.get(sig);
if (sigmethods != null) {
for (ProxyMethod pm : sigmethods) {
if (returnType == pm.returnType) {
List<Class<?>> legalExceptions = new ArrayList<>();
collectCompatibleTypes( exceptionTypes, pm.exceptionTypes, legalExceptions);
collectCompatibleTypes( pm.exceptionTypes, exceptionTypes, legalExceptions);
pm.exceptionTypes = new Class<?>[legalExceptions.size()];
pm.exceptionTypes = legalExceptions.toArray(pm.exceptionTypes);
return;
}
}
} else {
sigmethods = new ArrayList<>(3);
proxyMethods.put(sig, sigmethods);
}
sigmethods.add(new ProxyMethod(name, parameterTypes, returnType, exceptionTypes, fromClass));
}

为接口实现一个默认类

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
static <T> T beanInstance(Class<T> _interface, Object ... args) {
//...
return null;
}

static <T> T plugin(Class<T> _interface, Object ... args) {

if (_interface.isInterface()) {
T target = beanInstance(_interface, args);

InvocationHandler invocationHandler = null;
if (target == null) {
invocationHandler = (proxy, method, params) -> null;
} else {
invocationHandler = (proxy, method, params) -> method.invoke(target, params);

}
return _interface.cast(Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{_interface}, invocationHandler));
}
throw new IllegalArgumentException("only support interface plugin");

}


public static void main(String[] args) {
// 即使没有实现类,也不影响接口调用
InterfaceClass plugin = plugin(InterfaceClass.class);
plugin.test("hello");
}

InvocationHandler 针对 void 方法,可以直接返回 null

CGLIB字节码代理

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;

public class CGlibProxy {
public static void main(String[] args) {
//在指定目录下生成代理类的class文件
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/li/Downloads");
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Target.class);
enhancer.setCallback((MethodInterceptor) (target, method, objects, methodProxy) -> {
System.out.println("method : " + method);
return methodProxy.invokeSuper(target, args);
});
Target target = (Target) enhancer.create();
target.log();
}

}

public class Target {
public void log() {
System.out.println("target");
}
}

反编译生成的字节码

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.li.springboot.aop;

import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class Target$$EnhancerByCGLIB$$1d0dbdbc extends Target implements Factory {
private boolean CGLIB$BOUND;
public static Object CGLIB$FACTORY_DATA;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback[] CGLIB$STATIC_CALLBACKS;
private MethodInterceptor CGLIB$CALLBACK_0;
private static Object CGLIB$CALLBACK_FILTER;
private static final Method CGLIB$log$0$Method;
private static final MethodProxy CGLIB$log$0$Proxy;
private static final Object[] CGLIB$emptyArgs;
private static final Method CGLIB$equals$1$Method;
private static final MethodProxy CGLIB$equals$1$Proxy;
private static final Method CGLIB$toString$2$Method;
private static final MethodProxy CGLIB$toString$2$Proxy;
private static final Method CGLIB$hashCode$3$Method;
private static final MethodProxy CGLIB$hashCode$3$Proxy;
private static final Method CGLIB$clone$4$Method;
private static final MethodProxy CGLIB$clone$4$Proxy;

static void CGLIB$STATICHOOK1() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class var0 = Class.forName("com.li.springboot.aop.Target$$EnhancerByCGLIB$$1d0dbdbc");
Class var1;
Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
CGLIB$equals$1$Method = var10000[0];
CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
CGLIB$toString$2$Method = var10000[1];
CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
CGLIB$hashCode$3$Method = var10000[2];
CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");
CGLIB$clone$4$Method = var10000[3];
CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
CGLIB$log$0$Method = ReflectUtils.findMethods(new String[]{"log", "()V"}, (var1 = Class.forName("com.li.springboot.aop.Target")).getDeclaredMethods())[0];
CGLIB$log$0$Proxy = MethodProxy.create(var1, var0, "()V", "log", "CGLIB$log$0");
}

final void CGLIB$log$0() {
super.log();
}

public final void log() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}

if (var10000 != null) {
var10000.intercept(this, CGLIB$log$0$Method, CGLIB$emptyArgs, CGLIB$log$0$Proxy);
} else {
super.log();
}
}

final boolean CGLIB$equals$1(Object var1) {
return super.equals(var1);
}

public final boolean equals(Object var1) {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}

if (var10000 != null) {
Object var2 = var10000.intercept(this, CGLIB$equals$1$Method, new Object[]{var1}, CGLIB$equals$1$Proxy);
return var2 == null ? false : (Boolean)var2;
} else {
return super.equals(var1);
}
}

final String CGLIB$toString$2() {
return super.toString();
}

public final String toString() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}

return var10000 != null ? (String)var10000.intercept(this, CGLIB$toString$2$Method, CGLIB$emptyArgs, CGLIB$toString$2$Proxy) : super.toString();
}

final int CGLIB$hashCode$3() {
return super.hashCode();
}

public final int hashCode() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}

if (var10000 != null) {
Object var1 = var10000.intercept(this, CGLIB$hashCode$3$Method, CGLIB$emptyArgs, CGLIB$hashCode$3$Proxy);
return var1 == null ? 0 : ((Number)var1).intValue();
} else {
return super.hashCode();
}
}

final Object CGLIB$clone$4() throws CloneNotSupportedException {
return super.clone();
}

protected final Object clone() throws CloneNotSupportedException {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}

return var10000 != null ? var10000.intercept(this, CGLIB$clone$4$Method, CGLIB$emptyArgs, CGLIB$clone$4$Proxy) : super.clone();
}

public static MethodProxy CGLIB$findMethodProxy(Signature var0) {
String var10000 = var0.toString();
switch(var10000.hashCode()) {
case -1097399887:
if (var10000.equals("log()V")) {
return CGLIB$log$0$Proxy;
}
break;
case -508378822:
if (var10000.equals("clone()Ljava/lang/Object;")) {
return CGLIB$clone$4$Proxy;
}
break;
case 1826985398:
if (var10000.equals("equals(Ljava/lang/Object;)Z")) {
return CGLIB$equals$1$Proxy;
}
break;
case 1913648695:
if (var10000.equals("toString()Ljava/lang/String;")) {
return CGLIB$toString$2$Proxy;
}
break;
case 1984935277:
if (var10000.equals("hashCode()I")) {
return CGLIB$hashCode$3$Proxy;
}
}

return null;
}

public Target$$EnhancerByCGLIB$$1d0dbdbc() {
CGLIB$BIND_CALLBACKS(this);
}

public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) {
CGLIB$THREAD_CALLBACKS.set(var0);
}

public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) {
CGLIB$STATIC_CALLBACKS = var0;
}

private static final void CGLIB$BIND_CALLBACKS(Object var0) {
Target$$EnhancerByCGLIB$$1d0dbdbc var1 = (Target$$EnhancerByCGLIB$$1d0dbdbc)var0;
if (!var1.CGLIB$BOUND) {
var1.CGLIB$BOUND = true;
Object var10000 = CGLIB$THREAD_CALLBACKS.get();
if (var10000 == null) {
var10000 = CGLIB$STATIC_CALLBACKS;
if (var10000 == null) {
return;
}
}

var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
}

}

public Object newInstance(Callback[] var1) {
CGLIB$SET_THREAD_CALLBACKS(var1);
Target$$EnhancerByCGLIB$$1d0dbdbc var10000 = new Target$$EnhancerByCGLIB$$1d0dbdbc();
CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
return var10000;
}

public Object newInstance(Callback var1) {
CGLIB$SET_THREAD_CALLBACKS(new Callback[]{var1});
Target$$EnhancerByCGLIB$$1d0dbdbc var10000 = new Target$$EnhancerByCGLIB$$1d0dbdbc();
CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
return var10000;
}

public Object newInstance(Class[] var1, Object[] var2, Callback[] var3) {
CGLIB$SET_THREAD_CALLBACKS(var3);
Target$$EnhancerByCGLIB$$1d0dbdbc var10000 = new Target$$EnhancerByCGLIB$$1d0dbdbc;
switch(var1.length) {
case 0:
var10000.<init>();
CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
return var10000;
default:
throw new IllegalArgumentException("Constructor not found");
}
}

public Callback getCallback(int var1) {
CGLIB$BIND_CALLBACKS(this);
MethodInterceptor var10000;
switch(var1) {
case 0:
var10000 = this.CGLIB$CALLBACK_0;
break;
default:
var10000 = null;
}

return var10000;
}

public void setCallback(int var1, Callback var2) {
switch(var1) {
case 0:
this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2;
default:
}
}

public Callback[] getCallbacks() {
CGLIB$BIND_CALLBACKS(this);
return new Callback[]{this.CGLIB$CALLBACK_0};
}

public void setCallbacks(Callback[] var1) {
this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0];
}

static {
CGLIB$STATICHOOK1();
}
}

具体实现细节类似JDK动态代理,通过某种方式生成字节码文件。

Spring使用AOP

通过BeanPostProcessor返回动态代理的bean,

1
2
3
4
5
6
@Component
public class Target {
public void log() {
System.out.println("target");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
public class AOPBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("bean = " + bean);
if(bean instanceof Target){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(bean.getClass());
enhancer.setCallback((MethodInterceptor) (target, method, args, methodProxy) -> {
System.out.println("BeanPostProcessor");
return methodProxy.invokeSuper(target, args);
});
return enhancer.create();
}
return bean;
}
}

redis

发表于 2019-09-02 更新于 2020-12-02 分类于 db
  1. redis集群
  2. redis管道
  3. redis发布订阅
  4. redis-script
  5. redis-过期时间
  6. redis事务
  7. redis-清除策略
  8. redis-分区
  9. redis-主从复制
  10. redis-持久化

安装

在 centos 上使用 yum 安装

1
2
3
4
5
6
# 安装源
sudo yum install epel-release yum-utils
sudo yum install http://rpms.remirepo.net/enterprise/remi-release-7.rpm
sudo yum-config-manager --enable remi

sudo yum install redis

单点启动

1
2
3
4
5
6
7
8
9
sudo systemctl start redis
sudo systemctl enable redis


# 查看启动状态
sudo systemctl status redis

# 也可以使用redis-server启动,其中redis.conf中可以配置后台进程启动
redis-server redis.conf

可通过查看 redis 日志来查看启动过程或者运行过程中的各种问题

日志开启需要在redis.conf配置

1
logfile /var/log/redis/redis.log

配置

在 redis 客户端可以使用config set设定临时配置,该配置仅临时有效,可通过config rewrite将临时配置写入到redis.conf中使其永久有效

  1. maxmemory 设定最大内存使用

    1
    2
    # 设置为5m
    maxmemory 5m
  2. maxmemory_policy 过期策略

使用

redis 的备份文件 dump.db 在启动 redis-server 的目录下直接生成

使用shell执行redis命令

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

#标准语法
redis-cli keys '*'

#
echo keys '*'|redis-cli -h '127.0.0.1' -p 6379 -a 'password'

# 逐行批量执行
cat commands.txt|redis-cli -h '127.0.0.1' -p 6379 -a 'password'


# 加载lua脚本执行,可带参数
$ cat /tmp/script.lua
return redis.call('set',KEYS[1],ARGV[1])
$ redis-cli --eval /tmp/script.lua foo , bar
OK
  • 查询所有key,以及过期时间,省略ip,端口,密码

    1
    echo keys '*' |redis-cli |awk '{print "echo -n "$1    " && echo ttl " $1 " |redis-cli"}'|sh
  • 查看占用内存较多的 key

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    ~$ redis-cli --bigkeys

    # Scanning the entire keyspace to find biggest keys as well as
    # average sizes per key type. You can use -i 0.1 to sleep 0.1 sec
    # per 100 SCAN commands (not usually needed).

    [00.00%] Biggest string found so far 'hello3' with 3 bytes

    -------- summary -------

    Sampled 3 keys in the keyspace!
    Total key length in bytes is 17 (avg len 5.67)

    Biggest string found 'hello3' has 3 bytes

    0 lists with 0 items (00.00% of keys, avg size 0.00)
    0 hashs with 0 fields (00.00% of keys, avg size 0.00)
    3 strings with 6 bytes (100.00% of keys, avg size 2.00)
    0 streams with 0 entries (00.00% of keys, avg size 0.00)
    0 sets with 0 members (00.00% of keys, avg size 0.00)
    0 zsets with 0 members (00.00% of keys, avg size 0.00)
  • 查看运行时相关信息

    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
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    127.0.0.1:6379> info
    # Server
    redis_version:6.0.3
    redis_git_sha1:00000000
    redis_git_dirty:0
    redis_build_id:eb1391459d1a8dbb
    redis_mode:standalone
    os:Linux 3.10.0-1062.el7.x86_64 x86_64
    arch_bits:64
    multiplexing_api:epoll
    atomicvar_api:atomic-builtin
    gcc_version:8.3.1
    process_id:6077
    run_id:963b3a75b32efa8801d755a1d8ef0129a009ffc8
    tcp_port:6379
    uptime_in_seconds:307
    uptime_in_days:0
    hz:10
    configured_hz:10
    lru_clock:6921052
    executable:/root/redis-server
    config_file:/etc/redis.conf

    # Clients
    connected_clients:1
    client_recent_max_input_buffer:2
    client_recent_max_output_buffer:0
    blocked_clients:0
    tracking_clients:0
    clients_in_timeout_table:0

    # Memory
    used_memory:867312
    used_memory_human:846.98K
    used_memory_rss:3375104
    used_memory_rss_human:3.22M
    used_memory_peak:867312
    used_memory_peak_human:846.98K
    used_memory_peak_perc:100.18%
    used_memory_overhead:820850
    used_memory_startup:803712
    used_memory_dataset:46462
    used_memory_dataset_perc:73.05%
    allocator_allocated:1019552
    allocator_active:1335296
    allocator_resident:3698688
    total_system_memory:1922482176
    total_system_memory_human:1.79G
    used_memory_lua:37888
    used_memory_lua_human:37.00K
    used_memory_scripts:0
    used_memory_scripts_human:0B
    number_of_cached_scripts:0
    maxmemory:0
    maxmemory_human:0B
    maxmemory_policy:noeviction
    allocator_frag_ratio:1.31
    allocator_frag_bytes:315744
    allocator_rss_ratio:2.77
    allocator_rss_bytes:2363392
    rss_overhead_ratio:0.91
    rss_overhead_bytes:-323584
    mem_fragmentation_ratio:4.09
    mem_fragmentation_bytes:2550304
    mem_not_counted_for_evict:0
    mem_replication_backlog:0
    mem_clients_slaves:0
    mem_clients_normal:16986
    mem_aof_buffer:0
    mem_allocator:jemalloc-5.1.0
    active_defrag_running:0
    lazyfree_pending_objects:0

    # Persistence
    loading:0
    rdb_changes_since_last_save:3
    rdb_bgsave_in_progress:0
    rdb_last_save_time:1600756265
    rdb_last_bgsave_status:ok
    rdb_last_bgsave_time_sec:-1
    rdb_current_bgsave_time_sec:-1
    rdb_last_cow_size:0
    aof_enabled:0
    aof_rewrite_in_progress:0
    aof_rewrite_scheduled:0
    aof_last_rewrite_time_sec:-1
    aof_current_rewrite_time_sec:-1
    aof_last_bgrewrite_status:ok
    aof_last_write_status:ok
    aof_last_cow_size:0
    module_fork_in_progress:0
    module_fork_last_cow_size:0

    # Stats
    total_connections_received:4
    total_commands_processed:22
    instantaneous_ops_per_sec:0
    total_net_input_bytes:633
    total_net_output_bytes:18936
    instantaneous_input_kbps:0.00
    instantaneous_output_kbps:0.00
    rejected_connections:0
    sync_full:0
    sync_partial_ok:0
    sync_partial_err:0
    expired_keys:0
    expired_stale_perc:0.00
    expired_time_cap_reached_count:0
    expire_cycle_cpu_milliseconds:5
    evicted_keys:0
    keyspace_hits:12
    keyspace_misses:0
    pubsub_channels:0
    pubsub_patterns:0
    latest_fork_usec:0
    migrate_cached_sockets:0
    slave_expires_tracked_keys:0
    active_defrag_hits:0
    active_defrag_misses:0
    active_defrag_key_hits:0
    active_defrag_key_misses:0
    tracking_total_keys:0
    tracking_total_items:0
    tracking_total_prefixes:0
    unexpected_error_replies:0

    # Replication
    role:master
    connected_slaves:0
    master_replid:4b6a97cd5859089f722814036486d8d06bbf8432
    master_replid2:0000000000000000000000000000000000000000
    master_repl_offset:0
    master_repl_meaningful_offset:0
    second_repl_offset:-1
    repl_backlog_active:0
    repl_backlog_size:1048576
    repl_backlog_first_byte_offset:0
    repl_backlog_histlen:0

    # CPU
    used_cpu_sys:0.233602
    used_cpu_user:0.263460
    used_cpu_sys_children:0.000000
    used_cpu_user_children:0.000000

    # Modules

    # Cluster
    cluster_enabled:0

    # Keyspace
    db0:keys=3,expires=0,avg_ttl=0

布隆过滤器

本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure),特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”。

相比于传统的 List、Set、Map 等数据结构,它更高效、占用空间更少,但是缺点是其返回的结果是概率性的,而不是确切的。

布隆过滤器是一个 bit 向量或者说 bit 数组,长这样: redis_2020-05-08-13-46-02.png 如果我们要映射一个值到布隆过滤器中,我们需要使用多个不同的哈希函数生成多个哈希值,并对每个生成的哈希值指向的 bit 位置 1,比如针对值geeks进行三个 hash 函数分布生成 hash 值 1,4,7,则上图变成如下 redis_2020-05-08-13-46-53.png 而当我们需要查询 geeks 这个值是否存在的话,那么哈希函数必然会返回 1、4、7,然后我们检查发现这三个 bit 位上的值均为 1,那么我们可以说 geeks 存在了么?答案是不可以,只能是 geeks 这个值可能存在。 传统的布隆过滤器并不支持删除操作。

如何选择哈希函数个数和布隆过滤器长度

很显然,过小的布隆过滤器很快所有的 bit 位均为 1,那么查询任何值都会返回“可能存在”,起不到过滤的目的了。布隆过滤器的长度会直接影响误报率,布隆过滤器越长其误报率越小。

另外,哈希函数的个数也需要权衡,个数越多则布隆过滤器 bit 位置位 1 的速度越快,且布隆过滤器的效率越低;但是如果太少的话,那我们的误报率会变高。

Redis 因其支持 setbit 和 getbit 操作,且纯内存性能高等特点,因此天然就可以作为布隆过滤器来使用。但是布隆过滤器的不当使用极易产生大 Value,增加 Redis 阻塞风险,因此生成环境中建议对体积庞大的布隆过滤器进行拆分。

拆分的形式方法多种多样,但是本质是不要将 Hash(Key) 之后的请求分散在多个节点的多个小 bitmap 上,而是应该拆分成多个小 bitmap 之后,对一个 Key 的所有哈希函数都落在这一个小 bitmap 上。

相关操作

查看所有没有过期时间的 key

1
echo keys '*' |redis-cli |awk '{print "echo -n "$1"_"" && echo ttl " $1 " |redis-cli"}'|sh|grep '\-1$'|sed  's/_-1//' > 2.txt

问题集锦

访问redis集群

访问集群报错

Connection refused: /127.0.0.1:7000

需要对 redis 集群配置对外访问的 ip 地址,更改每个节点下的redis.conf配置,修改或增加配置

1
bind  xxx.xxx.xxx.xxx

del的相关问题

在使用redis-cli连接redis集群,进行数据操作时,有报错

(error) MOVED 5798 192.24.54.2:6379

这种情况一般是因为启动redis-cli时没有设置集群模式所导致。启动时使用-c参数来启动集群模式,命令如下:

1
redis-cli -c

del 删除 key 时内存不会 redis 占用的内存不会回收,而是由 redis 持续占用,在 redis 做其他操作时会去使用这部分内存

启动时没有启动成功

(error) CLUSTERDOWN Hash slot not served

需要重新分配 hash槽

重启时

Node 127.0.0.1:7000 is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0.

因为hash槽已经分配过了,无需重新分配。若要重新分配,将每个节点对应的node.conf删除掉再重新执行即可。

log4j

发表于 2019-08-27 更新于 2020-12-02 分类于 java

DEBUG 模式运行

log4j的配置文件中配置log4j.debug=true即可开启

概述

log4j的looger是层级结构的,例如com.li是com.li.springboot的父类logger
可使用如下方式取出logger

1
2
3
4
5
6
7
8
9
package com.li.springboot.advice.log4j;


import org.apache.log4j.Logger;

public class LevelTest {
Logger logger= Logger.getLogger(LevelTest.class);
Logger logger= Logger.getLogger("com.li.springboot.advice.log4j.LevelTest");
}

getLogger根据参数name,在任意处取出的logger都是同一个 root logger是所有logger的父类,一定存在但是它不能直接使用getLogger通过name取出。可使用如下方式取出

1
Logger.getRootLogger()

可使用的日志级别org.apache.log4j.Level

TRACE,DEBUG,INFO,WARN,ERROR and FATAL

当指定name的logger日志请求时,同时会将该请求转发至父类logger 当logger没有对应的配置时,会找最近的父类配置,默认情况下logger配置会继承父类的配置,可通过设置log4j.additivity.xxx=false使其不继承(xxx 是 logger 的 name)

配置

  1. 初始化 Logger 容器 Hierarchy,设置根节点为 RootLogger

  2. 初始 LoggerRepositorySelector(容器选择器)为默认的 DefaultRepositorySelector,容器为 Hierarchy

  3. 读取系统属性 log4j.defaultInitOverride,如果没有设置或者为 false 进行初始化,否则跳过初始化

  4. 读取系统属性 log4j.configuration(log4j 文件路径配置),如果存在对应的文件,则得到 URL.如果没有对应的文件,首先检查是否存在 log4j.xml 文件,如果存在,得到 Log4j 配置文件 URL,如果不存在 log4j.xml,继续检查是否存在 log4j.properties 文件,如果存在该文件,得到 log4j 配置文件的 URL,否则提示没有发现配置文件。

  5. 读取系统属性 log4j.configuratorClass(自定义 Configurator 配置类全路径,一般不自定义)

  6. 调用 OptionConverter.selectAndConfigure(url, configuratorClassName,LogManager.getLoggerRepository()),初始化 logger 容器

扩展配置

可使用BasicConfigurator.resetConfiguration()重置配置 可使用PropertyConfigurator.configure指定其他配置文件

tomcat下的log4j

当log4j的jar包在tomcat目录下的时候,使用BasicConfigurator.resetConfiguration()重置配置时,会修改tomcat下所有应用的日志打印,一般情况下 我们在主应用里做配置,忽略其他应用的配置即可。但是当你发布其他应用时,触发log4j的初始化配置,则会影响到主应用,可能造成主应用日志不打印。这个时候我们通过HierarchyEventListener来监听log4j的配置是否被修改,来在其他应用重置配置时,重新触发主应用的配置加载过程即可。

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
static {
Logger.getRootLogger().getLoggerRepository().addHierarchyEventListener(new HierarchyEventListener() {
@Override
public void addAppenderEvent(Category cat, Appender appender) {
LogLog.debug("add " + cat.getName() + " " + appender);
flag = false;
}
@Override
public void removeAppenderEvent(Category cat, Appender appender) {
//log4j配置被移除前的回调,此时配置还是生效的,所以这里重新加载是无效的,回调后就
//被重置了,所以需要在外面去重新加载,这里仅打一个标记
LogLog.debug("remove " + cat.getName() + " " + appender);
flag = true;
}
});
}

public static void initLog4j() {
BasicConfigurator.resetConfiguration();
Properties properties = new Properties();
try {
properties.load(ClassLoader.getSystemResourceAsStream("mylog4j.properties"));
} catch (IOException e) {
e.printStackTrace();
}
//配置logger时,已有的同名Category会被remove掉,即可以触发removeAppenderEvent事件
PropertyConfigurator.configure(properties);
}

for (int i = 0; i < cycle; i++) {
Thread.sleep(RandomUtils.nextInt(500, 1500));
if (flag) {
initLog4j();
}
logger.debug("123");
}

MDC

打造日志链路,MDC类似ThreadLocal类,根据线程存入一些数据,以供打印日志的时候输出(%X{name})

1
2
MDC.clear();
MDC.put("session", "xxxx");
1
log4j.appender.consoleAppender.layout.ConversionPattern= %X{session} %m%n

问题

日志输出问题

应用中需要将多个logger的日志输出到同一个文件中,且需要根据时间每天自动分割文件。我们使用DailyRollingFileAppender 配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 开启log4j的日志
log4j.debug=true
log4j.rootLogger=error, stdout
log4j.logger.l1=DEBUG, fuck1
log4j.logger.l2=DEBUG, fuck2

#l1
log4j.appender.fuck1=org.apache.log4j.DailyRollingFileAppender
log4j.appender.fuck1.DatePattern='.'-yyyy-MM-dd-HH-mm
log4j.appender.fuck1.layout=org.apache.log4j.PatternLayout
log4j.appender.fuck1.File=/Users/li/Downloads/log4j/log4j.log
log4j.appender.fuck1.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

#l2
log4j.appender.fuck2=org.apache.log4j.DailyRollingFileAppender
log4j.appender.fuck2.DatePattern='.'-yyyy-MM-dd-HH-mm
log4j.appender.fuck2.layout=org.apache.log4j.PatternLayout
log4j.appender.fuck2.File=/Users/li/Downloads/log4j/log4j.log
log4j.appender.fuck2.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

测试代码我使用Spring的Scheduled

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.li.springboot.util;

import org.apache.log4j.Logger;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class ScheduledTask {

Logger logger = Logger.getLogger("l1");
Logger logger2 = Logger.getLogger("l2");
private int index = 0;

@Scheduled(cron = "*/1 * * * * *")
public void log() {
logger.info("--------------------" + index++);
logger2.debug("--------------------" + index++);
}

}

*/1 * * * * *表示每秒执行一次

log4j.appender.fuck1.DatePattern='.'-yyyy-MM-dd-HH-mm表示每分钟分割一次文件

在执行定时任务到底切割点时,我们可以观察到日志输出

log4j自身的日志一定输出在System.out中

1
2
3
4
5
6
7
8
9
2019-09-05 21:16:59 DEBUG l2:22 - --------------------44
log4j: /Users/li/Downloads/log4j/log4j.log -> /Users/li/Downloads/log4j/log4j.log.-2019-09-05-21-16
log4j: setFile called: /Users/li/Downloads/log4j/log4j.log, true
log4j: setFile ended
2019-09-05 21:17:00 INFO l1:21 - --------------------45
log4j: /Users/li/Downloads/log4j/log4j.log -> /Users/li/Downloads/log4j/log4j.log.-2019-09-05-21-16
log4j: setFile called: /Users/li/Downloads/log4j/log4j.log, true
log4j: setFile ended
2019-09-05 21:17:00 DEBUG l2:22 - --------------------46

我们观察下源码分析下这个过程

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
  protected void subAppend(LoggingEvent event) {
long n = System.currentTimeMillis();
//检测当前时间点是否需要分割文件
if (n >= nextCheck) {
now.setTime(n);
nextCheck = rc.getNextCheckMillis(now);
try {
rollOver();
}
catch(IOException ioe) {
if (ioe instanceof InterruptedIOException) {
Thread.currentThread().interrupt();
}
LogLog.error("rollOver() failed.", ioe);
}
}
super.subAppend(event);
}
}

void rollOver() throws IOException {

/* Compute filename, but only if datePattern is specified */
if (datePattern == null) {
errorHandler.error("Missing DatePattern option in rollOver().");
return;
}

String datedFilename = fileName+sdf.format(now);
// It is too early to roll over because we are still within the
// bounds of the current interval. Rollover will occur once the
// next interval is reached.
if (scheduledFilename.equals(datedFilename)) {
return;
}

// close current file, and rename it to datedFilename
this.closeFile();
//如果存在其他分割后的文件,则删除
File target = new File(scheduledFilename);
if (target.exists()) {
target.delete();
}

File file = new File(fileName);
//将当前日志文件改名为代日期的文件
boolean result = file.renameTo(target);
if(result) {
LogLog.debug(fileName +" -> "+ scheduledFilename);
} else {
LogLog.error("Failed to rename ["+fileName+"] to ["+scheduledFilename+"].");
}

try {
// This will also close the file. This is OK since multiple
// close operations are safe.
//将log4j日志的输出重定向为不带日期的文件
this.setFile(fileName, true, this.bufferedIO, this.bufferSize);
}
catch(IOException e) {
errorHandler.error("setFile("+fileName+", true) call failed.");
}
scheduledFilename = datedFilename;
}
  1. logger的日志在logger2之前,因此先触发rollOver,此时没有文件log4j.log.-2019-09-05-21-16,将log4j.log重命名为log4j.log.-2019-09-05-21-16,并将logger的日志流重定向为log4j.log

  2. 紧接着logger2的日志流触发rollOver,此时会将log4j.log.-2019-09-05-21-16删除,同时将log4j.log重命名为log4j.log.-2019-09-05-21-16,并将logger2的日志流重定向为log4j.log。此时logger的日志流就的文件名被改名了。

  3. 我们可以看出第一轮的日志被logger2触发的rollOver删除了,而logger的日志流输出到上一轮

解决方案

根据分析,我们确保target.delete()和ile.renameTo(target)只被执行一次,且其他logger在指定时间重新将日志流指向到最新的log4j.log即可。

比如说简单的重写DailyRollingFileAppender,在rollOver代码处稍作修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
File target = new File(scheduledFilename);
//当目标文件已经存在时,就说明已经被切割过了,则简单重定向即可
if (!target.exists()) {
File file = new File(fileName);
boolean result = file.renameTo(target);
if (result) {
LogLog.debug(fileName + " -> " + scheduledFilename);
} else {
LogLog.error("Failed to rename [" + fileName + "] to [" + scheduledFilename + "].");
}
}
try {
this.setFile(fileName, true, this.bufferedIO, this.bufferSize);
} catch (IOException e) {
errorHandler.error("setFile(" + fileName + ", true) call failed.");
}
scheduledFilename = datedFilename;

spring循环依赖

发表于 2019-08-26 更新于 2020-06-30 分类于 spring

概述

一般只应用于单例模式,主要原理是将 bean 先缓存在 beanfactory,prototype无法解决循环依赖问题。 示例代码

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
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class A {

@Autowired
B b;

public A() {
System.out.println("-------------A------------------");
}
@PostConstruct
public void log(){

System.out.println("-------------B PostConstruct------------------");
}
}

@Component
public class B {

@Autowired
A a;
public B() {
System.out.println("-------------A------------------");
}
@PostConstruct
public void log(){

System.out.println("-------------B PostConstruct------------------");
}
}

getBean(A)->instance(A)->autowired(B)->getBean(B)->instance(B)->autowired(A)->循环依赖

解决方案: 1.A首先调用构造函数newInstance,此时A的引用值已确定

  1. 将A的引用缓存,创建B时直接使用缓存的A的引用

则实际实例化过程大致如下:

getBean(A)->instance(A)->cache reference(A)->autowired(B)->getBean(B)->instance(B)->autowired(A)->get reference(A)->postConstruct(B)->postConstruct(A)

源码分析

以AnnotationConfigApplicationContext的加载来举例

AppConfig为配置类,不重要

1
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

省略其他过程直接看刷新

1
2
3
4
5
public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
this();
register(annotatedClasses);
refresh();
}

省略扫描过程,直接看 bean 加载的过程

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
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();

// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);

try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);

// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);

// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);

// Initialize message source for this context.
initMessageSource();

// Initialize event multicaster for this context.
initApplicationEventMulticaster();

// Initialize other special beans in specific context subclasses.
onRefresh();

// Check for listener beans and register them.
registerListeners();

// 加载所有单例非懒加载的bean
finishBeanFactoryInitialization(beanFactory);

// Last step: publish corresponding event.
finishRefresh();
}
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
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// Initialize conversion service for this context.
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
beanFactory.setConversionService(
beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
}

// Register a default embedded value resolver if no bean post-processor
// (such as a PropertyPlaceholderConfigurer bean) registered any before:
// at this point, primarily for resolution in annotation attribute values.
if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
}

// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
for (String weaverAwareName : weaverAwareNames) {
getBean(weaverAwareName);
}

// Stop using the temporary ClassLoader for type matching.
beanFactory.setTempClassLoader(null);

// Allow for caching all bean definition metadata, not expecting further changes.
beanFactory.freezeConfiguration();

// 通过debug,一般情况下使用DefaultListableBeanFactory类
beanFactory.preInstantiateSingletons();
}

DefaultListableBeanFactory类preInstantiateSingletons代码片段

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
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
//非抽象类,单例,非延迟加载
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
if (bean instanceof FactoryBean) {
final FactoryBean<?> factory = (FactoryBean<?>) bean;
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
((SmartFactoryBean<?>) factory)::isEagerInit,
getAccessControlContext());
}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
getBean(beanName);
}
}
}
else {
getBean(beanName);
}
}
}

通过getBean方法可以追踪到org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String),然后 org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

1
2
3
4
5
6
7
8
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException

final String beanName = transformedBeanName(name);
Object bean;

// 单例的bean通过该方法获取
Object sharedInstance = getSingleton(beanName);

DefaultSingletonBeanRegistry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

public Object getSingleton(String beanName) {
return getSingleton(beanName, true);
}

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//我们可以清晰的看到singleton的bean简单的bean存储在ConcurrentHashMap中
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}

首次创建A时,singletonObject肯定为null,isSingletonCurrentlyInCreation的代码很简单

1
2
3
public boolean isSingletonCurrentlyInCreation(String beanName) {
return this.singletonsCurrentlyInCreation.contains(beanName);
}

我们只需要了解singletonsCurrentlyInCreation是何时被add,通过查看调用关系,最终可以发现org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean中

1
2
3
4
5
6
7
8
9
10
11
12
13
if (mbd.isSingleton()) {
//getSingleton代码里调用beforeSingletonCreation方法将beanName加入singletonsCurrentlyInCreation
//lambda表达式createBean则创建bean
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
} catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

继续回到A的创建,在getSingleton中未取到缓存是,A尝试createBean,也就是上述代码部分。 追踪调用关系可以知道最终调用org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])后进入org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

节选片段

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
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {

// 初始化bean
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
final Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}

// Allow post-processors to modify the merged bean definition.
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Post-processing of merged bean definition failed", ex);
}
mbd.postProcessed = true;
}
}

boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
//将bean的引用缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
...

我们查看下addSingletonFactory的细节

1
2
3
4
5
6
7
8
9
10
11
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
//根据beanName缓存可以取出bean的lambda中
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}

到这里bean通过构造器创建实例的过程结束了,但是bean在spring容器中的生命周期还未结束,后续发现A依赖B,则会去创建B,B在实例化后加载依赖时,会去创建A,不同的是在调用DefaultSingletonBeanRegistry的getSingleton时判断条件isSingletonCurrentlyInCreation时A已在创建过程中,那么就会去执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//我们可以清晰的看到singleton的bean简单的bean存储在ConcurrentHashMap中
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
//根据前面的分析我们知道A的引用被缓存在此处。
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}

此时B顺利完成整个Spring生命周期,从而A也完成了整个生命周期

spring-ioc 源码

发表于 2019-08-26 更新于 2020-06-30 分类于 spring

BeanDefinition

BeanDefinition是Spring用来定义一个类的元数据,它定义了

1. 如何创建一个`Bean`

2. `Bean`的生命周期

3. `Bean`的依赖关系

详细属性值如下图所示:

BeanDefinition
BeanDefinition

Spring在扫描类后将类的BeanDefiniton元数据存储在BeanFactory中,当需要时,BeanFactory根据BeanDefinition信息生成Bean

1…101112…15

lijun

与其感慨路难行,不如马上就出发
145 日志
30 分类
117 标签
© 2019 – 2020 lijun