王某某的笔记

记录我的编程之路

环境:
系统:CentOS 7
Redis: 4.0.1

测试记录,按照操作流程随手记


创建几个测试目录,在里面放redis.conf文件

1
2
3
4
5
6
7
8
9
$ ll
总用量 1708
drwxr-xr-x 2 root root 23 5月 31 10:30 d6379
drwxr-xr-x 2 root root 23 5月 31 10:31 d6380
drwxr-xr-x 2 root root 23 5月 31 10:32 d6381
drwxr-xr-x 2 root root 23 5月 31 10:32 d6382
drwxr-xr-x 2 root root 23 5月 31 10:32 d6383
drwxr-xr-x 2 root root 23 5月 31 10:32 d6384
drwxr-xr-x 2 root root 23 5月 31 10:33 d6385

文件内容如下,每个文件的端口不同,最好与目录对应,方便记忆

1
2
3
4
5
port 7000
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes

启动6个redis实例
注意要分别进入到每个目录中启动,启动后会自动在目录下创建nodes.conf文件

1
2
3
4
5
6
7
8
cd d6379
/usr/local/redis/bin/redis-server redis.conf

cd ..
cd d6380
/usr/local/redis/bin/redis-server redis.conf
...
...

查看一下进程

1
2
3
4
5
6
7
8
# ps -ef | grep redis
root 9608 1 0 11:10 ? 00:00:00 /usr/local/redis/bin/redis-server 0.0.0.0:6379 [cluster]
root 9615 1 0 11:11 ? 00:00:00 /usr/local/redis/bin/redis-server 0.0.0.0:6380 [cluster]
root 9620 1 0 11:11 ? 00:00:00 /usr/local/redis/bin/redis-server 0.0.0.0:6381 [cluster]
root 9625 1 0 11:12 ? 00:00:00 /usr/local/redis/bin/redis-server 0.0.0.0:6382 [cluster]
root 9630 1 0 11:12 ? 00:00:00 /usr/local/redis/bin/redis-server 0.0.0.0:6383 [cluster]
root 9635 1 0 11:12 ? 00:00:00 /usr/local/redis/bin/redis-server 0.0.0.0:6384 [cluster]
root 9640 1 0 11:12 ? 00:00:00 /usr/local/redis/bin/redis-server 0.0.0.0:6385 [cluster]

使用这些实例来创建集群, 并为每个节点编写配置文件。

通过使用 Redis 集群命令行工具 redis-trib, 编写节点配置文件的工作可以非常容易地完成:redis-trib 位于 Redis 源码的 src 文件夹中,它是一个 Ruby 程序,这个程序通过向实例发送特殊命令来完成创建新集群,检查集群,或者对集群进行重新分片(reshared)等工作。

1
./redis-trib.rb create --replicas 1 127.0.0.1:6379 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 127.0.0.1:6385

报错:

1
/usr/bin/env: ruby: 没有那个文件或目录

安装ruby

1
yum install ruby

报错:

1
2
3
/usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require': cannot load such file -- redis (LoadError)
from /usr/share/rubygems/rubygems/core_ext/kernel_require.rb:55:in `require'
from ./redis-trib.rb:25:in `<main>'

使用RubyGems安装ruby脚本所需要的redis包

1
gem install redis

报错:

1
2
3
Fetching: redis-4.0.1.gem (100%)
ERROR: Error installing redis:
redis requires Ruby version >= 2.2.2.

看了下当前安装的ruby版本

1
2
# ruby --version
ruby 2.0.0p648 (2015-12-16) [x86_64-linux]

CentOS7 yum库中ruby的版本只支持到2.0.0,使用RVM(Ruby Version Manager)程序升级Ruby的版本
先安装RVM程序

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
curl -L get.rvm.io | bash -s stable 
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 194 100 194 0 0 308 0 --:--:-- --:--:-- --:--:-- 307
100 24361 100 24361 0 0 12772 0 0:00:01 0:00:01 --:--:-- 89410
Downloading https://github.com/rvm/rvm/archive/1.29.3.tar.gz
Downloading https://github.com/rvm/rvm/releases/download/1.29.3/1.29.3.tar.gz.asc
gpg: 已创建目录‘/root/.gnupg’
gpg: 新的配置文件‘/root/.gnupg/gpg.conf’已建立
gpg: 警告:在‘/root/.gnupg/gpg.conf’里的选项于此次运行期间未被使用
gpg: 钥匙环‘/root/.gnupg/pubring.gpg’已建立
gpg: 于 2017年09月11日 星期一 04时59分21秒 CST 创建的签名,使用 RSA,钥匙号 BF04FF17
gpg: 无法检查签名:没有公钥
Warning, RVM 1.26.0 introduces signed releases and automated check of signatures when GPG software found. Assuming you trust Michal Papis import the mpapis public key (downloading the signatures).

GPG signature verification failed for '/usr/local/rvm/archives/rvm-1.29.3.tgz' - 'https://github.com/rvm/rvm/releases/download/1.29.3/1.29.3.tar.gz.asc'! Try to install GPG v2 and then fetch the public key:

gpg2 --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3

or if it fails:

command curl -sSL https://rvm.io/mpapis.asc | gpg2 --import -

the key can be compared with:

https://rvm.io/mpapis.asc
https://keybase.io/mpapis

NOTE: GPG version 2.1.17 have a bug which cause failures during fetching keys from remote server. Please downgrade or upgrade to newer version (if available) or use the second method described above.

无法验证签名,按照提示下载公钥

说明:
gpg2 –recv-keys : 该命令从密钥服务器下载一个或多个公钥。每个key-id都是一个密钥ID。该命令需要使用keyserver选项来指定gpg应该从哪个keyserver下载密钥。

1
2
3
4
5
6
7
# gpg2 --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
gpg: 下载密钥‘D39DC0E3’,从 hkp 服务器 keys.gnupg.net
gpg: /root/.gnupg/trustdb.gpg:建立了信任度数据库
gpg: 密钥 D39DC0E3:公钥“Michal Papis (RVM signing) <mpapis@gmail.com>”已导入
gpg: 没有找到任何绝对信任的密钥
gpg: 合计被处理的数量:1
gpg: 已导入:1 (RSA: 1)

