迷途小书童

读过几年书,尘世间一枚不起眼的小书童


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

React实战笔记---如何动态加载查询条件

发表于 2019-11-07 | 分类于 React实战笔记

React实战笔记—如何动态加载查询条件

如图需要动态加载第五个查询条件
图p1

由于框架原因,没法使用父组件直接调用子组件的方式。
下面给我这边的解决方案–>通过页面加载完成之后再根据最新数据强制渲染一次的方式,
代码如下:

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
//页面渲染之前加载数据
componentWillMount() {
this.getCCYType();
}

//页面渲染完成之后再根据加载的数据强制渲染一次
//时间间隔为0.001ms
componentDidMount() {
setInterval(() => {
       this.setState(() => {
            return { unseen: "does not display" }
});}, 0.001);
}

//获取数据方法
getCCYType = async () => {
const retdata = await getCCYType();
var data = [];
if (retdata.code === 20000) {
let ccylist = retdata.result.item_list;
for (var i = 0; i < ccylist.length; i++) {
data.push({id: ccylist[i].currencyCode, name: ccylist[i].currencyName})
}
this.formList.push( {
type: 'SELECT',
label: '币种',
field: 'currencyType',
placeholder: '请输入导入日期',
list: data,
width: 100,
});
} else {
message.error("获取币种类型异常! error=" + retdata.msg)
}
};

这样就可以以动态方式加载查询条件了,如下
图p2

笔记篇---快速搭建vsftpd文件服务器

发表于 2019-11-06 | 分类于 环境笔记篇

vsftpd搭建文件服务器

1
2
yum remove vsftpd
yum -y install vsftpd

先删除已安装的软件,再重新安装完整的vsftpd;
然后添加一个ftp用户和密码

1
2
useradd ftpuser
passwd ftpuser

防火墙开21端口

1
vim /etc/sysconfig/iptables

(在有 22 -j ACCEPT 的下面复制一行,端口改成21,wq保存并退出)
重启 iptables

1
service iptables restart

修改 selinux(外网可以访问,但无法返回目录,因为selinux)

1
2
3
getsebool -a | grep ftp(查看状态)
setsebool -P allow_ftpd_full_access on (设置为 on)
setsebool -P ftp_home_dir on(设置为 on)

关闭匿名访问

1
2
vim /etc/vsftpd/vsftpd.conf(anonymous_enable=NO)
service vsftpd restart

开机启动 vsftpd 服务

1
chkconfig vsftpd on

接下来就可以通过filezilla使用ftp协议访问了如图:

图p1

**图p1**

另外这里附上FtpUtil.java工具类,首先如果你是gradle项目需要引入如下依赖包

1
2
compile 'commons-fileupload:commons-fileupload:1.3.1'
compile 'commons-net:commons-net: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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package com.yyicbc.utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;

public class FtpUtil {

/**
* Description: 向FTP服务器上传文件
* @param host FTP服务器hostname
* @param port FTP服务器端口
* @param username FTP登录账号
* @param password FTP登录密码
* @param basePath FTP服务器基础目录
* @param filePath FTP服务器文件存放路径。例如分日期存放:/2019/01/01。文件的路径为basePath+filePath
* @param filename 上传到FTP服务器上的文件名
* @param input 输入流
* @return 成功返回true,否则返回false
*/
public static boolean uploadFile(String host, int port, String username, String password, String basePath,
String filePath, String filename, InputStream input) {
boolean result = false;
FTPClient ftp = new FTPClient();
try {
int reply;
ftp.connect(host, port);// 连接FTP服务器
// 如果采用默认端口,可以使用ftp.connect(host)的方式直接连接FTP服务器
ftp.login(username, password);// 登录
reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftp.disconnect();
return result;
}
//切换到上传目录
if (!ftp.changeWorkingDirectory(basePath+filePath)) {
//如果目录不存在创建目录
String[] dirs = filePath.split("/");
String tempPath = basePath;
for (String dir : dirs) {
if (null == dir || "".equals(dir)) continue;
tempPath += "/" + dir;
if (!ftp.changeWorkingDirectory(tempPath)) {
if (!ftp.makeDirectory(tempPath)) {
return result;
} else {
ftp.changeWorkingDirectory(tempPath);
}
}
}
}
//设置上传文件的类型为二进制类型
ftp.setFileType(FTP.BINARY_FILE_TYPE);
//上传文件
if (!ftp.storeFile(filename, input)) {
return result;
}
input.close();
ftp.logout();
result = true;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ftp.isConnected()) {
try {
ftp.disconnect();
} catch (IOException ioe) {
}
}
}
return result;
}

