SpringBoot-高级

缓存,消息,检索,任务,安全,分布式,监控管理,部署

1. 缓存

Java Caching定义了5个接口:

  • CachingProvider

    创建,配置,获取,管理和控制多个CacheManager。一个应用可以在运行期间访问多个CachingProvider

  • CacheManager

    定义了创建,配置,获取和控制多个唯一命名的cache,这些cache存在于CacheManager的上下文中。一个CacheManager仅为一个CacheProvider拥有

  • Cache

    一个类似map的数据结构并临时存储以key为索引的值。一个cache仅被一个CacheManager所拥有

  • Entry

    一个存储在cache中的key-value中

  • Expiry

    每一存储在cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问,更新和删除。缓存有效期可以通过ExpiryPollicy设置

1.1. Maven依赖

https://www.javadoc.io/doc/javax.cache/cache-api/1.1.1/index.html

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>

<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
</dependency>

1.2. JSR107

在线:JSR107.pdf

下载:JSR107.pdf

1.3. 缓存注解

注解 解释
Cache 缓存接口
CacheManager 缓存管理器
@Cacheable 针对方法,根据方法参数对结果进行缓存
@CacheEvict 清空缓存
@CachePut 更新缓存
@EnableCaching 开启基于注解的缓存
keyGenerator 缓存数据时key生成策略
serialize 缓存数据时value序列化策略

1.3.1. @Cacheable 需要主类注解@EnableCaching

将方法的运行结果进行缓存,以后要相同结果时,直接从缓存中获取

cacheManager 管理多个Cache组件,对缓存的真正CRUD操作在Cache中,每一个缓存都有自己的名称

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
public @interface Cacheable {
//缓存Cache组件名
@AliasFor("cacheNames")
String[] value() default {};

@AliasFor("value")
String[] cacheNames() default {};

//缓存数据使用的key,默认使用方法参数值为key,方法返回值为value
//可以指定key为#id参数值,#root.args[0]第一个参数
String key() default "";

//key生成器,可以自己指定key生成器的组件id
//key/keyGenerator 指定只能二选一
String keyGenerator() default "";

//指定缓存管理器
String cacheManager() default "";
//指定解析器
String cacheResolver() default "";

//符合条件才缓存
String condition() default "";
//unless指定条件为true,不缓存
//如:取返回结果 #result==null不缓存
String unless() default "";
//异步模式
boolean sync() default false;
}

@EnableCaching注解在有main方法的类,启动注解

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

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@MapperScan(basePackages = "com.runaccpeted.mapper")
@EnableCaching
public class CachewebApplication {

public static void main(String[] args) {
SpringApplication.run(CachewebApplication.class, args);
}

}

编辑控制类方法,实现缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.runaccpeted.service.impl;

import com.runaccpeted.mapper.UserMapper;
import com.runaccpeted.pojo.User;
import com.runaccpeted.service.UserService;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class UserServiceImpl implements UserService {

@Resource
UserMapper userMapper;

@Cacheable(cacheNames = "user")
@Override
public User find(int id)
{
return userMapper.find(id);
}
}

1.3.1.1. key

1.3.1.2. KeyGenerator

创建自己的键生成器

generate方法定义

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
package com.runaccpeted.config;


import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.Method;
import java.util.Arrays;


@Configuration
public class CacheConfig {

@Bean(name = "myKeyGenerator")
public KeyGenerator keyGenerator(){
return new KeyGenerator(){

@Override
public Object generate(Object target, Method method, Object... params) {
//以方法名[参数] 为主键
return method.getName()+"["+ Arrays.asList(params).toString()+"]";
}
};

}
}

装配到缓存组件中

1
2
3
@Cacheable(cacheNames = "user",keyGenerator = "myKeyGenerator")

@Cacheable(cacheNames = "user",key = "#root.methodName+'['+#id+']'")

实现

1.3.1.3. condition

符合条件才进行缓存

condition=”#id>1 and #id<10” 1<id<10时缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.runaccpeted.service.impl;

import com.runaccpeted.mapper.UserMapper;
import com.runaccpeted.pojo.User;
import com.runaccpeted.service.UserService;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class UserServiceImpl implements UserService {

@Resource
UserMapper userMapper;

@Cacheable(cacheNames = "user",keyGenerator = "myKeyGenerator",condition="#id>1")
@Override
public User find(int id)
{
return userMapper.find(id);
}
}

1.3.1.4. unless

为true时不缓存

unless=”#id=2” id=2时不缓存

1.3.2. 原理

1.3.2.1. CacheAutoConfiguration

1.3.2.2. SimpleCacheConfiguration

1.3.2.3. ConcurrentMapCacheManager

默认加载SimpleCacheConfiguration,注册了一个CacheManager

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
class SimpleCacheConfiguration {

@Bean
ConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties,
CacheManagerCustomizers cacheManagerCustomizers) {
//获取和创建ConcurrentMapCacheManager类型的缓存组件,将数据保存在ConcurrentMap中
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
List<String> cacheNames = cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return cacheManagerCustomizers.customize(cacheManager);
}
}

public void setCacheNames(@Nullable Collection<String> cacheNames) {
if (cacheNames != null) {
for (String name : cacheNames) {
this.cacheMap.put(name, createConcurrentMapCache(name));
}
this.dynamic = false;
}
else {
this.dynamic = true;
}
}

1.3.3. 流程

  1. 查询Cache组件,按照cacheNames指定的名字获取 getCache(name)

    第一次获取缓存会自动创建Cache组件 createConcurrentMapCache(name)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {

    ConcurrentMap<String, Cache> cacheMap = new ConcurrentMap<>();

    public Cache getCache(String name) {
    Cache cache = this.cacheMap.get(name);
    if (cache == null && this.dynamic) {
    synchronized (this.cacheMap) {
    cache = this.cacheMap.get(name);
    if (cache == null) {
    cache = createConcurrentMapCache(name);
    this.cacheMap.put(name, cache);
    }
    }
    }
    return cache;
    }
    }
  2. 查找缓存中的内容,使用一个key,默认方法为参数,key按照某种策略生成,默认使用keyGenerator,使用SimpleKeyGenerator 没有key,new SimpleKey(),有一个参数,key=参数值,多个参数,new SimpleKey(params)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public static Object generateKey(Object... params) {
    if (params.length == 0) {
    //EMPTY = new SimpleKey();
    return SimpleKey.EMPTY;
    }
    if (params.length == 1) {
    Object param = params[0];
    if (param != null && !param.getClass().isArray()) {
    return param;
    }
    }
    return new SimpleKey(params);
    }
  3. 没有产生缓存就执行方法

  4. 将目标方法返回结果放入缓存中

