大狗哥传奇

  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

响应式编程学习

发表于 2019-11-26 更新于 2020-06-30 分类于 响应式编程

git

发表于 2019-11-21 更新于 2020-12-02 分类于 tips

安装 git

系统使用 centos7

1
2
3
4
5
6
7
8
9
10
11
$ cd /usr/local/src wget https://github.com/git/git/archive/master.zip
$ unzip master.zip
# 由于系统中已存在 git,所以先备份现有 git。
$ mv /usr/bin/git{,.bak}
# 编译安装
cd /usr/local/src/git-master
$ make configure
$ ./configure --prefix=/usr/bin
$ make && make install
# 拷贝到原有 Git 所在位置
$ cp /usr/local/src/git-master/git /usr/bin

配置

gitignore

  1. /mtk过滤整个文件夹,但不过滤子文件夹
  2. mtk/过滤文件夹所有文件
  3. \*.zip 过滤所有.zip 文件
  4. /mtk/do.c 过滤某个具体文件
  5. !/mtk/one.txt 追踪(不过滤)某个具体文件

注意:如果你创建.gitignore 文件之前就push了某一文件,那么即使你在.gitignore文件中写入过滤该文件的规则,该规则也不会起作用,git 仍然会对该文件进行版本管理。

gitignore不生效

.gitignore 中已经标明忽略的文件目录下的文件,git push 的时候还会出现在 push 的目录中,原因是因为在 git 忽略目录中,新建的文件在 git 中会有缓存,如果某些文件已经被纳入了版本管理中,就算是在.gitignore 中已经声明了忽略路径也是不起作用的,这时候我们就应该先把本地缓存删除,然后再进行 git 的 push,这样就不会出现忽略的文件了。git 清除本地缓存命令如下:

1
2
3
git rm -r --cached .
git add .
git commit -m 'update .gitignore'

别名

我们可通过配置 alias 来简化 git 命令 git config –-global --edit来编辑[alias]下面的配置别名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[alias]
al = config --get-regexp alias
b = branch
cm = commit -m
co = checkout --
cp = cherry-pick
dt = difftool
mt = mergetool
mg = merge --no-ff -m
l = log --graph --pretty=oneline --abbrev-commit
pl = pull
ps = push
st = status -s
sw = checkout

查看

查看状态

git show默认查看当前<commit>提交的内容,可使用git show <commit>查看具体某个提交的信息

git status查看当前工作区与暂存区状态,可使用git status -s简化输出信息。类似如下的信息

  • M表示有改动
  • D表示删除,
  • ??表示未add,
  • A表示新增但未提交

M 1.txt D 2.txt ?? 4.txt A 5.txt

查看缓存区文件

git ls-fles 显示index和工作区的文件的信息。 选项

含义

-c, --cached 显示缓存了的文件(默认) -d, --deleted 显示删除了的文件 -m, --modified 显示修改了的文件 -o, --others 显示其他类型的文件(比如未追踪的) -i, --ignored 显示忽略了的文件(满足忽略模式的) -s, --stage 显示暂存的条目的相关信息(模式位,文件哈希后的值,暂存号和文件名),

日志

git log 命令显示从最近到最远的提交日志,如果嫌输出信息太多,看得眼花缭乱的,可以试试加上--pretty=oneline参数

1
git log --pretty=oneline

显示所有分支关系

1
git log --graph --decorate --oneline --all

使用自带图形化工具查看

1
gitk --all

查看文件修改历史记录,显示差异

1
git log -p <file>

比较差异

git diff比较工作区和缓存区,当修改工作区内容时且未add,缓存区的内容则与工作区有差异,此时缓存区与仓库同样是没有变动,所以使用git diff --cached,发现没有差异,当使用add后,缓存区则和工作区相同了。单因为没有commit,所以使用git diff --cached,可以看到差异的内容。当使用commit后,则变成一致的状态
用 git diff HEAD -- readme.txt 命令可以查看工作区和版本库里面最新版本的区别,HEAD可以使用git log或者git reflog查看的版本号替换

1
2
3
4
5
6
7
git diff
git diff --cached
git diff HEAD

git diff -- file
git diff --cached --file
git diff HEAD -- file

可使用第三方软件来比较 目前mac上安装的是Kaleidoscope git difftool使用方式类似git diff

比较两个commit的差异

1
git diff <commit1> <commit2>

要查看有哪些文件发生了变化,可以加上--stat 参数

1
git diff <commit1> <commit2> --stat

若想查看目录的变化,可以使用--dirstat

1
git diff <commit1> <commit2> --dirstat=files

仅查看某文件夹下的版本差异

1
git diff --stat <commit1> <commit2> dir

显示冲突conflict 文件,--diff-filter也可用来过滤新增文件等

1
git diff --name-only --diff-filter=U

命令历史记录

git reflog用来记录你的每一次命令

查找丢失记录

git fsck --full检查数据库的完整性。 如果使用一个 --full 选项运行它,它会向你显示出所有没有被其他对象指向的对象:

提交

追加提交

重写更改提交信息,执行后会把最近add的内容一并放入到此次commit

1
git commit --amend

回退,撤销

HEAD^或HEAD~表示上一个版本,用git log可以查看提交历史,以便确定要回退到哪个版本。git reflog查看命令历史,以便确定要回到未来的哪个版本。

checkout

  1. 未add到stage时可用git checkout -- file,丢弃工作区的修改。
  2. 以add到stage先用git reset HEAD <file>可以把暂存区的修改撤销掉(unstage),重新放回工作区,然后再丢弃工作区修改
  3. git checkout <branch>切换分支
  4. git checkout -b <branch> <commit> 在commit创建一个分支

reset

深入git_reset细节.png
深入git_reset细节.png

revert

git revert <commit>,撤销提交。不同于reset,revert不是回退版本,而是通过一个新的反向的提交来实现。会进入交互模式,需要填写新的commit信息 git revert -n <commit>,撤销提交,需要手动commit

apply

补丁

分支

创建分支

我们创建dev分支,然后切换到dev分支:

1
git checkout -b dev`

查看当前分支

查看当前分支,当前分支前面会标一个*号。

1
git branch

重命名分支

在当前分支下重命名分支

1
git branch -m new-name

分支策略

首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。

所以,团队合作的分支看起来就像这样: git_2019-12-05-00-32-43.png

合并分支

合并分支,把dev分支的工作成果合并到master分支上:

1
git merge dev

通常,合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息。如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。

1
git merge --no-ff -m "merge with no-ff" dev

删除分支

如果要丢弃一个没有被合并过的分支,可以通过 git branch -D <name>强行删除。

移动分支

我有两个git分支,A和B,并提交编号 1 到 8。我的历史记录如下

1 -> 2 -> 3 -> 4[A] -> 5 -> 6 -> 7 -> 8[B]

我想更改它,以便我的历史记录如下:

1 -> 2 -> 3 -> 4 -> 5 -> 6[A] -> 7 -> 8[B]

也就是说,我想将分支A的头从提交 4 移动到提交 6。

1
git branch -f A 6

挑选提交

同上示例,加入A分支仅希望用 7 这个提交 那么我们可以使用cherry-pick,在A分支下使用

1
git  cherry-pick 7

挑选文件

当我们需要用别的分支的文件覆盖本分支的文件时可以使用如下命令,该命令会同时覆盖工作区和缓存区的文件

1
git checkout <branch> <file>

标签

新建标签

分支打标签,默认标签是打在最新提交的commit上的。还可以创建带有说明的标签,用-a指定标签名,-m指定说明文字

1
2
git tag v1.0
git tag -a v0.1 -m "version 0.1 released"

默认标签是打在最新提交的commit上的。有时候,如果忘了打标签,比如,现在已经是周五了,但应该在周一打的标签没有打,怎么办?
方法是找到历史提交的commit id,然后打上就可以了:

1
git tag v0.9 f52c633

删除标签

1
git tag -d v0.1

查看标签

查看所有标签

1
git tag

查看标签信息,可以看到标签说明文字

1
git show tag

操作标签

类似<commit>的作用,可以使用checkout,reset等

更新远程标签

当使用git fetch命令时,本地已经有的标签不会被更新为远程的值 我们需要先将本地的标签删除后再更新

1
git tag -l |xargs git tag -d

rebase

提交信息过多过杂,或者分支合并操作过多,可以使用git rebase整理提交记录

1
git rebase -i ID #-i交互模式,来觉得哪些分支的记录会被保留

交互模式的command选项

pick:正常选中 reword:选中,并且修改提交信息; edit:选中,rebase 时会暂停,允许你修改这个 commit(参考这里) squash:选中,会将当前 commit 与上一个 commit 合并 fixup:与 squash 相同,但不会保存当前 commit 的提交信息 exec:执行其他 shell 命令 merge:将几个 commit 合并为一个 commit

合并第一条和第二条记录

  1. 首先我们查看当前提交记录 git_2020-01-04-11-18-26.png
  2. 我们使用git rebase -i da0e80b git_2020-01-04-11-19-59.png
  3. 将df060c5前更改为edit状态后保存退出 git_2020-01-04-11-21-41.png
  4. 使用git reset --soft HEAD^,保留第二版的改动回退到第一个版本 git_2020-01-04-11-27-41.png
  5. 将改动追加提交到第一个版本即可git commmit --amend git_2020-01-04-11-29-59.png
  6. 继续rebase,使用git rebase --continue git_2020-01-04-11-30-56.png

当然我们可以使用更简单的命令git rebase -i --root

1
git pull --rebase

合并策略

基本使用命令如下,仅介绍两个策略

1
git merge <branch> --strategy=<strategy>

ours

在合并的时候,无论有多少个合并分支,当前分支就直接是最终的合并结果。无论其他人有多少修改,在此次合并之后,都将不存在(当然历史里面还有)。你可能觉得这种丢失改动的合并策略没有什么用。但如果你准备重新在你的仓库中进行开发(程序员最喜欢的重构),那么当你的修改与旧分支合并时,采用此合并策略就非常有用,你新的重构代码将完全不会被旧分支的改动所影响。

recursive

默认的合并策略是recursive,此策略可以指定额外参数

  1. ours如果不冲突,那么与默认的合并方式相同。但如果发生冲突,将自动应用自己这一方的修改。注意策略里面也有一个ours,与这个不同的。

  2. theirs这与 ours相反。如果不冲突,那么与默认的合并方式相同。但如果发生冲突,将自动应用来自其他人的修改(也就是 merge参数中指定的那个分支的修改)。

1
2
3
git merge --no-ff -m 'msg' <branch> --strategy=recursive --strategy-option=theirs
#可以简化成
git merge --no-ff -m 'msg' <branch> --strategy-option=theirs

远程仓库

将新项目导入远程仓库

1
2
3
4
5
6
7
git init
git remote add origin <uri>
git remote rename origin old-origin #重命名
git add .
git commit -m 'init '
git push -u origin master
git push -u origin --all #推送所有分支

查看远程仓库

1
git remote

删除远程分支

1
2
3
4
git push origin --delete test

# 远程已经删除的分支,在本地执行 git branch -a 显示还存在,可通过如下方式删除
git remote prune origin

拉取远程仓库的更新

1
2
3
git fetch
git fetch --all
git fetch -p #会自动删除本地已经失效的远程分支

强制更新覆盖为远程分支

1
git reset --hard <remote>/<branch>

推送到远程

1
2
git push #推送前会校验是否在最新状态
git push -f #强制推送

Git原理

参考深入git

分布式锁实现

发表于 2019-11-20 更新于 2020-06-30 分类于 java

临界区

临界区指的是一个访问共用资源(例如:共用设备或是共用存储器)的程序片段,而这些共用资源又无法同时被多个线程访问的特性。当有线程进入临界区段时,其他线程或是进程必须等待(例如:bounded waiting 等待法),有一些同步的机制必须在临界区段的进入点与离开点实现,以确保这些共用资源是被互斥获得使用,例如:semaphore。只能被单一线程访问的设备,例如:打印机。

锁

当多个线程访问临界区资源时,可能会引起冲突。那么我们需要实现以下效果

  1. 同一时间内,仅有一个线程可以访问临界区
  2. 具备可重入性

同时为了防止死锁,我们需要

  1. 具备锁失效机制
  2. 具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。一般情况下在尝试一段时间后返回失败

java 线程锁

在了解分布式锁之前我们先了解一下java的两种锁的实现

synchronized

1
2
3
Object lock = new Object();
synchronized (lock) {
}

查看上述代码的字节码

1
2
3
11: monitorenter
12: aload_2
13: monitorexit

我们可以观察到在进入synchronized代码块时,使用monitorenter指定获取锁,在结束代码块的语句时,使用monitorexit,释放锁。

等待锁的过程 分布式锁实现_2019-11-20-01-13-39.png

参考java数组中关于对象头的部分,对象在有锁的情况下,会保存占用锁的线程指针。从而保证在同一个时刻,仅允许一个线程可以占用monitor锁,无法获取monitor锁的线程被阻塞直到对象释放锁。

AbstractQueuedSynchronizer

以ReentrantLock为代表的同步锁框架是基于AQS,AQS依赖FIFO队列实现。线程获取锁通过tryAcquire向FIFO插入并使用乐观锁CAS的方式,判断是否为HEAD节点,若是HEAD节点则表示获取到锁,释放锁则通过tryRelease该队列的HEAD节点,并唤醒等待节点。

JVM 源码分析之 Object.wait/notify 实现

分布式锁

构建测试程序

在分布式场景下,无法通过JVM内存来保持一致性。我们需要一个定义一个临界区。对于锁的实现,和java锁没有本质区别,仅在于实现方式不同

首先我们构造测试程序,我们使用SpringBoot构建项目

pom依赖

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.leaderli</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
spring:
application:
name: demo
datasource:
url: jdbc:mysql://localhost:3306/li
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
logging:
level:
root: error
lock:
use: nolock
bean:
name: coral
machine: mymac

临界资源

1
2
3
4
5
package com.leaderli.demo.lock;

public class CriticalSection {
public static int ID = 0;
}

锁接口

1
2
3
4
5
6
7
8
package com.leaderli.demo.lock;

public interface Lock {

boolean lock();

void unlock();
}

为了使代码清晰,封装一些方法

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
package com.leaderli.demo.util;

import java.util.Random;

public class SomeUtils {

public static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

static Random random = new Random();

public static int random(int boundary) {
return random.nextInt(boundary);
}

public static void logWithThread(Object msg) {
System.out.println(Thread.currentThread() + "\t" + String.valueOf(msg));
}

public static void join(Thread thread) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}

测试程序

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
package com.leaderli.demo.lock;


import com.leaderli.demo.util.SomeUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;

import java.util.ArrayList;
import java.util.List;

@SpringBootTest
class LockTest {
@Autowired
ApplicationContext applicationContext;


int maxThreadCount = 10;
int endID = 1000;
//设置合适的值,确保持有锁期间,其他线程企图获取锁
int randomBoundary = 10;

@Test
public void test() {
Runnable task = () -> {
//所有Lock实现类都为非单例,保证所有线程的锁不为同一个对象以模拟分布式锁
Lock lock = applicationContext.getBean(Lock.class);
while (true) {
//不断消费序列号,直到测试结束
if (task(lock)) {
return;
}
}

};

List<Thread> threadPool = new ArrayList<>();
for (int i = 1; i <= maxThreadCount; i++) {
Thread cosumer = new Thread(task);
cosumer.setName((100 - i) + "");
threadPool.add(cosumer);
}
threadPool.forEach(Thread::start);
threadPool.forEach(SomeUtils::join);
//断言程序是否正常结束
Assertions.assertEquals(endID, CriticalSection.ID);
}

public boolean task(Lock lock) {
if (lock.lock()) {
//模拟重入锁
if (SomeUtils.random(3) == 1) {
if (task(lock)) {
return true;
}
}
try {
int a = CriticalSection.ID + 1;
SomeUtils.sleep(SomeUtils.random(randomBoundary));
if (CriticalSection.ID >= endID) {
//达到最大值,测试结束
return true;
}
CriticalSection.ID++;
if (a != CriticalSection.ID) {
//断言失败,结束程序
return true;
}
} finally {
lock.unlock();
}
}
return false;
}
}

通过使用SpringBoot的注解ConditionalOnProperty来方便测试 首先我们测试没有锁的情况下

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;

@ConditionalOnProperty(prefix = "lock", name = "use", havingValue = "nolock")
@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class NoLock implements Lock {

@Override
public boolean lock() {
return true;
}

@Override
public void unlock() {
}
}

配置文件中将NoLock类激活,即lock.use=nolock
分布式锁实现_2019-11-21-00-56-35.png 我们很容易就观察到没有锁时,一个线程在运行期间,临界区资源被修改了

数据库锁

根据锁的原则,我们使用数据库锁需要考虑的实现方式:

  1. 我们通过数据库唯一约束(主键约束)来确保同一时间内,仅有一个线程可以访问临界区资源。通过唯一ID,获取锁时插入锁,释放锁则删除锁记录
  2. 为了确保可重入性,我们需要记录占用锁的线程,同时为了保证仅在所有重入锁都释放后再释放锁,我们需要记录重入锁的次数,释放锁时判断重入锁次数是否为0,为0则删除锁记录

  3. 为了具有锁失效机制,当线程占用锁后出现异常情况,没有释放锁,会导致锁一直被占用,其他线程无法获取锁。所以我们需要记录获取锁的时间,以及设定锁的失效时间。则在获取锁时判断是否锁已经失效,失效则删除锁。

  4. 为了具有非阻塞锁特性,在线程获取锁时,首先查询数据库锁是否被占用,若被占用,则重新尝试,达到指定次数后,则直接返回获取锁失败。通过insert插入锁失败,也直接返回失败

redis锁

--

zookeeper锁

--

Stream流工作原理浅析与模仿

发表于 2019-11-17 更新于 2020-06-30 分类于 java

尝试分析Stream流工作原理,并模仿其工作原理实现一些轮子。

使用AND,OR,NOT拆分复杂boolean操作

参照Stream的工作方式,我们将运算逻辑缓存,仅在终结节点end()才开始计算 要实现链式调用,那么方法and(),or(),not(),test()就需要返回类型相同的对象,我们定义PipeLine类来进行链式调用 对于我们需要实现的boolean工具,我们需要关心的问题

  1. 如何进行计算
  2. 何时结束
  3. 规则校验
  4. 运算结果如何保存

我们定义Sink类来保存运算逻辑:

  1. accpet方法,进行逻辑运算以及流程走向
  2. cancel方法,决定是否需要终结流程
  3. valid方法,校验链式调用语法是否合法,因为连续调用两次and,是无法进行计算的
  4. 通过外部传递的Bool对象来保存运算结果
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
260
261
262
263
264
265
266
267
268
package com.leaderli.demo.bool;


import java.util.Arrays;
import java.util.function.Predicate;

public class PipeLine<T> {


/**
* 在调用end时,需要找到第一个节点进行运算
*/
private PipeLine<T> prev;
/**
* 存储结果,仅在显式new PipeLine时初始化
*/
private Bool bool;
/**
* 每个PipeLine绑定一个操作
*/
private Sink<T> sink;


private PipeLine(Bool bool) {
this.bool = bool;
}

public PipeLine() {
this.bool = new Bool();
begin();
}

/**
* 本身不做任何逻辑运算,仅锚定开始位置
*/
private void begin() {
this.sink = new Sink<T>(bool, Sink.Type.BEGIN) {
@Override
public Sink<T> accept(T test) {
return next;
}

@Override
public void valid() {
valid(Type.TEST, Type.NOT);

}
};
this.prev = null;
}

/**
* 实现链表
*
* @param sink 当前运算逻辑
* @param type 运算结果
* @return 新增链表节点并返回
*/
private PipeLine<T> add(Sink<T> sink, Sink.Type type) {
PipeLine<T> pipeLine = new PipeLine<>(this.bool);
pipeLine.prev = this;
pipeLine.sink = sink;
return pipeLine;
}

public PipeLine<T> test(Predicate<T> predicate) {
assert predicate != null;
Sink<T> sink = new Sink<T>(bool, Sink.Type.TEST) {
@Override
public void valid() {
valid(Type.END, Type.AND, Type.OR);
}
};
sink.predicate = predicate;
return add(sink, Sink.Type.TEST);
}

/**
* 对于or来说,如果上一个运算逻辑为true,则整个表达式都为true,所以可以直接结束
* 否则可以直接忽略or操作前面的运算结果
*/
public PipeLine<T> or() {
Sink<T> sink = new Sink<T>(bool, Sink.Type.OR) {
@Override
public boolean cancel(Bool bool) {
return bool.result;
}

@Override
public Sink<T> accept(T test) {
return this.next;
}

@Override
public void valid() {
valid(Type.TEST, Type.NOT);
}
};
return add(sink, Sink.Type.OR);
}

/**
* 返回下一个test的否定
*/
public PipeLine<T> not() {
Sink<T> sink = new Sink<T>(bool, Sink.Type.NOT) {

@Override
public Sink<T> accept(T test) {
Sink<T> sink = this.next;
this.next = new Sink<T>(bool, Type.TEST) {
@Override
public Sink<T> accept(T test) {
Sink<T> accept = sink.accept(test);
this.bool.result = !this.bool.result;
return accept;
}
};
return this.next;
}

@Override
public void valid() {
valid(Type.TEST);
}
};
return add(sink, Sink.Type.NOT);
}

/**
* 对于or来说,如果上一个运算逻辑为false,则整个表达式都为false,所以可以直接结束
* 否则可以直接忽略or操作前面的运算结果
*/
public PipeLine<T> and() {
Sink<T> sink = new Sink<T>(bool, Sink.Type.AND) {

@Override
public boolean cancel(Bool bool) {
return !bool.result;
}

@Override
public Sink<T> accept(T test) {
return this.next;
}

@Override
public void valid() {
valid(Type.TEST, Type.NOT);


}
};
return add(sink, Sink.Type.OR);
}

/**
* 向前查找PipeLine,同时将Sink链接起来
*
* @return 返回链表第一个节点,即BEGIN
*/
public PipeLine<T> end() {
PipeLine<T> pipeLine = this;
Sink<T> temp = pipeLine.sink;
//最后一个操作执行一个一定会cancel的终结节点
temp.next = new Sink<T>(bool, Sink.Type.END) {
@Override
public boolean cancel(Bool result) {
return true;
}
};
PipeLine<T> pr = pipeLine.prev;
while (pr != null) {
pipeLine = pr;
pr = pipeLine.prev;
pipeLine.sink.next = temp;
temp = pipeLine.sink;
}
pr = null;
return pipeLine;
}

/**
* 依次执行Sink,直到触发cancel或者所有Sink执行完成,支持多次操作
*
* @param test 数据
* @return 逻辑运算结果
*/
public boolean accept(T test) {
return forSink(sink, test);
}

private static class Bool {
boolean result = false;
}

private static class Sink<T> {
/**
* 标记操作的类型,主要用来校验表达式是否合法
*/
public enum Type {
BEGIN,
TEST,
NOT,
OR,
END,
AND
}

protected Type type;

public Sink(Bool bool, Type type) {
this.bool = bool;
this.type = type;
}

Bool bool;
Predicate<T> predicate;
/**
* 使用链表的方式,将所有操作步骤串联起来
*/
Sink<T> next;

/**
* 是否需要提前结束表达式
*/
public boolean cancel(Bool result) {
return false;
}

/**
* @param test 断言
* @return 返回下一个操作
*/
public Sink<T> accept(T test) {
this.bool.result = this.predicate.test(test);
return next;
}

/**
* 表达式是否合法,一般只需要考虑当前类型操作的下一个操作类型可以为
*/
public void valid() {
}

void valid(Type... types) {
if (next == null) {
throw new IllegalStateException("must have end()");
}
if (Arrays.stream(types).noneMatch(type -> next.type == type)) {
throw new IllegalStateException(type + " --> " + Arrays.toString(types) + "; actual is : " + next.type);
}
}

}

private boolean forSink(Sink<T> sink, T test) {
while (sink != null) {
sink.valid();
if (sink.cancel(bool)) {
break;
}
sink = sink.accept(test);
}
return bool.result;
}

}

参考Stream 流水线原理

Spring_Autowired源码分析

发表于 2019-11-14 更新于 2020-12-02

indexOfContent 123121 indexOfContent 123122 indexOfContent 123123 indexOfContent 123124 indexOfContent 123125 indexOfContent 123126 indexOfContent 123127 indexOfContent 123128 indexOfContent 123129

spring_initMethod执行过程

发表于 2019-11-13 更新于 2020-06-30 分类于 spring

准备

pom依赖,jdk版本为1.8

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.leaderli</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

为了方便分析Spring中bean的生命周期,我们使用@Lazy来注解bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
public class BeAutowired {
static {
System.out.println("-------BeAutowired--------");
}
}

@Component
@Lazy
public class Person {
@Autowired
BeAutowired beAutowired;
@PostConstruct
public void begin(){
System.out.println(" beAutowired= " + beAutowired);
}
}

我们使用测试程序来分析person的加载过程

1
2
3
4
5
6
public static void main(String[] args) {
String packageName = Person.class.getPackage().getName();
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(packageName);
Person person = context.getBean(Person.class);
System.out.println("person = " + person);
}

分析

doGetBean

因为使用了@Lazy,所以Person在执行context.getBean(Person.class)时才加载,我们debug进去大致了解一下关键的加载过程

1
2
3
4
5
6
//交由BeanFactory去加载
@Override
public <T> T getBean(Class<T> requiredType) throws BeansException {
assertBeanFactoryActive();
return getBeanFactory().getBean(requiredType);
}

省略中间过程,最终会调用BeanFactory的doGetBean方法

1
2
3
4
5
public <T> T getBean(String name, @Nullable Class<T> requiredType, @Nullable Object... args)
throws BeansException {

return doGetBean(name, requiredType, args, false);
}

doGetBean大致流程如下:

  1.  如果是单例模式的,则尝试从缓存中获取bean
  2. 尝试从父类BeanFactory中加载bean
  3. 加载依赖bean, @DependsOn注解的bean
  4. 使用方法createBean(beanName, mbd, args),加载单例 bean

doGetBean源码,无视其他代码,只关心我们需要了解的

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
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;

// 1. 如果是单例模式的,则尝试从缓存中获取bean
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
if (logger.isTraceEnabled()) {
if (isSingletonCurrentlyInCreation(beanName)) {
logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
}
else {
logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
}
}
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}

else {
// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}

// 2. 尝试从父类BeanFactory中加载bean
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// Not found -> check parent.
String nameToLookup = originalBeanName(name);
if (parentBeanFactory instanceof AbstractBeanFactory) {
return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
nameToLookup, requiredType, args, typeCheckOnly);
}
else if (args != null) {
// Delegation to parent with explicit args.
return (T) parentBeanFactory.getBean(nameToLookup, args);
}
else if (requiredType != null) {
// No args -> delegate to standard getBean method.
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
else {
return (T) parentBeanFactory.getBean(nameToLookup);
}
}

if (!typeCheckOnly) {
markBeanAsCreated(beanName);
}

try {
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);

// 3. 加载依赖bean, @DependsOn注解的bean
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
if (isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
registerDependentBean(dep, beanName);
try {
getBean(dep);
}
catch (NoSuchBeanDefinitionException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
}
}
}

// 4. 加载单例bean
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}

else {
String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {
throw new BeanCreationException(beanName,
"Scope '" + scopeName + "' is not active for the current thread; consider " +
"defining a scoped proxy for this bean if you intend to refer to it from a singleton",
ex);
}
}
}
catch (BeansException ex) {
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
}

// Check if required type matches the type of the actual bean instance.
if (requiredType != null && !requiredType.isInstance(bean)) {
try {
T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
if (convertedBean == null) {
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
return convertedBean;
}
catch (TypeMismatchException ex) {
if (logger.isTraceEnabled()) {
logger.trace("Failed to convert bean '" + name + "' to required type '" +
ClassUtils.getQualifiedName(requiredType) + "'", ex);
}
throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
}
}
return (T) bean;
}

createBean

createBean大致流程如下:

  1. createBeanInstance,使用构造器或者工厂方法实例化bean
  2. populateBean,对bean进行数据的初始化操作,比如加载@autowire的bean,@value的属性赋值,@PostConstruct方法执行等
  3. initializeBean,执行BeanDefinition中定义的initMethod,一般在xml 配置中(<bean class="Person" init-method="initMethod"/>),或者@Bean(initMethod = "initMethod")中定义的方法名

createBean源码,无视其他代码,只关心我们需要了解的

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

// 1. 使用构造器实例化`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");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