再次安装RVM

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
]# curl -L get.rvm.io | bash -s stable 
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 194 100 194 0 0 303 0 --:--:-- --:--:-- --:--:-- 302
100 24361 100 24361 0 0 11395 0 0:00:02 0:00:02 --:--:-- 41430
Downloading https://github.com/rvm/rvm/archive/1.29.3.tar.gz
Downloading https://github.com/rvm/rvm/releases/download/1.29.3/1.29.3.tar.gz.asc
gpg: 于 2017年09月11日 星期一 04时59分21秒 CST 创建的签名,使用 RSA,钥匙号 BF04FF17
gpg: 完好的签名,来自于“Michal Papis (RVM signing) <mpapis@gmail.com>”
gpg: 亦即“Michal Papis <michal.papis@toptal.com>”
gpg: 亦即“[jpeg image of size 5015]”
gpg: 警告:这把密钥未经受信任的签名认证!
gpg: 没有证据表明这个签名属于它所声称的持有者。
主钥指纹: 409B 6B17 96C2 7546 2A17 0311 3804 BB82 D39D C0E3
子钥指纹: 62C9 E5F4 DA30 0D94 AC36 166B E206 C29F BF04 FF17
GPG verified '/usr/local/rvm/archives/rvm-1.29.3.tgz'
Creating group 'rvm'

Installing RVM to /usr/local/rvm/
Installation of RVM in /usr/local/rvm/ is almost complete:

* First you need to add all users that will be using rvm to 'rvm' group,
and logout - login again, anyone using rvm will be operating with `umask u=rwx,g=rwx,o=rx`.

* To start using RVM you need to run `source /etc/profile.d/rvm.sh`
in all your open shell windows, in rare cases you need to reopen all shell windows.

设置下环境变量,在查看下rvm版本