1.3.4. @CachePut 更新

先执行方法,再将结果放入缓存,故每次都会访问数据库

以User对象为主键,返回结果为值

1
2
3
4
5
6
@CachePut(value = "user")
@Override
public User update(User user){
userMapper.update(user);
return user;
}

以user的id为主键 #result.id or #user.id

这样才能和find的id主键对应,更新的内容放入同一cache组件中,find查到的内容才是更新过的内容

1
2
3
4
5
6
@CachePut(value = "user",key = "#user.id")
@Override
public User update(User user){
userMapper.update(user);
return user;
}

1.3.5. @CacheEvict 清除缓存

1
2
3
4
5
@Override
@CacheEvict(value = "user")
public int delete(Integer id) {
return userMapper.delete(id);
}

CacheEvict中的属性

1
2
3
4
5
6
7
8
9
10
11
String[] value() default {};
String[] cacheNames() default {};
String key() default "";
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
String condition() default "";

boolean allEntries() default false;

boolean beforeInvocation() default false;

1.3.5.1. allEntries

清除缓存中所有数据 allEntries=true

1.3.5.2. beforeInvocation

在方法之前执行缓存,当方法中存在错误时有用

1.3.6. @Caching

组合操作

1
2
3
4
5
6
7
8
9
public @interface Caching {

Cacheable[] cacheable() default {};

CachePut[] put() default {};

CacheEvict[] evict() default {};

}

缓存主键分别用id,username,password

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Caching(
cacheable = {
@Cacheable(cacheNames = "user")
},
put={
@CachePut(value = "user",key = "#user.username"),
@CachePut(value = "user",key = "#user.password")
},
evict={}
)
public User updateEach(User user){
userMapper.update(user);
return user;
}

1.3.7. @CacheConfig

1
2
3
4
5
6
public @interface CacheConfig {
String[] cacheNames() default {};
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
}

整个类配置缓存名

1
2
3
@Service
@CacheConfig(cacheNames = "user")
public class UserServiceImpl implements UserService {}

1.4. redis

1.4.1. 树莓派 docker安装redis

https://hub.docker.com/r/arm32v7/redis/tags

1
2
3
4
5
6
7
8
9
10
11
12
root@pi:/# docker pull arm32v7/redis:5
root@pi:/# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
arm32v7/redis 5 9db682290662 10 hours ago 72.1MB
portainer/portainer latest 6cb2cfd64c93 4 months ago 63.7MB
hypriot/rpi-mysql latest 4f3cbdbc3bdb 20 months ago 209MB
root@pi:/# docker run --name redis -p 6380:6379 -d 9db682290662
root@pi:/# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
936bddacc6c4 9db682290662 "docker-entrypoint.s…" 8 seconds ago Up 5 seconds 0.0.0.0:6380->6379/tcp redis
root@pi:/# iptables -I INPUT -i eth0 -p tcp --dport 6380 -j ACCEPT
root@pi:/# iptables -I OUTPUT -o eth0 -p tcp --sport 6380 -j ACCEPT

1.4.2. linux docker安装redis

下载镜像 https://hub.docker.com/_/redis

开放6379 端口

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@localhost linux]# docker pull redis
[root@localhost linux]# docker run --name redis -p 6379:6379 -d redis
[root@localhost linux]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b85b53b3fe8f redis "docker-entrypoint..." 4 seconds ago Up 3 seconds 0.0.0.0:6379->6379/tcp redis
939ddacac964 portainer/portainer "/portainer" 3 days ago Up 5 minutes 0.0.0.0:9000->9000/tcp portainer
[root@localhost linux]#
[root@localhost linux]# firewall-cmd --zone=public --add-port=6379/tcp --permanent
success
[root@localhost linux]#
[root@localhost linux]# firewall-cmd --reload
success
[root@localhost linux]#

1.4.3. 开启连接

1
2
LearningtekiMacBook-Air:~ Learning$ redis-cli -h 192.168.0.104 -p 6379
192.168.0.104:6379>

客户端下载

https://www.7down.com/mac/233286.html

1.4.4. 常用命令

http://www.redis.cn/commands.html

添加key-value:set key value

附加key的value:append key value

获取value值:keys *

获取所有keys:get keys

删除key:del key

1.4.5. Maven依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

application.properties

1
spring.redis.host=192.168.0.104

1.4.6. StringRedisTemplate

使用RedisTemplate,StringRedisTemplate封装方法

字符串opsForValue

列表 opsForList

集合 opsForSet

散列 opsForHash

有序列表 opsForZSet

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

import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
class CachewebApplicationTests {

@Autowired
StringRedisTemplate redisTemplate;

@Test
void contextLoads() {
redisTemplate.opsForValue().set("a", "123");
}
}

测试数据

1
2
3
4
5
6
192.168.0.104:6379> keys *
1) "a"
192.168.0.104:6379>
192.168.0.104:6379> get a
"123"
192.168.0.104:6379>

1.4.7. RedisTemplate<Object, Object>

存入对象 org.springframework.data.redis.serializer.SerializationException: Cannot serialize; 对象必须序列化

1
public class User implements Serializable {}

存入redis的是 string 默认使用

存入对象时,默认使用Jdk序列化器

1
this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
1
2
3
4
5
6
7
192.168.0.104:6379> keys *
1) "a"
2) "\xac\xed\x00\x05sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x00\x01"
192.168.0.104:6379>
192.168.0.104:6379> get "\xac\xed\x00\x05sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x00\x01"
"\xac\xed\x00\x05sr\x00\x19com.runaccpeted.pojo.User\xf0V\xab\xca\x8a\xe3\x12\xe7\x02\x00\x03L\x00\x02idt\x00\x13Ljava/lang/Integer;L\x00\bpasswordt\x00\x12Ljava/lang/String;L\x00\busernameq\x00~\x00\x02xpsr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x00\x01t\x00\x03123t\x00\x01a"
192.168.0.104:6379>

