0%

看了几年前的Redis集群安装,那还是3.4的Redis,恍如时光

在Github上下载了一个项目,spring也没有用上,各种都是那人自己手动封装,包括解析xml,牛逼是牛逼,倒是跑起来也是贼麻烦的,一直卡在Redis的集群里,本来想让公司的运维装个Redis集群直接让我用, 结过,弄了两次,代码连接redis一直报错

也不太想麻烦别人了,就自己临时搭一个,还是招这几年前的笔记,哈哈哈,有点怀恋

阅读全文 »

看完公司的基于Netty的游戏框架,框架中用到了多态,函数式编程和事件驱动编程,第一次看到事件驱动的时候,就想到跟观察者模式很像.

事件驱动初上手感觉还很好用,在我自己写的项目里,要写很多爬虫,比如下面爬虫的例子,我只是想关心拼接URL地址,和关心不同的网站怎么解析DOM元素,写一个回调就好

多态,函数式编程和事件驱动编程,这三个还是然让我学到很多,可以用一个框架的基础,比如在Netty中,继承SimpleChannelInboundHandler<TextWebSocketFrame>,实现这里里面的方法,就能接收到请求,很方便.

阅读全文 »

前两写了两篇,都是为SpringCloud+Docker部署做准备,在部署的时候,不同服务器,不同的Docker容器之间的通信,还好没有掉到坑里去,在公司里用了新技术,还是很开心的,小有成就感,之前一直想用上Docker,太忙就忽略了,这次是老大让我搭建一个预发布环境,一直都是重复性的工作,就很枯燥,心想着用新技术吧,这三篇文章,都是在各种爬坑中过来的

阅读全文 »

一:JDK动态代理

1.创建接口

1
2
3
4
5
public interface Subject {
public void rent();

public void hello(String str);
}

2.创建接口实现类

1
2
3
4
5
6
7
8
9
10
public class RealSubject implements Subject {

public void rent() {
System.out.println("I want to rent my house");
}

public void hello(String str) {
System.out.println("hello: " + str);
}
}

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
public class DynamicProxy implements InvocationHandler {

private Object subject;

//构造方法,给我们要代理的真实对象赋初值
public DynamicProxy(Object subject) {
this.subject = subject;
}

//实现接口的方法
public Object invoke(Object object, Method method, Object[] args) throws Throwable {
//在代理真实对象前我们可以添加一些自己的操作
System.out.println("before rent house");

System.out.println("Method:" + method);

//当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
method.invoke(subject, args);

//在代理真实对象后我们也可以添加一些自己的操作
System.out.println("after rent house");

return null;
}

}

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
public class Client {

public static void main(String[] args) {
//我们要代理的真实对象
Subject realSubject = new RealSubject();

//我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
InvocationHandler handler = new DynamicProxy(realSubject);

/*
* 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
* 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
* 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
* 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
*/
Subject subject = (Subject) Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject .getClass().getInterfaces(), handler);

System.out.println(subject.getClass().getName());

//调用第一个方法
subject.rent();

//调用第二个方法
subject.hello("world");

}

}

代理接口

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
public interface IHello {
String say(String aa);
}

public class FacadeProxy implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("接口方法调用开始");
//执行方法
System.out.println("method toGenericString:"+method.toGenericString());
System.out.println("method name:"+method.getName());
System.out.println("method args:"+(String)args[0]);
System.out.println("接口方法调用结束");
return "调用返回值";
}

public static <T> T newMapperProxy(Class<T> mapperInterface) {
ClassLoader classLoader = mapperInterface.getClassLoader();
Class<?>[] interfaces = new Class[]{mapperInterface};
FacadeProxy proxy = new FacadeProxy();
return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);
}
}

public class Test {
public static void main(String[] args) {
IHello hello = FacadeProxy.newMapperProxy(IHello.class);
System.out.println(hello.say("hello world"));
}
}

二:CLIiB动态代理

JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。

1.创建对象

