xiaofang's blog

  • 首页

  • 标签

  • 分类

  • 归档

信号量

发表于 2019-06-05 | 更新于 2020-02-05 | 分类于 并发 | 评论数: | 阅读次数:

信号量

Semaphore 和操作系统的信号量很类似,但是这里它也可以作为锁使用。即可以作为锁与协调线程作用

  • 锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class SemaphoreDemo {
static Semaphore semaphore = new Semaphore(1,true);//一个信号量且是公平的,默认非公平
static Runnable r = () -> {
try {
System.out.println(Thread.currentThread().getName() + "等待");
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"进入");
semaphore.release();
System.out.println(Thread.currentThread().getName() + "释放");
} catch (InterruptedException e) {
e.printStackTrace();
}
};

public static void main(String[] args) {
Thread t1 = new Thread(r,"1");
Thread t2 = new Thread(r,"2");
Thread t3 = new Thread(r,"3");
t1.start();
t2.start();
t3.start();
}
}

打印结果
2等待
1等待
3等待
2进入
2释放
1进入
1释放
3进入
3释放

可以看到首先3个线程同时到来临界区边界,但是同时只有一个线程能进入临界区。

  • 协调线程
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
public class SemaohoreDemo2 {
public static void main(String[] args) {
ExecutorService pool = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(3, true);
for (int i = 0; i < 6; i++) {
Runnable runnable = () -> {
try {
semaphore.acquire();//获取信号灯许可
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(2000);//为了打印,进入临界区总的线程数量
System.out.println(Thread.currentThread().getName()+"进入临界区");
Thread.sleep(4000);//模拟业务逻辑处理
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println(Thread.currentThread().getName()+"释放信号");
semaphore.release();//释放信号灯

};
pool.execute(runnable);

}
pool.shutdown();
}
}

打印结果
pool-1-thread-3进入临界区
pool-1-thread-2进入临界区
pool-1-thread-1进入临界区
pool-1-thread-3释放信号
pool-1-thread-1释放信号
pool-1-thread-2释放信号
pool-1-thread-4进入临界区
pool-1-thread-5进入临界区
pool-1-thread-6进入临界区
pool-1-thread-4释放信号
pool-1-thread-5释放信号
pool-1-thread-6释放信号

从结果可以看到有3个线程同时进入了临界区。

LockSupport

发表于 2019-06-05 | 更新于 2020-02-05 | 分类于 并发 | 评论数: | 阅读次数:

线程阻塞

LockSupport 相比较suspend会造成永久被挂起,它是安全的,即使unpark()方法再在前面执行也无所谓,因为它的底层使用的信号量,为每一个线程准备了许可,如果许可可用,则park惠消费它,如果不可以不可用则阻塞等待,而unpark是将一个许可变成可用,但是和信号量有些许区别,它是不可以累加的,每次最多有一个许可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class LockSupportDemo {
static Runnable r = () -> {
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + "执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.park();
};

public static void main(String[] args) throws Exception {
Thread t1 = new Thread(r, "1");
Thread t2 = new Thread(r, "2");
t1.start();
t2.start();
LockSupport.unpark(t1);
LockSupport.unpark(t2);
t1.join();
t2.join();
}
}

事务

发表于 2019-06-05 | 更新于 2020-02-05 | 评论数: | 阅读次数:

事务在平常开发会经常遇到,介于有些同学对事务的理解还不是很透彻,在此稍作总结下

1.什么是事务
把一堆事情绑在一起做,都成功了才算完成,否则就恢复之前的样子。

2.事务的特性ACID(核心为C,即一致性,其他三个均是为一致性服务的)
原子性:事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做 。
一致性:事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。比如,当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统在运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数 据库,这时数据库就处于一种不正确的状态,或者说是不一致的状态。
隔离性:一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。
持续性:指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。
3.spring中事务
Spring事务管理高层抽象主要包括3个接口
PlatformTransactionManager:事务管理器—主要用于平台相关事务的管理
TransactionDefinition: 事务定义信息(隔离、传播、超时、只读)—通过配置如何进行事务管理
TransactionStatus:事务具体运行状态—事务管理过程中,每个时间点事务的状态信息
3.1Spring为不同的持久化框架提供了不同PlatformTransactionManager接口实现类

事务 说明
org.springframework.jdbc.datasource.DataSourceTransactionManager 使用Spring JDBC或iBatis 进行持久化数据时使用(咱项目是ORM是myBatis,所以用这)
org.springframework.orm.hibernate3.HibernateTransactionManager 使用Hibernate3.0版本进行持久化数据时使用
org.springframework.orm.jpa.JpaTransactionManager 使用JPA进行持久化时使用
org.springframework.jdo.JdoTransactionManager 当持久化机制是Jdo时使用
org.springframework.transaction.jta.JtaTransactionManager 使用一个JTA实现来管理事务,在一个事务跨越多个资源时必须使用
DataSourceTransactionManager针对JdbcTemplate、MyBatis 事务控制 ,使用Connection(连接)进行事务控制 :
开启事务 connection.setAutoCommit(false);
提交事务 connection.commit();
回滚事务 connection.rollback();
HibernateTransactionManager针对Hibernate框架进行事务管理, 使用Session的Transaction相关操作进行事务控制 :
开启事务 session.beginTransaction();
提交事务 session.getTransaction().commit();
回滚事务 session.getTransaction().rollback();
TransactionDefinition事务定义信息,该接口主要提供的方法:
lgetIsolationLevel:获取隔离级别
lgetPropagationBehavior:获取传播行为
lgetTimeout:获取超时时间(事务的有效期)
lisReadOnly 是否只读(保存、更新、删除—对数据进行操作-变成可读写的,查询-设置这个属性为true,只能读不能写),事务管理器能够根据这个返回值进行优化。
这些事务的定义信息,都可以在配置文件中配置和定制。

4.事务的隔离级别
隔离级别 说明
DEFAULT 使用后端数据库默认的隔离级别(spring中的默认选择项,mysql为可重复度)
READ_UNCOMMITED 允许你读取还未提交的改变了的数据。可能导致脏、幻、不可重复读
READ_COMMITTED 允许在并发事务已经提交后读取。可防止脏读,但幻读和 不可重复读仍可发生
REPEATABLE_READ 对相同字段的多次读取是一致的,除非数据被事务本身改变。可防止脏、不可重复读,但幻读仍可能发生。
SERIALIZABLE 完全服从ACID的隔离级别,确保不发生脏、幻、不可重复读。这在所有的隔离级别中是最慢的,它是典型的通过完全锁定在事务中涉及的数据表来完成的。
5.事务不回滚原因
默认spring事务只在发生未被捕获的 runtimeexcetpion时才回滚;
spring aop 异常捕获原理:被拦截的方法需显式抛出异常,并不能经任何处理,这样aop代理才能捕获到方法的异常,才能进行回滚,默认情况下aop只捕获runtimeexception的异常,但可以通过配置来捕获特定的异常并回滚 !
解决方案
如果是service层处理事务,那么service中的方法中不做异常捕获,或者在catch语句中最后增加throw new RuntimeException()语句,以便让aop捕获异常再去回滚,并且在service上层(webservice客户端,view层action)要继续捕获这个异常并处理
在service层方法的catch语句中增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();语句,手动回滚,这样上层就无需去处理异常

多线程

发表于 2019-06-05 | 更新于 2020-02-05 | 分类于 基础 | 评论数: | 阅读次数:

前言:目前大部分操作系统都是以线程为CPU调度和分派基本单位。多线程在日常程序中运用的还是比较多的,潜在的我们web容器帮我们在http层面可以同时处理多个请求,这些可能多我们是无感的。平常的开发中,我们需要结合业务场景来合理运用多线程,例如大文件的IO操作,大量消息发送,都可以运用多线程来处理,充分利用多核CPU性能。

一、创建线程的方式

继承Thread类创建线程
实现Runnable接口创建线程
使用Callable和Future创建线程
使用线程池
a.继承Thread类,重写run()

public class Test extends Thread {
@Override
public void run() {
System.out.println(“hello,word!”);
}
public static void main(String[] args) {
Test test = new Test();
test.start();
}
}

b.实现Runnable接口创建线程

public class Test implements Runnable {
@Override
public void run() {
System.out.println(“hello,word!”);
}
public static void main(String[] args) {
Thread thread = new Thread(new Test());
thread.start();
}
}
//Java 8 lambda写法
public class Test {
public static void main(String[] args) {
Thread thread = new Thread(
() -> System.out.println(“hello,word!”));
thread.start();
}
}
c.使用Callable和Future创建线程

public class Test implements Callable {
@Override
public String call() throws Exception {
return “hello,word!”;
}

public static void main(String[] args) {
    Callable<String> test = new Test();
    try {
        FutureTask<String> futureTask = new FutureTask<>(test);
        Thread thread = new Thread(futureTask);
        thread.run();
        System.out.println(futureTask.get());
    } catch (Exception e) {
        e.printStackTrace();
    }
}

}
//java8 lambda匿名类写法
public class Test {
public static void main(String[] args) {
FutureTask futureTask=new FutureTask<>(()->”hello,word!”);
Thread thread = new Thread(futureTask);
thread.start();
try {
System.out.println(futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
d.使用线程池

public class Test {
public static void main(String[] args) {
FutureTask futureTask=new FutureTask<>(()->”hello,word!”);
ExecutorService service=Executors.newFixedThreadPool(2);
service.submit(futureTask);
try {
System.out.println( futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException 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

## 二、Callable
Callable和Runnbale一样代表着任务,区别在于Callable有返回值并且可以抛出异常!Callable是一种可以返回结果的任务,这是它与Runnable的区别,但是通过适配器模式可以使Runnable与Callable类似。Future代表了一个异步的计算,可以从中得到计算结果、查看计算状态,其实现FutureTask可以被提交给Executor执行,多个线程可以从中得到计算结果。Callable和Future是配合使用的,当从Future中get结果时,如果结果还没被计算出来,那么线程将会被挂起,FutureTak内部使用一个单链表维持等待的线程;当计算结果出来后,将会对等待线程解除挂起,等待线程就都可以得到计算结果了。

# 三、Future接口、FutureTask类

Future是一个接口,代表了一个异步计算的结果。接口中的方法用来检查计算是否完成、等待完成和得到计算的结果。当计算完成后,只能通过get()方法得到结果,get方法会阻塞直到结果准备好了。如果想取消,那么调用cancel()方法。其他方法用于确定任务是正常完成还是取消了。一旦计算完成了,那么这个计算就不能被取消。

FutureTask类实现了RunnableFuture接口,而RunnnableFuture接口继承了Runnable和Future接口,所以说FutureTask是一个提供异步计算的结果的任务。 FutureTask可以用来包装Callable或者Runnbale对象。因为FutureTask实现了Runnable接口,所以FutureTask也可以被提交给Executor。

//FutureTask 2构造方法
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}

public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}


# 四、线程池工厂

>Executors面对开发人员的创建线程池的工厂类,它和线程执行器ExecutorService关系很紧密。

可以看到,提供了很多静态的方法,我们主要关注以下方法:

* newFixedThreadPool(int nThreads) 固定线程数量的线程池
* newScheduledThreadPool(int corePoolSize) 拥有至少corePoolSize数量的线程池,可以延迟或者固定频率执行
* newCachedThreadPool() 线程不固定线程池,常用于多且小的异步任务使用
* newSingleThreadExecutor() 串行按照提交顺序执行任务,的单线程化线程池
* newWorkStealingPool() 任务窃取线程池,JDK1.8以后加的,充分利用现有的cpu资源创建线程池,一个线程维护一个任务队列,常用在任务执行时间差别较大的业务

```java
public class ExecutorsDemo {

private static ExecutorService service = Executors.newCachedThreadPool();
private static ExecutorService service2 = Executors.newFixedThreadPool(3);
private static ExecutorService service3 = Executors.newScheduledThreadPool(3);
private static ExecutorService service4 = Executors.newSingleThreadExecutor();
private static ExecutorService service5 = Executors.newWorkStealingPool();

public static void main(String[] args) throws Exception {
addTask("newCachedThreadPool", service);
addTask("newFixedThreadPool", service2);
addTask("newScheduledThreadPool", service3);
addTask("newSingleThreadExecutor", service4);
addTask("newWorkStealingPool", service5);
}

private static void addTask(String type, ExecutorService service) throws Exception {
for (int i = 0; i < 10; i++) {
service.execute(() -> System.out.println(type + "," + Thread.currentThread().getName()));
}
service.shutdown();
service.awaitTermination(5, TimeUnit.MILLISECONDS);//等待任务完成再关闭主main线程
}
}

注意:虽然service.shutdown()是关闭线程,此时已经提交的线程会被执行完毕,但是打印需要时间,所以再最后加了一句 service.awaitTermination(5, TimeUnit.MILLISECONDS),目的是为了看到打印消息。

    • ScheduledExecutorService

      ScheduledExecutorService接口里面的4个方法

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 interface ScheduledExecutorService extends ExecutorService {

public <V> ScheduledFuture<V> schedule(Callable<V> callable,long delay, TimeUnit unit);

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit);

public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay,long delay,TimeUnit unit);
}
```

# 五、线程基础

* stop方法(Deprecated)

stop()会放弃持有的线程的锁,假设在为多个变量赋值的时候,刚好赋值到中间,执行了stop方法,很容易导致数据的不一致,此方法已经被废弃,尽量不要用,下面演示个demo

``` java
public class ThreadTest {
static Integer i, j;

static Runnable r1 = () -> {
while (true) {
i = new Random().nextInt();
j = i;
}
};

static Runnable r2 = () -> {
while (true) {
if (i != j) {
System.out.println("i:" + i + ",j:" + j);
} else {
System.out.println("00000");
}
}
};

public static void main(String[] args) {
Thread read = new Thread(r2);
read.start();
while (true) {
Thread write = new Thread(r1);
write.start();
try {
Thread.sleep(25);
} catch (InterruptedException e) {
e.printStackTrace();
}
write.stop();//Deprecated
}
}
}
  • 打印结果

    00000
    00000
    i:2071039910,j:1508352969
    i:-1148719051,j:-743674023
    00000
    00000

  • 线程中断来替代stop

    上面代码读取的不一致,核心原因是一以为线程突然停止,那么有没有一种优雅的停止呢,JDK为我们提供了更优雅的方式,中断。我们将上述代码稍做改变,数据的一致性立马可以得到保证。

    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
     public class ThreadTest {
    static Integer i, j;

    static Runnable r1 = () -> {
    while (true) {
    if(Thread.currentThread().isInterrupted()) {//判断是否有中断标志
    i = new Random().nextInt();
    j = i;
    break;
    }
    }
    };

    static Runnable r2 = () -> {
    while (true) {
    if (i != j) {
    System.out.println("i:" + i + ",j:" + j);
    } else {
    System.out.println("00000");
    }
    }
    };

    public static void main(String[] args) {
    Thread read = new Thread(r2);
    read.start();
    while (true) {
    Thread write = new Thread(r1);
    write.start();
    try {
    Thread.sleep(25);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    write.interrupt();// 设置中断标志
    }
    }
    }
  • 结果
    00000
    00000
    ···

注意点:若是被设置了中断的线程使用了sleep方法,则运行时可能会抛出中断异常且中断标志会被清空,这里贴出异常,代码就不写出来了。

1
2
3
4
5
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.andyfan.thread.ThreadTest.lambda$static$0(ThreadTest.java:20)
at com.andyfan.thread.ThreadTest$$Lambda$1/558638686.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
  • 等待、唤醒 wait/notify

wait和notify时Object里面定义的方法。

  • 工作原理:obj.wait(),线程进入等待队列,obj.notify() 则从等待队列里面随机选择一个线程继续执行等待前代码,意味着唤醒是非公平的。
  • wait和notify的调用必须在synchronized 代码块里,这也意味着必须要获得对象的监视器(锁)。
  • wait方法被调用后会主动释放监视器(锁),这个是sleep方法重要区分点。
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
public class ThreadTest2 {
final static Object object = new Object();

static Runnable r1 = () -> {
synchronized (object) {
long start = 0l;
try {
System.out.println("wait方法被执行了");
start = System.currentTimeMillis();
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println();
System.out.println("wait方法释放后被执行了,等待了notify方法释放锁" + (System.currentTimeMillis() - start)/1000 + "秒");
}
};

static Runnable r2 = () -> {
synchronized (object) {
object.notify();
System.out.println("notify方法被执行了");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("notify休眠1秒后");
}
};

public static void main(String[] args) {
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
  • 打印结果
    wait方法被执行了
    notify方法被执行了
    notify休眠1秒后
    wait方法释放后被执行了,等待了notify方法释放锁1秒

  • suspend 和resume (Deprecated)

    suspend线程会被挂起,它是不释放监视器(锁)的,必须等到resume 才会释放锁。这2个方法也被废弃了,因为若resume在suspend前执行,会导致线程永远别挂起,自己和其他线程由于获取不到监视器根本没办法正常工作,下面演示个被一直被挂起例子

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
public class ThreadTest3 {
final static Object object = new Object();

static Runnable r = () -> {
synchronized (object) {
String name = Thread.currentThread().getName();
System.out.println(name);
Thread.currentThread().suspend();//Deprecated
System.out.println(name + "被刮挂起后执行。。。");
}
};

public static void main(String[] args) {
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.resume();//Deprecated
t2.resume();//在t2的suspend前执行,导致t2一直被挂起!
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
  • 用jstack -l pid 命令 查看堆栈信息,可以看到线程t2一直处于Runnable状态
1
2
3
4
5
6
7
8
9
10
11
12
"Thread-1" #11 prio=5 os_prio=31 tid=0x00007fdbd703f800 nid=0x5903 runnable [0x000070000aa79000]
java.lang.Thread.State: RUNNABLE
at java.lang.Thread.suspend0(Native Method)
at java.lang.Thread.suspend(Thread.java:1029)
at com.andyfan.thread.ThreadTest3.lambda$static$0(ThreadTest3.java:14)
- locked <0x000000076abf9f58> (a java.lang.Object)
at com.andyfan.thread.ThreadTest3$$Lambda$1/558638686.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
```

* join和yield
* join 方法其实内部调用的是wait方法,JDK实现代码块:
   if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
        
* yield(谦让) 个人觉得这个方法使用需要勇气,有点鸡肋,因为它执行了,表明是让出当前CPU时间片,让和它同等优先级的线程优先执行,但是它持有的锁又不释放,共有资源还会再接下来时间竞争,且时间还不固定。

```java
public class ThreadTest4 {
static int j = 0;

public static void main(String[] args) throws Exception {
Thread thread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
j = i;
}
});
thread.start();
thread.join();//main的输出,等待线程执行完再执行,不然输出可能是0或者很小的值
System.out.println(j);
}
}

注册中心相关组件讨论

发表于 2019-05-25 | 更新于 2020-02-05 | 分类于 组件 | 评论数: | 阅读次数:

eureka 原理

1、鸟瞰


每个eureka客户端都需要添加@EnableDiscoverClient注解,作用是开启一个DiscoveryClient客户端,具体实现是在eureka相关配置类加载的时候调用了DiscoveryClient构造方法来初始化的

1
2
3
4
5
6
7
8
9
 
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({EnableDiscoveryClientImportSelector.class})
public @interface EnableDiscoveryClient {
boolean autoRegister() default true;
}

看看DiscoveryClient它的注解

构造函数

1
2
3
4
5
6
7
 @Inject
DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
Provider<BackupRegistry> backupRegistryProvider) {
...
initScheduledTasks();//初始化定时任务(注册、续约、取消续约、拉取服务)
...
}

1
2
3
4
5
6
7
8
9
10
private void initScheduledTasks() {
// 拉取服务
if (clientConfig.shouldFetchRegistry()) {
。。。
}
// 注册 & 续约
if (clientConfig.shouldRegisterWithEureka()) {
。。。
}
}

客户端

注册&续约

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
       if (clientConfig.shouldRegisterWithEureka()) {
// 续约、心跳时间间隔
int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
// 续约、心跳如果失败,则重新开始时间倍数
int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
logger.info("Starting heartbeat executor: " + "renew interval is: " + renewalIntervalInSecs);
//续约、心跳 定时任务
scheduler.schedule(
new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new HeartbeatThread()
),
renewalIntervalInSecs, TimeUnit.SECONDS);

// 1、注册的主要类,实际上是个线程 implement runnable接口
instanceInfoReplicator = new InstanceInfoReplicator(
this,
instanceInfo,
clientConfig.getInstanceInfoReplicationIntervalSeconds(),
2); // burstSize
// 2、服务状态监听器,发生错误了会激活主动退回送信息到注册中心
statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
@Override
public String getId() {
return "statusChangeListener";
}
@Override
public void notify(StatusChangeEvent statusChangeEvent) {
if (InstanceStatus.DOWN == statusChangeEvent.getStatus() ||
InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) {
// log at warn level if DOWN was involved
logger.warn("Saw local status change event {}", statusChangeEvent);
} else {
logger.info("Saw local status change event {}", statusChangeEvent);
}
// 主动推送信息到注册中心
instanceInfoReplicator.onDemandUpdate();
}
};
if (clientConfig.shouldOnDemandUpdateStatusChange()) {
applicationInfoManager.registerStatusChangeListener(statusChangeListener);
}
// 开启注册服务
instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
}

我们来看看InstanceInfoReplicator 这个类的start和run方法

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
  public void start(int initialDelayMs) {
// cas 原子比较(定时任务和服务出错2操作并存,保证单线程操作)
if (started.compareAndSet(false, true)) {
instanceInfo.setIsDirty(); // for initial register
Future next = scheduler.schedule(this, initialDelayMs, TimeUnit.SECONDS);
scheduledPeriodicRef.set(next);
}
}

public void run() {
try {
discoveryClient.refreshInstanceInfo();
Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
if (dirtyTimestamp != null) {
// 真正的注册方法
discoveryClient.register();
instanceInfo.unsetIsDirty(dirtyTimestamp);
}
} catch (Throwable t) {
logger.warn("There was a problem with the instance info replicator", t);
} finally {
Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
scheduledPeriodicRef.set(next);
}
}

DiscoveryClient里面的register注册方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
boolean register() throws Throwable {
logger.info(PREFIX + appPathIdentifier + ": registering service...");
EurekaHttpResponse<Void> httpResponse;
try {
httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
} catch (Exception e) {
logger.warn("{} - registration failed {}", PREFIX + appPathIdentifier, e.getMessage(), e);
throw e;
}
if (logger.isInfoEnabled()) {
logger.info("{} - registration status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode());
}
return httpResponse.getStatusCode() == 204;
}

实际上它是调用了 RetryableEurekaHttpClient 的execute 方法

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
@Override
protected <R> EurekaHttpResponse<R> execute(RequestExecutor<R> requestExecutor) {
List<EurekaEndpoint> candidateHosts = null;
int endpointIdx = 0;
// 循环次数,默认3次,成功就reutrn
for (int retry = 0; retry < numberOfRetries; retry++) {
EurekaHttpClient currentHttpClient = delegate.get();
EurekaEndpoint currentEndpoint = null;
if (currentHttpClient == null) {
if (candidateHosts == null) {
// 获取注册中心的节点(全部和无效取交集)
candidateHosts = getHostCandidates();
if (candidateHosts.isEmpty()) {
throw new TransportException("There is no known eureka server; cluster server list is empty");
}
}
if (endpointIdx >= candidateHosts.size()) {
throw new TransportException("Cannot execute request on any known server");
}
// 只获取了一个节点
currentEndpoint = candidateHosts.get(endpointIdx++);
currentHttpClient = clientFactory.newClient(currentEndpoint);
}
try {
// 发送请求注册
EurekaHttpResponse<R> response = requestExecutor.execute(currentHttpClient);
if (serverStatusEvaluator.accept(response.getStatusCode(), requestExecutor.getRequestType())) {
delegate.set(currentHttpClient);
if (retry > 0) {
logger.info("Request execution succeeded on retry #{}", retry);
}
// 直接reutrn 了
return response;
}
logger.warn("Request execution failure with status code {}; retrying on another server if available", response.getStatusCode());
} catch (Exception e) {
logger.warn("Request execution failed with message: {}", e.getMessage()); // just log message as the underlying client should log the stacktrace
}
// Connection error or 5xx from the server that must be retried on another server
delegate.compareAndSet(currentHttpClient, null);
if (currentEndpoint != null) {
// 无效的节点
quarantineSet.add(currentEndpoint);
}
}
throw new TransportException("Retry limit reached; giving up on completing the request");
}

代码太多就不继续跟进了,实质是向注册中心的一个节点发送POST方法向注册中心注册,那么注册中心是怎么处理这个请求的呢,后面有分析。

同理我们看看DiscoveryClient里面的renew注册方法,不再具体分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
boolean renew() {
EurekaHttpResponse<InstanceInfo> httpResponse;
try {
// 发送http请求
httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
logger.debug("{} - Heartbeat status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode());
// 各种状态码判断
if (httpResponse.getStatusCode() == 404) {
REREGISTER_COUNTER.increment();
logger.info("{} - Re-registering apps/{}", PREFIX + appPathIdentifier, instanceInfo.getAppName());
return register();
}
return httpResponse.getStatusCode() == 200;
} catch (Throwable e) {
logger.error("{} - was unable to send heartbeat!", PREFIX + appPathIdentifier, e);
return false;
}
}

服务获取

这个比较简单,也不再赘述,按照注册思路可以很容易看明白

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
if (clientConfig.shouldFetchRegistry()) {
// registry cache refresh timer
int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
scheduler.schedule(
new TimedSupervisorTask(
"cacheRefresh",
scheduler,
cacheRefreshExecutor,
registryFetchIntervalSeconds,
TimeUnit.SECONDS,
expBackOffBound,
// 这个线程为实现拉取服务主要工作
new CacheRefreshThread()
),
registryFetchIntervalSeconds, TimeUnit.SECONDS);
}
// 内部类,一个线程
class CacheRefreshThread implements Runnable {
public void run() {
// 真正实现功能方法
refreshRegistry();
}
}

服务端

  • 接受注册
    怎么找到接受注册的类呢,我们看看日志

1
2
3
4
5
6
7
8
9
10
11
@Override
public void register(final InstanceInfo info, final boolean isReplication) {
int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
leaseDuration = info.getLeaseInfo().getDurationInSecs();
}
// 调用了父类的注册方法
super.register(info, leaseDuration, isReplication);
// 同步到其他节点
replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
}

父类的regiser方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
try {
read.lock();
//首先根据appName获取一些列的服务实例对象,如果为Null,则新创建一个map并把当前的注册应用程序信息添加到此Map当中
Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
REGISTER.increment(isReplication);
if (gMap == null) {
final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>();
gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
if (gMap == null) {
gMap = gNewMap;
}
}
... 注册过程

} finally {
read.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
34
35
36
37
38
39
40
41
42
43
    private void replicateToPeers(Action action, String appName, String id,
InstanceInfo info /* optional */,
InstanceStatus newStatus /* optional */, boolean isReplication) {
...
//重点
replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
}
} finally {
tracer.stop();
}
}


private void replicateInstanceActionsToPeers(Action action, String appName,
String id, InstanceInfo info, InstanceStatus newStatus, PeerEurekaNode node) {
try {
InstanceInfo infoFromRegistry = null;
CurrentRequestVersion.set(Version.V2);
switch (action) {
case Cancel:
node.cancel(appName, id);
break;
case Heartbeat:
InstanceStatus overriddenStatus = overriddenInstanceStatusMap.get(id);
infoFromRegistry = getInstanceByAppAndId(appName, id, false);
node.heartbeat(appName, id, infoFromRegistry, overriddenStatus, false);
break;
case Register:
node.register(info);
break;
case StatusUpdate:
infoFromRegistry = getInstanceByAppAndId(appName, id, false);
node.statusUpdate(appName, id, newStatus, infoFromRegistry);
break;
case DeleteStatusOverride:
infoFromRegistry = getInstanceByAppAndId(appName, id, false);
node.deleteStatusOverride(appName, id, infoFromRegistry);
break;
}
} catch (Throwable t) {
logger.error("Cannot replicate information to {} for action {}", node.getServiceUrl(), action.name(), t);
}
}

其他源码不讲了,大致差不多的思路。

feign 机制原理

使用

  • 在启动类上新增@EnableFeignClients注解
  • 在service接口上添加@FeignClient(“注册到eureka服务名字”)
  • service方法上添加 @RequestMapping(提供者controller的url)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@SpringBootApplication
@RestController
@EnableEurekaClient
@EnableFeignClients
public class EurekaConsumerApplication {
public static void main(String[] args) {

SpringApplication.run(EurekaConsumerApplication.class, args);

}
}

@FeignClient("produce-service-eureka")
public interface FeignClientService {

@RequestMapping("ek/provider")
String consumer();
}

原理

  • 通过EnableFeignClients 注解开启FeignClien,他会扫描带有@FeignClient的注解,注入到IOC容器。
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
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);

Set<String> basePackages;
//获取带有EnableFeignClients注解的默认元数据
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
...
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name, attributes.get("configuration"));
// 注册
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}

private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
// 关键,将BeanDefinition包装成FeignClientFactoryBean类型的工厂Bean,待注入到IOC
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
。。。
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

工厂Bean我们都知道通过getObject方法获取的是具体的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
@Override
public Object getObject() throws Exception {
FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {
String url;
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
url += cleanPath();
//加入robbin作负载均衡
return loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, url));
}
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// not lod balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient)client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
// jdk 动态代理生成代理实例
return targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
}

再一步步跟进去会发现是ReflectiveFeign 类的newInstance方法

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
   @Override
public <T> T newInstance(Target<T> target) {
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
} else if(Util.isDefault(method)) {
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
// jdk动态代理
InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);
// 注入handler,拦截器
for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}

SynchronousMethodHandler的invoke方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 @Override
public Object invoke(Object[] argv) throws Throwable {
// 请求模板
RequestTemplate template = buildTemplateFromArgs.create(argv);
// 重试配置类,默认5次
Retryer retryer = this.retryer.clone();
while (true) {
try {
// 发送请求
return executeAndDecode(template);
} catch (RetryableException e) {
retryer.continueOrPropagate(e);
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}

  • 负载均衡
    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
       Object executeAndDecode(RequestTemplate template) throws Throwable {
    Request request = targetRequest(template);
    if (logLevel != Logger.Level.NONE) {
    logger.logRequest(metadata.configKey(), logLevel, request);
    }
    Response response;
    long start = System.nanoTime();
    try {
    // 发送请求,默认开启了负载,调用的是LoadBalancerFeignClient 的execute方法
    response = client.execute(request, options);
    // ensure the request is set. TODO: remove in Feign 10
    response.toBuilder().request(request).build();
    } catch (IOException e) {
    if (logLevel != Logger.Level.NONE) {
    logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
    }
    throw errorExecuting(request, e);
    }
    // 返回结果状态判断和关闭连接
    ···
    }
    }

    @Override
    public Response execute(Request request, Request.Options options) throws IOException {
    try {
    URI asUri = URI.create(request.url());
    String clientName = asUri.getHost();
    URI uriWithoutHost = cleanUrl(request.url(), clientName);
    FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
    this.delegate, request, uriWithoutHost);
    IClientConfig requestConfig = getClientConfig(options, clientName);
    // 核心,用负载均衡器执行
    return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
    requestConfig).toResponse();
    }
    catch (ClientException e) {
    IOException io = findIOException(e);
    if (io != null) {
    throw io;
    }
    throw new RuntimeException(e);
    }
    }

    public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
    LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
    try {
    // 命令模式构建
    return command.submit(
    new ServerOperation<T>() {
    @Override
    public Observable<T> call(Server server) {
    URI finalUri = reconstructURIWithServer(server, request.getUri());
    S requestForServer = (S) request.replaceUri(finalUri);
    try {
    return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
    } catch (Exception e) {
    return Observable.error(e);
    }
    }
    })
    .toBlocking()
    .single();
    } catch (Exception e) {
    Throwable t = e.getCause();
    if (t instanceof ClientException) {
    throw (ClientException) t;
    } else {
    throw new ClientException(e);
    }
    }
    }



    public Observable<T> submit(final ServerOperation<T> operation) {
    。。。
    // 重点方法selectServer
    Observable<T> o =
    (server == null ? selectServer() : Observable.just(server))
    .concatMap(new Func1<Server, Observable<T>>() {
    @Override
    // Called for each server being selected
    public Observable<T> call(Server server) {
    context.setServer(server);
    。。。

    }
    }

    private Observable<Server> selectServer() {
    return Observable.create(new OnSubscribe<Server>() {
    @Override
    public void call(Subscriber<? super Server> next) {
    try {
    // 交给loadBalancerContext 来处理
    Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);
    next.onNext(server);
    next.onCompleted();
    } catch (Exception e) {
    next.onError(e);
    }
    }
    });
    }

整个负载还是比较复杂的,继续跟踪下去太多,大家可以自行看源码。

  • 熔断

jwt权限

不知道具体业务,只能做个demo。

eureka 示例优化

Vim 使用

发表于 2019-05-19 | 分类于 Linux | 评论数: | 阅读次数:

vim 对于程序员的重要性,不言语。一个合格的程序员熟练使用命令编辑器,是必要条件。

Vim三种模式

  • 命令模式
    刚打开vim时候状态,一切键盘的操作都被看着是命令输入
  • 输入模式
    在命令模式按下a i o进入的状态
  • 底线命令模式
    在输入模式下,按下:(英文)

命令模式下常用的快捷键

  • 上下左右移动
    h(左)j(下)k(上)l(右) 在一排,可以很方便的移动光标,字母前面加数字,代表移动的单位。
  • 行首尾
    0行首,$ 行尾
  • 文件的头尾
    gg=1G 文件头,G 文件尾
  • 文件某一行
    2G/2gg
  • 删除一个字符
    x向前删除,X向后删除
  • 删除光标所在行
    dd
  • 删除光标向下n行
    ndd
  • 删除光标所在行到第一行
    d1G
  • 删除光标所在行到最后一行
    dG
  • 删除光标所在处到该行最后
    d$
  • 删除光标所在处到该行开头
    d0
  • 复制当前行
    yy
  • 向下复制n行
    nyy
  • 复制光标所在行到第一行
    y1G
  • 复制光标所在行到最后一行
    yG
  • 复制光标所在处到该行最后
    y$
  • 复制光标所在处到该行开头
    y0
  • 粘贴
    p 向下 P向上
  • 恢复上一次操作
    u
  • 重做上一次操作
    ctr + r
  • 在若干行首添加注释
    ctr + v 进入visul block(可以单选字符,不是整行哦),选择,然后再按I(行首插入),例如:// ,再按2下Esc即可
  • 去除行首注释
    ctr + v 进入visul block(可以单选字符,不是整行哦),选择你要删除的一些注释,然后再按d(删除),例如选中://, 再按2下Esc即可

    输入模式

    i I a A o O 都行,基本差别很小,不再叙述,就当做都是进入到输入模式吧。

    底线模式下

  • 设置、取消行号
    set nu set nonu
  • 查找
    /word 向下查找,查找过程中按n,你代表重复这个查找动作,N也是重复这查找,但是查找方向会反向
    ?word 向上查找,n与N效果如上表述一样
  • 查找与替换
    n1,n2s/word1/word2/g 在n1与n2行之间查找word1并全局替换成word2,其中n1=1,n2=$ 则代表全文

Python 若干基本语法

发表于 2019-05-05 | 分类于 Python | 评论数: | 阅读次数:

while 循环

1
2
3
while 判断条件:
语句
else:

if 语句

和Java 差不多,就是每个判断表达式后面需要带上冒号,另外结尾不需要封号。

1
2
3
4
5
6
if condition_1:
statement_block_1
elif condition_2:
statement_block_2
else:
statement_block_3

for 循环

1
2
3
4
for <variable> in <sequence>:
<statements>
else:
<statements>
1
2
3
4
5
6
7
8
9
sites = ["Baidu", "Google","Runoob","Taobao"]
for site in sites:
if site == "Runoob":
print("菜鸟教程!")
break
print("循环数据 " + site)
else:
print("没有循环数据!")
print("完成循环!")

pass 语句

1
2
while True:
pass # 等待键盘中断 (Ctrl+C)

迭代器

1
2
3
4
5
6
7
8
9
10
import sys         # 引入 sys 模块

list=[1,2,3,4]
it = iter(list) # 创建迭代器对象

while True:
try:
print (next(it))
except StopIteration:
sys.exit()

函数定义

1
2
def 函数名(参数列表):
函数体

变量作用域

  • Python 中只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,其它的代码块(如 if/elif/else/、try/except、for/while等)是不会引入新的作用域的,也就是说这些语句内定义的变量,外部也可以访问,如下代码:
1
2
3
if True:
msg='huang'
print(msg) # 可以

导包

Spring 概述

发表于 2019-04-24 | 更新于 2019-04-25 | 分类于 spring | 评论数: | 阅读次数:

一、鸟瞰图

二、组件

1、 核心容器

  1. spring-core, spring-beans
    这两个模块是Spring的基石,里面包括了IOC,BeanFactory等核心概念。
  2. spring-context
    构建与core和bean基础之上,主要是提供了供程序员使用的ApplicationContext。

  3. spring-expression
    强大的表达式语言,供Spring对于各种文件中变量的访问和操控。

2、AOP和instrument

  1. spring-aop
    提供了符合Alliance标准的面向切面编程的实现。允许定义方法拦截器和切入点,干净的解耦业务代码和切面代码。
  2. spring-aspects
    提供与AspectJ的集成
  3. spring-instrument
    模块提供了在某些应用程序服务器中使用的类检测支持和类加载器实现。 spring-instrument-tomcat 模块包含Spring的Tomcat检测代理。

3、Web

  1. spring-web
    提供基本的面向Web的集成功能,例如多部分文件上载功能以及使用Servlet侦听器和面向Web的应用程序上下文初始化IoC容器。它还包含一个HTTP客户端以及Spring的远程支持的Web相关部分
  2. spring-webmvc
    就是我们经常使用spingMVC
  3. spring-webmvc-portlet
    portlet 环境下的springMVC,我没用过。
  4. spring-websocket
    websocket 和spring的集成,使用起来很方便。

4、数据访问/集成

  1. spring-jdbc
    提供了一个 JDBC -abstraction层,无需进行繁琐的JDBC编码和解析数据库供应商特定的错误代码。
  2. spring-tx
    spirng 的事务
  3. spring-orm,
    将常规的ORM框架和spring集成,包括JPA JDO Hibernate
  4. spring-oxm
    提供了一个支持 Object/XML mapping 实现的抽象层,例如JAXB,Castor,XMLBeans,JiBX和XStream。
  5. spring-jms
    包含用于生成和使用消息的功能。从Spring Framework 4.1开始,它提供了与 spring-messaging 模块的集成。

5、测试

spring-test 模块支持带有JUnit或TestNG的Spring组件的 unit testing 和 integration testing 。它提供了一致的 ApplicationContext 的Spring ApplicationContext 和 caching 这些上下文。它还提供 mock objects ,您可以使用它来单独测试代码。

映射器

发表于 2019-04-01 | 更新于 2020-02-05 | 分类于 基础 | 评论数: | 阅读次数:

映射器

映射器可以理解为我们平常说的mapper接口,一般映射器可以由接口+xml/注解方式组成。生命周期:一个会话内。

一、使用

  • 接口+xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 @Mapper
public interface userMapper{
User selectById(id);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.vxiaoke.cp.base.mapper.UserAccountMapper">

<resultMap id="BaseResultMap" type="com.vxiaoke.cp.base.models.UserAccount">
<id column="id" property="id" />
<result column="username" property="ctime" />
<result column="age" property="mtime" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, ctime, mtime, username, age
</sql>

<select id="selectById" resultMap="BaseResultMap">
select <include refid="Base_Column_List"/>
from user where id = #{id}
</select>
</mapper>
  • 接口+注解
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
 @Select(value="select *id,username,age from user where id=#{id}")
public interface userMapper{
User selectById(id);
}
```
### 二、组成
* MappedStatement
* SqlSource
* BoundSql

### 三、说明
* MappedStatement
这个类涉及的东西是比较多的,可以看看下面的表

|属性 | 类型|说明|
|---|---|---|----|
|resource | String| 类似mybatis-config.xml 文件名|
|Configuration | Configuration|配置类|
|String | id| 查找到哪个mapper标识,例如getById|
|KeyGenerator | keyGenerator| key生成,例如在insert后返回id|
|boolean | useCache| 是否用耳机缓存|
|SqlSource | sqlSource| 根据参数组装sql|
|ParameterMap | parameterMap|参数|
|... | ...| ...|

* SqlSource

> 根据参数/其他规则组装sql,

![](http://47.95.12.0:3389/ftp/SqlSource.png)

```java
public interface SqlSource {

BoundSql getBoundSql(Object parameterObject);

}
```

* BoundSql

```java
public class BoundSql {

private final String sql;//我们写的原生sql
private final List<ParameterMapping> parameterMappings;
private final Object parameterObject;//参数本身,例如POJO,Map等传入的参数,@Parm注解会解析成Map
private final Map<String, Object> additionalParameters;//每个元素都是map,如:属性名,类型javaType,typeHandler等
private final MetaObject metaParameters;

public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
this.sql = sql;
this.parameterMappings = parameterMappings;
this.parameterObject = parameterObject;
this.additionalParameters = new HashMap<>();
this.metaParameters = configuration.newMetaObject(additionalParameters);
}

public String getSql() {
return sql;
}

public List<ParameterMapping> getParameterMappings() {
return parameterMappings;
}

public Object getParameterObject() {
return parameterObject;
}

public boolean hasAdditionalParameter(String name) {
String paramName = new PropertyTokenizer(name).getName();
return additionalParameters.containsKey(paramName);
}

public void setAdditionalParameter(String name, Object value) {
metaParameters.setValue(name, value);
}

public Object getAdditionalParameter(String name) {
return metaParameters.getValue(name);
}
}
  • Mapper本质
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
// MapperProxy工厂
public class MapperProxyFactory<T> {

private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}

public Class<T> getMapperInterface() {
return mapperInterface;
}

public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}

@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}

}

//MapperProxy JDK的动态代理,实现InvocationHandler 接口
public class MapperProxy<T> implements InvocationHandler, Serializable {

private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;

public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 是否是个类,显然不是
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 构建MapperMethod 对象,跳到下面*处
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 核心方法(注意!)
return mapperMethod.execute(sqlSession, args);
}

// * 是否在缓存里,否则初始化mapperMethod
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
...

//看看核心方法
public class MapperMethod {

private final SqlCommand command;
private final MethodSignature method;

public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}

// 命令模式,对底层还是sqlSession来执行。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);//sqlSession
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}

MyBatis-StatementHandler

发表于 2019-04-01 | 更新于 2020-02-05 | 分类于 基础 | 评论数: | 阅读次数:

四大金刚-StatementHandler

四大金刚中的核心,使用数据库的PreparedStatement执行数据库操作。

  • StatementHandler 数据库会话器
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
public interface StatementHandler {

Statement prepare(Connection connection, Integer transactionTimeout)
throws SQLException;

void parameterize(Statement statement)
throws SQLException;//ParameterHandler 参数设置类

void batch(Statement statement)
throws SQLException;

int update(Statement statement)
throws SQLException;

<E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException;//ResultSetHandler 结果集处理类

<E> Cursor<E> queryCursor(Statement statement)
throws SQLException;

BoundSql getBoundSql();

ParameterHandler getParameterHandler();

}
  • 引出另外2大金刚
    • ParameterHandler
    • ResultSetHandler
1234

安迪·梵

keep it simple and stupid
34 日志
13 分类
4 标签
RSS
E-Mail GitHub Weibo
© 2020 安迪·梵
本站总访问量 次 | 有人看过我的博客