/**
* Description: 从FTP服务器下载文件
* @param host FTP服务器hostname
* @param port FTP服务器端口
* @param username FTP登录账号
* @param password FTP登录密码
* @param remotePath FTP服务器上的相对路径
* @param fileName 要下载的文件名
* @param localPath 下载后保存到本地的路径
* @return
*/
public static boolean downloadFile(String host, int port, String username, String password, String remotePath,
String fileName, String localPath) {
boolean result = false;
FTPClient ftp = new FTPClient();
try {
int reply;
ftp.connect(host, port);
// 如果采用默认端口,可以使用ftp.connect(host)的方式直接连接FTP服务器
ftp.login(username, password);// 登录
reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftp.disconnect();
return result;
}
ftp.changeWorkingDirectory(remotePath);// 转移到FTP服务器目录
FTPFile[] fs = ftp.listFiles();
for (FTPFile ff : fs) {
if (ff.getName().equals(fileName)) {
File localFile = new File(localPath + "/" + ff.getName());

OutputStream is = new FileOutputStream(localFile);
ftp.retrieveFile(ff.getName(), is);
is.close();
}
}

ftp.logout();
result = true;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (ftp.isConnected()) {
try {
ftp.disconnect();
} catch (IOException ioe) {
}
}
}
return result;
}

public static void main(String[] args) {
try {
FileInputStream in=new FileInputStream(new File("E:\\ftptest\\test.jpg"));
boolean flag = uploadFile("xxxx", 21, "xxxx", "xxxx", "/home/xxxx/files","/2019/11/06", "test.jpg", in);
System.out.println(flag);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}

Hello World

发表于 2019-11-05 | 分类于 Hexo框架使用

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

笔记篇---Linux环境下安装JDK完整步骤

发表于 2019-11-05 | 分类于 环境笔记篇

1、检查一下系统中的jdk版本

1
2
3
4
5
6
[root@localhost software]# java -version  
显示:

openjdk version "1.8.0_102"
OpenJDK Runtime Environment (build 1.8.0_102-b14)
OpenJDK 64-Bit Server VM (build 25.102-b14, mixed mode)

2、检测jdk安装包

1
2
3
4
5
6
7
8
9
10
[root@localhost software]# rpm -qa | grep java
显示:

java-1.7.0-openjdk-1.7.0.111-2.6.7.8.el7.x86_64
python-javapackages-3.4.1-11.el7.noarch
tzdata-java-2016g-2.el7.noarch
javapackages-tools-3.4.1-11.el7.noarch
java-1.8.0-openjdk-1.8.0.102-4.b14.el7.x86_64
java-1.8.0-openjdk-headless-1.8.0.102-4.b14.el7.x86_64
java-1.7.0-openjdk-headless-1.7.0.111-2.6.7.8.el7.x86_64

3、卸载openjdk

1
2
3
4
5
[root@localhost software]# rpm -e --nodeps tzdata-java-2016g-2.el7.noarch
[root@localhost software]# rpm -e --nodeps java-1.7.0-openjdk-1.7.0.111-2.6.7.8.el7.x86_64
[root@localhost software]# rpm -e --nodeps java-1.7.0-openjdk-headless-1.7.0.111-2.6.7.8.el7.x86_64
[root@localhost software]# rpm -e --nodeps java-1.8.0-openjdk-1.8.0.102-4.b14.el7.x86_64
[root@localhost software]# rpm -e --nodeps java-1.8.0-openjdk-headless-1.8.0.102-4.b14.el7.x86_64

或者使用

1
[root@localhost jvm]# yum remove *openjdk*

之后再次输入rpm -qa | grep java 查看卸载情况:

1
2
3
[root@localhost software]# rpm -qa | grep java
python-javapackages-3.4.1-11.el7.noarch
javapackages-tools-3.4.1-11.el7.noarch

4、安装新的jdk

首先到jdk官网上下载你想要的jdk版本,下载完成之后将需要安装的jdk安装包放到Linux系统指定的文件夹下,并且命令进入该文件夹下:

1
2
3
4
5
6
7
[root@localhost software]# ll
total 252664
-rw-r--r--. 1 root root 11830603 Jun 9 06:43 alibaba-rocketmq-3.2.6.tar.gz
-rw-r--r--. 1 root root 43399561 Jun 9 06:42 apache-activemq-5.11.1-bin.tar.gz
-rwxrw-rw-. 1 root root 185540433 Apr 21 09:06 jdk-8u131-linux-x64.tar.gz
-rw-r--r--. 1 root root 1547695 Jun 9 06:44 redis-3.2.9.tar.gz
-rw-r--r--. 1 root root 16402010 Jun 9 06:40 zookeeper-3.4.5.tar.gz

解压 jdk-8u131-linux-x64.tar.gz安装包

1
2
[root@localhost software]# mkdir -p /usr/lib/jvm
[root@localhost software]# tar -zxvf jdk-8u131-linux-x64.tar.gz -C /usr/lib/jvm

5、设置环境变量

1
[root@localhost software]# vim /etc/profile

在最前面添加:

1
2
3
4
export JAVA_HOME=/usr/lib/jvm/jdk1.8.0_131  
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH

6、执行profile文件

1
[root@localhost software]# source /etc/profile

这样可以使配置不用重启即可立即生效。

7、检查新安装的jdk

1
[root@localhost software]# java -version

显示:

1
2
3
java version "1.8.0_131"
Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)