这是一个需要被代理的类,也就是父类,通过字节码技术创建这个类的子类,实现动态代理。

1
2
3
4
5
public class SayHello {
public void say(){
System.out.println("hello everyone");
}
}

2.创建动态代理类

该类实现了创建子类的方法与代理的方法。getProxy(SuperClass.class)方法通过入参即父类的字节码,通过扩展父类的class来创建代理对象。intercept()方法拦截所有目标类方法的调用,obj表示目标类的实例,method为目标类方法的反射对象,args为方法的动态入参,proxy为代理类实例。proxy.invokeSuper(obj, args)通过代理类调用父类中的方法。

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 class CglibProxy implements MethodInterceptor {

private Enhancer enhancer = new Enhancer();

public Object getProxy(Class clazz){
//设置需要创建子类的类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
//通过字节码技术动态创建子类实例
return enhancer.create();
}

//实现MethodInterceptor接口方法
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

System.out.println("前置代理");

//通过代理类调用父类中的方法
Object result = proxy.invokeSuper(obj, args);

System.out.println("后置代理");

return result;
}
}

3.客户端

1
2
3
4
5
6
7
8
public class DoCGLib {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
//通过生成子类的方式创建代理类
SayHello proxyImp = (SayHello)proxy.getProxy(SayHello.class);
proxyImp.say();
}
}

三:对比

CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。

线程与进程

1 线程:进程中负责程序执行的执行单元
线程本身依靠程序进行运行
线程是程序中的顺序控制流,只能使用分配给程序的资源和环境

2 进程:执行中的程序
一个进程至少包含一个线程

3 单线程:程序中只存在一个线程,实际上主方法就是一个主线程

4 多线程:在一个程序中运行多个任务
目的是更好地使用CPU资源

线程的实现

继承Thread类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Test {
public static void main(String[] args) {
System.out.println("主线程ID:"+Thread.currentThread().getId());
MyThread thread1 = new MyThread("thread1");
thread1.start();
MyThread thread2 = new MyThread("thread2");
thread2.run();
}
}

class MyThread extends Thread{
private String name;

public MyThread(String name){
this.name = name;
}

@Override
public void run() {
System.out.println("name:"+name+" 子线程ID:"+Thread.currentThread().getId());
}
}