// Initialize the bean instance.
Object exposedObject = bean;
try {
//2. 对bean进行数据的初始化操作,比如加载@autowire的bean,@value的属性赋值,@PostConstruct方法执行等
populateBean(beanName, mbd, instanceWrapper);
//3. 执行BeanDefinition中定义的initMethod,一般在xml配置中,或者@Bean(initMethod = "initMethod")中定义的方法名
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}

if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}

// Register bean as disposable.
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}

return exposedObject;
}

populateBean

这里我们简单介绍一下@Autowired是何时被加载的。我们通过打断点,并设置evaluate语句,在断点处输出日志的方式查看
spring-initMethod执行过程_2019-11-14-01-27-39.png

断点调试最后输出的日志如下
spring-initMethod执行过程_2019-11-14-01-29-22.png 我们可以得知被@Autowired的属性或方法是否处理器AutowiredAnnotationBeanPostProcessor去实现的,具体加载过程参考

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
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
if (bw == null) {
if (mbd.hasPropertyValues()) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");
}
else {
// Skip property population phase for null instance.
return;
}
}

// Give any InstantiationAwareBeanPostProcessors the opportunity to modify the
// state of the bean before properties are set. This can be used, for example,
// to support styles of field injection.
boolean continueWithPropertyPopulation = true;

