☻Blog("Laziji")

System.out.print("辣子鸡的博客");

0%

介绍如何用 Fabric Java SDK 进行简单的数据块插入和查询

环境

  • Hyperledger Fabric 2.0
  • Fabric Java SDK 2.0.0

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class LocalUser implements User {
private String name;
private String mspId;
private Enrollment enrollment;

LocalUser(String name, String mspId, String keyFile, String certFile) throws Exception {
this.name = name;
this.mspId = mspId;
this.enrollment = loadFromPemFile(keyFile, certFile);
}

private Enrollment loadFromPemFile(String keyFile, String certFile) throws Exception {
byte[] keyPem = Files.readAllBytes(Paths.get(keyFile));
byte[] certPem = Files.readAllBytes(Paths.get(certFile));
CryptoPrimitives suite = new CryptoPrimitives();
PrivateKey privateKey = suite.bytesToPrivateKey(keyPem);
return new X509Enrollment(privateKey, new String(certPem));
}

@Override
public String getName() {
return name;
}

public Set<String> getRoles() {
return null;
}

public String getMspId() {
return mspId;
}

@Override
public Enrollment getEnrollment() {
return enrollment;
}

@Override
public String getAccount() {
return null;
}

@Override
public String getAffiliation() {
return null;
}

}
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
public class FabricTest {

private static final String BASE_PATH = "./fabric";
private static final String CHANNEL_NAME = "CHANNEL_NAME";
private static final String CHAINCODE_ID = "CHAINCODE_ID";
private static final Map<String, String> ORDERER_MAP = new HashMap<>();
private static final Map<String, String> PEER_MAP = new HashMap<>();
private static final boolean TLS = true;

static
String protocol = "grpc";
if(TLS) {
protocol += "s";
}
ORDERER_MAP.put("test.com", protocol + "://127.0.0.1:7050");
PEER_MAP.put("peer.test.com", protocol + "://127.0.0.1:7051");
}

public static void main(String[] args) throws Exception {
String keyFile = BASE_PATH + "***/user-key.pem";
String certFile = BASE_PATH + "***/user-cert.pem";
LocalUser user = new LocalUser("username", "mspId", keyFile, certFile);

HFClient client = HFClient.createNewInstance();
client.setCryptoSuite(CryptoSuite.Factory.getCryptoSuite());
client.setUserContext(user);

Channel channel = client.newChannel(CHANNEL_NAME);
for (Map.Entry<String, String> entry : ORDERER_MAP.entrySet()) {
Properties properties = new Properties();
if(TLS) {
properties.setProperty("clientCertFile", "对应配置目录下users/***/tls/client.crt文件");
properties.setProperty("clientKeyFile", "对应配置目录下users/***/tls/client.key文件");
properties.setProperty("pemFile", "对应配置目录下/tls/server.crt文件");
properties.setProperty("hostnameOverride", entry.getKey());
properties.setProperty("sslProvider", "openSSL");
properties.setProperty("negotiationType", "TLS");
}
channel.addOrderer(client.newOrderer(entry.getKey(), entry.getValue(), properties));
}
for (Map.Entry<String, String> entry : PEER_MAP.entrySet()) {
Properties properties = new Properties();
if(TLS) {
properties.setProperty("clientCertFile", "对应配置目录下users/***/tls/client.crt文件");
properties.setProperty("clientKeyFile", "对应配置目录下users/***/tls/client.key文件");
properties.setProperty("pemFile", "对应配置目录下/tls/server.crt文件");
properties.setProperty("hostnameOverride", entry.getKey());
properties.setProperty("sslProvider", "openSSL");
properties.setProperty("negotiationType", "TLS");
}
channel.addPeer(client.newPeer(entry.getKey(), entry.getValue(), properties));
}
channel.initialize();

System.out.format("Valid: %s\n", invokeRequest(client, channel, "insert", "ID1","VALUE222"));
System.out.format("Message: %s\n", queryRequest(client, channel, "query", "ID1"));
}

private static String queryRequest(HFClient client, Channel channel, String function, String... args) throws Exception {
QueryByChaincodeRequest request = client.newQueryProposalRequest();
request.setChaincodeName(CHAINCODE_ID);
request.setFcn(function);
request.setArgs(args);
ProposalResponse[] responses = channel.queryByChaincode(request).toArray(new ProposalResponse[0]);
return responses[0].getProposalResponse().getResponse().getPayload().toStringUtf8();
}

