0%

动态代理

什么是动态代理

代理类在程序运行时创建的代理方式被称之为动态代理。这中代理类非同与手动指定,而是在代码中配置动态生成的。

相比较于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。不过其代理对象必须某个接口的实现,他是通过在运行期间创建一个接口的实现类来实现目标的代理。

使用动态代理

InvocationHandler 接口

在使用动态代理时,需要定义一个Handler充当代理类动向的处理器,当我们调用代理类的当法时候会自动执行到Handler的Invoke方法中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14

public interface InvocationHandler {


/**
* @param proxy 代理方法被调用的代理实例
* @param method 调用的代理类方法
* @param args 方法参数
* @return 返回一个代理对象的处理器
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;

}

生成代理类

Java提供了Proxy.newProxyInstance来生成对应的代理类。

1
2
3
4
5
6
7
8
9
10

/**
* @param loader classLoader
* @param interfaces 方法所继承的接口
* @param h 执行器
* @return 代理对象
*/
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h);

执行方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

public class Test {

@Tset
public void test() {
String target = "";
String method = ""
Class<?> aClass = Class.forName(target);
InvocationHandler handler = new DefaultInvocationHandler(aClass);
Object o = Proxy.newProxyInstance(aClass.getClassLoader(),
new Class<?>[]{aClass},
handler);
Method me = o.getClass().getDeclaredMethod(method);
me.invoke(o);
}
}

~~~


## 为接口动态生成代理方法
在研究feign的源码的时候,发现feign仅仅就声明了一个接口,并没有对应的实现方法。

其原理也是通过代理模式为接口自动生成了一个执行器。我自己也大致模拟了一下,假如class对象是interface的话,自动为其创建一个代理类。如果有default方法则执行default方法,否则在找不到实现方法的时候不执行。

Class<?> aClass = Class.forName(target);

if (aClass.isInterface()) {
InvocationHandler handler = new DefaultInvocationHandler(aClass);
Object o = Proxy.newProxyInstance(aClass.getClassLoader(),
new Class<?>[]{aClass},
handler);
Method me = o.getClass().getDeclaredMethod(method);
return me.invoke(o);
}

static class DefaultInvocationHandler implements InvocationHandler {

private final Class target;

DefaultInvocationHandler(Class target) {
    this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // if method is default,don't rewrite
    if (method.isDefault()) {
        Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
                .getDeclaredConstructor(Class.class, int.class);
        constructor.setAccessible(true);

        Class<?> declaringClass = method.getDeclaringClass();
        int allModes = MethodHandles.Lookup.PUBLIC | MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED | MethodHandles.Lookup.PACKAGE;

        return constructor.newInstance(declaringClass, allModes)
                .unreflectSpecial(method, declaringClass)
                .bindTo(proxy)
                .invokeWithArguments(args);
    }

    return null;
}

}

~~~

CURL的常用命令

curl [option] [URL…]

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

-o out 将指定curl返回保存为out文件,内容从html/jpg到各种MIME类型文件
-O 按服务器上的名称保存下载的文件
-r/--range from-to 下载指定range内的数据
-C 在保存文件时进行续传
-A/--user-agent <ua> 指定发送请求的用户代理 如 curl -A My-Agent/1.0.0 www.apple.com
-x ip:port 指定使用的http代理,如curl -x 192.168.1.1:8080 www.apple.com
-D head.txt 将服务器的返回的header保存为文件,头部的cookie也可被保存 curl -D header.txt www.apple.com
-c <file> 保存服务器的cookie文件
-b/--cookie <name=data> 向服务器提交cookie,若无=则name视为文件名 curl -b cookie.txt www.apple.com
-e url 设置引用头的值
-T localfile 向服务器PUT文件 如curl -T 1.mp3 www.apple.com/upload.PHP
-d <key=value> 向服务器POST表单数据 如curl -d "order=111&count=2" http://www.apple.com/buy
-F <key=value> 向服务器POST表单 curl -F "web=@index.html;type=text/html" url.com
如果希望从本地文件中获取表单数据,则在文件名前加@ 如 curl -d @data.xml http://www.apple.com
若希望从标准输入获取则用curl -d - http://www.apple.com
-E cert.pem 指定本地证书
-H <header:value> 为HTTP请求设置任意header及值。如curl -H "Connection:keep-alive" http://www.apple.com
-I 构造一个HEAD请求
-X/--request method 用户定义的HTTP请求方法名如 curl -X GET www.baidu.com
--compressed 采用压缩方式接收返回数据
--connect-timeout <s> 设置超时时间
-v 详细输出,包含请求和响应的首部
--retry num
--retry timeo 指定重试的次数和间隔
--tcp-nodelay 打开TCP_NODELAY选项 不进行捎带确认

