Fork me on GitHub
夸克的博客


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

HashMap拾遗(一)

发表于 2020-03-28 | 分类于 Java基础 | 热度: ℃
字数统计: 6,552 | 阅读时长 ≈ 29

开始

HashMap是在开发工作中经常使用的集合类之一,熟悉其源码应该是基本要求。这篇文章对jdk1.8版本中的HashMap的一些常用方法的源码进行个记录。ps:这篇文章没有对其中的树化进行深究,比如提供的TreeNode内部类的结构和在扩容、Hash碰撞的时候的静态方法,之后有时间再研究下。

阅读全文 »

Comparator接口在java8中的优化

发表于 2020-03-22 | 分类于 Java语法 , Java8 | 热度: ℃
字数统计: 2,506 | 阅读时长 ≈ 12

开始

Comparator接口或者Comparable接口在日常开发工作中是经常用到的,用于比较一组数据或者对象,在java8之后,也可以看到在Comparator接口中加入了一些default方法和static方法,这里做一个简单说明。

阅读全文 »

栈与队列相互实现

发表于 2020-03-22 | 分类于 数据结构 | 热度: ℃
字数统计: 1,171 | 阅读时长 ≈ 5

背景

一道很经典的数据结构的题目实现。

栈:一般是后进先出的顺序,可以看下java中的Stack这个类。

队列:一般是先进先出的顺序,但是java中的Queue接口中也写了注释,没有要求是必须严格的先进先出,比如java中也有优先级队列、双端队列Deque。

阅读全文 »

@Async加循环依赖的启动报错问题

发表于 2020-03-18 | 分类于 spring , spring boot | 热度: ℃
字数统计: 2,092 | 阅读时长 ≈ 9

现象及相关引用博客

https://segmentfault.com/a/1190000021217176

问题

Spring其实是可以帮助解决循环依赖的,但是在循环依赖的两个bean上有一个加入了@Async注解之后,在启动的时候就报错不能进行循环依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Component
public class A {

@Autowired
private B b;

@Async
public void testA() {
System.out.println(Thread.currentThread().getName());
}
}


@Component
public class B {
@Autowired
private A a;

public void testB() {
System.out.println("调用到了B");
}
}

对应的错误:

1
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Bean with name 'a' has been injected into other beans [b] 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.

注意这里@Transaction虽然也是使用的代理,但是循环引用如果是@Transaction注解 是不影响启动的 可以在最早初始化类实例的时候就能拿到代理对象, 而async是在postProcessor后置处理器当中处理的,所以在循环引用时会放入原始对象而不是代理对象 在之后的check时会报错。这里要做下区分。

问题分析及解决方案