到这里,整个安装过程就结束了。

SpringBoot学习篇

发表于 2019-08-24 | 分类于 springboot相关

to be continue

笔记篇---集群启动shell脚本编写

发表于 2019-08-24 | 分类于 环境笔记篇

编写shell脚本启动zk集群or redis集群

先去到伪zk集群的文件夹付所在目录

1
2
3
touch zkStartAll.sh

vi ./zkStartAll.sh

编写内容,其实就是依次进入文件夹启动单机实例

1
2
3
4
5
6
cd /usr/local/zookeeper1/bin
./zkServer.sh start
cd /usr/local/zookeeper2/bin
./zkServer.sh start
cd /usr/local/zookeeper3/bin
./zkServer.sh start

给文件授权,运行文件

1
2
3
chmod u+wxr startAllZk.sh

./startAllZk.sh

结果就是集群成功启动

1
2
3
4
5
6
7
8
9
10
[root@nginxslaver local]# ./startAllZk.sh
JMX enabled by default
Using config: /usr/local/zookeeper1/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
JMX enabled by default
Using config: /usr/local/zookeeper2/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
JMX enabled by default
Using config: /usr/local/zookeeper3/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED

redis伪集群启动方式类似

用来记录项目中需要配置但是容易忘记的一些记录

Slogan:学过几年技术,尘世间一枚不起眼的迷途小书童

java提升篇--1(简单模拟NC服务端客户端通讯模型---RMI)

发表于 2019-08-05 | 分类于 java高级篇

第一节 RMI(Remote Mothed invoke)是什么?

一句话简单总结,我个人认为RMI就是RPC的java版本实现,当然他们本身底层也有很大的区别,比如序列化协议,网络通讯模型等等,今天我们就来简单的学习一下NC使用的RMI技术;

第二节 RMI简单实现

废话不多说我们直接上代码,我这里区分了服务端与客户端,见下图:
图p1

