我已经用了很长一段时间 Vim 了,但是 Vim 最可贵之处便在于你永远达不到 Vim 的天花板,在使用的过程中我永远会发现操作 Vim 的其他便捷方法。最近看了一个关于 Vim 的讲座 ,革新我对 Vim 命令的认识。可以说掌握这样的一个世界观可以对 Vim 的操作上到另外一个层次。下面就总结一下这个视频中的精髓内容。
@ChrisToomey 定义了一种 Vim Language,Vim 的语法由数词 + 动词 + 名词
组成,比如:
d 删除
w 单词
将两个字母组合起来就是 删除单词
这个经常使用的命令非常容易记住。如果想要删除三个单词,则是 3dw
,所以可以总结出
{number}{command}{text-object}
这样的形式
重复和撤销,相信使用过一段时间 Vim 的人应该会知道 .
表示重复上一次命令, u
表示撤销上一次操作。而重复和撤销是针对命令而不是针对输入的,因此每使用一次 .
或者 u
都是命令级别。因此这就给予了 .
操作非常强大的功能。
Verbs: 常用的动作举例
d Delete
c Change delete and enter insert mode
y yank
> intend 缩进
v 选择
Nouns: 常见的动作 Motion
w 移动到下一个 word 开始
e 移动到下一个 word 的结尾
b 移动到上一个 word 的开始 back
2j 向下移动 2 lines
Vim 中定义了很多移动操作
基于内容 Nouns: Text Objects
w => words 表示移动一个单词
s => sentences 移动一个句子
p => paragraphs 向下移动一个段落
t => tags (html xml)
动作 motions
a => all
i => in
t => 'till
f => find forward
F => find backward
iw => inner word
it => inner tag
i" => inner quotes
ip => inner paragraph
as => a sentence
命令 commands
d => delete(also cut)
c => change(delete, then into insert mode)
y => yank (copy)
v => visual select
组合举例
diw delete in word,即使光标在 word 中也能够快速删除 word
yi) yank all text inside the parentheses,光标在 `()` 中复制括号中的所有内容
上面的 Text Object
{command}{text object or motion}
在单词中间,diw
删除光标下的单词,dit
删除光标下 tag 中的内容
Nouns: Parameterized Text Objects
f,F => find the next character
t,T => find till
/,? => search
比如有一行文本
the program print ("Hello, World!")
如果想要删除 Hello 前面的内容,可以在行首使用 dtH
, 解读这个命令可以理解为 d => delete unti H
从这里删除直到遇到 H。典型的 verb + noun 组合。
记住动作加移动,能够很快的记忆很多命令。
比如使用 https://github.com/tpope/vim-surround 这个插件,可以轻松的实现,使用命令 cs"'
来将
"hello world"
变成
'hello world'
命令的语义解释就是 change surroundding double quote to single quote 将包围的双引号变成单引号
使用 cs'<p>
将单引号变成 <p>
<p>hello world</p>
使用 cst"
将 surrounding 变成双引号
"hello world"
或者可以使用 ds"
来将 surrounding 双引号删除 delete surrounding “
hello world
Vim 中的 “.” 命令,以命令为单位,重复上一个命令。
Sublime , IntelliJ IDEA 中经常被人提及的 multiple cursor 的功能,能够在编辑器中提供多个光标一起编辑,其实 Vim 不需要多光标就能够做到,结合强大的 .
和 n .
可以快速的编辑大量重复的内容。
比如在 Vim 中的 workflow 就是
/pattern
来所有需要编辑的内容cw
当前单词 ESC
退出,一个完成的动作n
来找到下一个匹配的地点.
来重复编辑操作,可以直接将单词替换为上一个动作编辑Vim 允许记录一个宏来完成一组命令
qa # 将命令记录到寄存器 a 中
q # 再次 q 结束记录
@a # 使用寄存器
@@ # 使用上一次寄存器
一些相关的命令
sudo fdisk -l # 列出磁盘分区表
结果是这样的:
Disk /dev/ram0: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram1: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram2: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram3: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram4: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram5: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram6: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram7: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram8: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram9: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram10: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram11: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram12: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram13: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram14: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/ram15: 4 MiB, 4194304 bytes, 8192 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk /dev/mmcblk0: 7.4 GiB, 7948206080 bytes, 15523840 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x1fdbda7f
Device Boot Start End Sectors Size Id Type
/dev/mmcblk0p1 8192 93813 85622 41.8M c W95 FAT32 (LBA)
/dev/mmcblk0p2 94208 15523839 15429632 7.4G 83 Linux
Disk /dev/sda: 1.4 TiB, 1500301909504 bytes, 2930277167 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x4c63688c
Device Boot Start End Sectors Size Id Type
/dev/sda1 2048 2930272255 2930270208 1.4T 7 HPFS/NTFS/exFAT
在最后可以看到一块磁盘 /dev/sda1
。
然后可以使用如下的方式手动挂载。
为了让 Linux 能够读取 NTFS 格式的硬盘,需要安装 ntfs-3g
。然后可以手动挂载。
sudo apt-get install ntfs-3g
sudo mkdir -p /media/sda1
sudo mount -t ntfs-3g /dev/sda1 /media/sda1
挂载完成后可以查看
sudo df -h
Filesystem Size Used Avail Use% Mounted on
/dev/root 7.3G 2.2G 4.8G 31% /
devtmpfs 460M 0 460M 0% /dev
tmpfs 464M 0 464M 0% /dev/shm
tmpfs 464M 13M 452M 3% /run
tmpfs 5.0M 4.0K 5.0M 1% /run/lock
tmpfs 464M 0 464M 0% /sys/fs/cgroup
/dev/mmcblk0p1 42M 21M 21M 51% /boot
tmpfs 93M 0 93M 0% /run/user/1000
/dev/sda1 1.4T 1.1T 363G 75% /media/sda1
通过编辑 fstab 来让系统自动挂载
sudo vim /etc/fstab
插入
/dev/sda1 /mnt/hdd ext4 defaults 0 0
从而实现 USB 设备的自动挂载
sudo vim /etc/udev/rules.d/10-usbstorage.rules
KERNEL!="sd*", GOTO="media_by_label_auto_mount_end"
SUBSYSTEM!="block",GOTO="media_by_label_auto_mount_end"
IMPORT{program}="/sbin/blkid -o udev -p %N"
ENV{ID_FS_TYPE}=="", GOTO="media_by_label_auto_mount_end"
ENV{ID_FS_LABEL}!="", ENV{dir_name}="%E{ID_FS_LABEL}"
ENV{ID_FS_LABEL}=="", ENV{dir_name}="Untitled-%k"
ACTION=="add", ENV{mount_options}="relatime,sync"
ACTION=="add", ENV{ID_FS_TYPE}=="vfat", ENV{mount_options}="iocharset=utf8,umask=000"
ACTION=="add", ENV{ID_FS_TYPE}=="ntfs", ENV{mount_options}="iocharset=utf8,umask=000"
ACTION=="add", RUN+="/bin/mkdir -p /media/%E{dir_name}", RUN+="/bin/mount -o $env{mount_options} /dev/%k /media/%E{dir_name}"
ACTION=="remove", ENV{dir_name}!="", RUN+="/bin/umount -l /media/%E{dir_name}", RUN+="/bin/rmdir /media/%E{dir_name}"
LABEL="media_by_label_auto_mount_end"
如果要卸载文件系统,比如将挂载的 /media/sda1
卸载
umount /media/sda1
什么是跨域或者说什么是CORS(Cross-origin resource sharing),中文叫”跨域资源共享”。在了解 CORS 之前首先要知道“同源策略”,出于安全考虑,浏览器会限制Ajax中发起的跨站请求。比如,使用 XMLHttpRequest 对象发起 HTTP 请求就必须遵守同源策略(same-origin policy),”同源策略“是浏览器安全的基石。具体而言,Web 应用程序能且只能使用 XMLHttpRequest 对象向其加载的源域名发起 HTTP 请求,而不能向任何其它域名发起请求。阮一峰写的一篇关于 CORS 的文章 介绍得非常详细,这里主要记录一下重点以及 Spring MVC 中如何处理 CORS。
CORS 做到了不破坏即有规则,只要服务端实现了 CORS 接口,就可以跨源通信。
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
需要同时满足以下两大条件,才属于简单请求。
请求方法仅仅为以下三种方法之一:
HEAD、GET、POST
HTTP的头信息不超出以下几种字段:
Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
Response Header | 选项 |
---|---|
Access-Control-Allow-Origin | 必须,值要么是请求的 Origin,要么是 * ,表示接受所有域名请求 |
Access-Control-Allow-Credentials | 该字段可选。它的值是一个布尔值,表示是否允许客户端发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。 |
Access-Control-Expose-Headers | 该字段可选。扩展客户端能够访问的字段。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader(‘FooBar’)可以返回FooBar字段的值。 |
简单请求的处理过程可以参考下图:
对于简单请求,CORS 的策略是请求时,在头信息中添加一个 Origin 字段,服务器收到请求后,根据该字段判断是否允许该请求。
浏览器先于用户得到返回结果,根据有无 Access-Control-Allow-Origin 字段来决定是否拦截该返回结果。
script 或者 image 标签触发的 GET 请求不包含 Origin 头,所以不受到 CORS 的限制,依旧可用。如果是 Ajax 请求,HTTP 头信息中会包含 Origin 字段,由于服务器没有做任何配置,所以返回结果不会包含 Access-Control-Allow-Origin,因此返回结果会被浏览器拦截,接口依旧不可以被 Ajax 跨源访问。
而对于真正实现中的请求,可能会使用 Content-Type:application/json
,也有可能有自定义 Header,所以了解非简单请求的处理也非常必要。
对于 Content-Type
为 application/json
的特殊请求,需要服务端特殊对待的请求,在正式通信前会增加一次“预检”请求(preflight)。浏览器会先询问服务器,当前网页所在的域名是否在服务器的许可名单,以及可以使用哪些HTTP动词和头信息,得到服务端回复才会发出正式的请求,否则报错。
CORS 请求相关 Header
Request Header | value |
Access-Control-Request-Method | 真实请求使用的 HTTP 方法 |
Access-Control-Request-Headers | 真实请求包含的自定义 Header |
在服务端收到客户端发出的预检请求后,校验 Origin
,Access-Control-Request-Method
,Access-Control-Request-Headers
,通过校验后在返回中加入如下的header:
Response Header | value |
---|---|
Access-Control-Allow-Methods | 必须,值为逗号分割的字符串,表明服务器支持的所有跨域请求方法,返回的是所有支持的方法,为了避免多次“预检”请求。 |
Access-Control-Allow-Headers | 如果浏览器请求包括 Access-Control-Request-Headers 字段,则 Access-Control-Allow-Headers 字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在”预检”中请求的字段。 |
Access-Control-Allow-Credentials | 与简单请求含义相同 |
Access-Control-Max-Age | 该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。 |
一旦服务器通过了”预检”请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin
头信息字段。
这里主要针对 Spring 3.x 来处理, 在 Spring 4.2 之后官方引入了 @CrossOrigin 注解,处理 CORS 变的非常方便。所以接下来就记录下 3.x 中的处理方法。
更新 web.xml 让 Spring 开启 OPTIONS 处理.
<servlet>
<servlet-name>application</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>dispatchOptionsRequest</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
使用 Interceptor
public class CorsInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
addOriginHeader(request, response);
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(200);
return false;
}
return true;
}
private void addOriginHeader(HttpServletRequest request, HttpServletResponse response) {
String origin = request.getHeader("Origin");
response.addHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type");
response.addHeader("Access-Control-Allow-Credentials", "true"); // 可选,是否允许Cookie
response.addHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
response.addHeader("Access-Control-Max-Age", "1728000");
}
}
在 XML 中配置 Interceptor
然后在 Controller 中
@RequestMapping(value = "/test/hello", method = {RequestMethod.GET, RequestMethod.OPTIONS})
然后OK
这篇文章用来收集整理爬虫相关的资料。
如果只想单纯的自己开发,可以使用 Python + Celery + Redis/MySQL 基本能满足 80% 的需求。
如果想要选用框架 Scrapy,pyspider,等等都是非常不错的选择,我甚至在 GitHub 上看到过 Java 的分布式爬虫。
这本书在网上有部分 gitbook,链接在这里
网上公开的部分都是无关痛痒的部分,不过提及的工具倒是可以参考一下。大部分我之前的文章也都有提及
树莓派官网有很多系统可以选择,我选了官方维护的 Raspbian 基于 Debian 的衍生版,主要是熟悉他的 APT 包管理,看评价三方维护的 Snappy Ubuntu Core 换用了其他的 snap 的管理,不是很了解,所以还是选择了 Raspbian。
官网提供的教程非常方便, 采用开源的镜像烧录工具 Etcher 非常方便的就可以在三大平台上完成镜像到 SD 的烧录。当然如果熟悉各个平台的工具也可以自己手动完成烧制。
在将系统写入 microSD 卡之后,将卡插入树莓派板子,启动树莓派,开机即可,可以用 HDMI 接口连接显示器,用一个外接键盘来输入。树莓派的默认用户名是: pi
,默认密码为: raspberry
。
使用如下命令给 root 账户设置密码并允许登录
sudo passwd root
# 然后输入密码
# 用同样的方式修改默认账户 pi 的密码
sudo passwd pi
raspbian 自带 SSH ,启动
sudo service ssh start
raspi-config
运行该命令可以看到一系列的选项,比如修改 Hostname ,修改密码等等选项,可以配置一下。
换用清华的源 : https://mirror.tuna.tsinghua.edu.cn/help/raspbian/
apt install vim
apt install samba samba-common-bin
apt install zsh
apt-get install nginx
sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
chsh -s /bin/zsh
apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev \
libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev \
xz-utils tk-dev
curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash
apt install mysql-server
apt-get install libmysqlclient-dev
Redis 在设计上,是用来被可信客户端访问的,也就意味着不适合暴露给外部环境非可信客户端访问。
最佳的实践方法是在 Redis 前增加一个访问控制层,用于校验用户请求。
Redis 本身提供了一些简单的配置以满足基本的安全控制。
requirepass
以明文的形式配置在 conf 文件里的,所以要尽可能得长和复杂,降低被破解的风险。因为 redis 非常快,外部环境可以在一秒内 150k 次暴力破解,所以配置密码一定要复杂。对于直接暴露在互联网的 Redis,应该使用防火墙阻止外部访问 Redis 端口。客户端应该只通过回环接口访问 Redis。
在 redis.conf 文件添加
bind 127.0.0.1
由于 Redis 设计的初衷,如果不能成功阻止外部访问 Redis 端口,会有很大的安全影响。外部攻击者使用一个 FLUSHALL 命令就可以删除整个数据集。
虽然 Redis 没有实现访问控制,但是提供了一个简单的身份验证功能。
在配置文件中修改:
requirepass mypassword
重启 redis
sudo service redis-server restart
登录验证
./redis-cli -h 127.0.0.1 -p 6379 -a mypassword
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "mypassword"
如上输出,配置正确。也可以在连接之后使用 auth
验证
./redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> auth mypassword
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "mypassword"
通过修改 server 的 conf 文件方式需要重启 server, 也可以通过客户端来修改密码
127.0.0.1:6379> config set requirepass mypassword
OK
使用客户端设置的密码,Redis 重启之后还会使用 redis.conf 配置文件中的密码。
设置密码之后 Redis 集群中 slave 中也需要配置 和 master 一样的密码
masterauth master-password
身份验证是一个可选的冗余层,如果防火墙或者其他保护 Redis 安全的系统被攻破,对于不知道授权密码的情况,攻击者依然不能访问 Redis。
auth
命令是明文传输的,所以依然不能阻止那些获得网络访问权限的攻击者嗅探。
很久没有更新这个分类下的文章了,其实一直在体验不同的产品,只是真的很少有能拿出来讲一下的东西。不管是硬件还是软件,最近几年使用的东西越来越狭窄,越来越收缩,当然对于某一个特定的需求,总有一个产品能够占领绝大多数市场,而也有部分产品能够瓜分小众市场。这里要介绍的这个 NodeQuery 就不是一个大而全的产品,而是一个很精细的小众产品。我用它也一年多了,我的需求很简单,能够实时监控我的 VPS,能够在宕机或者高负载时报警。NodeQuery 完全能够满足我的需求。
用 NodeQuery 自己的话描述自己就是:”一个轻量、易用的 Linux 服务器监控服务”.
NodeQuery provides a lightweight and easy to use Linux server monitoring service.
NodeQuery 免费账户可以提供 10 台机器的监控,
官网地址: https://nodequery.com/
同样使用也非常方便,新建 Server,然后会给出一个一键脚本,在自己的 VPS 上一键安装就行,脚本同样是开源的托管在 GitHub 上。人人都可以审查。
2021 年 10月更新
很多年没有上 NodeQuery 查看,发现 NodeQuery 已经不更新了,这里记录一下移除 NodeQuery 的命令:
rm -R /etc/nodequery && (crontab -u nodequery -l | grep -v "/etc/nodequery/nq-agent.sh") | crontab -u nodequery - && userdel nodequery
这个网站也能够提供 API 支持,能够读取历史情况下 Server 的状态,目前写功能暂时还无法使用。
不过需要提醒的是,这个网站自从 2014 年起就再没有更新,不清楚背后发生了什么事情,但是也是感到非常的可惜。
许多使用静态语言比如 C、 C++ 或者 Java 的人,在转到 Python 的时候可能第一个会疑惑的就是 Python 不需要显示的指定类型,那么 Python 是怎么知道变量的类型呢?
在 Python 中,变量的创建遵循着一个非常合理的方式,以 a=3
来举例子:
变量创建
一个变量(名字)比如 a
,当第一次被赋值时被创建。
变量类型 Variable Types
一个变量永远不会有任何类型信息或者约束,类型的概念和 Object 关联,而不是变量名字。变量都是通用的(泛型),变量总是在特定时间指向一个特定的 Object 。
变量使用 Variable use
当变量出现在表达式中,他会立即使用当前指向的 Object 替换。因此,所有的变量在使用之前都必须显式的被赋值,当使用未被赋值的变量时会产生错误。
总结来说,变量会在赋值时被创建,并且能够指向任何类型的对象,在引用前必须已经被赋值。这就是动态类型模型和传统静态语言最显著的差别。
所以对于 a=3
Python 会执行三个完全不同的步骤来完成,下面的步骤显示了 Python 语言中所有赋值会执行的步骤:
a
a
连接到对象 3变量和对象会分别保存到不同的内存中。变量永远会指向 Objects,永远不会指向其他变量,但是大型的 Objects 可能会有指向其他 Objects 的链接(比如 list 对象就可能会有很多指向其他对象的链接)。
在 Python 中,这些从变量指向对象的链接被称为引用 references ,也就是说引用是一种关联,在内存中表现为指针。每当一个变量被使用, Python 会自动跟随着 variable to object 链接。
所以这些术语理解起来要简单得多,具体来讲:
概念上讲,每一次创建新的值, Python 都会创建新的对象(开辟内存空间)来表示值。内部来说,作为优化,Python 会使用缓存重用一些特定的不会改变的对象,比如比较小的 integers,strings,比如 0 并不会单独每一次都开辟内存空间,而是使用缓存。但是从逻辑上,每一个表达式的结果都会为不同的对象,每一个对象都有自己的内存空间。
每一个对象都有两个标准的 header fields:type designator
用来表明对象的类型,reference counter
来记录引用次数,何时对象应该被回收。
Types live with Objects, Not Variables
当对同一个变量重复赋值,那么变量之前指向的 Object 会发生什么?
a = 3
a = 'Spam'
当 a 被重新赋值为 Spam 时,对象 (3) 会发生什么。在 Python 中,当一个变量名被赋值到新对象时,之前对象的空间会被回收(也就是说当当一个对象不再被变量或者其他对象引用时会被回收)。这种自动回收对象空间的机制被称为 垃圾回收 (garbage collection)。
内部实现来说,Python 通过在每一个 Object 中存储一个 counter,来追踪当前对象被引用的次数来实现垃圾回收。一旦引用计数变为 0,对象的内存空间就会被自动回收。
上面的内容都是单一变量,如果出现变量赋值比如
a = 3
b = a
这个时候 Python 会怎么处理呢? 当变量 b 被创建时,会创建一个从 b 指向对象 3 的引用。这个有多个变量名字指向相同的对象的场景,被称为 shared reference
在上面的基础上,如果
a = 'spam'
a 被重新赋值 spam
这个时候不会影响 b 指向的对象 3.
如果
a = a + 2
同样不会影响 b 的值,对象 integer 5 会作为加号的结果放到新的内存空间,是一个新的对象,所以也不会改变 b 的值。事实上,也没有任何方法可以改变对象 3 的值,就像之前关于 Python 类型一文 中说的那样,integer 是不可变类型,因此不能原地修改其内容。
当使用可变对象,比如 list 时,如果存在共享引用,要特别注意,当修改其中一个引用的对象的值时,会影响其他指向这个对象的引用。
L1 = [2,3,4]
L2 = L1
L1[0] = 'spam'
这个时候 L1,L2 变量指向的对象值都被改变了。
如果需要深度拷贝 list 时,就需要特别注意,不要使用引用。
L1 = [2,3,4]
L2 = L1[:] # 创建了一个 L1 的拷贝
L1[0] = 'spam' # 此时再修改 L1 则不会对 L2 造成影响
对 L1 的修改不会影响 L2 ,因为 L2 指向和 L1 完全不同的内存区域。
import copy
X = copy.copy(Y) # make top-level "shadow" copy of any object Y
X = copy.deepcopy(Y) # make deep copy of any object Y: copy all nested parts
因为 Python 的引用模型,所有有两种完全不同的判断相等的方法
L = [1,2,3]
M = L
L == M # Same 这时候判断的是引用是否相同,判定值是否相同
L is M # Same is 操作符判断是指向的对象是否相同,更强的相等判断
更比如说
L = [1,2,3]
M = [1,2,3]
L == M # Same 相同的内容
L is M # False 不同的对象
再比如
X = 42
Y = 42 # 应该是两个不同的对象
X == Y # True
X is Y # 相同的对象,caching 在起作用
之前说过 Python 垃圾回收时,优化机制,导致 small integer 和 strings 会被缓存和重新使用,is
操作符告诉我们他们引用得是相同的对象。
事实上,如果想要知道对象的引用次数,可以使用 getrefcount
方法,在 sys
模块中。
import sys
sys.getrefcount(1)
检查对象 1 被引用的次数,常常会发现超过期待。因为 Integer 是不可变的,所以也就无所谓多少引用指向相同的对象了。
Python 很重要的一个概念 module,用来组织代码结构。
最终,这些路径都会存在 sys.path
中,是一个保存着一系列搜索路径的 list。
>>> import sys
>>> sys.path
一个目录的 Python code 被称为 package,这样的导入被成为 package import。
import dir1.dir2.mod
from dir1.dir2.mod import x
文件目录结构可能是
dir0\dir1\dir2\mod.py
每一个 package 被定义时都会产生一个 __init__.py
的文件,该文件可以像普通文件一样包含 Python 代码,也可以为空。
Package 初始化
当 Python 导入 Package 时,会自动跑 `__init__.py` 下的内容,所以 `__init__.py` 文件下是天然的存放初始化内容的地方,比如初始化数据库连接等等。
如果定义了 __all__
,在 from *
时导入,就会选择性导入指定内容
__all__ = ["Error", "encode", "decode"]
从 WizNote 中整理。
POJO, Plain Old java object, 最简单的 Java 对象
[[Dependency Injection]] 带来的最大好处,松耦合,如果一个对象只通过接口(而不是具体实现或初始化过程)来表明依赖关系,那么这种依赖就能够在对象本身毫不知情的情况下,用不同的具体实现进行替换。
AOP aspect-oriented programming, 面向切面编程允许将遍布应用各处的功能分离出来形成可重用的组件
依赖注入让互相协作的软件组件保持松散耦合,而 AOP 则是让遍布各处的功能分离出来形成可重用的组件。
Spring 框架提供约 20 个模块。
由核心,Bean,上下文,表达式语言模块
数据访问 / 集成层包括 JDBC,ORM,OXM,JMS 和事务处理模块
Web 层由 Web,Web-MVC,Web-Socket 和 Web-Portlet 组成
还有其他一些重要的模块,像 AOP,Aspects,Instrumentation,Web 和测试模块
DispatcherServlet 是前置控制器,配置在 web.xml 文件中的。拦截匹配的请求,Servlet 拦截匹配规则要自已定义,把拦截下来的请求,依据相应的规则分发到目标 Controller 来处理,是配置 spring MVC 的第一步。
视图名称解析器
@Controller
负责注册一个 bean 到 spring 上下文中,类名前加此注解,告知 Spring 容器这是一个控制器组件,负责注册一个 bean 到 spring 上下文中
Controller 注解示例
@Controller
@RequestMapping("/mvc")
public class mvcController {
@RequestMapping("/hello")
public String hello(){
return "hello";
}
}
@Controller
@RequestMapping("/rest")
public class RestController {
@RequestMapping(value="/user/{id}",method=RequestMethod.GET)
public String get(@PathVariable("id") Integer id){
System.out.println("get"+id);
return "/hello";
}
@RequestMapping(value="/user/{id}",method=RequestMethod.POST)
public String post(@PathVariable("id") Integer id){
System.out.println("post"+id);
return "/hello";
}
@RequestMapping(value="/user/{id}",method=RequestMethod.PUT)
public String put(@PathVariable("id") Integer id){
System.out.println("put"+id);
return "/hello";
}
@RequestMapping(value="/user/{id}",method=RequestMethod.DELETE)
public String delete(@PathVariable("id") Integer id){
System.out.println("delete"+id);
return "/hello";
}
}
@RequestMapping
类方法前加,注解为 Controller 指定可以处理哪些 URL 请求
@RequestMapping(value = "/register", method = RequestMethod.POST)
三个常用属性:value,params,method
value 必填属性,代表请求的 url,支持模糊配置。(value 字可以省略,但是属性值必须填)
@RequestMapping(value="/users/**") 匹配"/users/abc/abc";
@RequestMapping(value="/product?") 匹配"/product1"或"/producta",但不匹配"/product"或"/productaa";
@RequestMapping(value="/product*") 匹配“/productabc”或“/product”,但不匹配“/productabc/abc”;
@RequestMapping(value="/product/*") 匹配“/product/abc”,但不匹配“/productabc”;
params 可选属性,代表对请求参数进行过滤
@RequestMapping(value="/login.do",params="flag") 代表请求中必须要有名为 flag 的提交项
@RequestMapping(value="/login.do",params="!flag") 代表请求中不能有名为 flag 的提交项
@RequestMapping(value="/login.do",params="flag=hello") 代表请求中必须有名为 flag 的提交项,且值为 hello
@RequestMapping(value="/login.do",params="flag!=hello") 代表请求中如果有名为 flag 的提交项,其值不能为 hello
@RequestMapping(value="/login.do",params={"flag1","flag2=hello"}) 代表请求中必须有名为 flag1 的提交项,同时必须有名为 flag2 的提交项,且 flag2 的值必须为 hello
method 可选属性,代表请求方式
@RequestMapping(value="/login.do",method=RequestMethod.POST)
@RequestMapping(value="/login.do",method=RequestMethod.GET)
@RequestMapping(value="/login.do", method= {RequestMethod.POST, RequestMethod.GET}"
@RequestBody 该注解用于读取 Request 请求的 body 部分数据,使用系统默认配置的 HttpMessageConverter 进行解析,然后把相应的数据绑定到要返回的对象上 , 再把 HttpMessageConverter 返回的对象数据绑定到 Controller 中方法的参数上
@ResponseBody 该注解用于将 Controller 的方法返回的对象,通过适当的 HttpMessageConverter 转换为指定格式后,写入到 Response 对象的 body 数据区
@ModelAttribute 在方法定义上使用 @ModelAttribute 注解:Spring MVC 在调用目标处理方法前,会先逐个调用在方法级上标注了 @ModelAttribute 的方法
在方法的入参前使用 @ModelAttribute 注解:可以从隐含对象中获取隐含的模型数据中获取对象,再将请求参数 –绑定到对象中,再传入入参将方法入参对象添加到模型中
@RequestParam 在处理方法入参处使用 @RequestParam 可以把请求参数传递给请求方法
@RequestMapping(value = "/check", method = RequestMethod.GET)
public
@ResponseBody
String check(@RequestParam(value = "signature", required = true, defaultValue = "") String signature,
@RequestParam(value = "timestamp", required = true, defaultValue = "") String timestamp,
@RequestParam(value = "nonce", required = true, defaultValue = "") String nonce,
@RequestParam(value = "echostr", required = true, defaultValue = "") String echostr,
HttpServletRequest request,
HttpServletResponse response
) {
response.addHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
if (checkSignature(signature, timestamp, nonce)) {
return echostr;
}
return "";
}
@PathVariable
绑定 URL 占位符到参数
@ExceptionHandler
注解到方法上,出现异常时会执行该方法
@ControllerAdvice
使 Contoller 成为全局的异常处理类,类中用 @ExceptionHandler 方法注解的方法可以处理所有 Controller 中发生的异常
@ControllerAdvice
public class GlobalExceptionHandler {
private static Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(value = Exception.class)
@ResponseBody
public void exceptionHandler(HttpServletRequest req, Exception e) throws Exception {
//todo add request info to log
logger.error("error: {}", e);
return;
}
}
自动装配主要使用 @ComponentScan、@Component 和 @Autowired。
@Autowired
@Resource
参数绑定注解
@PathVariable
当使用 @RequestMapping URI template 样式映射时, 即 someUrl/{paramId}, 这时的 paramId 可通过 @Pathvariable 注解绑定它传过来的值到方法的参数上。
示例代码:
@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {
@RequestMapping("/pets/{petId}")
public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
// implementation omitted
}
}
上面代码把 URI template 中变量 ownerId 的值和 petId 的值,绑定到方法的参数上。若方法参数名称和需要绑定的 uri template 中变量名称不一致,需要在 @PathVariable(“name”) 指定 uri template 中的名称。
@RequestHeader、@CookieValue
@RequestHeader 注解,可以把 Request 请求 header 部分的值绑定到方法的参数上。
示例代码:
这是一个 Request 的 header 部分:
Host
localhost:8080
Accept text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language fr,en-gb;q=0.7,en;q=0.3
Accept-Encoding gzip,deflate
Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive 300
代码:
@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@RequestHeader("Accept-Encoding") String encoding, @RequestHeader("Keep-Alive") long keepAlive) {
//...
}
上面的代码,把 request header 部分的 Accept-Encoding 的值,绑定到参数 encoding 上了, Keep-Alive header 的值绑定到参数 keepAlive 上。
@CookieValue 可以把 Request header 中关于 cookie 的值绑定到方法的参数上。
例如有如下 Cookie 值:
JSESSIONID=415A4AC17
参数绑定的代码:
@RequestMapping("/displayHeaderInfo.do") public void displayHeaderInfo(@CookieValue("JSESSIONID") String cookie) { //... } 即把 JSESSIONID 的值绑定到参数 cookie 上。
@RequestParam, @RequestBody
@RequestParam
示例代码:
@Controller
@RequestMapping("/pets")
@SessionAttributes("pet")
public class EditPetForm {
@RequestMapping(method = RequestMethod.GET)
public String setupForm(@RequestParam("petId") int petId, ModelMap model) {
Pet pet = this.clinic.loadPet(petId);
model.addAttribute("pet", pet);
return "petForm";
}
}
@RequestBody
该注解常用来处理 Content-Type: 不是 application/x-www-form-urlencoded 编码的内容,例如 application/json, application/xml 等;
它是通过使用 HandlerAdapter 配置的 HttpMessageConverters 来解析 post data body,然后绑定到相应的 bean 上的。
因为配置有 FormHttpMessageConverter,所以也可以用来处理 application/x-www-form-urlencoded 的内容,处理完的结果放在一个 MultiValueMap<String, String>里,这种情况在某些特殊需求下使用,详情查看 FormHttpMessageConverter api;
示例代码:
@RequestMapping(value = "/something", method = RequestMethod.PUT) public void handle(@RequestBody String body, Writer writer) throws IOException { writer.write(body); }
4、@SessionAttributes, @ModelAttribute
@SessionAttributes:
该注解用来绑定 HttpSession 中的 attribute 对象的值,便于在方法中的参数里使用。
该注解有 value、types 两个属性,可以通过名字和类型指定要使用的 attribute 对象;
示例代码:
@Controller @RequestMapping("/editPet.do") @SessionAttributes("pet") public class EditPetForm { // ... }
@ModelAttribute
该注解有两个用法,一个是用于方法上,一个是用于参数上;
用于方法上时: 通常用来在处理 @RequestMapping 之前,为请求绑定需要从后台查询的 model;
用于参数上时: 用来通过名称对应,把相应名称的值绑定到注解的参数 bean 上;要绑定的值来源于:
A) @SessionAttributes 启用的 attribute 对象上;
B) @ModelAttribute 用于方法上时指定的 model 对象;
C) 上述两种情况都没有时,new 一个需要绑定的 bean 对象,然后把 request 中按名称对应的方式把值绑定到 bean 中。
用到方法上 @ModelAttribute 的示例代码:
// Add one attribute // The return value of the method is added to the model under the name "account" // You can customize the name via @ModelAttribute("myAccount") @ModelAttribute public Account addAccount(@RequestParam String number) { return accountManager.findAccount(number); }
这种方式实际的效果就是在调用 @RequestMapping 的方法之前,为 request 对象的 model 里 put(“account”, Account);
用在参数上的 @ModelAttribute 示例代码:
@RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(@ModelAttribute Pet pet) { }
首先查询 @SessionAttributes 有无绑定的 Pet 对象,若没有则查询 @ModelAttribute 方法层面上是否绑定了 Pet 对象,若没有则将 URI template 中的值按对应的名称绑定到 Pet 对象的各属性上。
补充讲解:
问题: 在不给定注解的情况下,参数是怎样绑定的?
通过分析 AnnotationMethodHandlerAdapter 和 RequestMappingHandlerAdapter 的源代码发现,方法的参数在不给定参数的情况下:
若要绑定的对象时简单类型: 调用 @RequestParam 来处理的。
若要绑定的对象时复杂类型: 调用 @ModelAttribute 来处理的。
这里的简单类型指 Java 的原始类型 (boolean, int 等)、原始类型对象(Boolean, Int 等)、String、Date 等 ConversionService 里可以直接 String 转换成目标对象的类型;
RequestMappingHandlerAdapter 中使用的参数绑定,代码稍微有些不同,有兴趣的可以分析下,最后处理的结果都是一样的。
示例:
@RequestMapping ({"/", "/home"}) public String showHomePage(String key){ logger.debug("key="+key);
return "home";
}
这种情况下,就调用默认的 @RequestParam 来处理。
@RequestMapping (method = RequestMethod.POST) public String doRegister(User user){ if(logger.isDebugEnabled()){ logger.debug("process url[/user], method[post] in "+getClass()); logger.debug(user); } return "user"; }
这种情况下,就调用 @ModelAttribute 来处理。