1
2
3
[root@wwh214 redis]# source /etc/profile.d/rvm.sh
[root@wwh214 redis]# rvm --version
rvm 1.29.3 (latest) by Michal Papis, Piotr Kuczynski, Wayne E. Seguin [https://rvm.io]

使用RVM升级Ruby版本
查看rvm库中已知的ruby版本

1
2
3
4
5
6
7
8
9
10
11
12
13
$ rvm list known
# MRI Rubies
[ruby-]1.8.6[-p420]
[ruby-]1.8.7[-head] # security released on head
[ruby-]1.9.1[-p431]
[ruby-]1.9.2[-p330]
[ruby-]1.9.3[-p551]
[ruby-]2.0.0[-p648]
[ruby-]2.1[.10]
[ruby-]2.2[.7]
[ruby-]2.3[.4]
[ruby-]2.4[.1]
ruby-head

安装2.4版本的ruby

1
2
3
4
5
6
7
8
9
10
11
$ rvm install 2.4.1
Searching for binary rubies, this might take some time.
Found remote file https://rvm_io.global.ssl.fastly.net/binaries/centos/7/x86_64/ruby-2.4.1.tar.bz2
Checking requirements for centos.
Installing requirements for centos.
Installing required packages: libffi-devel, readline-devel, sqlite-devel, libyaml-devel............
Requirements installation successful.
ruby-2.4.1 - #configure
ruby-2.4.1 - #download
...
...

在查看一下

1
2
3
4
5
6
7
8
9
10
11
12
[root@wwh214 redis]# rvm list

rvm rubies

=* ruby-2.4.1 [ x86_64 ]

# => - current
# =* - current && default
# * - default

[root@wwh214 redis]# ruby --version
ruby 2.4.1p111 (2017-03-22 revision 58053) [x86_64-linux]

之前通过yum安装的ruby并没有在rvm管理中,将其卸载掉

1
yum remove ruby

再次使用RubyGems安装ruby脚本所需要的redis包

1
2
3
4
5
6
7
# gem install redis
Fetching: redis-4.0.1.gem (100%)
Successfully installed redis-4.0.1
Parsing documentation for redis-4.0.1
Installing ri documentation for redis-4.0.1
Done installing documentation for redis after 0 seconds
1 gem installed

再次使用命令创建集群

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
./redis-trib.rb create --replicas 1 127.0.0.1:6379 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 127.0.0.1:6385
>>> Creating cluster
>>> Performing hash slots allocation on 6 nodes...
Using 3 masters:
127.0.0.1:6379
127.0.0.1:6381
127.0.0.1:6382
Adding replica 127.0.0.1:6383 to 127.0.0.1:6379
Adding replica 127.0.0.1:6384 to 127.0.0.1:6381
Adding replica 127.0.0.1:6385 to 127.0.0.1:6382
M: b2ba9914a49563dabdf022c1f8031577e4b30e44 127.0.0.1:6379
slots:0-5460 (5461 slots) master
M: d38eaf2cf3a5ed2a4d4b0bafcb174748fac009dc 127.0.0.1:6381
slots:5461-10922 (5462 slots) master
M: b54907e579a7daf1fb7155a4f25f5ae485374636 127.0.0.1:6382
slots:10923-16383 (5461 slots) master
S: 602191b3b062238e10c1284343b4677b51fd31a3 127.0.0.1:6383
replicates b2ba9914a49563dabdf022c1f8031577e4b30e44
S: aa4feef4e0c426ead799d4b34288cb3c5b9baa58 127.0.0.1:6384
replicates d38eaf2cf3a5ed2a4d4b0bafcb174748fac009dc
S: a642a61c228ea354d94c695ca9dcaa048750590e 127.0.0.1:6385
replicates b54907e579a7daf1fb7155a4f25f5ae485374636
Can I set the above configuration? (type 'yes' to accept):

这个命令在这里用于创建一个新的集群, 选项–replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。之后跟着的其他参数则是这个集群实例的地址列表,3个master3个slave,redis-trib 会打印出一份预想中的配置给你看, 如果你觉得没问题的话, 就可以输入 yes , redis-trib 就会将这份配置应用到集群当中,让各个节点开始互相通讯,最后可以得到如下信息:

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
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join...
>>> Performing Cluster Check (using node 127.0.0.1:6379)
M: b2ba9914a49563dabdf022c1f8031577e4b30e44 127.0.0.1:6379
slots:0-5460 (5461 slots) master
1 additional replica(s)
S: a642a61c228ea354d94c695ca9dcaa048750590e 127.0.0.1:6385
slots: (0 slots) slave
replicates b54907e579a7daf1fb7155a4f25f5ae485374636
S: aa4feef4e0c426ead799d4b34288cb3c5b9baa58 127.0.0.1:6384
slots: (0 slots) slave
replicates d38eaf2cf3a5ed2a4d4b0bafcb174748fac009dc
M: d38eaf2cf3a5ed2a4d4b0bafcb174748fac009dc 127.0.0.1:6381
slots:5461-10922 (5462 slots) master
1 additional replica(s)
M: b54907e579a7daf1fb7155a4f25f5ae485374636 127.0.0.1:6382
slots:10923-16383 (5461 slots) master
1 additional replica(s)
S: 602191b3b062238e10c1284343b4677b51fd31a3 127.0.0.1:6383
slots: (0 slots) slave
replicates b2ba9914a49563dabdf022c1f8031577e4b30e44
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

再对比一下node.conf配置文件:
以d6381目录为例子,redis启动时创建的缺省文件是:

1
2
d38eaf2cf3a5ed2a4d4b0bafcb174748fac009dc :0@0 myself,master - 0 0 0 connected
vars currentEpoch 0 lastVoteEpoch 0

运行脚本之后,文件变成了:

1
2
3
4
5
6
7
602191b3b062238e10c1284343b4677b51fd31a3 127.0.0.1:6383@16383 slave b2ba9914a49563dabdf022c1f8031577e4b30e44 0 1527749631645 4 connected
d38eaf2cf3a5ed2a4d4b0bafcb174748fac009dc 127.0.0.1:6381@16381 myself,master - 0 1527749631000 2 connected 5461-10922
b54907e579a7daf1fb7155a4f25f5ae485374636 127.0.0.1:6382@16382 master - 0 1527749631144 3 connected 10923-16383
aa4feef4e0c426ead799d4b34288cb3c5b9baa58 127.0.0.1:6384@16384 slave d38eaf2cf3a5ed2a4d4b0bafcb174748fac009dc 0 1527749631244 5 connected
b2ba9914a49563dabdf022c1f8031577e4b30e44 127.0.0.1:6379@16379 master - 0 1527749630140 1 connected 0-5460
a642a61c228ea354d94c695ca9dcaa048750590e 127.0.0.1:6385@16385 slave b54907e579a7daf1fb7155a4f25f5ae485374636 0 1527749631000 6 connected
vars currentEpoch 6 lastVoteEpoch 0

使用redis-cli登录测试一下:

1
2
3
4
5
6
7
8
9
# /usr/local/redis/bin/redis-cli
127.0.0.1:6379> info
......
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6383,state=online,offset=742,lag=0
......

在往集群中某个redis中放入值的时候会计算key的hash所 属的槽,然后再计算槽是不是属于当前实例,如果不是会返回error,并且返回正确的节点地址和端口,如下:

1
2
3
4
5
127.0.0.1:6379> put aaa aaa
(error) ERR unknown command 'put'
127.0.0.1:6379> set aaa aaa
(error) MOVED 10439 127.0.0.1:6381

而在6381上可以设值成功

1
2
3
4
# /usr/local/redis/bin/redis-cli -p 6381
127.0.0.1:6381> set aaa aaa
OK
127.0.0.1:6381>

不同的键值对存在不同的节点上,keys * 只会返回当前节点的所有key

配置Spring一般包括3种方式

  • 基于XML的配置文件
  • 基于注解的配置
  • 基于Java的配置

基于XML的配置

这个很常见,比如:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version = "1.0" encoding = "UTF-8"?>

<beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<bean id = "helloWorld" class = "com.tutorialspoint.HelloWorld">
<property name = "message" value = "Hello World!"/>
</bean>

</beans>

这种配置可以通过容器来加载,也可以通过FileSystemXmlApplicationContext、ClassPathXmlApplicationContext 等来加载,如:

1
2
3
AbstractApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

context.getBean("helloWorld");

使用注解

配置了<context:annotation-config />开启注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version = "1.0" encoding = "UTF-8"?>

<beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xmlns:context = "http://www.springframework.org/schema/context"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">

<context:annotation-config/>
<!-- bean definitions go here -->

</beans>
注解 说明
@Required 用于bean属性的setter方法
@Autowired 可以应用于bean属性setter方法、非setter方法、构造函数和属性
@Qualifier @Qualifier注解和@Autowired一起可以通过指定连接哪个bean来消除混淆。
JSR-250 Annotations Spring支持基于JSR-250的注解,包括@Resource、@PostConstruct和@PreDestroy注解。

基于java的方式

@Configuration & @Bean

使用@Configuration注解一个类表明该类可以被Spring IoC容器用作bean定义的来源。该@Bean注解告诉Spring与@Bean注释的方法将返回应注册为Spring应用程序上下文的bean的对象。最简单的@Configuration类如下:

1
2
3
4
5
6
7
8
9
10
package com.tutorialspoint;
import org.springframework.context.annotation.*;

@Configuration
public class HelloWorldConfig {
@Bean
public HelloWorld helloWorld(){
return new HelloWorld();
}
}

以上代码将等同于以下XML配置 -

1
2
3
<beans>
<bean id = "helloWorld" class = "com.tutorialspoint.HelloWorld" />
</beans>

这里,方法名用@Bean作为bean ID进行注释,并创建并返回实际的bean。你的配置类可以拥有多个@Bean的声明。一旦你的配置类被定义,你可以使用AnnotationConfigApplicationContext加载并提供给Spring容器,如下所示:

1
2
3
4
5
6
7
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(HelloWorldConfig.class);

HelloWorld helloWorld = ctx.getBean(HelloWorld.class);
helloWorld.setMessage("Hello World!");
helloWorld.getMessage();
}

可以按如下方式加载各种配置类:

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();

ctx.register(AppConfig.class, OtherConfig.class);
ctx.register(AdditionalConfig.class);
ctx.refresh();

MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}

@Import

@Import注解允许从另一个配置类加载@Bean定义
如配置类A

1
2
3
4
5
6
7
@Configuration
public class ConfigA {
@Bean
public A a() {
return new A();
}
}

可以在另一个Bean声明中导入上面的Bean声明,如下所示

1
2
3
4
5
6
7
8
@Configuration
@Import(ConfigA.class)
public class ConfigB {
@Bean
public B a() {
return new A();
}
}

现在,不需要在实例化上下文时指定ConfigA.class和ConfigB.class,只需要提供ConfigB,如下所示

1
2
3
4
5
6
7
8
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

// now both beans A and B will be available...
A a = ctx.getBean(A.class);
B b = ctx.getBean(B.class);
}

生命周期回调