if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
continueWithPropertyPopulation = false;
break;
}
}
}
}

if (!continueWithPropertyPopulation) {
return;
}

PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);

int resolvedAutowireMode = mbd.getResolvedAutowireMode();
if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
// Add property values based on autowire by name if applicable.
if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {
autowireByName(beanName, mbd, bw, newPvs);
}
// Add property values based on autowire by type if applicable.
if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
autowireByType(beanName, mbd, bw, newPvs);
}
pvs = newPvs;
}

boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);

PropertyDescriptor[] filteredPds = null;
if (hasInstAwareBpps) {
if (pvs == null) {
pvs = mbd.getPropertyValues();
}
//对满足条件的BeanPostProcessor,执行postProcessProperties方法,对bean的属性进行赋值操作
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) {
if (filteredPds == null) {
filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
}
pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) {
return;
}
}
pvs = pvsToUse;
}
}
}
if (needsDepCheck) {
if (filteredPds == null) {
filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
}
checkDependencies(beanName, mbd, filteredPds, pvs);
}

if (pvs != null) {
applyPropertyValues(beanName, mbd, bw, pvs);
}
}

initializeBean

initializeBean大致执行步骤如下:

  1. 若接口实现了Aware,则执行对应的方法
  2. 执行所有BeanPostProcessor的postProcessBeforeInitialization方法
  3. 执行BeanDefinition中定义的init-method
  4. 执行所有BeanPostProcessor的postProcessAfterInitialization方法
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
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
invokeAwareMethods(beanName, bean);
return null;
}, getAccessControlContext());
}
else {
//1. 若接口实现了`Aware`,则执行对应的方法
invokeAwareMethods(beanName, bean);
}

Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
2. 执行所有`BeanPostProcessor`的`postProcessBeforeInitialization`方法
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}