基本介绍

Postman的Pre-Request脚本能够很方便的对请求进行一个处理。

在发送request之前,编写pre-request script,定制化request。比如添加随机URL参数,RequestBody的加密等都可以使用pre-Request来实现。

前置登录脚本

在开发过程中经常需要通过token直接调取api,每次登录拿取token都非常麻烦,因此写个脚本在调用接口前拉取登录请求结果塞到环境变量里

解析jwt的代码取自 https://gist.github.com/benhowes/ebf9d2f905d7744e7aa093afe19ddbcd

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

var username = '123456'
var password = '123456'

var loginUrl = pm.environment.get("loginApi");

var usr = {

};

pm.sendRequest({
url: loginUrl,
method: 'POST',
header: {
'content-type': 'application/json',
'applicationId': request.headers.applicationid
},
body: {
mode: 'raw',
raw: JSON.stringify({'username': username,
'password':CryptoJS.MD5(password).toString() })

}
},function(err, response) {
const jsonResponse = response.json();
token = jsonResponse.token
var jwt = jwt_decode(token)
usr.userId = jwt.userId;
pm.environment.set("token", token);

});




function jwt_decode(jwt){
var parts = null;
try{
parts = decode_b64(jwt.split('.')[1]);
} catch (e){
console.log("error parsing JWT");
throw (e)
}
console.log(parts)
if (parts){
return JSON.parse(parts);
}
return {};
}

function InvalidCharacterError(message) {
this.message = message;
}

InvalidCharacterError.prototype = new Error();
InvalidCharacterError.prototype.name = 'InvalidCharacterError';

function atob (input) {
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
var str = String(input).replace(/=+$/, '');
if (str.length % 4 == 1) {
throw new InvalidCharacterError("'atob' failed: The string to be decoded is not correctly encoded.");
}
for (
// initialize result and counters
var bc = 0, bs, buffer, idx = 0, output = '';
// get next character
buffer = str.charAt(idx++);
// character found in table? initialize bit storage and add its ascii value;
~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
// and if not first of each 4 characters,
// convert the first 8 bits to one ascii character
bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
) {
// try to find character in table (0-63, not found => -1)
buffer = chars.indexOf(buffer);
}
return output;
}
function b64DecodeUnicode(str) {
return decodeURIComponent(atob(str).replace(/(.)/g, function (m, p) {
var code = p.charCodeAt(0).toString(16).toUpperCase();
if (code.length < 2) {
code = '0' + code;
}
return '%' + code;
}));
}

function decode_b64(str) {
var output = str.replace(/-/g, "+").replace(/_/g, "/");
switch (output.length % 4) {
case 0:
break;
case 2:
output += "==";
break;
case 3:
output += "=";
break;
default:
throw "Illegal base64url string!";
}

try{
return b64DecodeUnicode(output);
} catch (err) {
return atob(output);
}
};

SetUp

从官方git上下载最新的skywalking的docker目录

由于中国区拉取官方的镜像非常慢,因此把镜像设置成yanliangzhong/elasticsearch6.3.2,并且将data目录挂载出来

修改其中的docker-compose文件:

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
# "License"); you may not use this file except in compliance
#Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

version: '3.3'
services:
elasticsearch:
image: yanliangzhong/elasticsearch6.3.2
container_name: elasticsearch
restart: always
ports:
- 9200:9200
- 9300:9300
environment:
discovery.type: single-node
volumes:
- ./es:/usr/share/elasticsearch/data
oap:
image: skywalking/oap
container_name: oap
depends_on:
- elasticsearch
links:
- elasticsearch
restart: always
ports:
- 11800:11800
- 12800:12800
volumes:
- ./config:/apache-skywalking-apm-incubating/config
ui:
image: skywalking/ui
container_name: ui
depends_on:
- oap
links:
- oap
restart: always
ports:
- 8080:8080
environment:
collector.ribbon.listOfServers: oap:12800

docker的JVM下默认选取的UTC时区,因此还需要手动编译oap的dockerfile,手动设置jvm的时区