图p1、整体概况
- 服务端代码:
  1. HelloService接口存根

    1
    2
    3
    4
    5
    package RPC;

    public interface HelloService {
    public String hello(String name);
    }
  2. 上述接口的实现类HelloServiceImpl

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package RPC;

    public class HelloServiceImpl implements HelloService {

    @Override
    public String hello(String name) {
    return name+":from hkserver!";
    }

    }
  3. 服务器启动接口类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package RPC;

    import java.lang.reflect.InvocationTargetException;
    import java.util.HashMap;
    import java.util.Map;

    public interface Server {

    //启动服务器
    public void start() ;
    //停止服务器
    public void stop();
    //往服务容器中注册服务
    public void register() throws ClassNotFoundException;
    }
  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
    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
    package RPC;

    import java.io.IOException;
    import java.io.InputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.io.OutputStream;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.lang.reflect.TypeVariable;
    import java.net.InetSocketAddress;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.net.SocketAddress;
    import java.util.HashMap;
    import java.util.Map;

    public class ServerImpl implements Server {

    //注册中心用于处理
    private Map<String,Class> serviceRegister = new HashMap<String,Class>();

    private static final int PORT = 9999;


    //开放端口提供给client进行调用
    @Override
    public void start() {
    ServerSocket ss = null;
    OutputStream os = null;
    InputStream is = null;
    Socket sk = null;
    try {
    ss = new ServerSocket();
    ss.bind(new InetSocketAddress(PORT));
    sk = ss.accept();
    //阻塞等待
    is = sk.getInputStream();
    ObjectInputStream ois = new ObjectInputStream(is);
    String servicename = ois.readUTF();
    String methodname = ois.readUTF();
    Class[] paraTypes = (Class[])ois.readObject();
    Object[] args= (Object[])ois.readObject();
    Class serviceClass = serviceRegister.get(servicename);
    @SuppressWarnings("unchecked")
    Method invokeMethod = serviceClass.getMethod(methodname, paraTypes);
    Object result = invokeMethod.invoke(serviceClass.newInstance(), args);
    os = sk.getOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(os);
    oos.writeObject(result);
    oos.flush();
    } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    } catch (ClassNotFoundException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    } catch (NoSuchMethodException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    } catch (SecurityException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    } catch (IllegalAccessException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    } catch (IllegalArgumentException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    } catch (InvocationTargetException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    } catch (InstantiationException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }finally{
    if(os!=null){
    try {
    os.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    if(is!=null){
    try {
    is.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    try {
    ss.close();
    sk.close();
    } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    }

    }

    @Override
    public void stop() {

    }

    @Override
    public void register() throws ClassNotFoundException {
    serviceRegister.put("HelloService", Class.forName("RPC.HelloServiceImpl"));
    }

    }

5.服务端main方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package RPC;

public class ServerMain {
public static void main(String[] args){
ServerImpl center = new ServerImpl();
try {
center.register();
center.start();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}
}

以上我们服务端代码就完成了,简单而言就是通过一个main方法来启动我们的服务器,服务器启动的时候回向服务容器中注册我们的服务;(tips:这里我只是注册了一个服务实现类本身,如果结合之前我说过的日志记录以及事务处理等等,需要将服务实现类本身换成动态代理类),我这里并没有实现服务器的多线程处理,如果需要同时为多个客户端服务,需要将sk = ss.accept();获取到的Socket对象放进一个新的Thread进行操作,也就是说每当有一个client连接到服务器,服务器就新建一个Thread对该客户端进行服务!NC服务端就是采用的这种方式,同步阻塞IO,伪异步线程池;

  • 客户端代码:
  1. HClient

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

    import java.io.IOException;
    import java.io.InputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.io.OutputStream;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.net.InetSocketAddress;
    import java.net.Socket;

    public class Client {

    // 获取代表服务端接口HelloService的动态代理对象
    /*
    * @param
    */
    public static <T> T getRemoteProxyObj(final String serviceName, final InetSocketAddress addr) throws ClassNotFoundException {
    final Class<?> forName = Class.forName(serviceName);
    @SuppressWarnings("unchecked")
    T newProxyInstance = (T) Proxy.newProxyInstance(forName.getClassLoader(), new Class[] { forName },
    new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
    Socket sk = new Socket();
    OutputStream os = null;
    InputStream is = null;
    try{
    sk.connect(addr);
    os = sk.getOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(os);
    oos.writeUTF(forName.getSimpleName());
    oos.writeUTF(method.getName());
    oos.writeObject(method.getParameterTypes());
    oos.writeObject(args);
    oos.flush();
    is = sk.getInputStream();
    ObjectInputStream ois = new ObjectInputStream(is);
    Object result = ois.readObject();
    return result;
    }catch(Exception e){
    return null;
    }finally{
    if(os!=null){
    try {
    os.close();
    } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    }
    if(is!=null){
    try {
    is.close();
    } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    }
    try {
    sk.close();
    } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    }

    }
    });
    return newProxyInstance;
    }

    public static void main(String[] args) throws ClassNotFoundException{
    /*由于我这里演示是本机演示,没有通过ngrok外网映射,如果你打通了外网映射,你就可以实现你的对外服务器啦*/
    /*HelloService remoteProxyObj = (HelloService)getRemoteProxyObj("rpcclient.HelloService",new InetSocketAddress("free.idcfengye.com",18034));*/
    HelloService remoteProxyObj = (HelloService)getRemoteProxyObj("rpcclient.HelloService",new InetSocketAddress("127.0.0.1",9999));
    String result = remoteProxyObj.hello("hahaha");
    System.out.println(result);
    }
    }
  2. 客户端服务存根HelloService(这也就解释了我们的NC的接口代码为什么要放在public下面,因为客户端需要这个类的信息)

    1
    2
    3
    4
    5
    package rpcclient;

    public interface HelloService {
    public String hello(String name);
    }

以上就是我们的客户端代码,我们客户端只放了一个服务方法存根HelloService,那么我们是如何做到向服务端发送请求的了?答案就是动态代理,我们在获取HelloService对象的时候其实就是对该接口的匿名内部类实现进行了动态代理,然后再通过动态代理去向服务端发送请求,服务端拿到我们的请求方法名和请求参数等等去容器中寻找实现类并将执行结果返回到客户端,至此一次完整的RMI请求结束;

第三节 运行结果演示

1、启动服务端main方法如下图:

图p2

图p2、服务启动等待客户端连接

2、启动客户端连接服务器,发出请求得到结果:

图p3

图p3、客户端启动结果

Slogan:学过几年技术,尘世间一枚不起眼的迷途小书童

UAP应该知道的那些事儿--2(事务)

发表于 2019-07-24 | 分类于 UAP框架

第一节 事务是什么?隔离级别?传播属性?

开始本章内容之前,本人希望你已经看过了该系列的第一篇UAP应该知道的那些事儿–1!这对你充分理解本章的内容起到了很大的作用,废话不多说我们开始本章的内容!
在平时的开发中,除了写代码,你一定每天都和各种各样的数据库打过交道,Nosql我这里先不说,起码mysql、oracle、sqlserver这些常见的关系数据库你一定不陌生,那么用数据库有一个名词你是一定要了解的,那就是事务(分布式事务今天这里先不谈),啥是事务?

数据库事务(简称:事务)是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。
并非任意的对数据库的操作序列都是数据库事务。数据库事务拥有以下四个特性,习惯上被称之为ACID特性。

  • 原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
  • 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
  • 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
  • 持久性(Durability):已被提交的事务对数据库的修改应该永久保存在数据库中。

以上是官方对于数据库事务的一个定义,我这里也不对这些东西做解释了,自行去了解一下这些概念

图p1

图p8

spring七个事物传播属性:

  • PROPAGATION_REQUIRED -支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
  • PROPAGATION_SUPPORTS -支持当前事务,如果当前没有事务,就以非事务方式执行。
  • PROPAGATION_MANDATORY -支持当前事务,如果当前没有事务,就抛出异常。
  • PROPAGATION_REQUIRES_NEW -新建事务,如果当前存在事务,把当前事务挂起。
  • PROPAGATION_NOT_SUPPORTED -以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • PROPAGATION_NEVER -以非事务方式执行,如果当前存在事务,则抛出异常。
  • PROPAGATION_NESTED -如果当前存在事务则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。

以上这些不是本章的重点,看一看了解一下先即可。

第二节 NC用的什么隔离级别?支持哪几种传播属性?

ok,经过上一节介绍应该对事务有了一个大致的了解了,那么回来我们说说NC,NC支持Oracle和Sqlserver
(这里多说一句,通过修改源码可以支持db2以及mysql,本人已亲证),其实这两个数据库默认的隔离级别都是读已提交(read commit),也就是说一个事务只能读取到另外一个事务commit了的数据,当然你可以根据你项目的需求进行调整,
上一章我强调过了,从源码层面分析了我们NC其实是只支持两种传播属性:Requires和equiresNew
,见下图:

图p1

图p1

第三节 本章重点:如何让你的代码使用到事务!!!

不用多说这是本章的重点,在说本节内容之前,先提一个名词—>jdk动态代理,不知诸位对这个东东有没有了解过,没了解过的建议去百度一下,我这里直接上代码:
1、先有一个接口TargetItf:

1
2
3
4
5
6
7
8
9
package jdkdtproxy;

public interface TargetItf {

public void method1();

public String method2();

}

2、对该接口有一个实现类

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

public class Target implements TargetItf {

@Override
public void method1() {
System.out.println("method invoked");
}

@Override
public String method2() {
System.out.println("method invoked");
return "method2 result";
}

}

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
30
31
32
33
34
35
36
37
38
39
package jdkdtproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import org.junit.Test;
/**
*
* @ClassName: TargetTest
* @Description: TODO(这里用一句话描述这个类的作用)
* @author steven
* @date 2018年11月24日 上午9:53:24
*
*/
public class TargetTest {

@Test
public void test1(){

final Target target = new Target();
TargetItf newProxyInstance = (TargetItf)Proxy.
newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoke before");
Object invokeResult = method.invoke(target, args);
System.out.println("invoke after");
return invokeResult;
}
});
newProxyInstance.method1();
String method2 = newProxyInstance.method2();
System.out.println(method2);

}
}