try {
3. 执行`BeanDefinition`中定义的`init-method`
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, "Invocation of init method failed", ex);
}
if (mbd == null || !mbd.isSynthetic()) {
4. 执行所有`BeanPostProcessor`的`postProcessAfterInitialization`方法
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}

return wrappedBean;
}

我们对上述initializeBean的第三步骤不做过多介绍,其大致执行过程为在初始化BeanDefinition时,将initMethod的值通过下述方法加载

1
void setInitMethodName(@Nullable String initMethodName);

然后initializeBean通过invokeInitMethods来执行initMethod

applyBeanPostProcessorsBeforeInitialization

我们同样通过断点调试的方式来观察,@PostConstruct方法是何时被执行的
spring-initMethod执行过程_2019-11-14-01-45-47.png 我们可以得知是在CommonAnnotationBeanPostProcessor中被处理的

我们查看其源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
//查找是否有@PostConstruct注解的方法
LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
try {
//执行所有被注解了@PostConstruct的方法
metadata.invokeInitMethods(bean, beanName);
}
catch (InvocationTargetException ex) {
throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException());
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Failed to invoke init method", ex);
}
return bean;
}

查看findLifecycleMetadata-->buildLifecycleMetadata细节
我们只需要了解this.initAnnotationType, this.destroyAnnotationType的赋值情况即可

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
private LifecycleMetadata buildLifecycleMetadata(final Class<?> clazz) {
//是否有指定注解的方法存在
if (!AnnotationUtils.isCandidateClass(clazz, Arrays.asList(this.initAnnotationType, this.destroyAnnotationType))) {
return this.emptyLifecycleMetadata;
}

List<LifecycleElement> initMethods = new ArrayList<>();
List<LifecycleElement> destroyMethods = new ArrayList<>();
Class<?> targetClass = clazz;

do {
final List<LifecycleElement> currInitMethods = new ArrayList<>();
final List<LifecycleElement> currDestroyMethods = new ArrayList<>();

ReflectionUtils.doWithLocalMethods(targetClass, method -> {
if (this.initAnnotationType != null && method.isAnnotationPresent(this.initAnnotationType)) {
LifecycleElement element = new LifecycleElement(method);
currInitMethods.add(element);
if (logger.isTraceEnabled()) {
logger.trace("Found init method on class [" + clazz.getName() + "]: " + method);
}
}
if (this.destroyAnnotationType != null && method.isAnnotationPresent(this.destroyAnnotationType)) {
currDestroyMethods.add(new LifecycleElement(method));
if (logger.isTraceEnabled()) {
logger.trace("Found destroy method on class [" + clazz.getName() + "]: " + method);
}
}
});
//将扫描到的init,destroy方法放入LifecycleMetadata
initMethods.addAll(0, currInitMethods);
destroyMethods.addAll(currDestroyMethods);
targetClass = targetClass.getSuperclass();
}
while (targetClass != null && targetClass != Object.class);

return (initMethods.isEmpty() && destroyMethods.isEmpty() ? this.emptyLifecycleMetadata :
new LifecycleMetadata(clazz, initMethods, destroyMethods));
}