@Bean注解支持指定任意初始化和销毁回调方法,就像Spring XML的bean元素的init-method和destroy-method属性一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Foo {
public void init() {
// initialization logic
}
public void cleanup() {
// destruction logic
}
}
@Configuration
public class AppConfig {
@Bean(initMethod = "init", destroyMethod = "cleanup" )
public Foo foo() {
return new Foo();
}
}

指定Bean范围

默认范围是单例,但您可以使用@Scope注释覆盖它,如下所示

1
2
3
4
5
6
7
8
@Configuration
public class AppConfig {
@Bean
@Scope("prototype")
public Foo foo() {
return new Foo();
}
}

Spring的核心是ApplicationContext,它管理bean的整个生命周期。ApplicationContext在加载Bean时发布某些类型的事件。例如,ContextStartedEvent在上下文启动时发布,而ContextStoppedEvent在上下文停止时发布。

ApplicationContext中的事件处理通过ApplicationEvent类和ApplicationListener接口提供。如果一个bean实现了ApplicationListener,那么每当一个ApplicationEvent被发布到ApplicationContext时,该bean就会被通知。

Spring提供了以下标准事件

事件 描述
ContextRefreshedEvent 此事件在ApplicationContext初始化或刷新时发布。这也可以使用ConfigurableApplicationContext接口上的refresh()方法引发。
ContextStartedEvent 使用ConfigurableApplicationContext接口上的start()方法启动ApplicationContext时,会发布此事件。
ContextStoppedEvent 使用ConfigurableApplicationContext接口上的stop()方法停止ApplicationContext时发布此事件。
ContextClosedEvent 使用ConfigurableApplicationContext接口上的close()方法关闭ApplicationContext时发布此事件。一个关闭的上下文达到其生命的尽头,它不能被刷新或重新启动。
RequestHandledEvent 这是一个特定于web的事件,它告诉所有bean已经提供HTTP请求。

Spring的事件处理是单线程的,一个事件被发布,直到所有的接收者都处理完这个消息之前,进程会被阻塞,流程将不会继续。因此,如果要使用事件处理,应在设计应用程序时小心谨慎。

听上下文事件

要监听上下文事件,bean应该实现只有一个方法onApplicationEvent()的ApplicationListener接口。

HelloWorld.java文件

1
2
3
4
5
6
7
8
9
10
11
12
package com.tutorialspoint;

public class HelloWorld {
private String message;

public void setMessage(String message){
this.message = message;
}
public void getMessage(){
System.out.println("Your Message : " + message);
}
}

CStartEventHandler.java文件

1
2
3
4
5
6
7
8
9
10
11
12
package com.tutorialspoint;

import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextStartedEvent;

public class CStartEventHandler
implements ApplicationListener<ContextStartedEvent>{

public void onApplicationEvent(ContextStartedEvent event) {
System.out.println("ContextStartedEvent Received");
}
}

CStopEventHandler.java文件

1
2
3
4
5
6
7
8
9
10
11
12
package com.tutorialspoint;

import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextStoppedEvent;

public class CStopEventHandler
implements ApplicationListener<ContextStoppedEvent>{

public void onApplicationEvent(ContextStoppedEvent event) {
System.out.println("ContextStoppedEvent Received");
}
}

MainApp.java文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.tutorialspoint;

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
public static void main(String[] args) {
ConfigurableApplicationContext context =
new ClassPathXmlApplicationContext("Beans.xml");

// Let us raise a start event.
context.start();

HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
obj.getMessage();

// Let us raise a stop event.
context.stop();
}
}

配置文件Beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version = "1.0" encoding = "UTF-8"?>

<beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<bean id = "helloWorld" class = "com.tutorialspoint.HelloWorld">
<property name = "message" value = "Hello World!"/>
</bean>

<bean id = "cStartEventHandler" class = "com.tutorialspoint.CStartEventHandler"/>
<bean id = "cStopEventHandler" class = "com.tutorialspoint.CStopEventHandler"/>

</beans>

程序运行结果

1
2
3
ContextStartedEvent Received
Your Message : Hello World!
ContextStoppedEvent Received

自定义事件

CustomEvent.java文件

1
2
3
4
5
6
7
8
9
10
11
12
package com.tutorialspoint;

import org.springframework.context.ApplicationEvent;

public class CustomEvent extends ApplicationEvent{
public CustomEvent(Object source) {
super(source);
}
public String toString(){
return "My Custom Event";
}
}

CustomEventPublisher.java文件

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

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;

public class CustomEventPublisher implements ApplicationEventPublisherAware {
private ApplicationEventPublisher publisher;

public void setApplicationEventPublisher (ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void publish() {
CustomEvent ce = new CustomEvent(this);
publisher.publishEvent(ce);
}
}

CustomEventHandler.java文件

1
2
3
4
5
6
7
8
9
package com.tutorialspoint;

import org.springframework.context.ApplicationListener;

public class CustomEventHandler implements ApplicationListener<CustomEvent> {
public void onApplicationEvent(CustomEvent event) {
System.out.println(event.toString());
}
}

MainApp.java文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.tutorialspoint;

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
public static void main(String[] args) {
ConfigurableApplicationContext context =
new ClassPathXmlApplicationContext("Beans.xml");

CustomEventPublisher cvp =
(CustomEventPublisher) context.getBean("customEventPublisher");

cvp.publish();
cvp.publish();
}
}

配置文件Beans.xml

1
2
3
4
5
6
7
8
9
10
11
<?xml version = "1.0" encoding = "UTF-8"?>

<beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<bean id = "customEventHandler" class = "com.tutorialspoint.CustomEventHandler"/>
<bean id = "customEventPublisher" class = "com.tutorialspoint.CustomEventPublisher"/>

</beans>

运行结果

1
2
My Custom Event
My Custom Event

从这个地方随手翻译过来的
https://www.tutorialspoint.com/spring/index.htm

概述

Spring是企业Java最流行的应用程序开发框架。全球数以百万计的开发人员使用Spring Framework创建高性能,易于测试和可重用的代码。

Spring框架是一个开源的Java平台。它最初由Rod Johnson编写,并于2003年6月首次在Apache 2.0许可下发布。

Spring框架的核心功能可以用于开发任何Java应用程序,但是在Java EE平台之上还可以扩展构建Web应用程序。Spring框架的目标是通过启用基于POJO的编程模型来使J2EE开发更易于使用,并促进良好的编程实践。

依赖注入(DI)

Spring最认常用技术是控制反转的依赖注入(DI)。该控制反转(IOC)是一个笼统的概念,它可以在许多不同的方式来表达。依赖注入仅仅是控制反转的一个具体例子。