private static boolean invokeRequest(HFClient client, Channel channel, String function, String... args) throws Exception {
TransactionProposalRequest request = client.newTransactionProposalRequest();
request.setChaincodeName(CHAINCODE_ID);
request.setFcn(function);
request.setArgs(args);
Collection<ProposalResponse> responses = channel.sendTransactionProposal(request);
BlockEvent.TransactionEvent event = channel.sendTransaction(responses).get();
return event.isValid();
}
}

js中可以定义生成器函数, 使用yield迭代结果, 例如

1
2
3
4
5
6
7
8
9
function* gen() { 
yield 1;
yield 2;
yield 3;
}

let g = gen();
g.next();
g.next();

本文中尝试在Java环境中实现类似的效果

实现

Generator.java

生成器类

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
import java.util.concurrent.Semaphore;

public class Generator {

private Function function;

private Runnable runnable;
private Thread thread;

private Semaphore readLock;
private Semaphore writeLock;

private Result result;
private Object nextValue;
private boolean done = true;

public Generator(Function function) {
this.function = function;
this.runnable = () -> {
try {
this.writeLock.acquire();
Object value = this.function.run(this);
this.result = new Result(value, true);
this.readLock.release();
} catch (Exception e) {
e.printStackTrace();
}
};
this.reset();
}

public boolean isDone() {
return done;
}

public synchronized void reset() {
this.close();
this.readLock = new Semaphore(0);
this.writeLock = new Semaphore(0);
this.result = null;
this.nextValue = null;
this.done = false;
this.thread = new Thread(this.runnable);
this.thread.start();
}

public synchronized void close() {
if (this.thread != null) {
this.thread.stop();
this.thread = null;
}
}

public synchronized Result next() throws InterruptedException {
return this.next(null);
}

public synchronized Result next(Object value) throws InterruptedException {
if (this.done) {
return null;
}
this.nextValue = value;
this.writeLock.release();
this.readLock.acquire();
Result result = this.result;
this.done = result.isDone();
return result;
}

public Object yield(Object value) throws InterruptedException {
this.result = new Result(value, false);
this.readLock.release();
this.writeLock.acquire();
return this.nextValue;
}

@FunctionalInterface
public interface Function {
Object run(Generator context) throws Exception;
}

public static class Result {
private Object value;
private boolean done;

private Result(Object value, boolean done) {
this.value = value;
this.done = done;
}

public Object getValue() {
return value;
}

public boolean isDone() {
return done;
}

@Override
public String toString() {
return "Result{" +
"value=" + value +
", done=" + done +
'}';
}
}
}

测试

Java

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

public static void main(String[] args) throws InterruptedException {
Generator generator = new Generator(context -> {
int count = 0;
for (int i = 0; i < 10; i++) {
Integer value = (Integer) context.yield(i);
count += value == null ? 0 : value;
}
return count;
});

Generator.Result result = null;
do {
result = generator.next(result == null ? null : result.getValue());
System.out.println(result);
} while (!result.isDone());
}
}

/* 输出结果
Result{value=0, done=false}
Result{value=1, done=false}
Result{value=2, done=false}
Result{value=3, done=false}
Result{value=4, done=false}
Result{value=5, done=false}
Result{value=6, done=false}
Result{value=7, done=false}
Result{value=8, done=false}
Result{value=9, done=false}
Result{value=45, done=true}
*/

对应的JavaScript代码

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
let generator = (function* Generator() {
let count = 0;
for (let i = 0; i < 10; i++) {
count += (yield i) || 0;
}
return count;
})();

let result;
do {
result = generator.next(result && result.value);
console.log(result);
} while (!result.done)

/* 输出结果
{value: 0, done: false}
{value: 1, done: false}
{value: 2, done: false}
{value: 3, done: false}
{value: 4, done: false}
{value: 5, done: false}
{value: 6, done: false}
{value: 7, done: false}
{value: 8, done: false}
{value: 9, done: false}
{value: 45, done: true}
*/

使用vue-cli-service build对开发的库进行打包时使用--target lib