实现Runnable接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test {
public static void main(String[] args) {
System.out.println("主线程ID:"+Thread.currentThread().getId());
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
}
class MyRunnable implements Runnable{
public MyRunnable() {
}

@Override
public void run() {
System.out.println("子线程ID:"+Thread.currentThread().getId());
}
}

静态方法

1.currentThread()

1
2
3
4
5
6
public class Test2 {
public static void main(String[] args) {
System.out.println("主线程ID:"+Thread.currentThread().getId());
System.out.println("主线程名字:"+Thread.currentThread().getName());
}
}

2.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
public class Test2 {

private int i = 10;
private Object object = new Object();

public static void main(String[] args) throws IOException {
Test2 test = new Test2();
MyThread thread1 = test.new MyThread();
MyThread thread2 = test.new MyThread();
thread1.start();
thread2.start();
}

class MyThread extends Thread{
@Override
public void run() {
synchronized (object) {
i++;
System.out.println(Thread.currentThread().getName()+"===> i:"+i);
try {
System.out.println("线程"+Thread.currentThread().getName()+"进入睡眠状态");
Thread.currentThread().sleep(5000);
} catch (InterruptedException e) {
// TODO: handle exception
}
System.out.println("线程"+Thread.currentThread().getName()+"睡眠结束");
i++;
System.out.println("i:"+i);
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
Thread-0===> i:11
线程Thread-0进入睡眠状态
//这里会等待3000毫秒
线程Thread-0睡眠结束
i:12

Thread-1===> i:13
线程Thread-1进入睡眠状态
//这里会等待3000毫秒
线程Thread-1睡眠结束
i:14

3.yield

  • 调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程
  • 但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
  • 注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MyThread  extends Thread{
@Override
public void run() {
long beginTime=System.currentTimeMillis();
int count=0;
for (int i=0;i<50000000;i++){
count=count+(i+1);
//Thread.yield();
}
long endTime=System.currentTimeMillis();
System.out.println("用时:"+(endTime-beginTime)+" 毫秒!");
}
}

public class Run {
public static void main(String[] args) {
MyThread t= new MyThread();
t.start();
}
}
1
2
注释======>用时:23毫秒!
取消注释==>用时:4890 毫秒!

对象方法

start()

start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。

run()

run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。

getId()

1
2
3
4
5
6
7
public class Run{
public static void main(String[] args) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
System.out.println(thread.getId());
}
}
1
2
main
1

isAlive()

方法isAlive()的作用是测试线程是否偶处于活动状态。什么是活动状态呢?活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread thread2 = new MyThread("_2");
System.out.println(thread2.isAlive());
thread2.start();
Thread.sleep(1000);
System.out.println(thread2.isAlive());
}
}

class MyThread extends Thread{
private String name;

public MyThread(String name){
this.name = name;
}

@Override
public void run() {
System.out.println(this.isAlive());
}
}
1
2
3
false
true
false

join()

在很多情况下,主线程创建并启动了线程,如果子线程中药进行大量耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。

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
public class Thread4 extends Thread{

public Thread4(String name) {
super(name);
}
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(getName() + " " + i);
}
}

public static void main(String[] args) throws InterruptedException {
// 启动子进程
new Thread4("new thread").start();

for (int i = 0; i < 10; i++) {
if (i == 5) {
Thread4 th = new Thread4("joined thread");
th.start();
th.join();
}
System.out.println(Thread.currentThread().getName() + " " + i);
}

}
}
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
有join方法:
main 0
main 1
main 2
main 3
main 4
new thread 0
new thread 1
new thread 2
new thread 3
new thread 4
joined thread 0
joined thread 1
joined thread 2
joined thread 3
joined thread 4
main 5
main 6
main 7
main 8
main 9

没有join方法:
main 0
main 1
main 2
main 3
main 4
main 5
main 6
main 7
main 8
main 9
new thread 0
joined thread 0
joined thread 1
joined thread 2
joined thread 3
joined thread 4
new thread 1
new thread 2
new thread 3
new thread 4

getName和setName()

用来得到或者设置线程名称。

getPriority和setPriority()

用来获取和设置线程优先级。

setDaemon和isDaemon()

用来设置线程是否成为守护线程和判断线程是否是守护线程。

停止线程

停止线程是在多线程开发时很重要的技术点,掌握此技术可以对线程的停止进行有效的处理。

停止一个线程可以使用Thread.stop()方法,但最好不用它。该方法是不安全的,已被弃用。
在Java中有以下3种方法可以终止正在运行的线程:

  • 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止
  • 使用stop方法强行终止线程,但是不推荐使用这个方法,因为stop和suspend及resume一样,都是作废过期的方法,使用他们可能产生不可预料的结果。
  • 使用interrupt方法中断线程,但这个不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。

暂停线程

interrupt()方法

线程的优先级

JDK中使用3个常量来预置定义优先级的值,代码如下:

1
2
3
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;

线程优先级特性:

  • 继承性
    比如A线程启动B线程,则B线程的优先级与A是一样的。
  • 规则性
    高优先级的线程总是大部分先执行完,但不代表高优先级线程全部先执行完。
  • 随机性
    优先级较高的线程不一定每一次都先执行完。

守护线程

在Java线程中有两种线程,一种是User Thread(用户线程),另一种是Daemon Thread(守护线程)。
Daemon的作用是为其他线程的运行提供服务,比如说GC线程。其实User Thread线程和Daemon Thread守护线程本质上来说去没啥区别的,唯一的区别之处就在虚拟机的离开:如果User Thread全部撤离,那么Daemon Thread也就没啥线程好服务的了,所以虚拟机也就退出了。

