vagrant的安装和设置

  • 下载和安装

  • vagrant 官方box的下载方法:

  • 初始化vagrant

    1
    2
    3
    mkdir centos && cd centos # 新建centos目录
    vagrant box add centos_base d:/vagrant_images/vagrant-centos-7.2.box # 添加镜像到vagrant
    vagrant init centos_base # 初始化配置
  • 修改vagrant配置

    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
    #Vagrant的三种网络配置
    #端口映射(Forwarded port) 把宿主计算机的端口映射到虚拟机的某一个端口上,访问宿主计算机端口时,请求实际是被转发到虚拟机上指定端口的。
    #例如下面的配置,将访问宿主计算机8080端口的请求都转发到虚拟机的80端口上进行处理 #访问localhost:8080,对应访问虚拟机的80端口
    #优点:容易实现外网访问虚拟机
    #缺点:端口比较多时,配置麻烦;不支持在宿主机器上使用小于1024的端口来转发。比如:不能使用SSL的443端口来进行https连接。
    config.vm.network "forwarded_port", guest: 80, host: 8080

    #私有网络(Private network) ,只有主机可以访问虚拟机,如果多个虚拟机设定在同一个网段也可以互相访问,当然虚拟机是可以访问外部网络的。
    #优点:安全,只有自己能访问
    #缺点:因为私有的原因,所以团队成员其他人不能和你协作
    config.vm.network "private_network", ip: "192.168.33.10"

    #公有网络(Public network) ,虚拟机享受实体机器一样的待遇,一样的网络配置,vagrant1.3版本之后也可以设定静态IP。
    #优点:方便团队协作,别人可以访问你的虚拟机
    #缺点:需要有网络,有路由器分配IP
    config.vm.network "public_network", ip: "192.168.12.253"


    #设置文件同步,如下 ../work 是本地目录, /vagrant_data是虚拟机文件目录
    #两个目录设置为同步,这样就可以直接本地编辑文件,使用虚拟机配置的软件环境了。
    config.vm.synced_folder "../work", "/vagrant_data",
    :mount_options => ["dmode=775","fmode=664"]

    # 设置虚拟机的内存
    config.vm.provider "virtualbox" do |vb|
    vb.memory = "4096"
    end


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    Vagrant.configure("2") do |config|
    config.vm.define :web do |web|
    web.vm.provider "virtualbox" do |v|
    v.customize ["modifyvm", :id, "--name", "web", "--memory", "512"]
    end
    web.vm.box = "CentOs7"
    web.vm.hostname = "web"
    web.vm.network :private_network, ip: "192.168.33.10"
    end

    config.vm.define :redis do |redis|
    redis.vm.provider "virtualbox" do |v|
    v.customize ["modifyvm", :id, "--name", "redis", "--memory", "512"]
    end
    redis.vm.box = "CentOs7"
    redis.vm.hostname = "redis"
    redis.vm.network :private_network, ip: "192.168.33.11"
    end
    end
  • v1811.02版本centos box需要修改sshd的设置, 改成ssh允许用户名和密码登录

    • 参考 CentOS SSH 密钥登陆改为密码登陆

    • vagrant ssh命令进入centos

    • 打开/etc/ssh/sshd_config

      1
      2
      3
      vim /etc/ssh/sshd_config
      PermitRootLogin yes 删除前面的 #注释
      PasswordAuthentication no 改成 PasswordAuthentication yes
    • 重启sshd

      1
      service sshd restart
  • 启动和ssh链接, vagrant镜像有两个user, root和vagrant, 密码都是: vagrant, 开启xshell, 填入ip(192.168.33.10), 用户root和密码vagrant

    1
    2
    3
    4
    5
    6
    vagrant up  # 启动虚拟机
    vagrant halt # 关闭虚拟机
    vagrant reload # 重启虚拟机
    vagrant ssh # SSH 至虚拟机
    vagrant status # 查看虚拟机运行状态
    vagrant destroy # 销毁当前虚拟机
  • 打包

    1
    2
    vagrant package --vagrantfile Vagrantfile #将配置文件一块打包
    vagrant package --output centos.box # 要打包成的box名称,不会自动添加.box后缀,要手动加.默认值package.box
  • pycharm 的python项目用vagrant虚拟环境的python作为解释器, 参考 Pycharm 解释器配置

  • pycharm 配置terminal为vagrant虚拟机环境,

  • 开启ssh terminal, 勾选为current vagrant, 编码设置为utf-8

  • tools选择start ssh sessions

  • 在terminal中就可以直接使用远程terminal

  • mysql连接速度慢的解决方法

  • ssh 连接速度慢的解决方法

    • 编辑 /etc/ssh/sshd_config

      1
      UseDNS no, 找到此行取消注释,并且把yes改成no
    • 重启ssh

      1
      service sshd restart
  • 出现文件夹同步失败的解决方法