我们通过查看CommonAnnotationBeanPostProcessor的构造器发现了@PostConstruct的赋值过程。

1
2
3
4
5
6
public CommonAnnotationBeanPostProcessor() {
setOrder(Ordered.LOWEST_PRECEDENCE - 3);
setInitAnnotationType(PostConstruct.class);
setDestroyAnnotationType(PreDestroy.class);
ignoreResourceType("javax.xml.ws.WebServiceContext");
}

java面试题_1

发表于 2019-11-12 更新于 2020-06-30 分类于 面试题

HashMap,HashTable,CocurrentHashMap的共同点和区别

共同点:

  1. 底层使用拉链式数组
  2. 为了避免hash冲突,当当数组元素已用槽数量超过(容量*容载因子)就会扩容
  3. put时,对key进行hash计算槽,若槽没有元素则赋值,否则插入链表的结尾
  4. get时,对key进行hash计算槽,若槽没有元素或者仅有一个元素,则直接返回, 否则,通过equals方法比较key,返回指定的元素

不同点:

  1. HashTable的key和value不允许null
  2. hash方法不同,HashTable直接对hashcode进行取模运算,HashMap首先对hashcode进行扰动计算,尽量避免 hash 碰撞。然后因其数组长度恒定为2n2^n2n,所以直接通过与运算进行取模,
  3. HashMap线程不安全,HashTable通过synchronized修改关键方法确保线程安全,CoccurentHashMap通过分段锁的方式实现

说出几种幂等的实现方式

幂等操作指任意多次执行的结果和执行一次的结果一样。通俗来说,就是同一用户的对同一操作的多次请求的结果是一致的。

保证幂等性主要是三点:

  1. 对于同一操作的请求必须有唯一标识,例如订单支付系统,肯定包含订单ID,确保一个订单仅支付一次。
  2. 处理请求时需要有标识记录操作的状态,如正在处理中,已经处理完成等。
  3. 每次接受请求时,需要判断是否已经处理过该请求或者正在处理该请求。

实现方式:

  1. 分布式锁
  2. 数据库锁
  3. 事务

Spring的init-method,destroy-metdho的实现方式

根据spring_initMethod执行过程的分析,我们可以知道Spring在扫描bean的配置信息时,将 init-method,destroy-metdhod的信息存储在BeanDefinition中,在bean的生命周期的一开始即实例化bean,以及对bean的属性进行初始化赋值后,会查找当前BeanDefinition,是否有init-method方法,有则通过反射去执行。在bean的生命周期的最后,会查找当前BeanDefinition,是否有destroy-metdhod方法,有则通过反射去执行。

java数组

发表于 2019-11-12 更新于 2020-06-30 分类于 java

java 在堆中的内存分为三个部分

  1. 对象头 Object header
  2. 值 value
  3. 对齐 padding(不一定存在,java 内存需要对齐 8byte,不足部分填充)

java 对象头

1
2
3
4
5
|--------------------------------------------------------------|
| Object Header (64 bits) |
|------------------------------------|-------------------------|
| Mark Word (32 bits) | Klass Word (32 bits) |
|------------------------------------|-------------------------|

java 数组头

1
2
3
4
5
|---------------------------------------------------------------------------------|
| Object Header (96 bits) |
|--------------------------------|-----------------------|------------------------|
| Mark Word(32bits) | Klass Word(32bits) | array length(32bits) |
|--------------------------------|-----------------------|------------------------|

其中Mark Word

1
2
3
4
5
6
7
8
9
10
11
12
13
|-------------------------------------------------------|--------------------|
| Mark Word (32 bits) | State |
|-------------------------------------------------------|--------------------|
| identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 | Normal |
|-------------------------------------------------------|--------------------|
| thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 | Biased |
|-------------------------------------------------------|--------------------|
| ptr_to_lock_record:30 | lock:2 | Lightweight Locked |
|-------------------------------------------------------|--------------------|
| ptr_to_heavyweight_monitor:30 | lock:2 | Heavyweight Locked |
|-------------------------------------------------------|--------------------|
| | lock:2 | Marked for GC |
|-------------------------------------------------------|--------------------|
1
2
3
4
5
6
7
8
9
10
11
12
13
|------------------------------------------------------------------------------|--------------------|
| Mark Word (64 bits) | State |
|------------------------------------------------------------------------------|--------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 | Normal |
|------------------------------------------------------------------------------|--------------------|
| thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | lock:2 | Biased |
|------------------------------------------------------------------------------|--------------------|
| ptr_to_lock_record:62 | lock:2 | Lightweight Locked |
|------------------------------------------------------------------------------|--------------------|
| ptr_to_heavyweight_monitor:62 | lock:2 | Heavyweight Locked |
|------------------------------------------------------------------------------|--------------------|
| | lock:2 | Marked for GC |
|------------------------------------------------------------------------------|--------------------|

lock:2 位的锁状态标记位,由于希望用尽可能少的二进制位表示尽可能多的信息,所以设置了 lock 标记。该标记的值不同,整个 mark word 表示的含义不同。