1.4.7.1. jackson

1
2
3
4
5
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.10.0</version>
</dependency>

实例化ObjectMapper,调用writeValueAsString方法转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Autowired
StringRedisTemplate redisTemplate;
User user = new User();
user.setId(1);
user.setUsername("a");
user.setPassword("123");

ObjectMapper mapper = new ObjectMapper();
String str = null;
try {
str=mapper.writeValueAsString(user);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
System.out.println(str);
redisTemplate.opsForValue().set(user.getId()+"", str);

获取value

1
User user = mapper.readValue(str,User.class);

1.4.8. RedisCacheManager

底层对于对象采用jdk序列化,存入redis的是乱码

重写缓存管理方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
//键序列化器
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
//设置CacheManager的值序列化方式为json序列化,加入@Class属性
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();

// 配置序列化(解决乱码的问题)
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jsonRedisSerializer))
.disableCachingNullValues(); //禁用缓存空值,不缓存null校验

RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}

查询

1
2
3
4
5
6
@Cacheable(cacheNames = "user")
@Override
public User find(Integer id)
{
return userMapper.find(id);
}

redis中的key-value

1
2
3
192.168.0.104:6379> get "user::1"
"{\"@class\":\"com.runaccpeted.pojo.User\",\"id\":1,\"username\":\"abc\",\"password\":\"123\"}"
192.168.0.104:6379>

2. RabbitMQ

2.1. docker安装rabbitmq–linux

1
[root@localhost linux]# docker pull rabbitmq:management

management带管理界面

运行

docker run -d -p 5672:5672 -p 15672:15672 –name rabbit_Name image_ID

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@localhost linux]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/rabbitmq management 5788d93cd8ad 4 hours ago 180 MB
docker.io/tomcat latest 882487b8be1d 9 days ago 507 MB
docker.io/mongo latest 5255aa8c3698 10 days ago 361 MB
docker.io/mysql latest c8ee894bd2bd 11 days ago 456 MB
docker.io/redis latest de25a81a5a0b 11 days ago 98.2 MB
docker.io/portainer/portainer latest 4cda95efb0e4 2 weeks ago 80.6 MB
[root@localhost linux]#
[root@localhost linux]# docker run -d -p 5672:5672 -p 15672:15672 --name rabbit 5788d93cd8ad
25fb8684c917cc62e9b6d0f01a53af6a53688c06e005f81e2c876aee068cb52c
[root@localhost linux]#
[root@localhost linux]#
[root@localhost linux]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
25fb8684c917 5788d93cd8ad "docker-entrypoint..." 4 seconds ago Up 3 seconds 4369/tcp, 5671/tcp, 0.0.0.0:5672->5672/tcp, 15671/tcp, 25672/tcp, 0.0.0.0:15672->15672/tcp rabbit
939ddacac964 portainer/portainer "/portainer" 7 days ago Up 23 minutes 0.0.0.0:9000->9000/tcp portainer
[root@localhost linux]#

暴露端口

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@localhost linux]# firewall-cmd --zone=public --add-port=15672/tcp --permanent
success
[root@localhost linux]#
[root@localhost linux]# firewall-cmd --reload
success
[root@localhost linux]#
[root@localhost linux]#
[root@localhost linux]# firewall-cmd --zone=public --add-port=5672/tcp --permanent
success
[root@localhost linux]#
[root@localhost linux]# firewall-cmd --reload
success
[root@localhost linux]#

访问 http://192.168.0.105:15672/

账号密码默认 quest

2.2. 建立交换机和队列

交换机

direct 点对点发送

fanout 广播式发送

topic #匹配任意字符,*任意一个字符

队列

交换机中添加队列

点击交换机的名字,进入配置

发送消息

点对点

2.3. 树莓派docker 安装rabbitmq

docker pull arm32v7/rabbitmq:management

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pi@pi:~ $ docker pull arm32v7/rabbitmq:management
pi@pi:~ $ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
arm32v7/rabbitmq management d2183c759a2e 6 hours ago 146MB
redis latest de25a81a5a0b 12 days ago 98.2MB
portainer/portainer latest 6cb2cfd64c93 2 weeks ago 63.7MB
hypriot/rpi-mysql latest 4f3cbdbc3bdb 16 months ago 209MB
pi@pi:~ $
pi@pi:~ $
pi@pi:~ $ docker run -d -p 5672:5672 -p 15672:15672 --name rabbit d2183c759a2e
ecd741f6bb067dd113ad795187f8b6bd5423a025cdd4ba8ca23cc5a9fb2ae8ce
pi@pi:~ $ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ecd741f6bb06 d2183c759a2e "docker-entrypoint.s…" 7 seconds ago Up 4 seconds 4369/tcp, 5671/tcp, 0.0.0.0:5672->5672/tcp, 15671/tcp, 25672/tcp, 0.0.0.0:15672->15672/tcp rabbit
pi@pi:~ $
pi@pi:~ $ su
密码:
root@pi:/home/pi# iptables -I INPUT -i eth0 -p tcp --dport 15672 -j ACCEPT
root@pi:/home/pi#
root@pi:/home/pi# iptables -I OUTPUT -o eth0 -p tcp --sport 15672 -j ACCEPT
root@pi:/home/pi#
root@pi:/home/pi# exit
exit
pi@pi:~ $

2.4. Mac 安装rabbitmq

brew install rabbitmq

brew services start rabbitmq

brew services stop rabbitmq

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ brew install rabbitmq

==> erlang
Man pages can be found in:
/usr/local/opt/erlang/lib/erlang/man

Access them with `erl -man`, or add this directory to MANPATH.
==> rabbitmq
Management Plugin enabled by default at http://localhost:15672

Bash completion has been installed to:
/usr/local/etc/bash_completion.d

To have launchd start rabbitmq now and restart at login:
brew services start rabbitmq
Or, if you don't want/need a background service you can just run:
rabbitmq-server

$ brew services start rabbitmq
==> Successfully started `rabbitmq` (label: homebrew.mxcl.rabbitmq)

2.5. SpringBoot整合rabbitMQ

2.6. Maven依赖

1
2
3
4
5
6
7
8
9
10
 <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.7.2</version>
</dependency>