守护线程并非虚拟机内部可以提供,用户也可以自行的设定守护线程,方法:public final void setDaemon(boolean on) ;但是有几点需要注意:

  • thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。 (备注:这点与守护进程有着明显的区别,守护进程是创建后,让进程摆脱原会话的控制+让进程摆脱原进程组的控制+让进程摆脱原控制终端的控制;所以说寄托于虚拟机的语言机制跟系统级语言有着本质上面的区别)
  • 在Daemon线程中产生的新线程也是Daemon的。 (这一点又是有着本质的区别了:守护进程fork()出来的子进程不再是守护进程,尽管它把父进程的进程相关信息复制过去了,但是子进程的进程的父进程不是init进程,所谓的守护进程本质上说就是“父进程挂掉,init收养,然后文件0,1,2都是/dev/null,当前目录到/”)
  • 不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。因为在Daemon Thread还没来的及进行操作时,虚拟机可能已经退出了。

同步与死锁

  1. 同步代码块
    在代码块上加上”synchronized”关键字,则此代码块就称为同步代码块
  2. 同步代码块格式
1
2
3
synchronized(同步对象){
需要同步的代码块;
}
  1. 同步方法
    除了代码块可以同步,方法也是可以同步的

  2. 方法同步格式

    1
    synchronized void 方法名称(){}

一:首先

对MyBatis的使用我们在最开始都已经知道可以通过xml配置文件的方式,也可以通过Java代码创建Configuration对象的方式。 这两者实际上是一样,xml配置文件的方式最终也是通过解析xml配置文件创建一个Configuration对象。可能对于很多人(我也是)来说MyBatis通常是和Spring配合使用,用了N年MyBatis也不能把MyBatis说个所以出来

回顾一下mybatis实例的创建过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 第一步,创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 第二步,加载配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
// 第三步,创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
// 第四步,创建SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 第五步,使用SqlSession对象执行查询,得到User对象
// 第一个参数:执行查询的StatementId
User user = sqlSession.selectOne("getUserById", 10);
// 第六步,打印结果
System.out.println(user);
// 第七步,释放资源,每一个sqlSession就是一个连接
sqlSession.close();

二:创建 SqlSessionFactory

在创建一个SqlSession实例时,首先需要创建一个SqlSessionFactory实例,而又需要通过SqlSessionFactoryBuilder()build()来创建SqlSessionFactory

先看SqlSessionFactoryBuilder这个类,放在package org.apache.ibatis.session

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
public class SqlSessionFactoryBuilder {

//--------------------------------通过读取字符流(Reader)的方式构件SqlSessionFactory-----------------
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}

public SqlSessionFactory build(Reader reader, String environment) {
return build(reader, environment, null);
}

public SqlSessionFactory build(Reader reader, Properties properties) {
return build(reader, null, properties);
}

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}


//-----------------------通过读取字符流(Reader)的方式构件SqlSessionFactory---------------
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}

public SqlSessionFactory build(InputStream inputStream, String environment) {
return build(inputStream, environment, null);
}

public SqlSessionFactory build(InputStream inputStream, Properties properties) {
return build(inputStream, null, properties);
}

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
//--------------------------------创建SqlSessionFactory--------------------------
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
  • 第1,2种方式是通过配置文件方式,第3种是通过Java代码方式。

三.是如何创建SqlSessionFactory的

以通过InputStream字节流的方式来看,和它相关的一共有4个构造方法,其中第2个和第3个参数并不陌生

1
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties)

这相当于在告诉这两个配置项environment,properties是可以通过在构建SqlSessionFactory的时候进行配置的或重新配置(此时优先级最高)。XMLConfigBuilder工具类对配置文件进行解析成Configuration对象,

1
2
3
4
5
//参考上面的代码
public SqlSessionFactory build(){
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
}

再调用构建出SqlSessionFactory,构建出SqlSessionFactory