报错所在方法:
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
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
protected Object doCreateBean( ... ){
...
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
...

// populateBean这一句特别的关键,它需要给A的属性赋值,所以此处会去实例化B~~
// 而B我们从上可以看到它就是个普通的Bean(并不需要创建代理对象),实例化完成之后,继续给他的属性A赋值,而此时它会去拿到A的早期引用
// 也就在此处在给B的属性a赋值的时候,会执行到上面放进去的Bean A流程中的getEarlyBeanReference()方法 从而拿到A的早期引用~~
// 执行A的getEarlyBeanReference()方法的时候,会执行自动代理创建器,但是由于A没有标注事务,所以最终不会创建代理,so B合格属性引用会是A的**原始对象**
// 需要注意的是:@Async的代理对象不是在getEarlyBeanReference()中创建的,是在postProcessAfterInitialization创建的代理
// 从这我们也可以看出@Async的代理它默认并不支持你去循环引用,因为它并没有把代理对象的早期引用提供出来~~~(注意这点和自动代理创建器的区别~)

// 结论:此处给A的依赖属性字段B赋值为了B的实例(因为B不需要创建代理,所以就是原始对象)
// 而此处实例B里面依赖的A注入的仍旧为Bean A的普通实例对象(注意 是原始对象非代理对象) 注:此时exposedObject也依旧为原始对象
populateBean(beanName, mbd, instanceWrapper);

// 标注有@Async的Bean的代理对象在此处会被生成~~~ 参照类:AsyncAnnotationBeanPostProcessor
// 所以此句执行完成后 exposedObject就会是个代理对象而非原始对象了
exposedObject = initializeBean(beanName, exposedObject, mbd);

...
// 这里是报错的重点~~~
if (earlySingletonExposure) {
// 上面说了A被B循环依赖进去了,所以此时A是被放进了二级缓存的,所以此处earlySingletonReference 是A的原始对象的引用
// (这也就解释了为何我说:如果A没有被循环依赖,是不会报错不会有问题的 因为若没有循环依赖earlySingletonReference =null后面就直接return了)
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
// 上面分析了exposedObject 是被@Aysnc代理过的对象, 而bean是原始对象 所以此处不相等 走else逻辑
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
// allowRawInjectionDespiteWrapping 标注是否允许此Bean的原始类型被注入到其它Bean里面,即使自己最终会被包装(代理)
// 默认是false表示不允许,如果改为true表示允许,就不会报错啦。这是我们后面讲的决方案的其中一个方案~~~
// 另外dependentBeanMap记录着每个Bean它所依赖的Bean的Map~~~~
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
// 我们的Bean A依赖于B,so此处值为["b"]
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);

// 对所有的依赖进行一一检查~ 比如此处B就会有问题
// “b”它经过removeSingletonIfCreatedForTypeCheckOnly最终返返回false 因为alreadyCreated里面已经有它了表示B已经完全创建完成了~~~
// 而b都完成了,所以属性a也赋值完成儿聊 但是B里面引用的a和主流程我这个A竟然不相等,那肯定就有问题(说明不是最终的)~~~
// so最终会被加入到actualDependentBeans里面去,表示A真正的依赖~~~
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.");
}
}
}
}
...
}

debug看到的对象注入:

可以看到@Async注解标注的Bean的创建代理的时机是在检查bean中引用的之后的。看@EnableAsync注解会通过AsyncConfigurationSelector注入AsyncAnnotationBeanPostProcessor这个后置处理器,在其实现了postProcessAfterInitalization方法,创建代理即在此中。

这里的根本原理是只要能被切面AsyncAnnotationAdvisor切入的Bean都会在后置处理器中生成一个代理对象(如果已经是代理对象,那么加入该切面即可),赋值为上边doCreateBean中的exposedObject作为返回值加入到spring容器中。

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
// 关键是这里。当Bean初始化完成后这里会执行,这里会决策看看要不要对此Bean创建代理对象再返回~~~
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (this.advisor == null || bean instanceof AopInfrastructureBean) {
// Ignore AOP infrastructure such as scoped proxies.
return bean;
}

// 如果此Bean已经被代理了(比如已经被事务那边给代理了~~)
if (bean instanceof Advised) {
Advised advised = (Advised) bean;

// 此处拿的是AopUtils.getTargetClass(bean)目标对象,做最终的判断
// isEligible()是否合适的判断方法 是本文最重要的一个方法,下文解释~
// 此处还有个小细节:isFrozen为false也就是还没被冻结的时候,就只向里面添加一个切面接口 并不要自己再创建代理对象了 省事
if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
// Add our local Advisor to the existing proxy's Advisor chain...
// beforeExistingAdvisors决定这该advisor最先执行还是最后执行
// 此处的advisor为:AsyncAnnotationAdvisor 它切入Class和Method标注有@Aysnc注解的地方~~~
if (this.beforeExistingAdvisors) {
advised.addAdvisor(0, this.advisor);
} else {
advised.addAdvisor(this.advisor);
}
return bean;
}
}

// 若不是代理对象,此处就要下手了~~~~isEligible() 这个方法特别重要
if (isEligible(bean, beanName)) {
// copy属性 proxyFactory.copyFrom(this); 生成一个新的ProxyFactory
ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
// 如果没有强制采用CGLIB 去探测它的接口~
if (!proxyFactory.isProxyTargetClass()) {
evaluateProxyInterfaces(bean.getClass(), proxyFactory);
}
// 添加进此切面~~ 最终为它创建一个getProxy 代理对象
proxyFactory.addAdvisor(this.advisor);
//customize交给子类复写(实际子类目前都没有复写~)
customizeProxyFactory(proxyFactory);
return proxyFactory.getProxy(getProxyClassLoader());
}