2.7. RabbitAutoConfiguration

配置类RabbitAutoConfiguration

1
2
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
public class RabbitAutoConfiguration {}

RabbitProperties中定义了rabbitmq的连接配置

1
2
3
4
5
6
7
8
9
10
@ConfigurationProperties(prefix = "spring.rabbitmq")
public class RabbitProperties {
private String host = "localhost";
private int port = 5672;
private String username = "guest";
private String password = "guest";
private final Ssl ssl = new Ssl();
private String virtualHost;
private String addresses;
}

RabbitTemplate:交互rabbit

AmqpAdmin:建立交换机,队列等rabbit控制类

2.8. 实验一 – String

配置主机,默认端口5672,用户密码 guest

1
spring.rabbitmq.host=192.168.0.103

注入RabbitTemplate

发送消息 convertAndSend(exchange,routeKey,message)

接收消息 receiveAndConvert(queueName)

1
2
3
4
5
6
7
8
9
10
11
12
@RunWith(SpringRunner.class)
@SpringBootTest
class DemoApplicationTests {

@Autowired
RabbitTemplate rabbitTemplate;
@Test
void contextLoads() {
rabbitTemplate.convertAndSend("exchange.direct","runaccpeted.news","Hello World");
}

}

2.9. 实验二 – json

rabbitTemple默认传的是java序列化的结果,重写MessageConverter

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

import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class rabbitConfig {

@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
 @Test
void contextLoads() {
//rabbitTemplate.convertAndSend("exchange.direct","runaccpeted.news","Hello World");

User user = new User();
user.setId(1);
user.setUsername("abc");
user.setPsssword("123");
rabbitTemplate.convertAndSend("exchange.fanout","runaccpeted",user);
}

2.10. @RabbitListener

接收一个或多个队列中的消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.springframework.amqp.core.Messages;

@Service
public class rabbitListener {

@RabbitListener(queues = "runaccpeted")
public void getMsg(User user){
//收到对象消息
System.out.println(user);
}

@RabbitListener(queues = "runaccpeted")
public void getMsg(Message message){
//收到对象头信息
System.out.println(message.getBody());
System.out.println(message.getMessageProperties());
}
}

2.10.1. 应答

1
2
3
4
spring.rabbitmq.listener.direct.acknowledge-mode=manual
# Manual acks - user must ack/nack via a channel aware listener.
# Auto - the container will issue the ack/nack based on whether the listener returns normally, or throws an exception.
# None - No acks

Channel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import com.rabbitmq.client.Channel;


@RabbitListener(queues = "")
public void getMessage(Message message, Channel channel) throws IOException {
//* @param multiple true to acknowledge all messages up to and including the supplied delivery tag;
//应答
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);

// @param multiple true to reject all messages up to and including
// @param requeue true 消息重新入队,转给其他消费者
//拒绝
channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false);

// @param requeue true if the rejected message should be requeued rather than discarded/dead-lettered
//拒绝
channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
}

2.11. 异常关闭channel– unknown delivery tag 1

1
2
3
[ 127.0.0.1:5672] o.s.a.r.c.CachingConnectionFactory       : Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=406, reply-text=PRECONDITION_FAILED - unknown delivery tag 1, class-id=60, method-id=80)

[ntContainer#0-1] o.s.a.r.l.SimpleMessageListenerContainer : Restarting Consumer@92d1782: tags=[[amq.ctag--E7Yvof-WCqFzzbFZn6_kQ]], channel=Cached Rabbit Channel: AMQChannel(amqp://guest@127.0.0.1:5672/,1), conn: Proxy@1665fa54 Shared Rabbit Connection: SimpleConnection@17b37e9a [delegate=amqp://guest@127.0.0.1:5672/, localPort= 58113], acknowledgeMode=AUTO local queue size=0

rabbitmq依旧是自动确认acknowledgeMode=AUTO

使用的是 spring.rabbitmq.listener.simple.acknowledge-mode=manual

主类中添加注解

@EnableRabbit

1
2
3
4
5
6
7
8
9
@SpringBootApplication
@EnableRabbit
public class DemoApplication {

public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}

}

2.12. AmqpAdmin

1
2
3
4
5
6
7
8
9
10
@Autowired
AmqpAdmin amqpAdmin;
public void config(){
//交换机
amqpAdmin.declareExchange(new DirectExchange("exchange.java"));
//队列
amqpAdmin.declareQueue(new Queue("runaccpeted.java"));
//绑定new Binding(目的地,目的地类型,交换机,路由件,参数)
amqpAdmin.declareBinding(new Binding("runaccpeted.java", Binding.DestinationType.QUEUE,"exchange.java","runaccpeted.java",null));
}

2.13. 死信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Autowired
AmqpAdmin amqpAdmin;

public void config(){
Exchange exchange = new DirectExchange("user.order.exchage");
amqpAdmin.declareExchange(exchange);

Queue deadQueue = new Queue("user.order.queue",true,false,false,null);
amqpAdmin.declareQueue(deadQueue);

Binding binding = new Binding("user.order.queue", Binding.DestinationType.QUEUE,"user.order.exchange","order",null);
amqpAdmin.declareBinding(binding);

Map<String, Object> map = new HashMap<>();
map.put("x-message-ttl",10*1000);//过期时间10s
map.put("x-dead-letter-routing-key",exchange); //过期转至交换机
map.put("x-dead-letter-exchage",binding); //交换机发给哪个队列
Queue queue = new Queue("user.order.delay.queue",true,false,false,map);
amqpAdmin.declareQueue(queue);
}

3. ElasticSearch检索

分布式搜索服务

3.1. linux安装

1
2
docker pull elasticsearch
docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -d -p 9200:9200 -p 9300:9300 --name es id

3.2. 树莓派安装

https://www.elastic.co/guide/en/elasticsearch/reference/5.6/zip-targz.html#install-zip

Elasticsearch的学习与使用

树莓派上找不到docker elasticsearch的镜像

利用zip安装

1
2
3
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.6.16.zip
unzip elasticsearch-5.6.16.zip
cp -r elasticsearch-5.6.16 /usr/local/elasticsearch-5.6.16

修改jvm内存

1
nano config/jvm.options


-Xms2g

-Xmx2g

改为

-Xms512m

-Xmx512m

修改配置文件