执行结果:

1
2
3
4
5
6
7
invoke before
method invoked
invoke after
invoke before
method invoked
invoke after
method2 result

是不是觉得很神奇?竟然在你的目标方法内容前后都进行了操作!没错,这就是动态代理,它能够对你的目标对象进行拦截切面,而且我们NC的服务端容器就是完全依赖了这个东东,所有通过upm文件中写了cmt属性的接口在服务容器中都是一个动态代理对象而不是实现类本身,现在回应上一章的内容,你如果通过了NcLocator来获取到了一个对象,然后使用这个代理对象去调用对应的服务方法,然后的东西你明白了吗?来看看下面两张我精心挑选的图:
p6

图p2、Requires属性的处理方式

p6

图p3、RequiresNew属性的处理方式
现在是不是清晰了许多,我来总结一下:服务启动,会在内存中产生一个Locator容器keyvalue形式,key是接口类名称,value就是实现类的动态代理对象,通过上图很明显的看到,如果你想让你的代码能够使用到事务以及事务的传播属性,那你一定要通过Nclocator进行对象的获取,因为事务的开启与关闭以及服务方法调用前后的日志记录都是通过代理对象来实现的!

由于篇幅原因,Requires和RequiresNew具体区别我们下一张详细论述!!!,下一章我们通过实例来详细分析如何让你的代码能够使用到事务以及事务的传播属性。