redis4.0以上版本开启bloomfilter插件的方法

  • 安装redis4.0

    • 参考CentOS7 下 Redis4 安装与配置教程(Redis开机启动)
    • 安装基础依赖
      1
      sudo yum install -y gcc gcc-c++ make jemalloc-devel epel-release
    • 下载最新版redis并解压到指定目录, 然后编译和安装
      1
      2
      3
      4
      5
      6
      7
      8
      wget http://download.redis.io/releases/redis-4.0.2.tar.gz
      sudo tar -zvxf redis-4.0.2.tar.gz -C /usr/
      #进入目录
      cd /usr/redis/redis-4.0.2
      #编译&安装
      sudo make & make install

      sudo cp src/redis-cli /usr/local/bin/(將redis-cli拷貝到bin下,讓redis-cli指令可以在任意目錄下直接使用)
    • 加载配置文件启动redis-server
      1
      2
      cd /usr/redis/redis-4.0.11
      redis-server redis.conf
  • 安装Rebloom插件

    • 参考 ReBloom – Bloom Filter Datatype for Redis
    • 下载并编译
      1
      2
      3
      $ git clone git://github.com/RedisLabsModules/rebloom
      $ cd rebloom
      $ make
    • 命令行加载rebloom插件,并且设定每个bloomfilter key的容量和错误率
    1
    2
    3
    cd /usr/redis-4.0.11
    ./src/redis-server redis.conf --loadmodule /usr/rebloom/rebloom.so INITIAL_SIZE 1000000 ERROR_RATE 0.0001
    # 容量100万, 容错率万分之一, 占用空间是4m
  • 根据也无需求, 大于100万的bloomfilter key需要手动建立

    1
    2
    3
    BF.RESERVE 2018_ccgp 0.0001 28000000
    Memory Usage 2018_ccgp
    # (integer) 67108997 64M
  • 设置开机启动. crontab -e 输入

    1
    2
    @reboot /usr/redis-5.0.0/src/redis-server /usr/redis-5.0.0/redis.conf --loadmodule /usr/rebloom/rebloom.so INITIAL_SIZE 1000000 ERROR_RATE 0.0001

  • 阿里云服务器或者vangrant的centos镜像不需要设置防火墙的开放端口.

terminus的安装和配置

  • 下载git for windows并安装, 个人习惯安装时候不勾选git集成到右键菜单的的两个选项, 官方下载地址 Git for windows

  • 下载terminus并安装, 官方下载地址 Terminus

  • 设置, 单击左上角的齿轮

    • shell设置页, shell选择为Git-Bash

    • terminal设置页, 右键设置为粘贴, 勾选 软件打开时自动开启一个终端, 勾选 阻止自动执行复制的到终端的内容, 勾选 选择时自动复制

    • hotkeys设置页, 增加ctrl+v为粘贴快捷键. 这样, 最大的复合win的复制粘贴快捷键, 还是mac系统更合理, cmd+c是复制, cmd+v是粘贴. 与terminal的标准中断快捷键ctrl+c没有冲突

    • 新建~/.bashrc文件, 并加入下面的内容,

      1
      2
      3
      4
      5
      6
      7
      8
      # 按向上向下键自动匹配相应前缀的历史命令
      bind '"\e[A": history-search-backward'
      bind '"\e[B": history-search-forward'
      bind '"\e[C": forward-char'
      bind '"\e[D": backward-char'
      # 最大可能兼容win和linux的命令, 注意等号前后不能有空格
      alias ifconfig='ipconfig'
      alias ll='ls -lah'

      保存后, 重新打开terminus或者执行source ~/.bashrc即可

      参考链接: Bash (简体中文)