1
nano config/elasticsearch.yml

主要配置

1
2
3
4
5
6
7
8
bootstrap.memory_lock: false
bootstrap.system_call_filter: false
# 任何ip可访问
network.host: 0.0.0.0
# 配置了对外端口
http.port: 9200
# 启动后搜寻主机的IP地址
discovery.zen.ping.unicast.hosts: ["192.168.0.103"]

运行

1
2
3
cd ..
cd bin
./elasticsearch

ERROR: [1] bootstrap checks failed

[1]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]

ElasticSearch 5.0.0 安装部署常见错误或问题

1
2
3
4
5
nano /etc/sysctl.conf

vm.max_map_count=655360

sysctl -p

重新启动elasticsearch 访问http://192.168.0.103:9200/

3.3. 介绍

3.3.1. PUT /索引/类型/id 增加/更新

添加/更新

请求方式Content-Type=application/json

http://192.168.0.103:9200/runaccpeted/user/3

3.3.2. GET /索引/类型/id 查询

http://192.168.0.103:9200/runaccpeted/user/4

3.3.3. HEAD  /索引/类型/id 查询

没有记录则报404 NOT FOUND

有记录则为200 OK

http://192.168.0.103:9200/runaccpeted/user/5

3.3.4. DELETE /索引/类型/id 删除

查询后删除

3.3.5. GET _search 查询所有信息

查询所有记录

http://192.168.0.103:9200/runaccpeted/user/_search

请求内容 {} 请求方式 application/json

按照条件查询

GET http://192.168.0.103:9200/runaccpeted/user/_search?q=username:zzz

or

GET http://192.168.0.103:9200/runaccpeted/user/_search

1
2
3
4
5
6
7
{
"query":{
"match":{
"password":1234
}
}
}

3.3.6. Maven依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

3.3.7. SpringBoot整合Elasticsearch两种方式

3.3.7.1. org.springframework.boot.autoconfigure.elasticsearch.jest

1
spring.elasticsearch.jest.uris=http://192.168.0.103:9200
3.3.7.1.1. JestClient
1
2
3
4
5
<dependency>
<groupId>io.searchbox</groupId>
<artifactId>jest</artifactId>
<version>6.3.1</version>
</dependency>

加入数据

3.3.7.1.2. Index
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import io.searchbox.core.Index;

@Autowired
JestClient jestClient;
@Test
public void insert(){
User user = new User();
user.setId(1);
user.setUsername("abc");
user.setPsssword("123");
Index index = new Index.Builder(user).index("runaccpeted").type("user").id("1").build();
try {
jestClient.execute(index);
} catch (IOException e) {
e.printStackTrace();
}
}

读取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void search(){
String json = "{\"query\":{\"match\":{\"username\":\"abc\"}}}";
Search search = new Search.Builder(json).addIndex("runaccpeted").addType("user").build();

try {
SearchResult result = jestClient.execute(search);
//返回List<User>
System.out.println(execute.getSourceAsObjectList(User.class,true));
} catch (IOException e) {
e.printStackTrace();
}

}

3.3.7.2. org.springframework.boot.autoconfigure.data.elasticsearch

1
2
spring.data.elasticsearch.cluster-name=elasticsearch-test
spring.data.elasticsearch.cluster-nodes=192.168.0.103:9300

ElasticsearchRepository中指定了索引,查询方法

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
package org.springframework.data.elasticsearch.repository;

import org.elasticsearch.index.query.QueryBuilder;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.data.repository.NoRepositoryBean;

@NoRepositoryBean
public interface ElasticsearchRepository<T, ID> extends ElasticsearchCrudRepository<T, ID> {
<S extends T> S index(S var1);

<S extends T> S indexWithoutRefresh(S var1);

Iterable<T> search(QueryBuilder var1);

Page<T> search(QueryBuilder var1, Pageable var2);

Page<T> search(SearchQuery var1);

Page<T> searchSimilar(T var1, String[] var2, Pageable var3);

void refresh();

Class<T> getEntityClass();
}
3.3.7.2.1. extends ElasticsearchRepository

继承ElasticsearchRepository接口定义方法

1
2
3
4
5
6
7
8
9
10
package com.example.config;

import com.example.pojo.User;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Component;

@Component
public interface UserRepository extends ElasticsearchRepository<User,Integer> {

}

实现

1
2
3
//User类指定索引和类型
@Document(indexName = "runaccpeted",type = "user")
public class User {}

测试

1
2
3
4
5
6
7
8
9
10
@Autowired
UserRepository userRepository;
@Test
public void esInsert(){
User user = new User();
user.setId(2);
user.setUsername("bcd");
user.setPsssword("234");
userRepository.index(user);
}

http://192.168.0.103:9200/runaccpeted/user/_search

1
2
3
4
5
6
7
8
9
10
11
12
"hits": [
{
"_index": "runaccpeted",
"_type": "user",
"_id": "2",
"_score": 1.0,
"_source": {
"id": 2,
"username": "bcd",
"psssword": "234"
}
},
3.3.7.2.2. 自定义方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component
public interface UserRepository extends ElasticsearchRepository<User, Integer> {
//模糊查询
List<User> findByUsernameLike(String name);
}


//测试类
@Test
public void elastic2(){
List<User> user =userRepository.findByUsernameLike("b");
for (User u:user) {
System.out.println(u);
}

}

4. 任务

4.1. 异步

@Async 注解在方法中,表示方法是异步的

@EnableAsync 注解在主配置类上

4.2. 定时

@Scheduled

1
2
3
4
5
6
7
8
9
10
11
/**
second, minute, hour, day of month, month, and day of week
0 * * * * MON-FRI 周一到周五整秒启动
0,1,2,3 * * * * MON-FRI 第0,1,2,3秒执行
0-3 * * * * MON-FRI 0-3秒执行
0/4 * * * * MON-FRI 每4秒执行
0 0/5 14,18 * * ? 每天14点和18点整,每隔5分钟执行一次
0 0 2-4 ? * 1#1 每个月的第一个周一凌晨2-4点,每个整点执行一次
0 0 2 ?* 6L 每个月最后一个周六凌晨2点执行一次
*/
String cron() default "";

@EnableScheduling 配置在主配置类上

5. 邮件

5.1. Maven依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>

5.2. application.properties

1
2
3
4
spring.mail.host=smtp.qq.com
spring.mail.username=QQ
spring.mail.password=源自于qq邮箱-设置-SMTP服务-生成授权码
spring.mail.properties.mail.smtp.ssl.enable=true

5.3. Test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Autowired
JavaMailSenderImpl mailSender;

@Test
void contextLoads() {
SimpleMailMessage message = new SimpleMailMessage();
//主题
message.setSubject("通知");
//内容
message.setText("Hello");
//发件方
message.setFrom("1543315941@qq.com");
//收件方
message.setTo("wangtn01@163.com");

mailSender.send(message);
}

5.4. 复杂邮件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
void test() throws Exception {

MimeMessage mimeMessage = mailSender.createMimeMessage();

// MimeMessageHelper(MimeMessage mimeMessage, boolean multipart)
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true);

helper.setSubject("通知");

// public void setText(String text, boolean html)
helper.setText("<h1>Hello</h1><h2>World</h2>",true);

helper.addAttachment("1.jpg",new File("/Users/Learning/Downloads/1.jpg"));

helper.setFrom("1543315941@qq.com");
helper.setTo("wangtn01@163.com");
mailSender.send(mimeMessage);
}