面向切面编程(AOP)

Spring的关键组件之一是面向切面编程(AOP)框架。跨越应用程序多个点的功能称为横切关注点,这些横切关注点在概念上与应用程序的业务逻辑分离。有各种常见的很好的例子,包括日志记录,声明式事务,安全性,缓存等等。


框架

Spring框架提供了大约20个模块,可以根据应用需求使用。

核心容器

核心容器由Core,Beans,Context和Expression Language模块组成

  • Core 提供了框架的基本部分,包括IOC和依赖注入特征。
  • Beans 提供的BeanFactory,是工厂模式的一个复杂实现。
  • Context 上下文模块构建在Core和Beans模块提供的基础之上,它是访问定义和配置的任何对象的媒介。ApplicationContext接口是上下文模块的焦点。
  • Expression Language SpEL模块提供了一种强大的表达式语言,用于在运行时查询和操作对象图。

数据访问/集成

数据访问/集成层由JDBC,ORM,OXM,JMS和事务模块组成

  • JDBC模块提供了JDBC抽象层,消除了对冗长的JDBC相关编码的需要。
  • ORM模块为流行的对象-关系映射api提供集成层,包括JPA、JDO、Hibernate和iBatis。
  • OXM模块提供了一个抽象层,支持JAXB、Castor、XMLBeans、JiBX和XStream的对象/XML映射实现。
  • Java消息传递服务JMS模块包含用于生成和使用消息的功能。
  • 事务模块支持实现特殊接口和所有pojo的类的编程和声明式事务管理。

WEB

Web层由Web,Web-MVC,Web-Socket和Web-Portlet模块组成

  • Web模块提供面向Web的基本集成特性,如多部分文件上传功能,以及使用servlet侦听器和面向Web的应用程序上下文初始化IoC容器。
  • Web-MVC模块包含用于web应用程序的Spring的模型-视图-控制器(MVC)实现。
  • Web-Socket模块支持web应用程序中的客户端和服务器之间的基于websocket的双向通信。
  • Web-Portlet模块提供了将在portlet环境中使用的MVC实现,并镜像了web servlet模块的功能。

杂项

重要模块像AOP,Aspects(切面),仪表,消息和测试模块等

Bean定义

构成应用程序主干和由Spring IoC容器管理的对象称为bean。bean是由Spring IoC容器实例化、组装和管理的对象。这些bean是用你提供给容器的配置元数据创建的。例如,XML 定义的形式

配置一个bean容器需要知道如下信息:

  • 如何创建一个bean
  • Bean的生命周期细节
  • Bean的依赖关系
属性 说明
class 该属性是强制性的,指定了用于创建bean的类。
name 该属性唯一的指定了bean标识符。在基于XML的配置元数据中,使用id (and/or)name属性来指定bean标识符。
scope 指定了从特定的bean定义创建的对象的范围
constructor-arg 构造参数,用于注入依赖关系
properties 属性,用于注入依赖关系
autowiring mode 自动装配模式,用于注入依赖关系
lazy-initialization mode 延时加载,告诉IoC容器在第一次请求时创建一个bean实例,而不是在启动时
initialization method 初始化方法,在bean的所有必要属性设置之后立即调用的回调方面
destruction method 销毁方法,在bean的容器被销毁时要调用的回调函数

Spring 配置

配置Spring一般包括3中方式

  • 基于XML的配置文件
  • 基于注解的配置
  • 基于Java的配置

基于XML的配置文件示例:

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
<?xml version = "1.0" encoding = "UTF-8"?>

<beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<!-- A simple bean definition -->
<bean id = "..." class = "...">
<!-- collaborators and configuration for this bean go here -->
</bean>

<!-- A bean definition with lazy init set on -->
<bean id = "..." class = "..." lazy-init = "true">
<!-- collaborators and configuration for this bean go here -->
</bean>

<!-- A bean definition with initialization method -->
<bean id = "..." class = "..." init-method = "...">
<!-- collaborators and configuration for this bean go here -->
</bean>

<!-- A bean definition with destruction method -->
<bean id = "..." class = "..." destroy-method = "...">
<!-- collaborators and configuration for this bean go here -->
</bean>

<!-- more bean definitions go here -->

</beans>

Bean的范围

定义一个时,你可以选择为该bean声明一个范围。例如,为了强制Spring在每次需要时产生一个新的bean实例,你应该声明bean的scope属性是prototype。同样,如果你希望Spring在每次需要时都返回相同的bean实例,你应该声明bean的scope属性为singleton

Spring框架支持以下五个范围,其中三个仅在您使用Web感知的ApplicationContext时才可用。

范围 说明
singleton 单实例,每次都使用同一个bean(默认值),对象创建好之后会被缓存,
prototype 多实例,每次都返回新的实例
request 在一次HTTP请求中使用相同实例。
只有在Web感知的Spring ApplicationContext的上下文中才有效。
session 在一个HTTP会话中使用相同实例。
只有在Web感知的Spring ApplicationContext的上下文中才有效。
global-session 在一个全局HTTP会话中使用相同实例。
只有在Web感知的Spring ApplicationContext的上下文中才有效。

Bean的生命周期

初始化回调

1
2
3
4
5
public class ExampleBean implements InitializingBean {
public void afterPropertiesSet() {
// do some initialization work
}
}

对于基于XML的配置元数据,可以使用init-method属性来指定具有void无参数签名的方法的名称。例如:

1
2
3
4
5
6
7
<bean id = "exampleBean" class = "examples.ExampleBean" init-method = "init"/>

public class ExampleBean {
public void init() {
// do some initialization work
}
}

销毁回调

1
2
3
4
5
public class ExampleBean implements DisposableBean {
public void destroy() {
// do some destruction work
}
}

对于基于XML的配置元数据,可以使用destroy-method属性来指定具有void无参数签名的方法的名称。例如:

1
2
3
4
5
6
7
<bean id = "exampleBean" class = "examples.ExampleBean" destroy-method = "destroy"/>

public class ExampleBean {
public void destroy() {
// do some destruction work
}
}

默认初始化和销毁方法

如果你的bean有太多具有相同名称的初始化和/或销毁方法,则不需要在每个单独的bean上声明init-method和destroy-method。这种情况可以使用元素上的default-init-method和default-destroy-method属性来配置,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
<beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
default-init-method = "init"
default-destroy-method = "destroy">

<bean id = "..." class = "...">
<!-- collaborators and configuration for this bean go here -->
</bean>