Slogan:技术界一枚不起眼的迷途小书童

UAP应该知道的那些事儿--1(通讯模型)

发表于 2019-07-19 | 分类于 UAP框架

第一节 NCLocator了解吗,客户端与服务端如何进行通讯

在平时的开发中,如果我们想要在客户端调用服务端部署好的服务,一般我们是这样在客户端通过以下方式:

1
IxxxService service = NCLocator.getInstance().lookup(IxxxService.class);

获取到service这样一个接口对象,然后通过这个service调用对应的method就能实现与服务端的通讯,但是诸位有没有想过为什么这样就可以进行client到server的网络IO通讯,其实这里就是通过了jdk的动态代理来实现的,jdk动态代理是javase高级知识点,没接触过的可以去看看相关知识点,在java高级知识介绍中我会用一篇日记来仔细讲解,这里就不做详细介绍。继续讲我们在客户端获取到的service对象就是一个IxxxService接口的动态代理对象,这里看一下动态代理是如何获取的,如下代码:

1
Proxy.newProxyInstance(classloader, interfaces, InvocationHanddler);

图p8

图p8

有没有发现上图中h=RemoteInvocationHandler很眼熟,这就是一个InvocationHanddler的实现方式
这里我通过下面一个通用接口来进行详细说明:

1
IBillcodeManage lookup = NCLocator.getInstance().lookup(IBillcodeManage.class);

从上图我们看到在客户端拿到的lookup对象确实就是一个动态代理类,并非对象本身那么简单,换句话说我们可以理解,
NCLocator.getInstance()其实就是获取到客户端jvm中的一个hashmap容器,
key是IxxxService.class,value就是这个接口类的一个动态代理实例,
这样说或许会比较好容易理解,但事实上也确实如此,只是具体实现上面会复杂很多,好了
接下来就去看看这个客户端获取到的动态代理是如何往服务端发送请求的

图p2

图p2

图p3

图p3

图p4

图p4

看到这里是不是有种拨开云雾的赶脚,没错,你没有眼花,我们的NC客户端采用的就是最原始最古老的BIO通讯模型
,不是说这个BIO模型不好,而是因为他存在有很大的局限性,比如同步、阻塞、无法处理高并发访问等,但是用在
erp系统中我觉得是够用的,这里我在整理总结一下当你使用客户端接口的动态代理调用方法时具体发生了什么:

  • 1、进入RemoteInvocationHandler的invoke方法
  • 2、获取调用的方法名称和参数
  • 3、Tcp三次握手与服务端建立连接
  • 4、网络io发送调用方法的相关信息和参数信息到服务端
  • 5、同步等待服务端返回结果(这里就是一个所谓同步调用的概念)