6. 安全

6.1. shiro

6.2. spring security

6.2.1. Maven

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

6.2.2. 配置类 SecurityConfig

@EnableWebSecurity 内部注解@Configuration

配置类继承WebSecurityConfigurerAdapter,重写 configure方法

版本需要提供一个PasswordEncorder的实例,否则后台汇报错误:java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id “null”

PasswordEncoder实现类

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

import org.springframework.security.crypto.password.PasswordEncoder;

public class myPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}

@Override
public boolean matches(CharSequence charSequence, String s) {
return charSequence.toString().equals(s);
}
}

配置类

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
package com.example.config;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

/**
* 定制请求授权规则
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/").permitAll()
.antMatchers("/user/**").hasRole("user")
.antMatchers("/vip/**").hasRole("vip");

//默认访问/login
//登录失败,重定向到/login?error
http.formLogin();

//开启自动配置的注销功能
// logoutUrl = "/logout" 清空session
// logoutSuccessUrl = "/login?logout" 注销成功访问页面
// http.logout();
//<form th:action="@{/logout}" method="post">
// <input type="submit" value="注销">
//</form>
http.logout().logoutSuccessUrl("/");
}

/**
* 认证规则
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
myPasswordEncoder passwordEncoder = new myPasswordEncoder();
//java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
auth.inMemoryAuthentication().passwordEncoder(new myPasswordEncoder())
.withUser("a").password("123").roles("user")
.and()
.withUser("b").password("123").roles("vip");
}
}

6.2.3. TestController

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

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

@RequestMapping("/")
public String index(){
return "index";
}

@RequestMapping("/user")
public String user(){
return "user/index";
}

@RequestMapping("/vip")
public String vip(){
return "vip/index";
}
}

6.2.4. 前端页面

Index.html

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
index<br>
<a th:href="@{/user}">user</a><br>
<a th:href="@{/vip}">vip</a>
</body>
</html>

/user/index.html

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
User
</body>
</html>

/user/vip.html

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
vip
</body>
</html>

6.2.5. 访问

http://localhost:8080/

http://localhost:8080/user/ –> http://localhost:8080/login

输入a/123 跳转至http://localhost:8080/user/ –> user

清空缓存 访问http://localhost:8080/user/ –> http://localhost:8080/login 输入b/123 访问 http://localhost:8080/user/ forbidden

访问 http://localhost:8080/vip/ –> vip

6.2.6. thymeleaf-security❌

根据登录用户显示不同页面

1
2
3
4
5
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>

security.html

sec:authorize=”!isAuthenticated()” 开启认证

sec:authentication=”name” 获取登录名

sec:authentication=”principal.authorities” 获取登录角色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<meta charset="UTF-8">
<title>Security</title>
</head>
<body>
<a th:href="@{/user}"><h1>user</h1></a>
<a th:href="@{/vip}"><h1>vip</h1></a>
<div sec:authorize="!isAuthenticated()">
欢迎访问
</div>
<div sec:authorize="isAuthenticated()">
登录名:<span sec:authentication="name"></span><br>
角色为:<span sec:authentication="principal.authorities"></span><br>
<form th:action="@{/logout}" method="post" sec:authentication="isAuthenticated()">
<input type="submit" value="注销">
</form>
</div>
</body>
</html>

并没有效果

改变版本

1
2
3
4
5
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>

报500错误,Error retrieving value for property “isAuthenticated()” of authentication object of class org.springframework.security.authentication.UsernamePasswordAuthenticationToken

6.2.7. 记住我

1
http.rememberMe();

浏览器记录cookie数据

添加参数

1
http.rememberMe().rememberMeParameter("remember");
1
<input type="checkbox" name="remember">

6.2.8. 自定义登录页

1
http.formLogin().usernameParameter("usr").passwordParameter("pwd").loginPage("/userlogin").loginProcessingUrl("/login");

默认为POST的/login,参数为username,password

修改为 /userlogin,则处理请求的也是 /userlogin,故配置loginProcessingUrl

7. 分布式

7.1. zookeeper

7.1.1. 树莓派

https://archive.apache.org/dist/zookeeper/zookeeper-3.5.4-beta/

启动 zkServer.sh start
停止 zkServer.sh stop
状态 zkServer.sh status

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
root@pi:/home/pi/Downloads# tar zxvf zookeeper-3.5.4-beta.tar.gz
root@pi:/home/pi/Downloads# cp -r zookeeper-3.5.4-beta /usr/local/zookeeper
root@pi:/usr/local/zookeeper# nano /etc/profile

export ZOOKEEPER_HOME=/usr/local/zookeeper
export PATH=$PATH:$ZOOKEEPER_HOME/bin

root@pi:/usr/local/zookeeper# source /etc/profile

root@pi:/usr/local/zookeeper/conf# nano zoo_sample.cfg

tickTime=2000 通信心跳数,服务器和客户端心跳时间
initLimit=10 LF初始通信时限,Leader,follower之间初始连接最多容忍心跳数
syncLimit=5 LF之间最大响应时间单位,超过syncLimit*tickTime,leader认为follower死亡,删除follower
dataDir=/usr/local/zookeeper/data
clientPort=2181

root@pi:/usr/local/zookeeper/conf# mv zoo_sample.cfg zoo.cfg
root@pi:/usr/local/zookeeper/conf# iptables -I INPUT -i eth0 -p tcp --dport 2181 -j ACCEPT
root@pi:/usr/local/zookeeper/conf#
root@pi:/usr/local/zookeeper/conf# iptables -I OUTPUT -o eth0 -p tcp --sport 2181 -j ACCEPT
root@pi:/usr/local/zookeeper/conf# zkServer.sh start
root@pi:/usr/local/zookeeper/conf# zkServer.sh stop
root@pi:/usr/local/zookeeper/conf# zkServer.sh status
root@pi:/usr/local/zookeeper/conf# zkCli.sh

7.2. springboot整合dubbo+zookeeper

新建 springboot-web项目 provider-ticket,consumer.user

https://github.com/apache/dubbo-spring-boot-project/tree/0.2.x

7.2.1. provider-ticket

7.2.1.1. Maven依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<properties>
<java.version>1.8</java.version>
<dubbo.version>2.6.5</dubbo.version>
</properties>

<!-- dubbo -->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
</dependency>

<!-- ClassNotFoundException: org.apache.curator.framework.CuratorFrameworkFactory -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>

<!-- zookeeper 客户端 -->
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.11</version>
</dependency>

7.2.1.2. 配置application.properties

1
2
3
dubbo.application.name=provider-ticket
dubbo.registry.address=zookeeper://192.168.0.103:2181
dubbo.scan.base-packages=com.runaccpeted.service

7.2.1.3. service类

@Service dubbo注解

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

public interface TicketService {

public String getTicket();
}



package com.runaccpeted.service.impl;

import com.alibaba.dubbo.config.annotation.Service;
import com.runaccpeted.service.TicketService;
import org.springframework.stereotype.Component;


@Service
@Component
public class TicketServiceImpl implements TicketService {
@Override
public String getTicket() {
return "Ticket";
}
}

7.2.1.4. 运行provider-ticket

7.2.2. consumer-user

7.2.2.1. Maven依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<properties>
<java.version>1.8</java.version>
<dubbo.version>2.6.5</dubbo.version>
</properties>

<!-- dubbo -->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
</dependency>

<!-- ClassNotFoundException: org.apache.curator.framework.CuratorFrameworkFactory -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>

<!-- zookeeper 客户端 -->
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.11</version>
</dependency>

7.2.2.2. 写一样的TicketService

1
2
3
4
5
6
package com.runaccpeted.service;

public interface TicketService {

public String getTicket();
}

7.2.2.3. Userservice

@Reference 根据名字注入

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

import com.alibaba.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;

@Service
public class UserService {

@Reference
TicketService ticketService;


public void test(){
String str = ticketService.getTicket();
System.out.println("买到了 "+str);
}

}

7.2.2.4. 检验

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

import com.runaccpeted.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class ConsumerApplicationTests {

@Autowired
UserService service;

@Test
void contextLoads() {
service.test();
}

}

输出

7.3. SpringCloud

7.3.1. 五大组件

服务发现 Netflix Eureka

客户端负载均衡 Netflix Ribbon

断路器 Netflix Hystrix

服务网关 Netflix Zuul

分布式配置 Spring Cloud Config

7.3.2. eureka-server 注册中心

7.3.2.1. 导入eureka-server

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.runaccpeted</groupId>
<artifactId>eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-server</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.RC1</spring-cloud.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>

</project>

7.3.2.2. 配置application.yml

1
2
3
4
5
6
7
8
9
10
11
12
server:
port: 8761
eureka:
instance:
hostname: eureka-server
client:
# eureka自己不注册
register-with-eureka: false
# 不从eureka上获取服务注册信息
fetch-registry: false
# this.serviceUrl.put("defaultZone", "http://localhost:8761/eureka/");
# service-url:

7.3.2.3. 启动 @EnableEurekaServer

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

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {

public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}

}

http://localhost:8761/

7.3.3. provider-ticket 服务中心

7.3.3.1. Maven

添加web – Spring Web,Spring Cloud Discovery – Eureka Discovery Client

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.runaccpeted</groupId>
<artifactId>provider-ticket</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>provider-ticket</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.RC1</spring-cloud.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>

</project>

7.3.3.2. application.yml

1
2
3
4
5
6
7
8
9
10
11
12
server:
port: 8001
eureka:
instance:
# 注册服务时使用ip地址
prefer-ip-address: true
client:
service-url:
defaultZone: http://localhost:8761/eureka/
spring:
application:
name: provider-ticket

7.3.3.3. TickerService

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

import org.springframework.stereotype.Service;

@Service
public class TickerService {

public String getTicket(){
return "Ticket";
}
}

7.3.3.4. TicketController

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

import com.runaccpeted.service.TickerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TicketController {

@Autowired
TickerService service;

@RequestMapping("/ticket")
public String getTicket(){
return service.getTicket();
}
}

暴露 8001端口http://localhost:8001/ticket

server中有所改变

7.3.3.5. 打包项目,更改端口号

分别运行jar包

1
2
java -jar provider-ticket-0.0.1-SNAPSHOT-8002.jar
java -jar provider-ticket-0.0.1-SNAPSHOT-8001.jar

7.3.4. consumer-user 消费者

7.3.4.1. Maven

添加web – Spring Web,Spring Cloud Discovery – Eureka Discovery Client

同provider-ticket

7.3.4.2. application.yml

1
2
3
4
5
6
7
8
9
10
11
spring:
application:
name: consumer-user
eureka:
instance:
prefer-ip-address: true
client:
service-url:
defaultZone: http://localhost:8761/eureka/
server:
port: 8800

7.3.4.3. Main类

@EnableDiscoveryClient 开启服务

@LoadBalanced 负载均衡

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
package com.runaccpeted;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

//服务功能
@EnableDiscoveryClient
@SpringBootApplication
public class ConsumerUserApplication {

public static void main(String[] args) {
SpringApplication.run(ConsumerUserApplication.class, args);
}

//负载均衡
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}

}

7.3.4.4. UserController

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class UserController {

@Autowired
RestTemplate restTemplate;

@RequestMapping("/got")
public String getTicket(String name){
//对应于 仓库中的url
String str=restTemplate.getForObject("http://PROVIDER-TICKET/ticket",String.class);
return name +" got " +str;
}
}

http://localhost:8800/got?name=abc

Eureka 中存入ip

8. 热部署 devtools

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>

每次修改代码后,command+F9 build project,重新编译

9. 监控管理

9.1. Maven – actuator

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

9.2. 默认仅暴露health,info端口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// http://localhost:8080/actuator

{
"_links": {
"self": {
"href": "http://localhost:8080/actuator",
"templated": false
},
"health-path": {
"href": "http://localhost:8080/actuator/health/{*path}",
"templated": true
},
"health": {
"href": "http://localhost:8080/actuator/health",
"templated": false
},
"info": {
"href": "http://localhost:8080/actuator/info",
"templated": false
}
}
}

9.3. applicat.properties

1
2
#开启所有端点
management.endpoints.web.exposure.include=*

得到可访问的端口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// http://localhost:8080/actuator

{
"_links": {
"self": {
"href": "http://localhost:8080/actuator",
"templated": false
},
"beans": {
"href": "http://localhost:8080/actuator/beans",
"templated": false
},
"caches-cache": {
"href": "http://localhost:8080/actuator/caches/{cache}",
"templated": true
},
"caches": {
"href": "http://localhost:8080/actuator/caches",
"templated": false
},
"health": {
"href": "http://localhost:8080/actuator/health",
"templated": false
},
"health-path": {
"href": "http://localhost:8080/actuator/health/{*path}",
"templated": true
},
"info": {
"href": "http://localhost:8080/actuator/info",
"templated": false
},
"conditions": {
"href": "http://localhost:8080/actuator/conditions",
"templated": false
},
"configprops": {
"href": "http://localhost:8080/actuator/configprops",
"templated": false
},
"env": {
"href": "http://localhost:8080/actuator/env",
"templated": false
},
"env-toMatch": {
"href": "http://localhost:8080/actuator/env/{toMatch}",
"templated": true
},
"loggers-name": {
"href": "http://localhost:8080/actuator/loggers/{name}",
"templated": true
},
"loggers": {
"href": "http://localhost:8080/actuator/loggers",
"templated": false
},
"heapdump": {
"href": "http://localhost:8080/actuator/heapdump",
"templated": false
},
"threaddump": {
"href": "http://localhost:8080/actuator/threaddump",
"templated": false
},
"metrics-requiredMetricName": {
"href": "http://localhost:8080/actuator/metrics/{requiredMetricName}",
"templated": true
},
"metrics": {
"href": "http://localhost:8080/actuator/metrics",
"templated": false
},
"scheduledtasks": {
"href": "http://localhost:8080/actuator/scheduledtasks",
"templated": false
},
"mappings": {
"href": "http://localhost:8080/actuator/mappings",
"templated": false
}
}
}

内置端点默认曝光,启用了不代表可以直接访问,还需要将其暴露出来。

ID 描述 Web JMX
auditevents 显示当前应用程序的审计事件信息 No Yes
beans 显示一个应用中所有Spring Beans的完整列表 No Yes
conditions 显示配置类和自动配置类(configuration and auto-configuration classes)的状态及它们被应用或未被应用的原因 No Yes
configprops 显示一个所有@ConfigurationProperties的集合列表 No Yes
env 显示来自Spring的 ConfigurableEnvironment的属性 No Yes
flyway 显示数据库迁移路径,如果有的话 No Yes
health 显示应用的健康信息(当使用一个未认证连接访问时显示一个简单的’status’,使用认证连接访问则显示全部信息详情) Yes Yes
heapdump 返回一个GZip压缩的hprof堆dump文件 No N/A
httptrace 请求追踪 No Yes
info 显示任意的应用信息,在application.yml 中有info.* Yes Yes
jolokia 通过HTTP暴露JMX beans(当Jolokia在类路径上时,WebFlux不可用) No Yes
logfile 返回日志文件内容(如果设置了logging.file或logging.path属性的话),支持使用HTTP Range头接收日志文件内容的部分信息 No Yes
loggers 日志信息 No Yes
liquibase 展示任何Liquibase数据库迁移路径,如果有的话 No Yes
metrics 展示当前应用的metrics信息 No Yes
mappings 显示一个所有@RequestMapping路径的集合列表 No Yes
prometheus 以可以被Prometheus服务器抓取的格式显示metrics信息 No N/A
scheduledtasks 显示应用程序中的计划任务 No Yes
sessions 允许从Spring会话支持的会话存储中检索和删除(retrieval and deletion)用户会话。使用Spring Session对反应性Web应用程序的支持时不可用。 No Yes
shutdown 允许应用以优雅的方式关闭(默认情况下不启用) No Yes
threaddump 执行一个线程dump No Yes

使用以下技术特定的include属性列出了公开的端点的ID 和exclude属性列出了不应该公开的端点的ID

Property Default
management.endpoints.jmx.exposure.exclude *
management.endpoints.jmx.exposure.include *
management.endpoints.web.exposure.exclude *
management.endpoints.web.exposure.include *

9.4. shutdown

必须是POST请求 http://localhost:8080/actuator/shutdown

1
management.endpoint.shutdown.enabled=true

9.5. 定制端点

1
2
3
4
5
6
7
8
9
10
11
12
#访问http://localhost:8080/mybean 原来的/bean失效
endpoints.beans.id=mybean
endpoints.beans.path=/bean

#关闭所有端点访问
endpoints.enabled=false

#开启单个端点
endpoints.beans.enabled=true

#根路径
management.context-path=/manage

9.6. 自定义HealthIndicator

extends AbstractHealthIndicator

重写doHealthCheck

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

import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.stereotype.Component;

@Component
public class MyAppHealthIndicator extends AbstractHealthIndicator {
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {

//健康
builder.up().build();
//失效
//builder.down().withDetail("msg","health -- msg").build();
}
}
本文结束  感谢您的阅读
  • 本文作者: Wang Ting
  • 本文链接: /zh-CN/2019/10/24/SpringBoot-高级/
  • 发布时间: 2019-10-24 11:35
  • 更新时间: 2022-10-24 20:39
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!