biased_lock Tlock 状态
0 01 无锁
1 01 偏向锁
0 00 轻量级锁
0 10 重量级锁
0 11 GC 标记
  1. biased_lock`:对象是否启用偏向锁标记,只占 1 个二进制位。为 1 时表示对象启用偏向锁,为 0 时表示对象没有偏向锁。

  2. age:4 位的 Java 对象年龄。在 GC 中,如果对象在 Survivor 区复制一次,年龄增加 1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行 GC 的年龄阈值为 15,并发 GC 的年龄阈值为 6。由于 age 只有 4 位,所以最大值为 15,这就是-XX:MaxTenuringThreshold 选项最大值为 15 的原因。

  3. identity_hashcode:25 位的对象标识 Hash 码,采用延迟加载技术。调用方法 System.identityHashCode()计算,并会将结果写到该对象头中。当对象被锁定时,该值会移动到管程 Monitor 中。

  4. thread:持有偏向锁的线程 ID。

  5. epoch:偏向时间戳。

  6. ptr_to_lock_record:指向栈中锁记录的指针。

  7. ptr_to_heavyweight_monitor:指向管程 Monitor 的指针。

class pointer

这一部分用于存储对象的类型指针,该指针指向它的类元数据,JVM 通过这个指针确定对象是哪个类的实例。该指针的位长度为 JVM 的一个字大小,即 32 位的 JVM 为 32 位,64 位的 JVM 为 64 位。

array length

如果对象是一个数组,那么对象头还需要有额外的空间用于存储数组的长度,这部分数据的长度也随着 JVM 架构的不同而不同:32 位的 JVM 上,长度为 32 位;64 位 JVM 则为 64 位。64 位 JVM 如果开启+UseCompressedOops 选项,该区域长度也将由 64 位压缩至 32 位。

数组下标寻址

  1. 首先根据栈上的指针找到该数组对象在内存中的位置p

  2. 判断index是否越界,即与数组对象头中存储的array length做比较

  3. 根据数组对象头的class pointer确定数组元素的内存占用长度n

  4. 根据数组对象头长度base和下标计算出访问的元素的内存位置,即p+base+n*index

bitOperation

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

在 Java 中,位运算符有很多,例如与&、非~、或|、异或^、移位<<和>>等。这些运算符在日常编码中很少会用到。

在下面的一个例子中,会用到位掩码BitMask,其中包含大量的位运算。不只是在Java中,其他编写语言中也是可以使用的。

例如,在一个系统中,用户一般有查询Select、新增Insert、修改Update、删除Delete四种权限,四种权限有多种组合方式,也就是有16中不同的权限状态(2 的 4 次方)。

Permission

一般情况下会想到用四个boolean类型变量来保存:

1
2
3
4
5
6
7
8
9
10
public class Permission {
// 是否允许查询
private boolean allowSelect;
// 是否允许新增
private boolean allowInsert;
// 是否允许删除
private boolean allowDelete;
// 是否允许更新
private boolean allowUpdate;
}

上面用四个 boolean 类型变量来保存每种权限状态。

NewPermission

下面是另外一种方式,使用位掩码的话,用一个二进制数即可,每一位来表示一种权限,0表示无权限,1表示有权限。

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
public class NewPermission {
// 是否允许查询,二进制第1位,0表示否,1表示是
public static final int ALLOW_SELECT = 1 << 0; // 0001
// 是否允许新增,二进制第2位,0表示否,1表示是
public static final int ALLOW_INSERT = 1 << 1; // 0010
// 是否允许修改,二进制第3位,0表示否,1表示是
public static final int ALLOW_UPDATE = 1 << 2; // 0100
// 是否允许删除,二进制第4位,0表示否,1表示是
public static final int ALLOW_DELETE = 1 << 3; // 1000
// 存储目前的权限状态
private int flag;
/**
* 重新设置权限
*/
public void setPermission(int permission) {
flag = permission;
}
/**
* 添加一项或多项权限
*/
public void enable(int permission) {
flag |= permission;
}
/**
* 删除一项或多项权限
*/
public void disable(int permission) {
flag &= ~permission;
}
/**
* 是否拥某些权限
*/
public boolean isAllow(int permission) {
return (flag & permission) == permission;
}
/**
* 是否禁用了某些权限
*/
public boolean isNotAllow(int permission) {
return (flag & permission) == 0;
}
/**
* 是否仅仅拥有某些权限
*/
public boolean isOnlyAllow(int permission) {
return flag == permission;
}
}

以上代码中,用四个常量表示了每个二进制位代码的权限项。

例如:

ALLOW_SELECT = 1 << 0 转成二进制就是0001,二进制第一位表示Select权限。 ALLOW_INSERT = 1 << 1 转成二进制就是0010,二进制第二位表示Insert权限。

private int flag存储了各种权限的启用和停用状态,相当于代替了Permission中的四个boolean类型的变量。

用flag的四个二进制位来表示四种权限的状态,每一位的 0 和 1 代表一项权限的启用和停用,下面列举了部分状态表示的权限:

flag 删除 修改 新增 查询
1(0001) 0 0 0 1 只允许查询(即等于 ALLOW_SELECT)
2(0010) 0 0 1 0 只允许新增(即等于 ALLOW_INSERT)
4(0100) 0 1 0 0 只允许修改(即等于 ALLOW_UPDATE)
8(1000) 1 0 0 0 只允许删除(即等于 ALLOW_DELETE)
3(0011) 0 0 1 1 只允许查询和新增
0 0 0 0 0 四项权限都不允许
15(1111) 1 1 1 1 四项权限都允许

使用位掩码的方式,只需要用一个大于或等于0且小于16的整数即可表示所有的 16 种权限的状态。

此外,还有很多设置权限和判断权限的方法,需要用到位运算,例如:

1
2
3
public void enable(int permission) {
flag |= permission; // 相当于flag = flag | permission;
}

调用这个方法可以在现有的权限基础上添加一项或多项权限。

添加一项Update权限:

1
permission.enable(NewPermission.ALLOW_UPDATE);

假设现有权限只有Select,也就是flag是0001。执行以上代码,flag = 0001 | 0100,也就是0101,便拥有了Select和Update两项权限。

添加Insert、Update、Delete三项权限:

1
2
permission.enable(NewPermission.ALLOW_INSERT
| NewPermission.ALLOW_UPDATE | NewPermission.ALLOW_DELETE);

NewPermission.ALLOW_INSERT | NewPermission.ALLOW_UPDATE | NewPermission.ALLOW_DELETE运算结果是1110。假设现有权限只有Select,也就是flag是0001。flag = 0001 | 1110,也就是1111,便拥有了这四项权限,相当于添加了三项权限。

上面的设置如果使用最初的Permission类的话,就需要下面三行代码:

1
2
3
permission.setAllowInsert(true);
permission.setAllowUpdate(true);
permission.setAllowDelete(true);

二者对比

设置仅允许 Select 和 Insert 权限

Permission

1
2
3
4
permission.setAllowSelect(true);
permission.setAllowInsert(true);
permission.setAllowUpdate(false);
permission.setAllowDelete(false);

NewPermission

1
permission.setPermission(NewPermission.ALLOW_SELECT | NewPermission.ALLOW_INSERT);

判断是否允许 Select 和 Insert、Update 权限

Permission

1
if (permission.isAllowSelect() && permission.isAllowInsert() && permission.isAllowUpdate())

NewPermission

1
2
if (permission. isAllow (NewPermission.ALLOW_SELECT
| NewPermission.ALLOW_INSERT | NewPermission.ALLOW_UPDATE))

判断是只否允许 Select 和 Insert 权限

Permission

1
2
if (permission.isAllowSelect() && permission.isAllowInsert()
&& !permission.isAllowUpdate() && !permission.isAllowDelete())

NewPermission

1
if (permission. isOnlyAllow (NewPermission.ALLOW_SELECT | NewPermission.ALLOW_INSERT))

二者对比可以感受到MyPermission位掩码方式相对于Permission的优势,可以节省很多代码量,位运算是底层运算,效率也非常高,而且理解起来也很简单。

genericsAndReflect

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

void类型的范型方法

1
2
3
private <T> void set(T t) {
System.out.println(t);
}

java运行时无法捕获ClassCastException的解决办法

1
2
3
4
5
6
7
private static <T> T get(Object o, T def) {
try {
return (T) def.getClass().cast(o);
} catch (Throwable e) {
return (T) def;
}
}

通过查看字节码就可以了解,直接 return (T) value 是在方法外检测cast

数组的 class

1
Object[].class

可变参数方法的反射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void me(Object ... objects){
for (Object object : objects) {
System.out.println(object);
}
}
@Test
public void test() throws Exception {
Class clazz = this.getClass();
//Method method = clazz.getMethod("me",(new Object[0]).getClass());
//Method method = clazz.getMethod("me",Array.newInstance(Object.class,0).getClass());
Method method = clazz.getMethod("me",Class.forName("[Ljava.lang.Object;"));
//1
Object objs = Array.newInstance(object.class,2);
Array.set(objs,0,1);
Array.set(objs,1,"test");
method.invoke(clazz,objs);
//2
Object[] obj = {1,"test"}
method.invoke(clazz,new Object[]{obj});
}

可变参数不可直接显式使用 null 作为参数

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TestStatic {
public static void main(String[] args) {
String s = null;
m1(s);
Util.log("begin null");
m1(null);
}

private static void m1(String... strs) {
System.out.println(strs.length);
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
0: aconst_null          //将null压入操作栈
1: astore_1 //弹出栈顶(null)存储到本地变量1
2: iconst_1 //压栈1此时已经到方法m1了,在初始化参数,此值作为数组长度
3: anewarray #2 //新建数组 // class java/lang/String
6: dup //复制数组指针引用
7: iconst_0 //压栈0,作为数组0角标
8: aload_1 //取本地变量1值压栈,作为数组0的值
9: aastore //根据栈顶的引用型数值(value)、数组下标(index)、数组引用(arrayref)出栈,将数值存入对应的数组元素中
10: invokestatic #3 //此时实际传递的是一个数组,只是0位置为null的元素 Method m1:([Ljava/lang/String;)V
13: iconst_1
14: anewarray #4 //class java/lang/Object
17: dup
18: iconst_0
19: ldc #5 //String begin null
1: aastore
22: invokestatic #6 //Method li/Util.log:([Ljava/lang/Object;)V
25: aconst_null //此处并没有新建数组操作,直接压栈null
26: invokestatic #3 //此处一定会抛出空指针 Method m1:([Ljava/lang/String;)V
29: return

泛型 extends super

1
2
3
4
5
6
7
8
9
//不管是extends或是super,只能使用在变量声明上,实际赋值的时候,一定是指定具体实现类的。

//那么对于<? extends T>来说,具体的实现类的泛型A只是变量声明的泛型T的子类,如果以T进行插入时,是无法保证插入的class类型,一定是A,所以extends禁用插入动作
List<Apple> apples = new ArrayList<Apple>();
List<? extends Fruit> fruits = apples;
// 对于<? super T>来说,具体的实现类的泛型A一定是变量声明的泛型T的父类,如果以T类型进入取值操作,无法保证取出的值一定是T类型,因为A一定是T的父类,所以插入的所有实例一定也是A的多态

List<Fruit> fruits = new ArrayList<Fruit>();
List<? super Apple> = fruits;

如何修改 final 修饰符的值

1
2
3
4
5
6
7
8
9
10
11
12
String str = "fuck";

Field value = String.class.getDeclaredField("value");
//Field中包含当前属性的修饰符,通过改变修饰符的final属性,达到重新赋值的功能
Field modifier = Field.class.getDeclaredField("modifiers");
modifier.setAccessible(true);
modifier.set(value, value.getModifiers() & ~Modifier.FINAL);

value.setAccessible(true);
value.set(str, "notfuck".toCharArray());
//修改成功后重新加上final修饰符
modifier.set(value, value.getModifiers() | Modifier.FINAL);}

子类继承时不使用泛型

1
2
3
4
5
6
7
8
9
10
public interface Father<T> {
void func(T t);
}

public Son implements Father<String> {

public void func(String t){
...
}
}

数组对象与数组的区别

genericsAndReflect_代码示例.png
genericsAndReflect_代码示例.png

其字节码如下

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
public class com/leaderli/demo/TheClassTest {

public <init>()V
L0
LINENUMBER 3 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Lcom/leaderli/demo/TheClassTest; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1

public static varargs test([Ljava/lang/Object;)V
L0
LINENUMBER 8 L0
RETURN
L1
LOCALVARIABLE args [Ljava/lang/Object; L0 L1 0
MAXSTACK = 0
MAXLOCALS = 1

public static main([Ljava/lang/String;)V
L0
LINENUMBER 10 L0
ICONST_0 //压入0
ANEWARRAY java/lang/String //弹出栈顶,生成一个栈顶数值长度的String数组
ASTORE 1 //弹出栈顶到本地变量1,即p1
L1
LINENUMBER 11 L1
ICONST_1 //压入1
ANEWARRAY java/lang/Object //生成一个长度为1的Object数组
DUP //复制一份引用至栈顶
ICONST_0 //压入0
ALOAD 1 //压入本地变量1,即p1
AASTORE //弹出栈顶的引用型数值(value)、数组下标(index)、数组引用(arrayref)出栈,将数值存入对应的数组元素中。这里的意思是将p1存入到方法test的形参数组角标0的位置
INVOKESTATIC com/leaderli/demo/TheClassTest.test ([Ljava/lang/Object;)V // 弹出栈顶所有元素作为参数调用方法,方法返回值会被压入栈顶,因方法返回类型为V,操作栈则清空
L2
LINENUMBER 12 L2
ALOAD 1 //压入本地变量1
CHECKCAST [Ljava/lang/Object; //类型检查
CHECKCAST [Ljava/lang/Object; //类型检查
ASTORE 2 //弹出栈顶到本地变量2,即p2
L3
LINENUMBER 13 L3
ALOAD 2 //压入本地变量1,即p1
INVOKESTATIC com/leaderli/demo/TheClassTest.test ([Ljava/lang/Object;)V // 弹出栈顶所有元素作为参数调用方法,方法返回值会被压入栈顶,因方法返回类型为V,操作栈则清空
L4
LINENUMBER 14 L4
RETURN
L5
LOCALVARIABLE args [Ljava/lang/String; L0 L5 0
LOCALVARIABLE p1 Ljava/lang/Object; L1 L5 1 //描述符L表示它是对象类型
LOCALVARIABLE p2 [Ljava/lang/Object; L3 L5 2 //描述符[L表示它是对象数组类型
MAXSTACK = 4
MAXLOCALS = 3
}
1…91011…15

lijun

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