在oap的Dockerfile中加上-Duser.timezone=GMT+8

1
2
ENV DIST_NAME=apache-skywalking-apm-incubating \
JAVA_OPTS="-Duser.timezone=GMT+8 -Xms256M -Xmx512M"

最后docker-compose up 访问8080端口就能看到效果了

一、安装运行

安装tomcat之前必须安装java环境

1
2
3
4
5
6
// 下载tomcat
wget http://mirrors.hust.edu.cn/apache/tomcat/tomcat-7/v7.0.79/bin/apache-tomcat-7.0.79.tar.gz
// 解压
tar -zxvf apache-tomcat-7.0.79.tar.gz
// 复制解压后的文件到 /user/local/tomcat 目录
cp -r apache-tomcat-7.0.29 /user/local/tomcat

切换到tomcat目录

1
cd /user/local/tomcat

启动和关闭

1
2
./bin/startup.sh
./bin/shutdown.sh

二、可能遇到的问题

1、Tomcat启动时卡在“INFO: Deploying web application directory ……”

找到jdk1.x.x_xx/jre/lib/security/java.security文件,在文件中找到securerandom.source这个设置项,
将其改为:securerandom.source=file:/dev/./urandom

2、tomcat启动或者结束失败

用tail或者cat查看tomcat下log里面的日志文件,如果是端口的问题使用 netstat -lnp|grep 端口号 查看占用端口的程序,用kill命令结束

3、在有些linux发行版下不允许操作80端口

执行端口重定向命令:iptables -t nat -A PREROUTING -p tcp –dport 80 -j REDIRECT –to-port 8080

Transmission是一种BitTorrent客户端,特点是一个跨平台的后端和其上的简洁的用户界面。

安装步骤

一、安装epel-release源

1
2
yum install epel-release
yum -y update

二、安装Transmission

1
yum install transmission-cli transmission-common transmission-daemon

安装完之后启动,查看一下状态:

1
2
systemctl start transmission-daemon.service
systemctl status transmission-daemon.service

三、修改配置文件

1
2
systemctl stop transmission-daemon.service
vim /var/lib/transmission/.config/transmission-daemon/settings.json

并且修改相应的参数:

1
2
3
4
5
"rpc-authentication-required": true,
"rpc-enabled": true,
"rpc-password": "password",
"rpc-username": "username",
"rpc-whitelist-enabled": false

修改完成后启动服务

1
systemctl start transmission-daemon.service

启动完成后,访问Transmission
Transmission默认端口是9091
image.png

我们常常做的是将Java内存区域简单的划分为两种: 一种叫做栈内存,一种叫做堆内存。

栈内存

栈内存又分两种,一种是虚拟机栈,一种是本地方法栈。

本地方法栈是去调用native方法服务,和java关系不算太大。

虚拟机栈就牛逼了,它里面存了局部变量表,操作栈,动态链接,方法出口等信息。

局部变量表存放了编译期可知的各种基本数据类型,对象引用和一条字节码指令的地址。
其中64位长度的long和double类型的数据会占用2个局部变量空间,其余的基本类型占1个。

当进入一个方法时,这个方法需要分配多大的局部变量空间是完全确定的,在运行期间不会改变变量表的大小。

在java虚拟机规范中,这个区域会有两种异常情况产生,如果线程请求的栈深度大于虚拟机所允许的深度,将会抛出StackOverflowError异常。如果没有处理好递归调用的出口条件,很容易就会报这样的错。

如果虚拟机栈动态扩展的时候无法申请最够的内存,就会抛出OutOfMemoryError异常。

堆内存

Java的堆是Java虚拟机所管理的内存中最大的一块。

堆的唯一目的就是存放对象实例。所有的对象实例都会在这里分配内存。

堆产生了一个对象之后,还能够在栈里面定义一个变量指向堆的的首地址。
如果这个对象没有被引用,那么不一会就会被java当作垃圾回收,因此Java堆是垃圾收集的管理主要区域。

如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

参考资料

深入理解java虚拟机

类加载过程

类从被加载到虚拟机内存开始,直到被卸载出内存为止,它的整个生命周期过程是:

加载 —> 验证 —> 准备 —> 解析 —> 初始化 —> 使用 —> 卸载

阅读全文 »

背景

Java枚举类型是java1.5后提出的,解决了使用public static final定义的变量的类型安全性和程序可读性无法保证的问题。

阅读全文 »