开发时Vue通常是以devDependencies方式引入的只在开发中使用, 使用lib方式打包也一样不会把Vue打包进去

官方文档中描述

在库模式中,Vue 是外置的。这意味着包中不会有 Vue,即便你在代码中导入了 Vue。

打包

打包时通常会提示缺少vue-template-compiler 按以下安装并打包, 打包默认输出路径是./dist

1
2
3
4
npm install -g @vue/cli-service
npm install -g vue-template-compiler

vue-cli-service build --target lib --name myLib [entry]

配置

vue.config.js中进行打包相关的配置

1
2
3
4
5
6
7
8
module.exports = {
//不生成 .map 文件
productionSourceMap: false,
css: {
//css合并入js中, 实际使用中发现合并的时候会自动去掉空的class, 导致$style.class 取不到值
extract: false
}
}

引入

发布到npm上后, 若需要CDN方式引入, 可以使用unpkg 例如这个项目
https://github.com/GitHub-Laziji/menujs

1
<script src="https://unpkg.com/vue-contextmenujs/dist/contextmenu.umd.js">

dubbo官方文档中关于集成zookeeper的说明在2.7.1版本下缺少了必要的步骤, 做此记录

环境

  • Dubbo 2.7.1
  • Zookpper 3.5.5
  • Springboot 2.1.1.RELEASE

Maven

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
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.1</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.1</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.5.5</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>

配置

1
2
3
4
5
6
7
8
9
10
11
# 服务端
dubbo:
application:
name: ${spring.application.name}
scan:
base-packages: test.dubbo
registry:
address: zookeeper://localhost:2181
protocol:
name: dubbo
port: 9999
1
2
3
4
5
6
7
8
# 客户端
dubbo:
application:
name: ${spring.application.name}
registry:
address: zookeeper://localhost:2181
protocol:
name: dubbo

在js中需要将异步方法同步的时候, 经常使用的是asyncawait, 或者用Promise

偶然在dvajs中看到其使用yield迭代器实现了同步的效果, 例如

1
2
3
4
5
6
7
8
9
10
11
function* f(){
var a = Promise.resolve(1);
console.log(a); // Promise
var ra = yield a;
console.log(ra); // 1
var b = Promise.resolve(2);
console.log(b); // Promise
var rb = yield b;
console.log(rb); // 2
return "success";
}

当然直接运行不能得到预期的效果, Promise没用同步执行, yield返回的结果也是undefined, 因为还缺少对其的一层封装, 或者说还缺少个执行器

1
2
3
4
var it = f();
it.next();
it.next();
it.next();

传统的迭代器, 是这样使用的

1
2
3
4
5
6
7
8
9
function* range(){
for(let i=0;i<n;i++){
yield i;
}
}
var it = range();
console.log(it.next().value); // 0
console.log(it.next().value); // 1
console.log(it.next().value); // 2

如下封装, 在每一次yield返回的Promisethen中进行下一次迭代, 并把结果传入 g.next(r), 迭代函数的next(r)中的参数r会成为函数体中yield标识的表达式的返回值, 从而达到类似await的效果

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
function async(g) {
var ctx = this;
return new Promise(function(resolve, reject) {
g = g.apply(ctx);
next();
function next(r) {
var result = g.next(r);
if (result.done){
return resolve(result.value);
}
result.value.then(next);
}
});
}

async(function*(){
var a = Promise.resolve(1);
console.log(a); // Promise
var ra = yield a;
console.log(ra); // 1
var b = Promise.resolve(2);
console.log(b); // Promise
var rb = yield b;
console.log(rb); // 2
return "success";
}).then(v=>{
console.log(v) // success
});

当本地搭建gitlab服务器的时候经常使用的是直接ip访问, 没有域名, 所以无法访问到类似*.gitlab.io的Pages

这里提供一个取巧的解决办法, 因为gitlab仓库下的文件都可以通过/raw访问原始文本文件, 响应返回类型是文本

所以只需要通过Nginx把后缀为html的文件返回类型改为text/html, 图片类型的改为image/png即可直接访问到网页了