这里再多解释一句,NC服务端启动的时候会有一个ServerSocket.accept()的方法专门用来处理客户端
发送来的连接请求,这也是BIO模型的精髓所在<同步与阻塞>。
这里还有非常一个值得一提的地方见下图,客户端并不是无限制的创建Socket,而是设计了一个Socket连接池来进行Socket复用,原理类似数据库连接池;

图p5

图p5

Socket s = (Socket)sq.poll();
这就是通过有界阻塞队列来实现Socket容器。

第二节 事务与日志(NC系统的事务型组件)

上一节我们讲到了客户端接口类的动态代理发送请求到服务端,那么我们NC的服务端又是如何处理请求,
又是如何将请求发送回客户端的了?我们慢慢来讲,跟第一章一样,我们先通过:

1
2

IBillcodeManage lookup = NCLocator.getInstance().lookup(IBillcodeManage.class);

在服务端获取到lookup的动态代理对象,注意了这里我们是在服务端(private)获取了,不是在客户端(client)了哈,重要的事情说三遍:

  • 不是在客户端(client)
  • 不是在客户端(client)
  • 不是在客户端(client)

见下图:

图p1

图p1

是不是发现了异样,同样的方法在客户端和服务端拿到的lookup对象是完全不一样的!因为服务端获取到的是具体实现类的动态代理,是要去实实在在做事情的了,这也就解释了NC是分服务端容器和客户端两大容器的!见下图:

图p2

图p2

有没有,看了图p2是不是就感觉发现了新大陆,这就是NC所谓的事务型组件的设计原理:根据接口设计中的方法名后缀来进行判断决定采用何种事务的传播属性,另外强调一下
NC的事务传播属性并没有很多种,从源码层面来看NC只支持最基本的Requires和equiresNew,并非像UAP红皮书中说的还支持mandatory、support以及never,至少我在源码层面是没有发现支持
与spring的7大事务传播属性相差甚远,至于这两种传播属性具体有什么区别,我会在我的第二篇UAP文章中进行剖析,好了这里我再总结一下服务端接收到客户端请求后都做了一些什么操作,见图p11、p12、p13

  • 1、服务端bioserver开启SocketServer等待客户端请求到来
  • 2、服务端接受到请求后解析请求参数,通过servicename去服务端的服务实现类动态代理容器中寻找对应的服务类动态代理
  • 3、最后通过反射机制,mothod.invoke(obj)来实现事务控制以及日志记录以及实际的业务处理
  • 4、返回结果到客户端,至此一次bio完整的请求通讯结束

图p11

图p11

图p12

图p12

图p13

图p13

第三节 系统是如何保证一次请求中的多次数据库交互拿到的是同一个Connection

其实这是一个扩展思考的问题,当时也是因为这个事务问题笨人就多思考了一点,就是说你如果要保证事务有效,
前提条件你必须让你的一次请求过程中拿到的conn是同一个对象对吧,
这个问题我并不想描述的太多,因为这是一个很基础的并发编程问题–>如何让一个变量(conn)成为一个线程的独享变量,
从而达到解决线程安全的问题,这里的解决方案就是ThreadLocal;
我提供几个关键截图,朋友们可以自行去了解一下ThreadLocal的原理再回过头来就看这几行代码,
就能很好的理解其设计原理

p6

图p6

p7

图p7

上图中的connRef对象就是一个ThreadLocal变量。用于获取当前线程的conn,但是conn对象并非是存在这个变量中的,而是存储在每一个Thread的一个ThreadLocalMap的变量中的,这个ThreadLocalMap中key就是connRef的softreference形式,value就是conn对象,这里有点绕,所以我的建议是先了解原理再看代码;

前前后后写了三四个小时,终于写完了本人的第一篇博文,后续还会更新更多…

Slogan:技术界一枚不起眼的迷途小书童

1…45
史蒂芬猴

史蒂芬猴

dora is my precious

49 日志
27 分类
38 标签
GitHub
© 2023 史蒂芬猴
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
访客数 人 总访问量 次