个人wsl设置

  • win10开启wsl并安装ubuntu

    • win左下角搜索, 输入 功能, 找到 启用或者关闭windows功能
    • 找到适用于Linux的Windows子功能, 勾选
    • 重启电脑
    • 开始菜单MicroSoft Store, 搜索Ubuntu,找到ubuntu18.04安装
    • 开始菜单,启动ubuntu18.04
    • 等待一会后, 输入账号和密码
    • 更改源, 换成清华源, 参考Ubuntu 镜像使用帮助
  • 系统更新

    1
    2
    sudo apt-get update
    sudo apt-get upgrade
  • 开启ssh

    • 安装
      1
      2
      sudo apt-get remove openssh-server
      sudo apt-get install openssh-server
    • 修改配置,
      1
      sudo vim /etc/ssh/sshd_config
      1
      2
      Port 2222
      PasswordAuthentication yes
    • 启动
      1
      sudo service ssh --full-restart
  • 安装mysql

    • 安装
    1
    2
    sudo apt-get update
    sudo apt-get install mysql-server
    • 配置, 这一项可能与下面添加root账号冲突.
      1
      2
      sudo service mysql start
      sudo mysql_secure_installation
    • 修改并添加root账户密码, 参考 Ubuntu 18.04 安装记录 - 简书
    1
    2
    3
    4
    5
    6
    sudo service mysql start
    sudo mysql -u root -p
    DROP USER 'root'@'localhost';
    CREATE USER 'root'@'%' IDENTIFIED BY 'root';
    GRANT ALL PRIVILEGES ON *.* TO 'root'@'%';
    FLUSH PRIVILEGES;
    1
    sudo service mysql start
  • 安装redis

    • 安装
      1
      sudo apt-get install redis-server
    • 配置远程访问
      1
      sudo vim /etc/redis/redis.conf
      找到并注释掉下面一行, 或者把127.0.0.1改成0.0.0.0
      1
      # bind 127.0.0.1
    • 开启服务
      1
      sudo service redis-server start
  • 中文支持和字体

    1
    2
    3
    4
    5
    6
    sudo apt install language-pack-zh-hans
    sudo update-locale LANG=zh_CN.UTF-8
    sudo mkdir /usr/share/fonts
    sudo ln -s /mnt/c/Windows/Fonts /usr/share/fonts/windows
    sudo apt install fontconfig
    sudo fc-cache -fv
  • 安装zsh

    1
    2
    3
    4
    sudo apt-get install zsh
    sudo chsh -s $(which zsh)
    sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"

  • 启动zsh, ~/.bashrc

    1
    2
    3
    4
    # Launch Zsh
    if [ -t 1 ]; then
    exec zsh
    fi
  • 开机启动mysql redis 和ssh, 在~/.zshrc加入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    echo "检测开机启动项"
    if [ ! -e "/var/run/sshd.pid" ]; then
    echo '启动sshd'
    sudo service ssh start
    fi

    if test $( pgrep -f mysql | wc -l ) -lt 1
    then
    echo "启动mysql"
    sudo service mysql start
    fi

    if test $( pgrep -f redis | wc -l ) -lt 1
    then
    echo "启动redis"
    sudo service redis-server start
    fi
  • 启动显示 ~/.zshrc

    1
    2
    umask 0022
    export DISPLAY=localhost:0.0
  • 修改wsl里面查看windows磁盘文件的颜色1, 参考

    [在WSL环境下修改文件夹的颜色] (https://blog.royink.li/post/ls_color_in_wsl)
    LS_COLORS

    1
    2
    dircolors -p > $HOME/.dircolors
    vim $HOME/.dircolors


    STICKY_OTHER_WRITABLE 后面的数字改成02;32
    OTHER_WRITABLE 后面的数字改成01;34

    然后在$HOME/.bashrc(如果用zsh, $HOME/.zshrc)后面添加

    1
    eval $(dircolors -b $HOME/.dircolors)
  • 修改wsl里面查看windows磁盘文件的颜色2, 参考Quick rundown on my current setup on the Windows Subsystem for Linux.

    1
    2
    3
    4
    5
    6
    7
    #Change ls colours
    LS_COLORS="ow=01;36;40" && export LS_COLORS

    #make cd use the ls colours
    zstyle ':completion:*' list-colors "${(@s.:.)LS_COLORS}"
    autoload -Uz compinit
    compinit
  • 安装anaconda

    1
    export PATH=/home/xin/anaconda3/bin:$PATH
    • 更改权限
    1
    sudo chown -R xin:xin /home/xin/anaconda3

    或者

    1
    sudo chmod 777 -R /home/xin/anaconda3
    • 修改pip源
  • 安装jdk

    • 官网下载相应的jdk, 解压并拷贝到/opt目录下
    1
    2
    sudo cp jdk-8u171-linux-x64.tar.gz /opt
    sudo tar -zxvf jdk-8u171-linux-x64.tar.gz
    • 配置path, 打开~/.zshrc
    1
    2
    export JAVA_HOME=/opt/jdk1.8.0_171
    export PATH=$JAVA_HOME/bin:$PATH
  • 安装pycharm和idea

    官网下载, 拷贝到/opt目录里面解压缩,类似于jdk,
    运行

    1
    sh /opt/pycharm-2018.2/bin/pycharm.sh &
  • 安装maven

    • 下载, 设置类似于jdk
    • 设置阿里云源
  • 安装多个wsl的linux, 修改默认版本

    开始菜单右键, 选择管理员模式打开powershell

    1
    2
    wslconfig /list # 列出安装的wsl不同linux版本
    wslconfig /s ubuntu-18.04 # 设置默认版本
  • 下载并安装vcxsrv, VcXsrv Windows X Server,

    双击桌面xserver图标启动, 在Clipboard设置界面去掉 Clickboard勾选和Primary Selection. 这样可以屏蔽pycharm里面的select and copy(选择并拷贝).

    参考:

某8网站字体加密爬虫的处理方法

某8网站的一些数据在浏览器里面显示是正常的, 但是渲染前和渲染后的html源码都看不到字体, 渲染前看到的是16进制的4位字符, 渲染后看到的是一些方块.

搜索找到猫眼和汽车之家的解决方法, 某8网站比这些都复杂, 经过多次尝试得到解决.

首先在html源码里面找到woff字体的base4编码, 保存成”font.woff”字体文件, 用fontTools库将这个字体文件存储为”font.xml”文件.

然后在xml里面找到TTGlyph字段, 这个字段下面的 子字段都是用来画字符(包括中英文数字)的坐标. 同一个字符的坐标是一样的. 解析xml, 然后把这些坐标的属性字典按顺序都存到一个list里面, 然后序列化成json(加sort_keys=True参数)字符串. 用这个字符串当key, value是实际的字符, 存成一个constant_dict. 每次遇到新网页, 取出这个字符串, 然后根据字符串从constant_dict获取实际的字符.

每次获取font里面坐标list字符串的代码:

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
# font_decryption.py
from fontTools.ttLib import TTFont
from lxml import etree
from io import BytesIO
import base64
import config
import os
import json
import pub.common.error as error

_xml_file_path = os.path.join(config.temp_file_path, "tongcheng58.xml")


def make_font_file(base64_string: str):
bin_data = base64.decodebytes(base64_string.encode())
return bin_data


def convert_font_to_xml(bin_data):
font = TTFont(BytesIO(bin_data))
font.saveXML(_xml_file_path)


def parse_xml():
xml = etree.parse(_xml_file_path)
root = xml.getroot()
font_dict = {}
all_data = root.xpath('//glyf/TTGlyph')
for index, data in enumerate(all_data):
font_key = data.attrib.get('name')[3:].lower()
contour_list = []
if index == 0:
continue
for contour in data:
for pt in contour:
contour_list.append(dict(pt.attrib))
font_dict[font_key] = json.dumps(contour_list, sort_keys=True)
return font_dict


def make_path():
if not os.path.isdir(config.temp_file_path):
os.makedirs(config.temp_file_path)


def get_font_dict(base64_string):
try:
make_path()
bin_data = make_font_file(base64_string)
convert_font_to_xml(bin_data)
font_dict = parse_xml()
except Exception as e:
return (error.ERROR_UNKNOWN_RESUME_CONTENT, 'cannot_get_font, err=[{}]'.format(str(e))), None
return None, font_dict


调用

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
def decrypt_font(text, font_dict):
decryption_text = ""
for alpha in text:
hex_alpha = alpha.encode('unicode_escape').decode()[2:]
if hex_alpha in font_dict:
item_text = decryption_font_dict.get(font_dict[hex_alpha])
if item_text is None:
_logger.error("op=[DecryptFont], err={}".format("decryption_font_dict_have_no_this_font"))
else:
item_text = alpha
decryption_text += item_text
return decryption_text


def parse(html: str, request: ParseRequest):
user_info_dict = {}
# print(html)
base64_string = html.split("base64,")[1].split(')')[0].strip()
err, font_dict = get_font_dict(base64_string)
if err is not None:
return err, None
html = decrypt_font(html, font_dict)


if __name__ == "__main__":
html = open(file_name, "r", encoding="utf-8").read()

parse(html)

参考:
解析某电影和某招聘网站的web-font自定义字体
The ramblings of atbrask
python3 汉字转十六进制unicode

python和java的类当做map的键

python和java的类当做dict(map)键时候, 如果不覆写hash()和equals方法, 默认使用地址值的hash值存储到map里面, 两个同样属性的类, 虽然内容是一样的,但是地址不一样, 会被当做两个map的key存在.

两个内容相同,但是地址不同的字符串当做key的时候, map存储一个key.

1
2
3
4
5
6
7
string_dict = {}
string1 = "data"
string2 = "data"
string_dict[string1] = "value1"
string_dict[string2] = "value2"
print(string_dict)
{'data': 'value2'}

这样的意义在于在A函数定义了一个dict(map), B函数用这个dict获取key对应值的时候, 虽然这两个key的类的地址一般不会一致, 但是A和B函数的两个类key内容一样, 就可以获取到同一个value.

类需要覆写hash()和equals方法才能实现同样的功能

python测试代码

覆写了hash()和equals方法

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
class ClassName(object):
"""docstring for ClassName"""

def __init__(self, arg):
self.data = arg

def __eq__(self, another):
return hasattr(another, 'data') and self.data == another.data

def __hash__(self):
return hash(self.data)

def __lt__(self, another):
return self.data - another.data


def func_a() -> dict:
class_a = ClassName(1234)
print("class_a address_id=", id(class_a))
class_dict = {class_a: "value"}

return class_dict


def func_b(class_dict: dict):
class_b = ClassName(1234)
print("class_b address_id=", id(class_b))
value = class_dict.get(class_b)
print(value)


def main():
class_dict = func_a()
func_b(class_dict)


if __name__ == '__main__':
main()

输出是:

1
2
3
class_a address_id= 4492965144
class_b address_id= 4492965200
value

java相应测试代码:

没有覆写hash()和equals方法

1
2
3
4
5
6
7
8
用lombok注解实现构造方法方法
@Setter
@Getter
@AllArgsConstructor
public class Student {
private String name;
private Integer age;
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class App {
public static void main(String[] args) {
Map<Student, String> map =getStudentMap();
printStudent(map);
}
static private Map<Student, String> getStudentMap(){
Map<Student, String> map = new HashMap<>(16);
Student studentA = new Student("xin", 20);
System.out.println("studentA's address = " + studentA);
map.put(studentA, "value");
return map;
}

static private void printStudent(Map<Student, String> map){
Student studentB = new Student("xin", 20);
System.out.println("studentB's address = " + studentB);
System.out.println("两个函数是否相等: " + Objects.equals(studentB, map.keySet().toArray()[0]));
System.out.println("从map中获取到的值是:" + map.get(studentB));
}

}

输出是:

1
2
3
4
studentA's address = com.example.demo002.zhihu.Student@548c4f57
studentB's address = com.example.demo002.zhihu.Student@1218025c
两个函数是否相等: false
从map中获取到的值是:null

参考地址:
Java 用自定义类型作为HashMap的键
python: my classes as dict keys. how?

jdk动态代理和Cglib代理笔记

动态代理

https://www.imooc.com/video/4704 这个视频的笔记

java动态代理类位于java.lang.reflect包下, 一般主要是设计到一下两个类:

  • Interface InvocationHandler: 该接口种仅仅定义了一个方法
    public object invoke(Object obj, Method method, Object[] args)
    在实际使用时, 第一个参数obj一般指代理类, method只被代理的方法, args为该方法的参数数组.
    这个抽象方法在代理类种动态实现.

  • Proxy: 该类是动态代理类
    Static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h), 返回代理类的一个实例, 返回后的代理类可以被代理类使用(可使用被代理类种在接口中声明过的方法)

  • 描述
    所谓动态代理是这样一中class, 它在运行时生成的class(没有.java文件),
    该class实现了被代理对象的一组interface, 使用动态代理类是, 必须实现InvocationHandler接口

  • 步骤

    • 1: 创建一个实现接口 InvocationHandler的类, 必须实现invoke方法, 在被代理类的方法前后添加要实现的功能
    • 2: 创建被代理类以及接口
    • 3: 调用Proxy类的静态方法, 创建一个代理类
      Static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
    • 通过代理调用方法

接口

1
2
3
4
//定义接口
public interface Moveable {
void movie() throws InterruptedException;
}

被代理的类

1
2
3
4
5
6
7
public class Car implements Moveable {
@Override
public void movie() throws InterruptedException {
TimeUnit.SECONDS.sleep(new Random().nextInt(5));

}
}

动态代理

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
//实现InvocationHandler接口, 复写invoke, 在被代理对象方法前后增加新的功能, 返回被代理对象的返回值
public class TimeHandler implements InvocationHandler{
private Object target;

public TimeHandler(Object target) {
this.target = target;
}

/**
*
* @param proxy 被代理的对象
* @param method 被代理对象的方法
* @param args 被代理对象方法的参数
* @return 被代理Object对象 方法的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

long start = System.currentTimeMillis();
System.out.println("汽车开始行驶");
method.invoke(target);
long end = System.currentTimeMillis();
System.out.println("汽车终止行驶!");
System.out.println((end - start)/ 1000.0);
return null;
}
}

代理类的测试

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 {
/**
* jdk动态代理测试类
*/
public static void main(String[] args) throws InterruptedException {
Car car = new Car();
InvocationHandler handler = new TimeHandler(car);
Class<?> carClass = car.getClass();

/**
* loader: 类加载器
* interfaces 类实现的接口
* h InvocationHandler
*/
Moveable moveable = (Moveable) Proxy.newProxyInstance(
carClass.getClassLoader(),
carClass.getInterfaces(),
handler);
moveable.movie();

}
}

Cglib代理

也叫做子类代理. 用被代理类的子类对象增加功能来实现的.

被代理类

1
2
3
4
5
public class Train {
public void move(){
System.out.println("火车行驶中...");
}
}

创建cglib代理类

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
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();

public Object getProxy(Class cls) {
// 设置创建子类(代理类)的类(被代理的类)
enhancer.setSuperclass(cls);
enhancer.setCallback(this);
return enhancer.create();
}

/**
* cglib通过继承父类方法来实现代理
* 拦截所有目标类方法的调用
*
* @param o 目标类的实例
* @param method 目标方法的反射对象
* @param objects 目标方法的参数
* @param methodProxy 代理类的实例
* @return 目标类方法的返回值
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
throws Throwable {
System.out.println("日志开始记录");
// 代理类调用父类的方法
methodProxy.invokeSuper(o, objects);
System.out.println("日志结束...");
return null;
}
}

测试cglib代理

1
2
3
4
5
6
7
public class Client {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
Train train = (Train) proxy.getProxy(Train.class);
train.move();
}
}

输入结果, 在被代理类功能前后都增加了新功能.

1
2
3
日志开始记录
火车行驶中...
日志结束...

java注解笔记

  1. 注解的作用

    • 读懂别人写的代码, 特别是框架的相关代码
    • 让编程更加简洁, 代码更清晰
    • 会写自定义注解 提升自己的能力
  2. 定义:
    java提供了一中原程序中的元素关联任何信息和任何元数据的途径和方法.

  3. 常见注解

    • jdk自带
      • @Override 表示当前方法覆盖了父类的方法
      • @Deprecation 表示方法已经过时,方法上有横线,使用时会有警告
      • @SuppviseWarnings 表示关闭一些警告信息(通知java编译器忽略特定的编译警告), 比如使用过期的(即用@Deprecation标识的方法)时, 会有编译器警告, 加上这个注解可以消除这个警告.
    • Spring 注解
      • @Autowired spring 自动装配
      • @Qualifier(“JavaBea”) 配合 @Autowired 实现自动装配
      • @Resource(name=”JavaBean”) spring 自动装配, 不写参数直接装配同类型的类
      • @PostConstruct 类初始化的方法
      • @PreDestroy 类销毁的方法
      • @Component 表名类为 JavaBean
      • @Scope(“prototype” ) 指定Bean的作用范围, prototype为每次都重新实例化
      • @Repository 与 @Component 作用相同, 常用于数据持久层
      • @Service 与 @Component 作用相同, 常用于业务逻辑层
      • @Controller 与 @Component 作用相同, 常用于控制表现层
    • Mybatis 注解
      • @Select
      • @Option
    • Autowired 举例
      1. 用类实习创建类的实例
        1
        2
        3
        4
        5
        6
        7
        public class UerManagerImpl implements UserManager{
        private UserDao userDao;
        public void setUserDao(UserDao userDao){
        this.userDao = userDao;
        }
        }


      1. 用配置文件实现创建类实例
        1
        2
        3
        4
        5
        6
        <bean id="UerManagerImpl" class="xxx.service.UserManager">
        <property name ="userDao" ref="userDao" />
        </bean>
        <bean id="userDao" class="xxx.UerManagerImpl"
        <property name ="sessionFactory" ref="mySessionFactory" />
        </bean>

      1. 用注解实现类实例的创建

        1
        2
        3
        4
        public class UerManagerImpl implements UserManager{
        @Autowired
        private UserDao userDao;
        }
  • 注解分类

    • 按照运行机制分类
      • 源码注解 : 注解只在源码里面存在, 编译生成.class文件时就不存在了. (这里的源码指自己编写的java代码.)
      • 编译时注解: 注解在源码和.class文件中存在, jdk自带的3个注解都属于编译时注解
      • 运行时注解: 在运行阶段还起作用, 甚至影响运行逻辑的注解. spring的 @Autowired 属于运行时注解
    • 注解来源:
      • jdk注解
      • 第三方注解
      • 自定义注解
    • 元注解:
      • 注解的注解
  • 自定义注解

    1. 注解定义

      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
      import java.lang.annotation.Documented;
      import java.lang.annotation.ElementType;
      import java.lang.annotation.Inherited;
      import java.lang.annotation.Retention;
      import java.lang.annotation.RetentionPolicy;
      import java.lang.annotation.Target;

      /**
      * 下面4个被称为元注解,
      */

      // @Target: 注解的作用域, 包括(CONSTRUCTOR(构造方法), FIELD(类属性), LOCAL_VARIABLE(局部变量),
      // METHOD(方法), PACKAGE(包), PARAMETER(参数), TYPE(类, 接口))

      @Target({ElementType.METHOD, ElementType.TYPE})

      // @Retention: 注解的生命周期, 包括(SOURCE, CLASS, RUNTIME)
      // SOURCE: 只在源码种显示, 编译时丢弃,
      // CLASS 编译时会记录到class中, 运行时忽略
      // RUNTIME 运行时存在, 可以通过反射读取
      @Retention(RetentionPolicy.RUNTIME)

      // @Inherited 允许子类继承父类的类注解, 父类的方法注解无法继承
      // 注意, 注解定义到接口上, 实现的时候解析是无效的
      @Inherited

      // @Documented 生成javadoc时候回包含注解, 属于标识注解
      @Documented

      //使用 @interface 关键字定义注解
      public @interface Description {
      /**
      * 成员类型是受限的, 合法的类型包括原始类型及String, Class, Annotation, Enumeration
      *
      * 如果注解只有一个成员, 则成员名必须取名为value(), 在使用时可以忽略程一鸣和赋值号(=)
      *
      * 注解类可以没有成员, 没有成员的注解称为标识注解
      */


      //成员以无参无异常方式声明, 成员就是注解里面元素定义的名词
      String desc();

      String author();

      // 可以用default为成员制定一个默认值
      int age() default 18;


      }
    2. 注解使用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      /**
      * 使用自定义注解的语法:
      *
      * @<注解名>(<成员名1>=<成员值1>, <成员名1>=<成员值1>, ...)
      */

      @Description(desc = "描述信息, 注解在类上", author = "姓名")
      public class CustomAnnotation {
      public String string;

      @Description(desc = "描述信息, 注解在方法上", author = "作者名", age = 30)
      public String eyeColor() {
      return "black";
      }

      }
    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
      import java.lang.annotation.Annotation;
      import java.lang.reflect.Method;
      import java.util.Arrays;

      /**
      * @author: 个人姓名
      * @date: 2018/3/18 下午10:15
      * @className: TestAnnotation
      * @version: 1.0.0
      * @description:
      */
      public class TestAnnotation {

      /**
      * 能运行时候解析到结果的注解都是 运行时注解, 一定要用@Retention(RetentionPolicy.RUNTIME)
      */

      public static void main(String[] args) throws ClassNotFoundException {
      //1: 使用类加载器加载类
      Class cls = Class.forName("com.linkzp.jdkproxy.CustomAnnotation");
      // 2: 找到类上的注解,
      System.out.println(Arrays.toString(cls.getAnnotationsByType(Description.class)));
      boolean isExist = cls.isAnnotationPresent(Description.class);
      if (isExist) {
      // 3: 拿到注解实例

      Description description = (Description) cls.getAnnotation(Description.class);
      System.out.println(description.author());
      System.out.println(description.desc());
      System.out.println(description.age());
      }

      // 4: 找到方法的注解
      Method[] methods = cls.getMethods();

      for (Method method : methods) {
      boolean isMethodAnnotation = method.isAnnotationPresent(Description.class);
      if (isMethodAnnotation) {
      Description methodDescription = method.getAnnotation(Description.class);
      System.out.println(methodDescription.author());
      System.out.println(methodDescription.desc());
      System.out.println(methodDescription.age());
      }
      }

      // 5: 方法注解解析的另外一种解析方法

      for (Method method : methods) {
      Annotation[] annotations = method.getAnnotations();
      for (Annotation annotation : annotations) {
      if (annotation instanceof Description) {
      Description description = (Description) annotation;
      System.out.println(description.author());
      System.out.println(description.age());
      System.out.println(description.desc());
      }
      }
      }
      }
      }

参考来源
http://blog.csdn.net/zen99t/article/details/49506919
http://blog.csdn.net/zen99t/article/details/49508447
http://blog.csdn.net/zen99t/article/details/49512411
http://blog.csdn.net/zen99t/article/details/50351575
https://www.imooc.com/video/8867

一次springboot事务bug的处理

工作需要将一条数据分别存入es(Elasticsearch)和mysql, 实现方法是: 在 springboot框架里面用@Transactional注解应用的方法保证事务.

抄一条事务的解释:
事务的作用就是为了保证用户的每一个操作都是可靠的,事务中的每一步操作都必须成功执行,只要有发生异常就回退到事务开始未进行操作的状态。

今天发现一条数据存入了mysql, 但是没有存入es, 也就是事务失败了, es出现异常时候没有回退.
谷歌等各种搜索都说@Transactional处理事务是可以的.
只能尝试和猜测各种写法的问题, 在一次把方法的private改成public后,发现idea没有提示, 难道是因为这个原因? 再次发送数据后,发现事务成功了.

public 事务 当关键字在谷歌上一搜发现果然是:
只有@Transactional 注解应用到public 方法,才能进行事务管理

记录一下, 希望以后写事务的时候每次都记住这次大坑.

mac 安装和设置java maven

  • 1 安装和设置java

    • 1: 搜索官方下载jdk相应版本, 然后双击安装

    • 2: iterm运行

      1
      /usr/libexec/java_home -V

      出现类似

      1
      /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home

      说明安装成功

    • 3: 打开zsh, vim ~/.zshrc 最后面添加

      1
      export JAVA_HOME=$(/usr/libexec/java_home)

      保存
      4: 重新加载zshrc, source ~/.zshrc, 输入echo $JAVA_HOME
      出现

      1
      /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home

      说明安装成功

  • 2 安装和设置maven

    • 用brew安装maven brew install maven
    • item 运行 mvn -v
      出现这样结果说明安装成功
      1
      2
      3
      4
      5
      Maven home: /usr/local/Cellar/maven/3.5.2/libexec
      Java version: 1.8.0_151, vendor: Oracle Corporation
      Java home: /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre
      Default locale: zh_CN, platform encoding: UTF-8
      OS name: "mac os x", version: "10.12.6", arch: "x86_64", family: "mac"
    • 改成阿里源, 参考来源 /usr/local/Cellar/maven/3.5.2/libexec/conf 这个目录下找到setting.xml文件, 打开.
      mirrors字段下填入
      1
      2
      3
      4
      5
      6
      <mirror>
      <id>alimaven</id>
      <name>aliyun maven</name>
      <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
      <mirrorOf>central</mirrorOf>
      </mirror>
    • 修改idea的默认maven, 参考来源1, 来源2

  • Copyrights © 2015-2022 小信
  • Visitors: | Views:

请我喝杯咖啡吧~