gitlab方面无需配置, Nginx加入如下配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server {
listen 8080;
server_name gitlab;
location / {
proxy_pass http://127.0.0.1/;
add_header content-type "text/html; charset=utf-8";
if ($request_uri ~* "/[^/]+/[^/]+/raw/[^/]+/.+\.svg") {
add_header content-type 'image/svg+xml';
}
if ($request_uri ~* "/[^/]+/[^/]+/raw/[^/]+/.+\.png") {
add_header content-type 'image/png';
}
if ($request_uri ~* "/[^/]+/[^/]+/raw/[^/]+/.+\.(jpeg|jpg|icon)") {
add_header content-type 'image/jpeg';
}
}
}

配置之后例如用户LLL有个仓库blog的主分支master上存放着类似hexo搭建的博客文件,
只需要访问http://127.0.0.1:8080/LLL/blog/raw/master/index.html即可进入博客了

python版本3.6, 在windows以及linux上都进行过测试

由于pythonrsa加密存在长度限制, 虽然可以通过分片加密来解决,
但是更好的做法是通过rsa加密传输aes密钥给服务器, 携带的信息通过该密钥进行aes加密,
服务器通过rsa私钥得到aes密钥后解析信息, 并继续使用密钥进行双向通信

python中加密使用pycryptodome模块

1
pip install pycryptodome

Python

其中接受的参数text,key均为字符串

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
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
from Crypto.Cipher import AES
from base64 import b64decode
from base64 import b64encode
import re


def rsa_encode(text, public_key):
key = RSA.importKey(b64decode(public_key))
cipher = PKCS1_v1_5.new(key)
return b64encode(cipher.encrypt(text.encode(encoding='utf-8'))).decode('utf-8')


def aes_encode(text, key):
key = key.encode('utf-8')
cipher = AES.new(key, AES.MODE_CBC, key)
text = text + (16 - len(text) % 16) * chr(16 - len(text) % 16)
return b64encode(cipher.encrypt(text.encode(encoding='utf-8'))).decode('utf-8')


def aes_decode(cipher_text, key):
key = key.encode('utf-8')
cipher = AES.new(key, AES.MODE_CBC, key)
text = cipher.decrypt(b64decode(cipher_text)).decode('utf-8')
return re.compile('[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f\n\r\t]').sub('', text)

Java

java的加密代码不依赖其他包

AesUtils

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
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.UUID;

public class AesUtils {

private static final String TYPE = "AES";
private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
private static final String CHARSET = "UTF-8";

public static String createKey(){
return UUID.randomUUID().toString().replace("-","").substring(16);
}

public static String encrypt(String data, String key) throws Exception {
IvParameterSpec ivParameterSpec = new IvParameterSpec(key.getBytes());
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), TYPE);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec);
return Base64.getEncoder().encodeToString(cipher.doFinal(data.getBytes(CHARSET)));
}

public static String decrypt(String data, String key) throws Exception {
IvParameterSpec ivParameterSpec = new IvParameterSpec(key.getBytes());
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), TYPE);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivParameterSpec);
return new String(cipher.doFinal(Base64.getDecoder().decode(data.getBytes())), CHARSET);
}
}

RsaUtils

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
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class RsaUtils {

private static final String TYPE = "RSA";
private static final String ALGORITHM = "RSA/ECB/PKCS1PADDING";
private static final String CHARSET = "UTF-8";
private static final int KEY_SIZE = 1024;

public static KeyPair createKeyPair() {
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(TYPE);
keyPairGenerator.initialize(KEY_SIZE);
return keyPairGenerator.generateKeyPair();
} catch (Exception e) {
return null;
}
}

public static String getPublicKey(KeyPair keyPair) {
return Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
}

public static String getPrivateKey(KeyPair keyPair) {
return Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());
}

public static String encrypt(String data, String publicKeyString) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM);
KeyFactory keyFactory = KeyFactory.getInstance(TYPE);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKeyString));
RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return Base64.getEncoder().encodeToString(splitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET)));
}

public static String decrypt(String data, String privateKeyString) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM);
KeyFactory keyFactory = KeyFactory.getInstance(TYPE);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKeyString));
RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return new String(splitCodec(cipher, Cipher.DECRYPT_MODE, Base64.getDecoder().decode(data)), CHARSET);
}