</beans>

Bean Post Processors

BeanPostProcessor接口定义了回调方法,你可以实现这些方法来提供您自己的实例化逻辑、依赖解析逻辑等。还可以在Spring容器完成实例化、配置和初始化bean之后,通过插入一个或多个BeanPostProcessor实现来实现一些自定义逻辑。
可以配置多个BeanPostProcessor接口,并且您可以通过设置order属性来控制这些BeanPostProcessor接口执行的顺序。

ApplicationContext自动检测由BeanPostProcessor接口实现定义的任何bean,并将这些bean注册为postprocessor,然后容器在创建bean时相应地调用这些bean。

HelloWorld.java文件的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.tutorialspoint;

public class HelloWorld {
private String message;

public void setMessage(String message){
this.message = message;
}
public void getMessage(){
System.out.println("Your Message : " + message);
}
public void init(){
System.out.println("Bean is going through init.");
}
public void destroy(){
System.out.println("Bean will destroy now.");
}
}

InitHelloWorld.java文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.tutorialspoint;

import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.BeansException;

public class InitHelloWorld implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {

System.out.println("BeforeInitialization : " + beanName);
return bean; // you can return any other object as well
}
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {

System.out.println("AfterInitialization : " + beanName);
return bean; // you can return any other object as well
}
}

Beans.xml 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version = "1.0" encoding = "UTF-8"?>

<beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<bean id = "helloWorld" class = "com.tutorialspoint.HelloWorld"
init-method = "init" destroy-method = "destroy">
<property name = "message" value = "Hello World!"/>
</bean>

<bean class = "com.tutorialspoint.InitHelloWorld" />

</beans>

MainApp.java文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.tutorialspoint;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");

HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
obj.getMessage();
context.registerShutdownHook();
}
}

执行结果:

1
2
3
4
5
BeforeInitialization : helloWorld
Bean is going through init.
AfterInitialization : helloWorld
Your Message : Hello World!
Bean will destroy now.

Bean定义继承

bean定义可以包含许多配置信息,包括构造函数参数、属性值和容器特定的信息,如初始化方法、静态工厂方法名称等。

子bean定义从父定义继承配置数据。子定义可以覆盖一些值,或者根据需要添加其他值。
Spring Bean定义继承与Java类继承没有关系,但是继承概念是相同的。可以将父bean定义定义定义为模板,其他子bean可以从父bean继承所需的配置。

当使用基于xml的配置元数据时,您通过使用父属性指定子bean定义,指定父bean作为该属性的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version = "1.0" encoding = "UTF-8"?>

<beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<bean id = "helloWorld" class = "com.tutorialspoint.HelloWorld">
<property name = "message1" value = "Hello World!"/>
<property name = "message2" value = "Hello Second World!"/>
</bean>

<bean id =" helloIndia" class = "com.tutorialspoint.HelloIndia" parent = "helloWorld">
<property name = "message1" value = "Hello India!"/>
<property name = "message3" value = "Namaste India!"/>
</bean>
</beans>

定义模板

定义Bean定义模板时,不应指定类属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version = "1.0" encoding = "UTF-8"?>

<beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<bean id = "beanTeamplate" abstract = "true">
<property name = "message1" value = "Hello World!"/>
<property name = "message2" value = "Hello Second World!"/>
<property name = "message3" value = "Namaste India!"/>
</bean>

<bean id = "helloIndia" class = "com.tutorialspoint.HelloIndia" parent = "beanTeamplate">
<property name = "message1" value = "Hello India!"/>
<property name = "message3" value = "Namaste India!"/>
</bean>

</beans>

父bean不能自行实例化,因为它是不完整的,并且它也明确标记为抽象。当定义像这样抽象时,它只能用作纯模板bean定义,作为子定义的父定义。

依赖注入

依赖注入主要包括基于构造函数的依赖注入和基于Setter的依赖注入

基于setter的注入但使用内部bean的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version = "1.0" encoding = "UTF-8"?>

<beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<!-- Definition for textEditor bean using inner bean -->
<bean id = "textEditor" class = "com.tutorialspoint.TextEditor">
<property name = "spellChecker">
<bean id = "spellChecker" class = "com.tutorialspoint.SpellChecker"/>
</property>
</bean>

</beans>

注入集合

包括list、set、map、props

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
<?xml version = "1.0" encoding = "UTF-8"?>

<beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<!-- Definition for javaCollection -->
<bean id = "javaCollection" class = "com.tutorialspoint.JavaCollection">

<!-- results in a setAddressList(java.util.List) call -->
<property name = "addressList">
<list>
<value>INDIA</value>
<value>Pakistan</value>
<value>USA</value>
<value>USA</value>
</list>
</property>

<!-- results in a setAddressSet(java.util.Set) call -->
<property name = "addressSet">
<set>
<value>INDIA</value>
<value>Pakistan</value>
<value>USA</value>
<value>USA</value>
</set>
</property>

<!-- results in a setAddressMap(java.util.Map) call -->
<property name = "addressMap">
<map>
<entry key = "1" value = "INDIA"/>
<entry key = "2" value = "Pakistan"/>
<entry key = "3" value = "USA"/>
<entry key = "4" value = "USA"/>
</map>
</property>

<!-- results in a setAddressProp(java.util.Properties) call -->
<property name = "addressProp">
<props>
<prop key = "one">INDIA</prop>
<prop key = "one">INDIA</prop>
<prop key = "two">Pakistan</prop>
<prop key = "three">USA</prop>
<prop key = "four">USA</prop>
</props>
</property>
</bean>

</beans>

将bean引用注入为集合元素中,使引用和值混合在一起

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
<?xml version = "1.0" encoding = "UTF-8"?>

<beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<!-- Bean Definition to handle references and values -->
<bean id = "..." class = "...">

<!-- Passing bean reference for java.util.List -->
<property name = "addressList">
<list>
<ref bean = "address1"/>
<ref bean = "address2"/>
<value>Pakistan</value>
</list>
</property>

<!-- Passing bean reference for java.util.Set -->
<property name = "addressSet">
<set>
<ref bean = "address1"/>
<ref bean = "address2"/>
<value>Pakistan</value>
</set>
</property>

<!-- Passing bean reference for java.util.Map -->
<property name = "addressMap">
<map>
<entry key = "one" value = "INDIA"/>
<entry key = "two" value-ref = "address1"/>
<entry key = "three" value-ref = "address2"/>
</map>
</property>
</bean>