四 所以

兜兜转转,不管是配置文件还是Java代码,最后都会经过解析通过Configuration对象产生SqlSessionFactory,也就是最后一个build(Configuration config)

然而看最后一个方法的时候,返回的不是SqlSessionFactory,而是DefaultSqlSessionFactory实例,那是因为实际上SqlSessionFactory是一个接口,而DefaultSqlSessionFactory是它的实现类.暂且不管SqlSessionManager,暂时只需知道SqlSessionFactoryDefaultSqlSessionFactorySqlSessionManager

回顾SqlSession的创建过程,其实我们也能猜测得到SqlSessionFactory一定主要是创建SqlSession实例的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface SqlSessionFactory {

SqlSession openSession();

SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level);

SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection);

Configuration getConfiguration();
}

这么多的openSession重载方法,都是通过传入不同的参数构造SqlSession实例

  • 有通过设置事务是否自动提交”autoCommit”.
  • 有设置执行器类型”ExecutorType”来构造的
  • 还有事务的隔离级别等等

至于DefaultSqlSessionFactory对SqlSessionFactory的具体实现,除了以上方法之外,还包括了:openSessionFromDataSource、openSessionFromConnection、getTransactionFactoryFromEnvironment、closeTransaction。到这里我们似乎还是只停留在表面,并没有涉及相对比较底层的代码啊

我们这是刚走了一遍SqlSession创建过程的流程。

五: 下面我们从return new DefaultSqlSessionFactory(config)开始。

由于SqlSessionFactory的实现类DefaultSqlSessionFactory,源码过长,我们在其中以截取关键的代码作为解读。

DefaultSqlSessionFactory中的第1行代码实际上就非常值得我们思考:final关键字。

1
private final Configuration configuration;

为什么会使用final关键字对Configuration对象进行修饰呢?

  • Configuration应该是存在于MyBatis的整个生命周期那么意味着它应该是有且仅有一个实例的,而final关键字修饰的变量字段就代表它是不可变对象
  • 这也恰好能解释说明官方所说的SqlSessionFactory应该是单例的。

首先,MyBatis认为配置文件之所以是配置文件,那么就以为着它只有一种配置,就好比我们将一个新手机买回来过后,设置时间、日期就不再去更改,但我们可能会出国,这个时候就要配置选用另一个时区的时间,不过我还是使用的是这个手机的设置,换句话说,你的手机不可能有两个系统设置吧。

所以Configuration对象实际上就是我们手机上的系统设置。而SqlSessionFactory是通过Configuration来构造SqlSession的,对Configuration的引用当然是不可变的,如果可变,那相当于你手机里岂不是可以新建一个系统设置?那不就乱套了?索性final,对象不可变。

此时也就建议SqlSessionFactory是单例的了,你构建N个SqlSessionFactory,它们也是通过一个Configuration对象来构造的SqlSession实例,那还有必要有N个SqlSessionFactory了吗?显然没有必要,所以最好就是将SqlSessionFactory设计为单例。

这才对DefaultSqlSessionFactory类第一句话进行了解读,接着就是实现SqlSessionFactory接口的8个构造方法。DefaultSqlSessionFactory并没有直接实现这8个构造方法而是调用另外两个新的方法.

这8个构造方法实际上分为两大类:

  • 一个是从数据源中获取SqlSession.
  • 一个是从Connection中获取SqlSession(包含Connection参数的那两个构造函数)。

先看从数据源中获取SqlSession。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

如果没有传入ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit这三个参数就代表使用我们Configuration对象中的配置(看来Executor、TransactionIsolationLevel、autoCommit是可以灵活配置的)。第8行创建出一个DefaultSqlSession实例,可以猜测SqlSession是一个接口而DefaultSqlSession是其实现类。对于SqlSession的创建过程,我们马上就要走到最后一步SqlSession的构建。而这也是最关键最重要最发杂的一步

参考:https://www.cnblogs.com/yulinfeng/p/6063974.html