// No proxy needed.
return bean;
}

// 我们发现BeanName最终其实是没有用到的~~~
// 但是子类AbstractBeanFactoryAwareAdvisingPostProcessor是用到了的 没有做什么 可以忽略~~~
protected boolean isEligible(Object bean, String beanName) {
return isEligible(bean.getClass());
}
protected boolean isEligible(Class<?> targetClass) {
// 首次进来eligible的值肯定为null~~~
Boolean eligible = this.eligibleBeans.get(targetClass);
if (eligible != null) {
return eligible;
}
// 如果根本就没有配置advisor 也就不用看了~
if (this.advisor == null) {
return false;
}

// 最关键的就是canApply这个方法,如果AsyncAnnotationAdvisor 能切进它 那这里就是true
// 本例中方法标注有@Aysnc注解,所以铁定是能被切入的 返回true继续上面方法体的内容
eligible = AopUtils.canApply(this.advisor, targetClass);
this.eligibleBeans.put(targetClass, eligible);
return eligible;
}

分布式id雪花算法

发表于 2020-03-02 | 分类于 分布式ID | 热度: ℃
字数统计: 1,234 | 阅读时长 ≈ 5

基本原理

image-20220715235457678

代码

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
public class SnowflakeIdWorker {
/**
* 开始时间截 (2015-01-01)
*/
private final long twepoch = 1420041600000L;
/**
* 机器id所占的位数
*/
private final long workerIdBits = 5L;
/**
* 数据标识id所占的位数
*/
private final long datacenterIdBits = 5L;
/**
* 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
*/
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
/**
* 支持的最大数据标识id,结果是31
*/
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
/**
* 序列在id中占的位数
*/
private final long sequenceBits = 12L;
/**
* 机器ID向左移12位
*/
private final long workerIdShift = sequenceBits;
/**
* 数据标识id向左移17位(12+5)
*/
private final long datacenterIdShift = sequenceBits + workerIdBits;
/**
* 时间截向左移22位(5+5+12)
*/
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
/**
* 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)
*/
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
/**
* 工作机器ID(0~31)
*/
private long workerId;
/**
* 数据中心ID(0~31)
*/
private long datacenterId;
/**
* 毫秒内序列(0~4095)
*/
private long sequence = 0L;
/**
* 上次生成ID的时间截
*/
private long lastTimestamp = -1L;
/**
* 构造函数
* @param workerId 工作ID (0~31)
* @param datacenterId 数据中心ID (0~31)
*/
public SnowflakeIdWorker(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
/**
* 获得下一个ID (该方法是线程安全的)
* @return SnowflakeId
*/
public synchronized long nextId() {
long timestamp = timeGen();
// 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
if (timestamp < lastTimestamp) {
throw new RuntimeException(
String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
// 如果是同一时间生成的,则进行毫秒内序列
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
// 毫秒内序列溢出
if (sequence == 0) {
//阻塞到下一个毫秒,获得新的时间戳
timestamp = tilNextMillis(lastTimestamp);
}
}
// 时间戳改变,毫秒内序列重置
else {
sequence = 0L;
}
// 上次生成ID的时间截
lastTimestamp = timestamp;
// 移位并通过或运算拼到一起组成64位的ID
return ((timestamp - twepoch) << timestampLeftShift) //
| (datacenterId << datacenterIdShift) //
| (workerId << workerIdShift) //
| sequence;
}
/**
* 阻塞到下一个毫秒,直到获得新的时间戳
* @param lastTimestamp 上次生成ID的时间截
* @return 当前时间戳
*/
protected long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
/**
* 返回以毫秒为单位的当前时间
* @return 当前时间(毫秒)
*/
protected long timeGen() {
return System.currentTimeMillis();
}

public static void main(String[] args) throws InterruptedException {
SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);
for (int i = 0; i < 10; i++) {
long id = idWorker.nextId();
Thread.sleep(1);
System.out.println(id);
}
}
}

代码实现细节