private static byte[] splitCodec(Cipher cipher, int mode, byte[] data) throws Exception {
int maxBlock = KEY_SIZE / 8 - (mode == Cipher.DECRYPT_MODE ? 0 : 11);
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
byte[] buffer;
for (int offset = 0; offset < data.length; offset += maxBlock) {
buffer = cipher.doFinal(data, offset, Math.min(maxBlock, data.length - offset));
out.write(buffer, 0, buffer.length);
}
return out.toByteArray();
}
}
}

在JS中对于普通的json, 可用如下方式进行简单的深度拷贝

1
2
let json = { a: "aa" };
let newJson = JSON.parse(JSON.stringify(json));

不过当json中包含一些JS中的对象及函数的时候, 用这样的方法会使数据丢失, 并且这个无法解决循环引用的问题, 所谓循环引用指的是

1
2
3
4
5
6
7
let b={};
let a={
b:b
};
b.a=a;
console.log(a);
// console.log(JSON.stringify(a));

这时JSON.stringify(a)就出现了异常

由于存在这些问题, 所以就编写了一个拷贝函数, 来做这件事情, 代码实现如下

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function copyObject(o) {
let objectMap = new WeakMap();
function copy(object) {
if (objectMap.get(object)) {
return objectMap.get(object);
}
if (typeof object !== "object") {
return object;
}
if (object instanceof RegExp) {
return object;
}
let newObject = new object.constructor();
objectMap.set(object, newObject);
for (let k in object) {
newObject[k] = copy(object[k]);
}
return newObject;
}
return copy(o);
}

代码中通过let objectMap = new WeakMap();保存拷贝过的对象, 解决循环引用的问题

通过递归拷贝其中的对象, 若是基本类型、正则对象或函数则直接返回

测试代码

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
class ObjA {
constructor(v) {
this.a = v;
}
print() {
console.log(this.a || "!!!");
}
}
function ObjB(v) {
this.name
let a = v;
this.print = () => {
console.log(a || "!!!");
}
}
let objA = new ObjA(666);
let objB = new ObjB(777);
let json0 = {};
let json1 = {
a: () => 'aaa',
b: [123, "abc", /abcd/],
c: {
d: function () { return "ddd" },
e: [123, 2, 3],
f: objA,
g: objB
},
r: json0
}
json0.r = json1;

let json2 = copyObject(json1);
json2.c.e[1] = "asdasd";
json2.r.r.r.r.b[1] = "rrrr";
console.log(json1);
console.log(json2);

json2.c.f.print();
objA.a = 888;
objA.print();
json2.c.f.print();
json2.c.g.print();

经过测试, 以上场景的输出均与预计相同

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

传统Mybatis开发使用XML的方式编写SQL脚本进行数据库操作,
Mybatis允许使用动态SQL但是即使如此, 依然存在许多重复性工作, 因为每个基本表的增删改查语句模式其实都是相同的

Mybatis也可以使用Dao接口上以注解的形式编写SQL, 但是也是一样, 必须重复编写, 非常不方便

其实Mybatis已经考虑到了这点, 为我们提供了自定义通用Mapper的实现机制

项目地址

https://github.com/GitHub-Laziji/commons-mybatis

实现

实现方法分为两步

声明引用的方法

在Dao接口的方法上以注解的形式声明, 使用哪个类的哪个方法, 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface DODao<T extends DO> extends Dao<T> {

@SelectProvider(type = SqlProvider.class, method = "selectById")
T selectById(Long id);

@InsertProvider(type = SqlProvider.class, method = "insert")
@Options(useGeneratedKeys = true, keyColumn = "id")
int insert(T bean);

@UpdateProvider(type = SqlProvider.class, method = "update")
int update(T bean);

@DeleteProvider(type = SqlProvider.class, method = "delete")
int delete(Long id);
}

方法实现

在实现方法中可以接收Dao接口中传来的参数, 最后返回一个SQL字符串, 这个SQL可以是动态的, 例如可以使用id=#{id}这样的语法

T selectById(Long id)实现如下

1
2
3
4
5
6
7
8
9
10
public String selectById(ProviderContext context) {
Class clazz = getEntityClass(context);
assert clazz != null;
return new SQL()
.SELECT(getColumns(clazz))
.FROM(getTableName(clazz))
.WHERE("`id`=#{id}")
.toString();
}

我们可以通过context获取Dao的泛型类, 也就是实体类