一:sqlSession

在上一篇文章写到了sqlSession创建

SqlSession可以说是整个MyBatis的重中之重,在SqlSession中涉及到前一篇四大对象:Executor、StatementHandler、ParameterHandler、ResultHandler,所以在此先只对SqlSession有一个大概的了解。

1
2
SqlSession sqlSession = SessionFactory.getSqlSession(resource); 
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

在代码中我们可以看到当我们构造出一个SqlSession实例过后,可以通过SqlSession构造出Mappper映射器。UserMapper是一个接口,那么我们可以肯定的是,它一定是用了Java的动态代理生成了一个代理类。

通过打断点调试我们可以发现确实产生了一个叫MapperProxy的代理类。

二:MapperProxy的代理类

下面是DefaultSqlSession的getMapper方法:

1
2
3
4
//org.apache.ibatis.session.default.DefaultSqlSession
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}

这是一个泛型方法。看来是调用了Configuration的getMapper方法,还不是DefaultSqlSession实现了getMapper。接着再看Configuration的getMapper方法:

1
2
3
4
//org.apache.ibatis.session.Configuration
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}

Configuration.getMapper一共两个参数,一个是Class类型,一个是SqlSession,在DefaultSqlSession.getMapper调用Configuration.getMapper时,将传递进来的Class类型参数和其本身传递给了Configuration.getMapper。此时还不是在Configuration中实现了getMapper,看来还是一个叫做mapperRegistry的变量。this 这里是指sqlSession

1
2
//org.apache.ibatis.session.Configuration
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

看着名字好像是注册Mapper映射器的地方,想来也是,既然要得到Mapper的映射,那么所有的Mapper都要一个地方去注册(在我们的mybytis-config.xml里),注册好过后需要的时候再去查找是否已经注册,那么就是MapperRegistry,所以取一个好的变量名是非常重要的。

1
2
3
4
5
6
7
8
9
10
11
12
//org.apache.ibatis.binding.MapperRegistry
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}

一个叫knownMappers的变量取出MapperProxyFactory。这个knownMapper的定义:

1
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

既然能用get方法取,那说明就有add方法咯?果不其然我们在MapperRegistry类中发现了public void addMapper(Class type)方法,那么是在哪里调用的这个方法呢?

三:我们来重新理一理。

使用MyBatis的第一步是配置mybatis-config.xml,配置好过后,mybatis-config跑起来的第一步也一定是首先解析xml配置文件,将解析好的配置文件各个配置参数放入Configuration对象中,包括Mapper的配置,所以应该是在解析xml文件的某个类中解析过来后调用Configuration的方法将mapper放置到MapperRegister中。事实也的确如此,有兴趣可以跟踪下代码看看。回到MapperRegistry.getMapper的方法中。

当我们一切正确时,我们就能获取到一个MapperProxyFactory实例。想必MapperProxy代理类的生成正是通过MapperProxyFactory工厂类构建的,即第8行代码。进入MapperProxyFactory类。

1
2
3
4
5
//org.apache.ibatis.binding.MapperProxyFactory
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}

在这里终于看到了MapperProxy代理类,是通过sqlSession、mapperInterface、mechodCache三个参数构造的。

newInstance有一个重载方法:

1
2
3
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

四:MapperProxy

终于是走到头了,这里就是返回的一个代理类实例。最后来看看MapperProxy。

MapperProxy是一个重要的类,所以我们将其代码全部贴出:

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
//org.apache.ibatis.binding.MapperProxy
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 {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}

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

}

要使用Java的动态代理就必须得实现InvocationHandler接口,从下面代码中首先判断代理对象是一个接口还是一个类,显然我们没有对mapper接口进行任何实现,那么它将生成一个MapperMethod对象,接着调用其execute方法,把sqlSession和参数传递进去。

1
2
3
4
5
6
7
8
9
10
11
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}

参考:https://www.cnblogs.com/yulinfeng/p/6063974.html