  • nextId()方法是加锁的,同进程内需要竞争锁,因为内部有赋值维护sequence(序列号)和lastTimestamp(上次生成id的时间戳)。
  • 参数是组成机器部分的dataCenterId和workerId,各占5个Bit位。
  • 时钟回退是有可能发生的,如果发生了之后是可能会有分布式id重复的问题,这时候直接报错。(条件是当前时间戳 timeStampe < lastTimeStamp)
  • 如果当前时间戳timeStamp和lastTimeStamp相等,说明是同一个时间戳的获取分布式id的流程,这时候通过sequence增加来分配id。
  • 如果sequence + 1之后和sequence的掩码做 &操作,如果算出为0,则说明12位的sequence发生了溢出,这时要将timeStamp更新为下一个时间戳来获取分布式id。
  • 最后根据雪花算法,将移动对应的位之后再做或操作生成对应的64位id。

位运算的运用

  • sequence++之后判断是否溢出(大于对应的sequence的值的范围),用的是 sequence++ & sequenceMask == 0 来判断,这里掩码是2^12 - 1。与运算是二进制位都是1的时候才是1,其余为0。这里2^12-1的二进制位是 011111111111 做与操作结果为0 说明是和10000000000 做了与操作,即代表了溢出。
  • 最后按照雪花算法从高到低的位置左移对应的长度,再做或操作。或操作是当全为0时,结果的二进制位为0, 其余情况为1。高位比如时间戳已经左移了(10 + 12= 22位),后面的22位都是0, 此时和代表机器位置的数字做或操作,即将机器的10位二进制数字直接填充对应的位置即可。同理sequence的二进制位也一样。这样通过或运算就将 三部分的二进制位拼接了起来。

雪花算法流程

image-20220715235518521

数据库死锁日志查看

发表于 2020-01-21 | 分类于 mysql | 热度: ℃
字数统计: 763 | 阅读时长 ≈ 4

https://segmentfault.com/a/1190000018730103

1
show engine innodb status;

记录锁,间隙锁,Next-key 锁和插入意向锁。这四种锁对应的死锁如下:

记录锁(LOCK_REC_NOT_GAP): lock_mode X locks rec but not gap
间隙锁(LOCK_GAP): lock_mode X locks gap before rec
Next-key 锁(LOCK_ORNIDARY): lock_mode X
插入意向锁(LOCK_INSERT_INTENTION): lock_mode X locks gap before rec insert intention

关于显式锁和隐式锁

image-20220701102530237

死锁日志

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
=====================================
2018-08-05 21:20:27 0x7fd40c082700 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 4 seconds
-----------------
BACKGROUND THREAD
-----------------
srv_master_thread loops: 251 srv_active, 0 srv_shutdown, 22663 srv_idle
srv_master_thread log flush and writes: 22905
----------
SEMAPHORES
----------
OS WAIT ARRAY INFO: reservation count 513
OS WAIT ARRAY INFO: signal count 450
RW-shared spins 0, rounds 569, OS waits 286
RW-excl spins 0, rounds 127, OS waits 1
RW-sx spins 0, rounds 0, OS waits 0
Spin rounds per wait: 569.00 RW-shared, 127.00 RW-excl, 0.00 RW-sx
------------------------
LATEST DETECTED DEADLOCK
------------------------
2018-08-05 21:15:42 0x7fd40c0b3700
*** (1) TRANSACTION:
TRANSACTION 1095010, ACTIVE 21 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 5 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2
MySQL thread id 16, OS thread handle 140548578129664, query id 3052 183.6.50.229 root update
insert into t_bitfly values(7,7)
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 2514 page no 4 n bits 72 index num_key of table `test`.`t_bitfly` trx id 1095010 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
0: len 4; hex 80000007; asc ;;
1: len 8; hex 8000000000000008; asc ;;

*** (2) TRANSACTION:
TRANSACTION 1095015, ACTIVE 6 sec inserting
mysql tables in use 1, locked 1
4 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2
MySQL thread id 17, OS thread handle 140548711855872, query id 3056 183.6.50.229 root update
insert into t_bitfly values(5,5)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 2514 page no 4 n bits 72 index num_key of table `test`.`t_bitfly` trx id 1095015 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;

Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
0: len 4; hex 80000007; asc ;;
1: len 8; hex 8000000000000008; asc ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 2514 page no 4 n bits 72 index num_key of table `test`.`t_bitfly` trx id 1095015 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
0: len 4; hex 80000007; asc ;;
1: len 8; hex 8000000000000008; asc ;;

省略。。。

一些注释:
LATEST DETECTED DEADLOCK:标示为最新发生的死锁;
(1) TRANSACTION:此处表示事务1开始 ;
MySQL thread id 16, OS thread handle 140548578129664, query id 3052 183.6.50.229 root update:此处为记录当前数据库线程id;
insert into t_bitfly values(7,7):表示事务1在执行的sql ,不过比较悲伤的事情是show engine innodb status 是查看不到完整的事务的sql 的,通常显示当前正在等待锁的sql;
(1) WAITING FOR THIS LOCK TO BE GRANTED:此处表示当前事务1等待获取行锁;
(2) TRANSACTION:此处表示事务2开始 ;
insert into t_bitfly values(5,5):表示事务2在执行的sql
(2) HOLDS THE LOCK(S):此处表示当前事务2持有的行锁;
(2) WAITING FOR THIS LOCK TO BE GRANTED:此处表示当前事务2等待获取行锁;

windows下typora图床(附带阿里云教程)

发表于 2019-12-10 | 分类于 日常工具 | 热度: ℃
字数统计: 2,216 | 阅读时长 ≈ 8

typora

Typora是大家写博客、记笔记、写文档等日常使用场景下都会使用的一个MarkDown语法的软件,对于熟悉markdown语法和喜欢markdown简洁性的朋友来说,typora是不可或缺的工具。但是,对于图片处理,我们需要图床去将我们的本地图片(截图、流程图之类的)上传到第三方的对象存储上(当然自己的服务器也是可以的)。

本文基于一个typora在windows下的小插件windows下typora图床 来实现实时的粘贴图片到typora即将你的图片上传到阿里云OSS上,并且替换的实现过程,github上已经其实写的比较明白了,但是还是想把自己接入的过程和踩得坑记录一下。

对于typora的使用、阿里云OSS的使用(我记得一年只要个位数的钱)、markdown语法等网上有很多介绍和例子,下面是几个传送门:

  1. 阿里云对象存储

  2. typora.io

  3. markdown语法

typora图床

怎么发现的

如果你没有图床,那么在你写博客的过程中如果要使用阿里云图片的外链,得是这样的操作。

  1. 将图片上传到阿里云OSS对象存储上

image-20191210201936023

  1. 复制该图片的外链

image-20191210202043284

  1. 将外链用markdown语法粘贴到正文中

image-20191210202125030

这真的真的相当麻烦。(:з」∠)

所以图床就是用来解决这个问题,但是之前用的图床(之前用过chrome的一个图床插件)都是也只是省去了你登录对象存储在页面上上传的这一步,最后就还是要复制生成的url然后到typora的文章中。

这里就在网上搜了下typora的图床,看看有没有符合自己偷懒的想法的做法,一键截图之后复制到typora中然后就可以了。

然后就是Google搜了下,发现第一条就是日常学(划)习(水)的网站——知乎。

image-20191210202739755

于是就点开之后看到了typora的这个插件,进而有了这个文章

手工教程

首先贴一下这个插件的地址:github

这个文档中和知乎的回答差不多,我们可以直接来到使用这里开始:

image-20191210203153008

下载插件的代码到本地,这里不熟悉的github的同学可以直接点击图中的download zip即可。

image-20191210203319035

解压之后可以看到有这些文件,和github上的目录对应

image-20191210203431182

然后按照文档上的教程手工替换(复制plugins目录、替换window.html)到对应的typora安装目录下的resource\app目录下。替换完成之后的目录长这个样子。

image-20191210203640933

然后就是对里面的代码进行自己OSS的配置了。

打开plugins–>image–>upload.js文件(这里可以直接用记事本打开js文件,当然程序员自动sublime或者vscode。),拉到底就可以看到文档中说的init相关的代码。

image-20191210203936380

然后复制文档上的阿里云这段配置,把整个473行替换掉。

image-20191210204100544

接下来就按照注释(“//“后面的东西)来操作即可。

首先插件作者建议你添加一个子账号来单独操作你的OSS,这里简单理解下就是在你的阿里云上你可以建立多个用户组,而每个用户组中可以建立多个子用户,通过对用户组或者用户来设置权限达到一定操作。插件其实是用代码去调用阿里云提供的API来操作上传图片的,所以你肯定要在本地的typora的配置代码里填上一个关联你OSS的账号并且配置对应的权限才能成功上传图片;同时,出于安全考虑,你的这个账号应该只对你的OSS有写入和读的操作权限,所以建议来个子账号专门搞这个事情。

作者其实这里写的也很明白了,包括申请子用户的地址:https://ram.console.aliyun.com/users

打开之后点击新建用户,即可看到让你填用户账户信息。
image-20191210204747378

当然这里也可以直接添加用户组,然后在组下面添加用户。

填写你想要的登录名称(复杂点也没关系,在后面配置一般是关键字搜索选择的)、显示名称和勾选上编程访问。这里的编程访问我们也可以清楚看到是通过assess信息来支持开发者调用API访问的用户。

image-20191210205026239

点击确定,这时要收一个验证码:

image-20191210205203529

填写完之后这步很关键,可以看到会在页面上告知你一个accessKeyId和accessKeySercret,但是坑的地方是这个授权key的值只有在创建的这个页面才能看到,之后就看不到了,所以这里一定要进行复制或者保存这两个值。可以看到页面上也提供了对这两个值的复制功能。

image-20191210205455114

这时候就要给这个用户去设置权限。刚创建的子用户是没有任何权限的,这里你既然要对OSS进行上传写入的操作,肯定要给这个账户权限,可以看到在用户界面有拟刚才创建的子用户,并且可以配置权限

image-20191210205719451

这里在权限搜索框内输入OSS,就可以看到我们要配置的权限(管理OSS的权限),点击确定即可。

image-20191210205859885

到这里,init代码中需要的前三项就都有了。

image-20191210210321356

还剩个bucketDomain,这里作者其实也说的很明白了,在你的OSS概览页面上,会有对应的bucket域名,这里挑选一个即可,我挑选的是外网访问的这个bucket域名

image-20191210210533111

其实文档到这步也就没有了,我就以为是OK了,然后就很有自信的去保存了修改后的js文件去试了下。果然,不行(:з」∠)。

一直在提示我服务响应解析失败,错误。之后又对了遍文档发现也没漏啥。最后还是选择去看了upload代码。

首先这个错误肯定是作者定义的,然后就在upload.js中看到了这个错误。

image-20191210210949483

这里可以看到其实是调用接口异常了,所以返回的这个错误,所以大概率是刚才配置的问题,再往上翻才看到原来除了文件底部的init方法之外,还要去配置下代码中的setting信息,这里稍微吐槽下为啥文档里没有写(没有,就是我前端不熟就没看代码,菜是原罪= =)。

在setting配置里有这段代码:
image-20191210211346838

可以看到请求的域名和API访问是从这里解析的,所以你不配置这里,默认会用作者写死的jiebianjin去访问接口,当然调不通了(因为阿里云上并没有这个子用户)。这里还是刚才的accessKey信息和bucket信息。这里看到了设置了过期时间和上传的大小限制,进而手工改了下大小限制,默认是512k,我这里放大到了5M。

之后保存之后,又信心满满的去试了下,卧槽,居然还是报错。然后又对了下文档中的配置。

无奈打开了调试,typora其实就是个浏览器= =。windows下shift+f12是调试工具。这里没有把当时报错的接口的截图贴上,当时忘记截图了,这里是我在写这篇博客的时候调试截图的。通过一顿分析发现自己在配置BucketDomain的时候,忘记在最后加上一个反斜杠。这里大家在配置的过程中也可以注意下。

image-20191210212848266

给插件的建议

首先感谢插件作者 @Thobian 大佬写了这个插件,真的在typora下很方便,应该以后会比较重度使用。这里提几个建议。

  • 完善各个厂商对象存储的配置教程,其实腾讯云OSS我也用过,和阿里云大同小异。
  • 点击再上传这个功能可以加个弹窗之类的中间过程(不能省这个我get到,因为可能失败要重传),在写文过程中点击到图片会自动再上传一次,OSS上会有比较多的重复文件,不好管理
  • 接第二条,我在写文过程中喜欢ctrl+s保存,这个好像也会把我的图片再重新上传一次(不太确定),也会造成大量的相同文件。
  • 性能上有时会卡顿,如果是阿里云接口的锅当我没说哈(:з」∠)。
  • 继续开发更好的功能给大家用,会一如既往的支持的~

方法引用拾遗

发表于 2019-12-09 | 分类于 Java语法 , Java8 | 热度: ℃
字数统计: 955 | 阅读时长 ≈ 4

方法引用

方法引用是java8引入lambda表达式之后的一个特性,在日常的开发工作中经常使用,可以看做是一种函数指针(function pointer),也可以简单看做lambda表达式的一种语法糖。

阅读全文 »

使用@DependsOn解决一个spring启动问题

发表于 2019-08-04 | 分类于 spring | 热度: ℃
字数统计: 429 | 阅读时长 ≈ 2

前言

最近遇到了一个启动失败的问题,原因是在bean初始化完成之后的钩子方法中使用获取容器中bean的工具类,(对应工具类之前的一篇博客 获取springbean))。

分析

这里具体的场景是我想实现一个bean在钩子方法中往一个策略map中注册自己作为一个策略使用,但是在启动的时候报错:

第33行代码如下:

1
2
3
public static <T> T getBean(@NotNull Class<T> tClass) {
return context.getBean(tClass);
}

可以看到可能为空的是context,这个是通过在项目中启动时注入到ApplicationContextUtil中的静态变量context,很明显是在当前这个bean启动的时候,其钩子方法去调用这个变量还没实现context的注入。

1
2
3
4
@Override
public void afterPropertiesSet() throws Exception {
// 策略工厂中注册 自身 的代码
}

解决

这里主要是一个场景,其实在bean启动的时候是依赖ApplicationContextUtil这个bean的,但是因为getBean方法都static方法,在平常业务代码中调用都是容器启动完毕的时候,所以没有问题,但是这里是想实现在bean初始化时自动通过钩子往一个map工厂中注册bean实例,且该bean没有显示的@Resource依赖ApplicationContextUtil,所以在注册的时候applicationContextutil这个bean还没初始化好,这里在这些具体策略的类上加了@DependsOn(“applicationContextUtil”)

1
2
3
4
5
@Service
@Slf4j
@DependsOn(value = "applicationContextUtil")
public class AStrategy extends AbstractStrategy {
}

这表示这个bean的初始化是依赖 applicationContextUtil 这个bean初始化完成之后(也就是静态变量上下文被注入)才去初始化的,这样启动就不会报NPE了。

Lock接口用法和与synchronized的比较

发表于 2019-07-22 | 分类于 并发编程 , 并发基础 | 热度: ℃
字数统计: 1,804 | 阅读时长 ≈ 7

关于锁的一些知识

  1. 可重入锁

如果锁具有可重入性,则称为可重入锁。synchronized和ReentrantLock都是可重入锁。其实可重入锁实际上表明了锁的分配机制:是基于线程的分配还是基于方法调用的分配。
比如代码:

阅读全文 »
1…567…12
夸克

夸克

愿赌服输

114 日志
32 分类
121 标签
GitHub E-Mail csdn
© 2022 夸克 | Site words total count: 168.9k
|
主题 — NexT.Muse v5.1.4
博客全站共168.9k字

载入天数...载入时分秒...