1
2
3
4
5
6
7
8
9
10
11
12
private Class getEntityClass(ProviderContext context) {
for (Type type : context.getMapperType().getGenericInterfaces()) {
ResolvableType resolvableType = ResolvableType.forType(type);
if (resolvableType.getRawClass() == Dao.class
|| resolvableType.getRawClass() == DODao.class
|| resolvableType.getRawClass() == VODao.class) {
return resolvableType.getGeneric(0).getRawClass();
}
}
return null;
}

通过反射我们可以拿到对应的字段名, 类名, 字段名获取如下, Ignore 是自定义注解, 用于忽略一些字段

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
private String[] getVariables(Class clazz, String[] prefixes) {
List<String> variables = new ArrayList<>();
for (Method method : clazz.getMethods()) {
Ignore annotation = method.getAnnotation(Ignore.class);
if (annotation != null) {
continue;
}
String name = method.getName();
for (String prefix : prefixes) {
int length = prefix.length();
if (name.length() > length && name.startsWith(prefix)
&& name.charAt(length) >= 'A' && name.charAt(length) <= 'Z') {
String variableName = (char) (name.charAt(length) - 'A' + 'a') + name.substring(length + 1);
variables.add(variableName);
break;
}
}

}
return variables.toArray(new String[]{});
}

private String[] getReadVariables(Class clazz) {
return getVariables(clazz, new String[]{"is", "get"});
}

小结

使用通用Mapper无需编写任何SQL 只需创建空Dao 继承通用的Dao<T>即可

RPC是一种远程过程调用, 它是一种通过网络从远程计算机程序上请求服务, 而不需要了解底层网络技术的协议

RPC可以把远程服务像本地服务一样调用, 以Java中为例, 客户端与服务端一般共用一个核心包, 核心包中包含了需要调用服务的接口

在服务端实现这些接口, 客户端通过Socket等方式连接服务端, 发生调用的信息(方法名, 参数等)

服务端接收后执行相应动作, 最后通过网络返回计算结果, 一次RPC调用就完成了

下面是Java中的简单实现

共用接口

1
2
3
public interface TestService {
String print(String s);
}

一个简单的打印服务, 不包含实现

客户端

在客户端代码中自始至终没有编写服务的实现, 只有一个接口, 但是又可以得到服务的实例,

要做到这点需要用到Java中的动态代理Proxy.newProxyInstance

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
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.net.Socket;

public class Consumer {

public static void main(String[] as) {

TestService service = (TestService) Proxy.newProxyInstance(TestService.class.getClassLoader(), new Class<?>[]{TestService.class}, (Object proxy, Method method, Object[] args) -> {
try(Socket socket = new Socket()){
socket.connect(new InetSocketAddress(12306));
ObjectOutputStream os = new ObjectOutputStream(socket.getOutputStream());
os.writeUTF(method.getName());
os.writeObject(method.getParameterTypes());
os.writeObject(args);
return new ObjectInputStream(socket.getInputStream()).readObject();
}catch (Exception e){
return null;
}
});
System.out.println(service.print("abc"));
}
}

通过对象流把参数等信息发送给服务端

服务端

一般服务端是预先实例化服务, 以完整类名为Key把服务存进集合供调用, 或者依赖现成的Springboot等框架管理服务, 下面是服务端的实现

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
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class Producer {

public static void main(String[] args) {

TestService service = new TestServiceImpl();

try (ServerSocket serverSocket = new ServerSocket()){
serverSocket.bind(new InetSocketAddress(12306));

try(Socket accept = serverSocket.accept()){
ObjectInputStream is = new ObjectInputStream(accept.getInputStream());
String methodName = is.readUTF();
Class<?>[] parameterTypes = (Class<?>[]) is.readObject();
Object[] arguments = (Object[]) is.readObject();
Object result = TestService.class.getMethod(methodName,parameterTypes).invoke(service,arguments);
new ObjectOutputStream(accept.getOutputStream()).writeObject(result);
}
} catch (Exception e) {
e.printStackTrace();
}

}
}

服务实例

服务端编写服务实例代码

1
2
3
4
5
6
7
public class TestServiceImpl implements TestService{

@Override
public String print(String s) {
return "**"+s+"**";
}
}

结果

至此可以看到客户端调用的

1
System.out.println(service.print("abc"));

打印出了

1
**abc**

就好像调用了本地服务一样