</beans>

要使用上面的bean定义,你需要定义你的setter方法,以便它们也能够处理引用。

注入空和空字符串值

空字符串作为值:

1
2
3
<bean id = "..." class = "exampleBean">
<property name = "email" value = ""/>
</bean>

传递一个NULL值

1
2
3
<bean id = "..." class = "exampleBean">
<property name = "email"><null/></property>
</bean>

等同于:exampleBean.setEmail(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
   <!-- redis配置 -->
<!-- jedis pool配置 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="${redis.maxTotal}" />
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="maxWaitMillis" value="${redis.maxWaitMillis}" />
<property name="testOnBorrow" value="${redis.testOnBorrow}" />
</bean>

<!-- Jedis ConnectionFactory -->
<bean id='jedisConnectionFactory'
class='org.springframework.data.redis.connection.jedis.JedisConnectionFactory'>
<property name="usePool" value="true"></property>
<property name="hostName" value="${redis.host}" />
<property name="port" value="${redis.port}" />
<property name="password" value="${redis.pass}" />
<property name="timeout" value="${redis.timeout}" />
<property name="database" value="${redis.default.db}"></property>
<constructor-arg index="0" ref="jedisPoolConfig" />
</bean>

<bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
</bean>

<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="valueSerializer">
<bean class="com.xxx.xxx.xxx.redis.serializer.JsonRedisSerializer" />
</property>

<property name="hashKeySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>

<property name="hashValueSerializer">
<bean class="com.xxx.xxx.xxx.redis.serializer.JsonRedisSerializer" />
</property>
</bean>

序列化操作类

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
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;

import com.alibaba.fastjson.JSON;

public class JsonRedisSerializer implements RedisSerializer<Object> {

private static final String CHARSET_NAME = "UTF-8";

private static final Logger logger = LoggerFactory.getLogger(JsonRedisSerializer.class);

@Override
public byte[] serialize(Object data) throws SerializationException {
if (data == null) {
return null;
}

try {
// 先转成String
String json = JSON.toJSONString(data);
// 包装
SerializerObject so = new SerializerObject();
so.setClassName(data.getClass().getName());
so.setJsonString(json);
// 再转
String serializerStr = JSON.toJSONString(so);

return serializerStr.getBytes(CHARSET_NAME);
} catch (Exception e) {
logger.error("RedisSerializer序列化异常", e);
throw new SerializationException("RedisSerializer序列化异常");
}
}

@Override
public Object deserialize(byte[] bytes) throws SerializationException {
if (bytes == null) {
return null;
}
try {
String serializerStr = new String(bytes, CHARSET_NAME);
SerializerObject deSerObj = JSON.parseObject(serializerStr, SerializerObject.class);

Class<?> t = Class.forName(deSerObj.getClassName());
return JSON.parseObject(deSerObj.getJsonString(), t);

} catch (Exception e) {
logger.error("RedisSerializer反序列化异常", e);
throw new SerializationException("RedisSerializer反序列化异常");
}
}

}

class SerializerObject {
private String className;
private String jsonString;

public String getClassName() {
return className;
}

public void setClassName(String className) {
this.className = className;
}

public String getJsonString() {
return jsonString;
}

public void setJsonString(String jsonString) {
this.jsonString = jsonString;
}

}

简单例子

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
@Service("redisService")
public class RedisServiceImpl implements RedisService {

@Resource(name = "stringRedisTemplate")
private RedisTemplate<String, String> template;

@Resource(name = "stringRedisTemplate")
private ListOperations<String, String> listOps;

@Resource(name = "stringRedisTemplate")
private HashOperations<String, String, String> hashOps;

@Resource(name = "stringRedisTemplate")
private SetOperations<String, String> setOps;

@Override
public void delKey(String key) {
template.delete(key);
}

@Override
public boolean hasKey(String key) {
return template.hasKey(key);
}

@Override
public Long setAdd(String key, String... values) {
return setOps.add(key, values);
// return template.boundSetOps(key).add(values);
}

@Override
public Long setSize(String key) {
return setOps.size(key);
// return template.boundSetOps(key).size();
}

@Override
public Long rightPushList(String key, String value) {
return listOps.rightPush(key, value);
// return template.boundListOps(key).rightPush(value);
}

@Override
public String leftPopList(String key) {
return listOps.leftPop(key);
// return template.boundListOps(key).leftPop();
}

@Override
public Long listSize(String key) {
return listOps.size(key);
// return template.boundListOps(key).size();
}
...
...

操作
ValueOperations Redis String/Value 操作
ListOperations Redis List 操作
SetOperations Redis Set 操作
ZSetOperations Redis Sort Set 操作
HashOperations Redis Hash 操作
约束
BoundValueOperations Redis String/Value key 约束
BoundListOperations Redis List key 约束
BoundSetOperations Redis Set key 约束
BoundZSetOperations Redis Sort Set key 约束
BoundHashOperations Redis Hash key 约束

交换器 Exchanger

Exchanger 是 Java 并发包中的一个同步工具类,位于 java.util.concurrent 包下。它提供了一个用于在两个线程之间交换数据的同步点。Exchanger 用于当两个线程希望在某个同步点交换数据时,可以使用它来实现数据交换。

Exchanger 的工作机制如下:

  • 两个线程通过调用 exchange 方法来到达同步点。
  • 第一个线程调用 exchange 方法时,会等待第二个线程也调用 exchange 方法。
  • 当两个线程都调用 exchange 方法时,它们会交换数据,即第一个线程将自己的数据传递给第二个线程,同时接收第二个线程的数据。

下面是一个简单的示例,展示了如何使用 Exchanger 在两个线程之间交换数据:

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

public class ExchangerExample {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();

// 线程A
new Thread(() -> {
try {
String dataA = "Data from A";
System.out.println("Thread A is exchanging data: " + dataA);
String receivedData = exchanger.exchange(dataA);
System.out.println("Thread A received: " + receivedData);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();

// 线程B
new Thread(() -> {
try {
String dataB = "Data from B";
System.out.println("Thread B is exchanging data: " + dataB);
String receivedData = exchanger.exchange(dataB);
System.out.println("Thread B received: " + receivedData);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
}
}


Exchanger 通常用于一些需要线程之间进行数据交换的场景,比如生产者-消费者模型中,当生产者和消费者希望交换缓冲区时,可以使用 Exchanger 来实现。

倒计时闩锁 CountDownLatch

CountDownLatch类位于java.util.concurrent包下,利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。

CountDownLatch类只提供了一个构造器:

1
public CountDownLatch(int count) {  };  //参数count为计数值

然后下面这3个方法是CountDownLatch类中最重要的方法:

1
2
3
4
public void await() throws InterruptedException { };   //调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { }; //和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public void countDown() { }; //将count值减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
public class Test {
public static void main(String[] args) {
final CountDownLatch latch = new CountDownLatch(2);

new Thread(){
public void run() {
try {
System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");
Thread.sleep(3000);
System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();

new Thread(){
public void run() {
try {
System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");
Thread.sleep(3000);
System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();

try {
System.out.println("等待2个子线程执行完毕...");
latch.await();
System.out.println("2个子线程已经执行完毕");
System.out.println("继续执行主线程");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

信号量 Semaphore

Semaphore可以控同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。

Semaphore类位于java.util.concurrent包下,它提供了2个构造器:

1
2
3
4
5
6
public Semaphore(int permits) {          //参数permits表示许可数目,即同时可以允许多少线程进行访问
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) { //这个多了一个参数fair表示是否是公平的,即等待时间越久的越先获取许可
sync = (fair)? new FairSync(permits) : new NonfairSync(permits);
}

重要的几个方法,首先是acquire()、release()方法:

1
2
3
4
public void acquire() throws InterruptedException {  }     //获取一个许可
public void acquire(int permits) throws InterruptedException { } //获取permits个许可
public void release() { } //释放一个许可
public void release(int permits) { } //释放permits个许可
1
2
3
4
public boolean tryAcquire() { };    //尝试获取一个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException { }; //尝试获取一个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false
public boolean tryAcquire(int permits) { }; //尝试获取permits个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException { }; //尝试获取permits个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false

例子:

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
public class Test {
public static void main(String[] args) {
int N = 8; //工人数
Semaphore semaphore = new Semaphore(5); //机器数目
for(int i=0;i<N;i++)
new Worker(i,semaphore).start();
}

static class Worker extends Thread{
private int num;
private Semaphore semaphore;
public Worker(int num,Semaphore semaphore){
this.num = num;
this.semaphore = semaphore;
}

@Override
public void run() {
try {
semaphore.acquire();
System.out.println("工人"+this.num+"占用一个机器在生产...");
Thread.sleep(2000);
System.out.println("工人"+this.num+"释放出机器");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

同步屏障 CyclicBarrier

CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。

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

static CyclicBarrier c = new CyclicBarrier(2);

public static void main(String[] args) {
new Thread(new Runnable() {

@Override
public void run() {
try {
c.await();
} catch (Exception e) {

}
System.out.println(1);
}
}).start();

try {
c.await();
} catch (Exception e) {

}
System.out.println(2);
}

}

.gitconfig 文件

Git的设置文件为.gitconfig,它可以在用户主目录下(全局配置),也可以在项目目录下(项目配置)。

1
2
3
4
5
6
# 显示当前的Git配置
$ git config --list

# 编辑Git配置文件
$ git config -e [--global]

设置提交代码时的用户信息

1
2
3
$ git config [--global] user.name "[name]"
$ git config [--global] user.email "[email address]"

1
2
3
git config --global user.name "wwh"
git config --global user.email "wangwen135@163.com"

自动转换回车换行的问题

1
git config --global core.autocrlf false

通过全局设置将其关闭,默认是关闭的

建议使用UTF-8编码,和Unix 风格的界定符

Eclipse 上的配置方法:

windows –> preferences –> general –> Workspace

1
2
3
Text file encoding = UTF-8

New text file line delimiter = Unix

git bash 下log 中文乱码的问题

在窗口上右键 > Option > Text > zh_CN > UTF-8

1
2
3
git config --global gui.encoding utf-8
git config --global i18n.commitencoding utf-8
git config --global i18n.logoutputencoding utf-8

配置了sshkey 还是需要输入密码的问题

检查项目的 .git/config 文件

1
2
[remote "origin"]
url = 192.168.1.91:/data/git/dap.git

修改为使用git协议:

1
2
[remote "origin"]
url = git@192.168.1.91:/data/git/dap.git

使用HTTP协议时自动记住用户名密码

1
2
3
4
5
6
git config --global credential.helper store

#
# 会在 .gitconfig 文件下增加以下内容:
[credential]
helper = store

会在home文件下生成 .git-credentials 文件(如无法自动创建需要手动创建),第一次输入完之后就会记录下来

如:

1
2
3
4
http://wwh:123456@220.231.228.87%3a8100

# 格式
# https://{username}:{password}@github.com

生成ssh key

1
2
ssh-keygen -t rsa -C "wangwen135@163.com"  



差异比较工具(用开发工具吧,这个不好使)

在合并代码的是经常会出现冲突,此时就需要一个解决冲突的图形画工具

二进制文件一般没法合并,直接选择使用哪边的

1
2
3
$ git checkout - -ours a.file
或者
$ git checkout - -theirs a.file

git mergetool

1
2
3
4
5
6
7
git mergetool

This message is displayed because 'merge.tool' is not configured.
See 'git mergetool --tool-help' or 'git help config' for more details.
'git mergetool' will now attempt to use one of the following tools:
opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc codecompare emerge vimdiff

这里安装 kdiff3
https://sourceforge.net/projects/kdiff3/files/

配置 mergetool.kdiff3.path 来设置kdif3的绝对路径

如果将 kdiff3 添加到Path中,则不需要配置

1
2
git config --global mergetool.kdiff3.path "D:/Program Files/KDiff3/kdiff3.exe"

配置全局默认的merge tool 为 kdiff3

1
2
3
4
5
6
7
8
9
10
11
git config --global merge.tool kdiff3


#退出后询问
git config --global mergetool.kdiff3.trustExitCode false


#让git mergetool不再生成备份文件(*.orig)
git config --global mergetool.keepBackup false


在有冲突时候使用 git mergetool

1
2
3
4
5
6
7
8
9
Administrator@wwh MINGW64 /d/git/tmp/sample (wwh|MERGING)
$ git mergetool
Merging:
merger.txt

Normal merge conflict for 'merger.txt':
{local}: modified file
{remote}: modified file

git difftool
1
2
3
4
git config --global diff.guitool kdiff3
git config --global difftool.kdiff3.path "D:/Program Files/KDiff3/kdiff3.exe"
git config --global difftool.kdiff3.trustExitCode false

0%