缓存的场景
用的比较少
Java Caching定义了5个核心接口
CachingProvider
定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期间访问多个CachingProvider
CacheManager
定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManage的上下文中,一个CacheManage只被一个CachingProvider拥有
Cache
类似于Map的数据结构并临时储存以key为索引的值,一个Cache仅仅被一个CacheManage所拥有
Entry
存储在Cache中的key-value对
Expiry
存储在Cache的条目有一个定义的有效期,一旦超过这个时间,就会设置过期的状态,过期无法被访问,更新,删除。缓存的有效期可以通过ExpiryPolicy设置。
[外链图片转存失败(img-1E9DwsxS-1566554304009)(images/微信截图_20190813115130.png)]
包括一些JSR107的注解
CahceManager
Cache
重要的概念&缓存注解
功能 | |
---|---|
Cache | 缓存接口,定义缓存操作,实现有:RedisCache、EhCacheCache、ConcurrentMapCache等 |
CacheManager | 缓存管理器,管理各种缓存(Cache)组件 |
@Cacheable | 针对方法配置,根据方法的请求参数对其结果进行缓存 |
@CacheEvict | 清空缓存 |
@CachePut | 保证方法被调用,又希望结果被缓存 update,调用,将信息更新缓存 |
@EnableCaching | 开启基于注解的缓存 |
KeyGenerator | 缓存数据时key生成的策略 |
serialize | 缓存数据时value序列化策略 |
1、新建一个SpringBoot2.17+web+mysql+mybatis+cache
步骤方法:
一、搭建基本环境
1.导入数据库文件,创建出department表和employee表
2.创建javabean封装数据
3.整合Mybaits操作数据库
1.配置数据源信息
2.使用注解版的MyBatis;
1)使用 @MapperScan指定需要扫描的mapper接口所在的包
二、快速体验缓存
1.开启基于缓存的注解 @EnableCaching
2.标注缓存注解即可
@Cacheable 查询方法上
@CacheEvict 删除;增加方法上
@CachePut 修改方法上
将方法的运行结果进行缓存,以后要是再有相同的数据,直接从缓存中获取,不用调用方法
* CacheManager中管理多个Cache组件,对缓存的真正CRUD操作在Cache组件中,每个缓存组件都有自己的唯一名字;
*
* 属性:
* CacheName/value:指定存储缓存组件的名字
* key:缓存数据使用的key,可以使用它来指定。默认是使用方法参数的值,1-方法的返回值
* 编写Spel表达式:#id 参数id的值, #a0/#p0 #root.args[0]
* keyGenerator:key的生成器,自己可以指定key的生成器的组件id
* key/keyGendertor二选一使用
*
* cacheManager指定Cache管理器,或者cacheReslover指定获取解析器
* condition:指定符合条件的情况下,才缓存;
* unless:否定缓存,unless指定的条件为true,方法的返回值就不会被缓存,可以获取到结果进行判断
sync:是否使用异步模式,unless不支持
key和value的写法 入图所示:
[外链图片转存失败(img-DfzcniNJ-1566554304011)(images/微信截图_20190817104824.png)]
2、编写配置文件,连接Mysql
spring.datasource.url=jdbc:mysql://localhost:3306/spring_cache?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
server.port=8081
#开启驼峰命名
mybatis.configuration.map-underscore-to-camel-case=true
3、创建一个bean实例
Department
package com.bdqn.cache.bean;
public class Department {
private Integer id;
private String departmentName;
public Department() {
super();
// TODO Auto-generated constructor stub
}
public Department(Integer id, String departmentName) {
super();
this.id = id;
this.departmentName = departmentName;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getDepartmentName() {
return departmentName;
}
public void setDepartmentName(String departmentName) {
this.departmentName = departmentName;
}
@Override
public String toString() {
return "Department [id=" + id + ", departmentName=" + departmentName + "]";
}
}
Employee
package com.bdqn.cache.bean;
public class Employee {
private Integer id;
private String lastName;
private String email;
private Integer gender; //性别 1男 0女
private Integer dId;
public Employee() {
super();
}
public Employee(Integer id, String lastName, String email, Integer gender, Integer dId) {
super();
this.id = id;
this.lastName = lastName;
this.email = email;
this.gender = gender;
this.dId = dId;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
public Integer getdId() {
return dId;
}
public void setdId(Integer dId) {
this.dId = dId;
}
@Override
public String toString() {
return "Employee [id=" + id + ", lastName=" + lastName + ", email=" + email + ", gender=" + gender + ", dId="
+ dId + "]";
}
}
4、创建mapper接口映射数据库,并访问数据库中的数据
package com.bdqn.cache.mapper;
import com.bdqn.cache.bean.Employee;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface EmployeeMapper {
@Select("SELECT * from employee where id=#{id}")
Employee getEmpById(Integer id);
@Select("update employee set lastName=#{lastName},email=#{email},gender=#{gender},d_id=#{dId} where id=#{id}")
void updateEmp(Employee employee);
@Delete("delete from employee where id=#{id}")
void deleteEmployee(Integer id);
@Insert("insert into employee(lastName,email,gender,d_id) values(#{lastName},#{email},#{gender},#{dId})")
void insertEmployee(Employee employee);
}
5、主程序添加注解MapperScan,并且使用@EnableCaching开启缓存
package com.bdqn.cache;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 第一步:导入数据库
* 第二部:创建实体类
* 第三步:修改application.properties文件
*/
@MapperScan("com.bdqn.cache.mapper")
@SpringBootApplication
public class Springboot01CacheApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot01CacheApplication.class, args);
}
}
6、编写service,来具体实现mapper中的方法
package com.wdjr.cache.service;
import com.wdjr.cache.bean.Employee;
import com.wdjr.cache.mapper.EmployeeMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
/**
* 将方法的运行结果进行缓存,以后要是再有相同的数据,直接从缓存中获取,不用调用方法
* CacheManager中管理多个Cache组件,对缓存的真正CRUD操作在Cache组件中,每个缓存组件都有自己的唯一名字;
*
* 属性:
* CacheName/value:指定存储缓存组件的名字
* key:缓存数据使用的key,可以使用它来指定。默认是使用方法参数的值,1-方法的返回值
* 编写Spel表达式:#id 参数id的值, #a0/#p0 #root.args[0]
* keyGenerator:key的生成器,自己可以指定key的生成器的组件id
* key/keyGendertor二选一使用
*
* cacheManager指定Cache管理器,或者cacheReslover指定获取解析器
* condition:指定符合条件的情况下,才缓存;
* unless:否定缓存,unless指定的条件为true,方法的返回值就不会被缓存,可以获取到结果进行判断
除了什么。。什么 才保存
* sync:是否使用异步模式,unless不支持
*
*
* @param id
* @return
*/
@Cacheable(cacheNames = {"emp"},key = "#id",condition = "#id>0",unless = "#result==null")
public Employee getEmp(Integer id){
System.out.println("查询id= "+id+"的员工");
return employeeMapper.getEmpById(id);
}
}
7、编写controller测试
@RestController
public class EmployeeController {
@Autowired
EmployeeService employeeService;
@GetMapping("/emp/{id}")
public Employee getEmp(@PathVariable("id")Integer id){
return employeeService.getEmp(id);
}
}
8、测试结果
继续访问,就不会执行方法,因为直接在缓存中取值
原理:
1、CacheAutoConfiguration
2、导入缓存组件
3、查看哪个缓存配置生效
SimpleCacheConfiguration生效
4、给容器注册一个CacheManager:ConcurrentMapCacheManager
5、可以获取和创建ConcurrentMapCache,作用是将数据保存在ConcurrentMap中
运行流程
1、方法运行之前,先查Cache(缓存组件),按照cacheName的指定名字获取;
(CacheManager先获取相应的缓存),第一次获取缓存如果没有cache组件会自己创建
2、去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;
key是按照某种策略生成的,默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key
没有参数 key=new SimpleKey()
如果有一个参数 key=参数值
如果多个参数 key=new SimpleKey(params);
3、没有查到缓存就调用目标方法
4、将目标方法返回的结果,放回缓存中
方法执行之前,@Cacheable先来检查缓存中是否有数据,按照参数的值作为key去查询缓存,如果没有,就运行方法,存入缓存,如果有数据,就取出map的值。
如图:
[外链图片转存失败(img-5vKhgb3S-1566554304014)(images/微信截图_20190817112959.png)]提示:如果没有指定key key默认的就是传入的值
----1. 可以使用spel表达式自定义key key = “#root.methodName+’[’+#id+’]’”
@Override
@Cacheable(cacheNames = "emp",key = "#root.methodName+'['+#id+']'")
public Employee getEmployeeById(Integer id) {
System.out.println("查询"+id+"号,员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
----2使用keyGennerator:可以自己指定key的生成器的组件id
第一步:创建configuration配置类
package com.bdqn.cache.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.ArrayList;
import java.util.Arrays;
@Configuration
public class MyCacheConfig {
@Bean("myKeyGenerator")
public KeyGenerator keyGenerator(){
return new KeyGenerator(){
@Override
public Object generate(Object o, Method method, Object... objects) {
return method.getName()+Arrays.asList(objects).toString();
}
};
}
}
第二步,使用的是修改@cacheable的内容
@Cacheable(cacheNames = "emp",keyGenerator = "myKeyGenerator")
public Employee getEmployeeById(Integer id) {
System.out.println("查询"+id+"号,员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
注意:2.17与1.5的不同,2.17在一开始的时候,就将值注入进去,与原来的不同,而且也不用在加中括号 [ ]
修改数据库的某个数据,同时更新缓存
运行时机
先运行方法,再将目标结果缓存起来
cacheable的key是不能使用result的参数的
1、编写更新方法
/**
* 注意这里的key必须与查询的key一致,否则查询的key所对应的value值,就没有更改
* @param employee
*/
@Override
@CachePut(cacheNames = "emp",key = "#result.id")
public Employee updateEmployeeByEmployee(Employee employee) {
System.out.println("修改用户信息");
employeeMapper.updateEmp(employee);
return employee;
}
补充:注意这里的key必须与查询的key一致,否则查询的key所对应的value值,就没有更改
2、编写Controller方法
@GetMapping("/emp")
public Employee updateEmp(Employee employee){
employeeService.updateEmp(employee);
return employee;
}
测试
测试步骤
1、先查询1号员工
2、更新1号员工数据
3、查询1号员工
可能并没有更新,
是因为查询和更新的key不同
效果:
清除缓存
编写测试方法
/**
**/
@CacheEvict(value = "emp",key = "#id")
public void deleteEmp(Integer id){
System.out.println("delete的id"+id);
}
//删除所有的key
@CacheEvict(value = "emp",allEntries = true)
public void deleteEmp(Integer id){
System.out.println("delete的id"+id);
}
//在方法前删除所有的缓存;默认是false
@CacheEvict(value = "emp",beforeInvocation=true)
public void deleteEmp(Integer id){
System.out.println("delete的id"+id);
}
allEntries = true,代表不论清除那个key,都重新刷新缓存
beforeInvocation=true.方法执行前,清空缓存,默认是false,如果程序异常,就不会清除缓存
组合 是下面三个的组合
CacheConfig抽取缓存的公共配置
@CacheConfig(cacheNames = "emp")
@Service
public class EmployeeService {
然后下面的value=emp就不用写了
@Caching(
cacheable = {
@Cacheable(value = "emp",key = "#lastName")
},
put = {
@CachePut(value = "emp",key = "#result.id"),
@CachePut(value = "emp",key = "#result.gender")
}
)
public Employee getEmpByLastName(String lastName){
return employeeMapper.getEmpByLastName(lastName);
}
如果查完lastName,再查的id是刚才的值,就会直接从缓存中获取数据;但是,你直接再次查lastName的话,还是要执行sql语句,因为put是在方法后执行,都是要清掉的
以上内容:默认使用的是ConcurrentMapCacheMapper==ConcurrentMapCache;将数据保存在ConcurrentMap
但是,实际开发中使用的是 缓存中间件:redis、memcached、ehcache;
默认的缓存是在内存中定义HashMap,生产中使用Redis的缓存中间件
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件
安装redis在docker上
#拉取redis镜像
docker pull redis
#启动redis[bfcb1f6df2db]docker images的id
docker run -d -p 6379:6379 --name redis01 bfcb1f6df2db
Redis的常用五大数据类型
String【字符串】、List【列表】、Set【集合】、Hash【散列】、ZSet【有序集合】
分为两种一种是StringRedisTemplate,另一种是RedisTemplate
根据不同的数据类型,大致的操作也分为这5种,以StringRedisTemplate为例
stringRedisTemplate.opsForValue() --String
stringRedisTemplate.opsForList() --List
stringRedisTemplate.opsForSet() --Set
stringRedisTemplate.opsForHash() --Hash
stringRedisTemplate.opsForZset() -Zset
1、导入依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
2、修改配置文件
spring.redis.host=192.168.179.131 #这里填写你的ip地址 如果是windos下不用填,当使用linux的时候回,就要使用linux下连接的地址
3、添加测试类
@Autowired
StringRedisTemplate stringRedisTemplate;//操作字符串【常用】
@Autowired
RedisTemplate redisTemplate;//操作k-v都是对象
@Test
public void test01(){
//写入数据
// stringRedisTemplate.opsForValue().append("msg", "hello");
//读取数据
String msg = stringRedisTemplate.opsForValue().get("msg");
System.out.println(msg);
}
写入数据
[外链图片转存失败(img-ubQxJukE-1566554304016)(images/微信截图_20190819144608.png)]
读取数据
[外链图片转存失败(img-yTCaDJ5A-1566554304022)(images/微信截图_20190819144826.png)]
对象需要序列化
1、序列化bean对象
public class Employee implements Serializable {
2、将对象存储到Redis
@Test
public void test02(){
Employee emp = employeeMapper.getEmpById(2);
redisTemplate.opsForValue().set("emp-01", emp);
}
3、效果演示
[外链图片转存失败(img-zYzI7MTn-1566554304023)(images/微信截图_20190817180255.png)]
1、新建一个Redis的配置类MyRedisConfig,
@Configuration
public class MyRedisConfig {
@Bean
public RedisTemplate<Object, Employee> empRedisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Employee> template = new RedisTemplate<Object, Employee>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Employee> jsonRedisSerializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
template.setDefaultSerializer(jsonRedisSerializer);
return template;
}
2、编写测试类
@Autowired
RedisTemplate<Object,Employee> empRedisTemplate;
@Test
public void test02(){
Employee emp = employeeMapper.getEmpById(2);
empRedisTemplate.opsForValue().set("emp-01", emp);
}
3、测试效果
[外链图片转存失败(img-Hc7iXecI-1566554304031)(images/微信截图_20190819150621.png)]
1、异步处理
同步机制
[外链图片转存失败(img-YdzksOQW-1566554304037)(images/微信截图_20190819155008.png)]
并发机制
[外链图片转存失败(img-PRVZ7XA1-1566554304038)(images/微信截图_20190819155030.png)]
消息队列机制
[外链图片转存失败(img-jbZFXVbg-1566554304041)(images/微信截图_20190819155104.png)]
2、应用解耦
使用中间件,将两个服务解耦,一个写入,一个订阅
3、流量削锋
例如消息队列的FIFO,限定元素的长度,防止出现多次请求导致的误操作
商城秒杀、仓库订单管理、点对点发送消息
1、大多数应用,可以通过消息服务中间件来提升系统的异步通信、拓展解耦能力
2、消息服务中的两个重要概念:
消息代理(message broker)和目的地(destination),当消息发送者发送消息以后,将由消息代理接管,消息代理保证消息传递到指定的目的地。
3、消息队列主要的两种形式的目的地
1)、队列(queue):点对点消息通信【point-to-point】,取出一个没一个,一个发布,多个消费
2)、主题(topic):发布(publish)/订阅(subscribe)消息通信,多人【订阅者】可以同时接到消息
4、JMS(Java Message Service) Java消息服务:
5、AMQP(Advanced Message Queuing Protocol)
JMS | AMQP | |
---|---|---|
定义 | Java API | 网络线级协议 |
跨平台 | 否 | 是 |
跨语言 | 否 | 是 |
Model | (1)、Peer-2-Peer (2)、Pub/Sub | (1)、direct exchange (2)、fanout exchange (3)、topic change (4)、headers exchange (5)、system exchange 后四种都是pub/sub ,差别路由机制做了更详细的划分 |
支持消息类型 | TextMessage MapMessage ByteMessage StreamMessage ObjectMessage Message | byte[]通常需要序列化 |
6、SpringBoot的支持
spring-jms提供了对JMS的支持
spring-rabbit提供了对AMQP的支持
需要创建ConnectionFactory的实现来连接消息代理
提供JmsTemplate,RabbitTemplate来发送消息
@JmsListener(JMS).@RabbitListener(AMQP)注解在方法上的监听消息代理发布的消息
@EnableJms,@EnableRabbit开启支持
7、SpringBoot的自动配置
AMQP的实现
Message:消息头和消息体组成,消息体是不透明的,而消息头上则是由一系列的可选属性组成,属性:路由键【routing-key】,优先级【priority】,指出消息可能需要持久性存储【delivery-mode】
Publisher:消息的生产者,也是一个向交换器发布消息的客户端应用程序
Exchange:交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列,目前共有四种类型:direct、fanout、topic、headers。headers现在几乎用不到了,所以直接看剩下的三种类型。。。
Exchange的4中类型:direct【默认】点对点,fanout,topic和headers, 发布订阅,不同类型的Exchange转发消息的策略有所区别
Queue:消息队列,用来保存消息直到发送给消费者,它是消息的容器,也是消息的终点,一个消息可投入一个或多个队列,消息一直在队列里面,等待消费者连接到这个队列将数据取走。
Binding:绑定,队列和交换机之间的关联,多对多关系
Connection:网络连接,例如TCP连接
Channel:信道,多路复用连接中的一条独立的双向数据流通道,信道是建立在真是的TCP链接之内的虚拟连接AMQP命令都是通过信道发送出去的。不管是发布消息,订阅队列还是接受消息,都是信道,减少TCP的开销,复用一条TCP连接。
Consumer:消息的消费者,表示一个从消息队列中取得消息的客户端的 应用程序
VirtualHost:小型的rabbitMQ,相互隔离
Broker:表示消息队列 服务实体
[外链图片转存失败(img-9Tbpg6Aq-1566554304043)(images/微信截图_20190819162548.png)]
他的核心是 交换器 exchange 和 绑定规则 Binding ;交换器不同,绑定规则也就不同
Exchange的三种方式
direct:根据路由键直接匹配,一对一; 典型的 点对点 通信模型
[外链图片转存失败(img-6ETuv7Kq-1566554304044)(images/QQ截图20190819225145.png)]
fanout:不经过路由键,直接发送到每一个队列, 类似于 广播模式 发布订阅就使用 fanout;速度是最快的
[外链图片转存失败(img-q4i7WYMO-1566554304045)(images/QQ截图20190819231439.png)]
topic:类似模糊匹配的根据路由键,来分配绑定的队列
[外链图片转存失败(img-qMN3GuFy-1566554304047)(images/QQ截图20190819233551.png)]
1、打开虚拟机,在docker中安装RabbitMQ
#1.安装rabbitmq,使用镜像加速
docker pull rabbitmq:3-management
[root@node1 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
registry.docker-cn.com/library/rabbitmq 3-management c51d1c73d028 11 days ago 149 MB
#2.运行rabbitmq
##### 端口:5672 客户端和rabbitmq通信 15672:管理界面的web页面
docker run -d -p 5672:5672 -p 15672:15672 --name myrabbitmq c51d1c73d028
#3.查看运行
docker ps
2、打开网页客户端并登陆 网络ip地址:rabbitmq的端口号,账号【guest】,密码【guest】,登陆
[外链图片转存失败(img-BKq8P2Df-1566554304056)(images/QQ截图20190820000446.png)]
3、添加 【direct】【faout】【topic】的绑定关系等
1)、添加Exchange,分别添加exchange.direct、exchange.fanout、exchange.topic
[外链图片转存失败(img-AA7lxTkg-1566554304057)(images/QQ截图20190820000852.png)]
2)、添加 Queues,分别添加lxy.news、wdjr、wdjr.emps、wdjr.news
[外链图片转存失败(img-fj0zNkDr-1566554304061)(images/QQ截图20190820001116.png)]
3)、点击【exchange.direct】添加绑定规则
[外链图片转存失败(img-fVGEVJuu-1566554304062)(images/QQ截图20190820002024.png)]
4)、点击【exchange.fanout】添加绑定规则
[外链图片转存失败(img-DnjVivWJ-1566554304064)(images/QQ截图20190820002538.png)]
5)、点击【exchange.topic】添加绑定规则
[外链图片转存失败(img-drBWdW9p-1566554304072)(images/QQ截图20190820002741.png)]
/*: 代表匹配1个单词
/#:代表匹配0个或者多个单词
4、发布信息测试
【direct】发布命令,点击 Publish message
[外链图片转存失败(img-xhDRe11j-1566554304073)(images/QQ截图20190820003858.png)]
查看队列的数量
点击查看发送的信息
【fanout】的发布消息
队列信息
随意一个数据信息例如:wdjr.emp
【topic】发布信息测试
队列的值
信息查看
补充:消息队列是用来保存数据的,当别人将消息取出后,消息队列中的数据就会减少
1、RabbitAutoConfiguration
2、自动配置了连接工厂 ConnectionFactory
3、RabbitProperties封装了 RabbitMQ
4、RabbitTemplate:给RabbitMQ发送和接受消息的
5、AmqpAdmin:RabbitMQ的系统管理功能组件
6、@EnableRabbit + @RabbitListener 监听消息队列的内容 springboot2.x以后 默认配置了 @EnableRabbit,所以 在2.x后 不用配置,也可以监听消息队列的内容
1、新建SpringBoot工程,SpringBoot2.17+Integeration/RabbitMQ+Web
2、RabbitAutoConfiguration文件
3、编写配置文件application.yml
spring:
rabbitmq:
host: 192.168.179.131
port: 5672
username: guest
password: guest
4、编写测试类,将HashMap写入Queue
@Autowired
RabbitTemplate rabbitTemplate;
/*
测试1
*/
@Test
public void contextLoads() {
//Message需要自己构建一个;定义消息体内容和消息头
// rabbitTemplate.send(exchange, routingKey, message);
//Object 默认当成消息体,只需要传入要发送的对象,自动化序列发送给rabbitmq;
// rabbitTemplate.convertAndSend(exchange,routingkey,object);
// Map map = new HashMap<>();
//map.put("msg", "这是第一个信息");
//map.put("data", Arrays.asList("helloWorld",123,true));
//对象被默认序列以后发送出去
rabbitTemplate.convertAndSend("exchange.direct","wdjr.news",map);
}
/**
测试2
*/
@Test
public void testsend2() {
rabbitTemplate.convertAndSend("exchange.direct","wdjr","随便发点消息");
System.out.println("发送点对点消息");
}
5、查看网页的信息
//测试1 如下图
[外链图片转存失败(img-lyGLBYbL-1566554304074)(images/QQ截图20190820150420.png)]
//测试2如下图
[外链图片转存失败(img-SalWU4fB-1566554304077)(images/QQ截图20190820145848.png)]
6、取出队列的值
取出队列中数据就没了
@Test
public void reciverAndConvert(){
Object o = rabbitTemplate.receiveAndConvert("wdjr.news");
System.out.println(o.getClass());
System.out.println(o);
}
结果
class java.util.HashMap
{msg=这是第一个信息, data=[helloWorld, 123, true]}
12
7、更改默认的序列;使用Json方式传递,并传入对象Book
1)、MyAMQPConfig
@Configuration
public class MyAMQPConfig {
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
2)、编写Book实体类
package com.wdjr.amqp.bean;
public class Book {
private String bookName;
private String author;
public Book(){
}
public Book(String bookName, String author) {
this.bookName = bookName;
this.author = author;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
@Override
public String toString() {
return "Book{" +
"bookName='" + bookName + '\'' +
", author='" + author + '\'' +
'}';
}
}
3)、测试类
@Test
public void contextLoads() {
//对象被默认序列以后发送出去
rabbitTemplate.convertAndSend("exchange.direct","wdjr.news",new Book("百年孤独", "季羡林"));
}
4)、查看wdjr.news
[外链图片转存失败(img-tt5fQwXO-1566554304077)(images/QQ截图20190820154830.png)]
5)、取出数据
@Test
public void reciverAndConvert(){
Object o = rabbitTemplate.receiveAndConvert("wdjr.news");
System.out.println(o.getClass());
System.out.println(o);
}
6)、结果演示
class com.wdjr.amqp.bean.Book
Book{bookName='百年孤独', author='季羡林'}
1、新建一个BookService 用来 配置监听 类似 商家 与 订单单号 客户 客户点的单子 ,将单子发送到消息队列中,然后,商家就监听,消息队列中是否有消息, 有消息就处理;没有消息就不处理
@Service
public class BookService {
@RabbitListener(queues = "wdjr.news")
public void receive(Book book){
System.out.println(book);
}
@RabbitListener(queues = "wdjr")
public void receive02(Message message){
System.out.println(message.getBody());
System.out.println(message.getMessageProperties());
}
}
2、主程序开启RabbitMQ的注解 2.17 ===》后都不需要使用 @EnableRabbit
@EnableRabbit //开启基于注解的rabbitmq
@SpringBootApplication
public class AmqpApplication {
public static void main(String[] args) {
SpringApplication.run(AmqpApplication.class, args);
}
}
创建和删除 Exchange 、Queue、Bind
1)、创建Exchange
@Autowired
private AmqpAdmin amqpAdmin;
@Test
public void testAmqpaAdmin(){
//创建exchange交换器
amqpAdmin.declareExchange(new DirectExchange("amqpadmin.direct"));
System.out.println("Create Finish");
}
效果演示
[外链图片转存失败(img-lCb6EkB7-1566554304081)(images/QQ截图20190820163140.png)]
2)、创建Queue
@Test
public void createQueue(){
amqpAdmin.declareQueue(new Queue("amqpAdmin.queue",true));
System.out.println("Create Queue Finish");
}
[外链图片转存失败(img-PrQ9WTLZ-1566554304083)(images/QQ截图20190820163501.png)]
3)、创建Bind规则
@Test
public void createBind(){
amqpAdmin.declareBinding(new Binding("amqpAdmin.queue", Binding.DestinationType.QUEUE , "amqpadmin.direct", "amqp.haha", null));
}
[外链图片转存失败(img-H8tZeZUb-1566554304084)(images/QQ截图20190820163909.png)]
删除类似
@Test
public void deleteExchange(){
amqpAdmin.deleteExchange("amqpadmin.direct");
System.out.println("delete Finish");
}
ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。
1、安装java最新版本
2、安装Docker(非必须这是是在Docker中安装)
1、查看centos版本
# uname -r
3.10.0-693.el7.x86_64
要求:大于3.10
如果小于的话升级*(选做)
# yum update
2、安装docker
# yum install docker
3、启动docker
# systemctl start docker
# docker -v
4、开机启动docker
# systemctl enable docker
5、停止docker
# systemctl stop docker
3、安装ElasticSearch的镜像
docker pull registry.docker-cn.com/library/elasticsearch
4、运行ElasticSearch
-e ES_JAVA_OPTS="-Xms256m -Xmx256m" 表示占用的最大内存为256m,默认是2G
[root@node1 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
registry.docker-cn.com/library/elasticsearch latest 671bb2d7da44 32 hours ago 486 MB
[root@node1 ~]#
[root@node1 ~]# docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -d -p 9200:9200 -p 9300:9300 --name ES01 671bb2d7da44
12345
5、测试是否启动成功
访问9200端口:http://192.168.179.131:9200/ 查看是否返回json数据:如果返回以下内容,说明就是成功的!
{
"name" : "onB-EUU",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "j3SXX6tdThWUomW3tAvDFg",
"version" : {
"number" : "5.6.9",
"build_hash" : "877a590",
"build_date" : "2018-04-12T16:25:14.838Z",
"build_snapshot" : false,
"lucene_version" : "6.6.1"
},
"tagline" : "You Know, for Search"
}
最好的工具就是官方文档,以下操作都在文档中进行操作。
面向文档,JSON作为序列化格式,ElasticSearch的基本概念
索引(名词):
如前所述,一个 索引 类似于传统关系数据库中的一个 数据库 ,是一个存储关系型文档的地方。 索引 (index) 的复数词为 indices 或 indexes 。
索引(动词):
索引一个文档 就是存储一个文档到一个 索引 (名词)中以便它可以被检索和查询到。这非常类似于 SQL 语句中的 INSERT
关键词,除了文档已存在时新文档会替换旧文档情况之外。
类型:相当于数据库中的表
文档:相当于数据库中的行,即每条数据都叫一个文档
属性:相当于数据库中的列,即文档的属性
下载POSTMAN,并使用POSTMAN测试
具体信息查看官方示例
重点:PUT请求+请求体
PUT /megacorp/employee/1
{
"first_name" : "John",
"last_name" : "Smith",
"age" : 25,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
注意,路径 /megacorp/employee/1
包含了三部分的信息:
megacorp
索引名称
employee
类型名称
1
特定雇员的ID
[外链图片转存失败(img-fUR9IsGn-1566554304088)(images/QQ截图20190821090556.png)]
官方示例
重点:GET请求+URI+index+type+ID
GET /megacorp/employee/1
[外链图片转存失败(img-ycive1wL-1566554304089)(images/QQ截图20190821091202.png)]
官方文档
重点:GET请求+index+type+_search+条件(非必须)
搜索所有雇员: _search
GET /megacorp/employee/_search
查询字符串
GET /megacorp/employee/_search?q=last_name:Smith
官方文档
重点:GET+URI+index+type+_search+请求体【match】
注意:get提交方法 在body无法输入值!所以 要改成post提交
Query-string 搜索通过命令非常方便地进行临时性的即席搜索 ,但它有自身的局限性(参见 轻量 搜索 )。Elasticsearch 提供一个丰富灵活的查询语言叫做 查询表达式 ,它支持构建更加复杂和健壮的查询。
领域特定语言 (DSL), 指定了使用一个 JSON 请求。我们可以像这样重写之前的查询所有 Smith 的搜索 :
GET /megacorp/employee/_search
{
"query" : {
"match" : {
"last_name" : "Smith"
}
}
}
返回结果与之前的查询一样,但还是可以看到有一些变化。其中之一是,不再使用 query-string 参数,而是一个请求体替代。这个请求使用 JSON 构造,并使用了一个 match
查询(属于查询类型之一,后续将会了解)。
如图效果:
[外链图片转存失败(img-WFNgz9sp-1566554304090)(images/QQ截图20190821092446.png)]
官方文档
重点:GET+URI+index+type+_search + 请求体【match+filter】
注意:get提交方法 在body无法输入值!所以 要改成post提交
现在尝试下更复杂的搜索。 同样搜索姓氏为 Smith 的雇员,但这次我们只需要年龄大于 30 的。查询需要稍作调整,使用过滤器 filter ,它支持高效地执行一个结构化查询。
GET /megacorp/employee/_search
{
"query" : {
"bool": {
"must": {
"match" : {
"last_name" : "smith"
}
},
"filter": {
"range" : {
"age" : { "gt" : 30 }
}
}
}
}
}
[外链图片转存失败(img-3BPELVSR-1566554304092)(https://www.elastic.co/guide/cn/elasticsearch/guide/current/images/icons/callouts/1.png)] | 这部分与我们之前使用的 match 查询 一样。 |
---|---|
[外链图片转存失败(img-9XFfkO1u-1566554304094)(https://www.elastic.co/guide/cn/elasticsearch/guide/current/images/icons/callouts/2.png)] | 这部分是一个 range 过滤器 , 它能找到年龄大于 30 的文档,其中 gt 表示_大于(_great than)。 |
目前无需太多担心语法问题,后续会更详细地介绍。只需明确我们添加了一个 过滤器 用于执行一个范围查询,并复用之前的 match
查询。现在结果只返回了一个雇员,叫 Jane Smith,32 岁。
官方文档
重点:GET+index+type+_search+请求体【match】 ==》看相关性得分 得分越高,匹配度越高
截止目前的搜索相对都很简单:单个姓名,通过年龄过滤。现在尝试下稍微高级点儿的全文搜索——一项传统数据库确实很难搞定的任务。
搜索下所有喜欢攀岩(rock climbing)的雇员:
GET /megacorp/employee/_search
{
"query" : {
"match" : {
"about" : "rock climbing"
}
}
}
12345678
显然我们依旧使用之前的 match
查询在about
属性上搜索 “rock climbing” 。得到两个匹配的文档:
{
...
"hits": {
"total": 2,
"max_score": 0.16273327,
"hits": [
{
...
"_score": 0.16273327,
"_source": {
"first_name": "John",
"last_name": "Smith",
"age": 25,
"about": "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
},
{
...
"_score": 0.016878016,
"_source": {
"first_name": "Jane",
"last_name": "Smith",
"age": 32,
"about": "I like to collect rock albums",
"interests": [ "music" ]
}
}
]
}
}
“_score”:相关性得分
Elasticsearch 默认按照相关性得分排序,即每个文档跟查询的匹配程度。第一个最高得分的结果很明显:John Smith 的 about
属性清楚地写着 “rock climbing” 。
但为什么 Jane Smith 也作为结果返回了呢?原因是她的 about
属性里提到了 “rock” 。因为只有 “rock” 而没有 “climbing” ,所以她的相关性得分低于 John 的。
这是一个很好的案例,阐明了 Elasticsearch 如何 在 全文属性上搜索并返回相关性最强的结果。Elasticsearch中的 相关性 概念非常重要,也是完全区别于传统关系型数据库的一个概念,数据库中的一条记录要么匹配要么不匹配。
官方文档
重点:GET+index+type+_search+请求体【match_phrase 】
找出一个属性中的独立单词是没有问题的,但有时候想要精确匹配一系列单词或者短语 。 比如, 我们想执行这样一个查询,仅匹配同时包含 “rock” 和 “climbing” ,并且 二者以短语 “rock climbing” 的形式紧挨着的雇员记录。
为此对 match
查询稍作调整,使用一个叫做 match_phrase
的查询:
GET /megacorp/employee/_search
{
"query" : {
"match_phrase" : {
"about" : "rock climbing"
}
}
}
返回的信息
{
...
"hits": {
"total": 1,
"max_score": 0.23013961,
"hits": [
{
...
"_score": 0.23013961,
"_source": {
"first_name": "John",
"last_name": "Smith",
"age": 25,
"about": "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
}
]
}
}
官方地址
重点:GET+index+type+_search+请求体【match_phrase+highlight】==>返回关键字加了em标签
许多应用都倾向于在每个搜索结果中 高亮 部分文本片段,以便让用户知道为何该文档符合查询条件。在 Elasticsearch 中检索出高亮片段也很容易。
再次执行前面的查询,并增加一个新的 highlight
参数:
GET /megacorp/employee/_search
{
"query" : {
"match_phrase" : {
"about" : "rock climbing"
}
},
"highlight": {
"fields" : {
"about" : {}
}
}
}
当执行该查询时,返回结果与之前一样,与此同时结果中还多了一个叫做 highlight
的部分。这个部分包含了 about
属性匹配的文本片段,并以 HTML 标签 封装:
{
...
"hits": {
"total": 1,
"max_score": 0.23013961,
"hits": [
{
...
"_score": 0.23013961,
"_source": {
"first_name": "John",
"last_name": "Smith",
"age": 25,
"about": "I love to go rock climbing",
"interests": [ "sports", "music" ]
},
"highlight": {
"about": [
"I love to go rock climbing"
]
}
}
]
}
}
官方文档
重点:GET+index+type+_search+请求体【aggs-field】
aggs:聚合
终于到了最后一个业务需求:支持管理者对雇员目录做分析。 Elasticsearch 有一个功能叫聚合(aggregations),允许我们基于数据生成一些精细的分析结果。聚合与 SQL 中的 GROUP BY
类似但更强大。
举个例子,挖掘出雇员中最受欢迎的兴趣爱好:
GET /megacorp/employee/_search
{
"aggs": {
"all_interests": {
"terms": { "field": "interests" }
}
}
}
会报错
Fielddata is disabled on text fields by default. Set fielddata=true on [inte
默认情况下,字段数据在文本字段上禁用。设置字段数据= TRUE
下面已经解决了!这个问题,详细参照
首先开启数据结构
PUT megacorp/_mapping
{
"properties": {
"interests": {
"type": "text",
"fielddata": true
}
}
}
效果如图:
[外链图片转存失败(img-LP02CjC9-1566554304096)(images/QQ截图20190821102648.png)]
然后在进行请求
{
...
"hits": { ... },
"aggregations": {
"all_interests": {
"buckets": [
{
"key": "music",
"doc_count": 2
},
{
"key": "forestry",
"doc_count": 1
},
{
"key": "sports",
"doc_count": 1
}
]
}
}
}
可以看到,两位员工对音乐感兴趣,一位对林地感兴趣,一位对运动感兴趣。这些聚合并非预先统计,而是从匹配当前查询的文档中即时生成。
如果想知道叫 Smith 的雇员中最受欢迎的兴趣爱好,可以直接添加适当的查询来组合查询:
GET /megacorp/employee/_search
{
"query": {
"match": {
"last_name": "smith"
}
},
"aggs": {
"all_interests": {
"terms": {
"field": "interests"
}
}
}
}
all_interests
聚合已经变为只包含匹配查询的文档:
...
"all_interests": {
"buckets": [
{
"key": "music",
"doc_count": 2
},
{
"key": "sports",
"doc_count": 1
}
]
}
聚合还支持分级汇总 。比如,查询特定兴趣爱好员工的平均年龄:
GET /megacorp/employee/_search
{
"aggs" : {
"all_interests" : {
"terms" : { "field" : "interests" },
"aggs" : {
"avg_age" : {
"avg" : { "field" : "age" }
}
}
}
}
}
输出基本是第一次聚合的加强版。依然有一个兴趣及数量的列表,只不过每个兴趣都有了一个附加的 avg_age
属性,代表有这个兴趣爱好的所有员工的平均年龄。
即使现在不太理解这些语法也没有关系,依然很容易了解到复杂聚合及分组通过 Elasticsearch 特性实现得很完美。可提取的数据类型毫无限制。
综上所述:elasticSearch就是操作查询表达式;
1、新建项目SpringBoot2.1.7+Web+Nosql–>ElasticSearch
2、springBoot默认支持两种技术和ES进行交互
1、Jest【需要导入使用】
利用JestClient和服务器的9200端口进行http通信
2、SpringData ElasticSearch【默认】
1)、客户端: Client节点信息: clusterNodes: clusterName
2)、ElasticsearchTemplate操作es
3)、编写ElasticsearchRepository子接口 来操作 es
1、注释SpringDataElasticSearch的依赖,并导入Jest【x.xx】的相关依赖 版本 根据elastSearch版本 来区别
<dependency>
<groupId>io.searchboxgroupId>
<artifactId>jestartifactId>
<version>6.3.1version>
dependency>
2、修改配置文件application.properties
spring.elasticsearch.jest.uris=http://192.168.1.106:9200/
3、创建 bean.Article
package com.wdjr.springboot.bean;
import io.searchbox.annotations.JestId;
public class Article {
@JestId
private Integer id;
private String autor;
private String title;
private String content;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getAutor() {
return autor;
}
public void setAutor(String autor) {
this.autor = autor;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
4、运行程序
5、编写Jest Cilent的测试类
向wdjr-article中插入数据
@Autowired
JestClient jestClient;
@Test
public void contextLoads() {
//1.给se中索引保存一个文档
Article article = new Article();
article.setId(1);
article.setAutor("张三");
article.setContent("hello world");
article.setTitle("好消息");
//构建一个索引功能呢
Index build = new Index.Builder(article).index("wdjr").type("article").build();
//执行
try {
jestClient.execute(build);
} catch (IOException e) {
e.printStackTrace();
}
}
访问数据 请求路径 索引 +类型 +id
[外链图片转存失败(img-Yufubq1f-1566554304097)(images/QQ截图20190821154952.png)]
查询数据
@Test
public void search(){
//查询表达式
String json = "{\n" +
" \"query\" : {\n" +
" \"match\" : {\n" +
" \"content\" : \"Hello\"\n" +
" }\n" +
" }\n" +
"}";
//构建搜索操作
Search search = new Search.Builder(json).addIndex("wdjr").addType("article").build();
//执行
try {
SearchResult result = jestClient.execute(search);
System.out.println(result.getJsonString());
} catch (IOException e) {
e.printStackTrace();
}
}
更多操作可以上github学习:地址
1、下载对应版本的ElasticSearch elasticSearch 与 对应的spring date elasticSearch版本 地址
如果版本不适配,会报错,解决方案:升级SpringBoot版本,或者安装合适的ES
Spring Data Elasticsearch | Elasticsearch | |
---|---|---|
3.2.x | 6.8.1 | not yet released |
3.1.x | 6.2.2 | 3.0.x |
5.5.0 | 2.1.x | 2.4.0 |
2.0.x | 2.2.0 | 1.3.x |
2、在Docker中安装适合版本的ES【6.4.3】 这个版本 根据实际情况,选择
docker pull docker.io/elasticsearch:6.4.3
docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -d -p 9201:9200 -p 9301:9300 --name ES02 01e5bee1e059
3、编写配置文件 如果报错的话,就要重新安装es 或者更新版本 我这里是springboot最新的版本,所以没有启动报错 如果报错,操作方法如 上面的 2
#spring.elasticsearch.jest.uris=http://192.168.1.106:9200/
spring.data.elasticsearch.cluster-name=elasticsearch
spring.data.elasticsearch.cluster-nodes=192.168.1.106:9300
4、运行主程序
5、操作ElasticSearch有两种方式
1)、编写一个ElasticsearchRepositry
2)、编写一个ElasticsearchTemplate
6、ElasticsearchRepositry的操作
1)、新建一个bean/Book类
package com.bdqn.elastic.bean;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.data.elasticsearch.annotations.Document;
//indexName代表所以名称,type代表表名称
@Document(indexName = "book", type = "_doc")
public class Notice {
//id
@JsonProperty("auto_id")
private Long id;
//标题
@JsonProperty("title")
private String title;
//公告标签
@JsonProperty("exchange_mc")
private String exchangeMc;
//公告发布时间
@JsonProperty("create_time")
private String originCreateTime;
//公告阅读数量
@JsonProperty("read_count")
private Integer readCount;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getExchangeMc() {
return exchangeMc;
}
public void setExchangeMc(String exchangeMc) {
this.exchangeMc = exchangeMc;
}
public String getOriginCreateTime() {
return originCreateTime;
}
public void setOriginCreateTime(String originCreateTime) {
this.originCreateTime = originCreateTime;
}
public Integer getReadCount() {
return readCount;
}
public void setReadCount(Integer readCount) {
this.readCount = readCount;
}
@Override
public String toString() {
return "Notice{" +
"id=" + id +
", title='" + title + '\'' +
", exchangeMc='" + exchangeMc + '\'' +
", originCreateTime='" + originCreateTime + '\'' +
", readCount=" + readCount +
'}';
}
}
2)、新建一个repositry/BookRepositry
public interface BookRepositry extends ElasticsearchRepository<Notice, Long>{
//自定义查询方法
public List<Notice> findBytitleLike(String title);
}
2)存入索引信息
@Autowired
BookRepositry bookRepositry;
@Test
public void test(){
Notice article = new Notice();
article.setId(1l);
article.setReadCount(123);
article.setTitle("springboot整合elasticsearch,这个是新版本 2018年");
bookRepositry.save(article);
}
在向elasticSearch容器存入索引信息的时候,出现错误了
[外链图片转存失败(img-NUy5El5g-1566554304099)(images/QQ截图20190823171349.png)]
然后,解决思路在docker中换一个与spring data elasticsearch相匹配的容器
下载镜像 docker pull elasticsearch:6.5.1
docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -d -p 9200:9200 -p 9300:9300 --name es02 镜像ID
启动后一直连接不上,然后才发现,刚才启动的es又关闭了;
打开日志 docker logs -f 容器id 发现
显示max_map_count的值太小了,需要设大到262144
直接在界面运行这句话 sysctl -w vm.max_map_count=262144
启动后,在浏览器,显示了那一串json数据
[外链图片转存失败(img-DFH3ok1R-1566554304101)(images/QQ截图20190823173738.png)]
如上图显示:所以我们要修改properties文件
spring.data.elasticsearch.cluster-name=docker-cluster
# Comma-separated list of cluster node addresses.
spring.data.elasticsearch.cluster-nodes=192.168.1.104:9301
# Whether to enable Elasticsearch repositories.
spring.data.elasticsearch.repositories.enabled=true
重启测试方法
[外链图片转存失败(img-xvovo334-1566554304104)(images/QQ截图20190823173947.png)]
完美点亮:!
[外链图片转存失败(img-JQNw7j0D-1566554304105)(images/QQ截图20190823174156.png)]
最后感谢简书的这个朋友 地址
3)、编写测试类
@Test
public void test(){
Notice article = new Notice();
article.setId(1l);
article.setReadCount(123);
article.setTitle("springboot整合elasticsearch,这个是新版本 2018年");
bookRepositry.save(article);
}
###一、异步任务
在Java应用中,绝大多数情况下都是通过同步的方式来实现交互处理的;但是在
处理与第三方系统交互的时候,容易造成响应迟缓的情况,之前大部分都是使用
多线程来完成此类任务,其实,在Spring 3.x之后,就已经内置了@Async来完
美解决这个问题。
#### 1.使用方法
两个注解:
@EnableAysnc、@Aysnc
项目开发中经常需要执行一些定时任务,比如需要在每天凌晨时候,分析一次前
一天的日志信息。Spring为我们提供了异步执行任务调度的方式,提供
TaskExecutor 、TaskScheduler 接口。
1.使用方法
两个注解:@EnableScheduling、@Scheduled
cron表达式:
字段 允许值 允许的特殊字符
秒 0-59 , - * /
分 0-59 , - * /
小时 0-23 , - * /
日期 1-31 , - * ? / L W C
月份 1-12 , - * /
星期 0-7或SUN-SAT 0,7是SUN , - * ? / L C #
特殊字符 代表含义
, 枚举
‘-‘区间
任意
/ 步长
? 日/星期冲突匹配
L 最后
W 工作日
C 和calendar联系后计算过的值
邮件发送需要引入spring-boot-starter-mail
Spring Boot 自动配置MailSenderAutoConfiguration
定义MailProperties内容,配置在application.yml中
自动装配JavaMailSender
发送邮件的流程图
[外链图片转存失败(img-LAZhlxzk-1566554304107)(images/QQ截图20190822100316.png)]
####1.配置:properties文件 和导入pom文件
org.springframework.boot
spring-boot-starter-mail
#登录的邮箱
[email protected]
#允许邮箱在其他地方,登录时要输入授权码,注意:授权码就是你的密码
spring.mail.password=wqfgfomsxszfgahb
#发送邮件服务器
spring.mail.host=smtp.qq.com
#说明这是一个安全的连接
spring.mail.properties.mail.smtp.ssl.enable=true
####2.编写复杂和简单发送邮件
@Autowired
JavaMailSenderImpl mailSender;
@Test
public void contextLoads() {
SimpleMailMessage message = new SimpleMailMessage();
//邮件设置
message.setSubject("通知-今晚开会");
message.setText("你在干什么");
//发给谁
message.setTo("[email protected]");
//谁发的
message.setFrom("[email protected]");
mailSender.send(message);
}
//复杂的,还可以带文件 发送
@Test
public void test02() throws Exception{
//1、创建一个复杂的消息邮件
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
//邮件设置
helper.setSubject("通知-今晚开会");
helper.setText("今天 7:30 开会",true);
helper.setTo("[email protected]");
helper.setFrom("[email protected]");
//上传文件
helper.addAttachment("1.jpg",new File("C:\\Users\\lfy\\Pictures\\Saved Pictures\\1.jpg"));
helper.addAttachment("2.jpg",new File("C:\\Users\\lfy\\Pictures\\Saved Pictures\\2.jpg"));
mailSender.send(mimeMessage);
}
apache旗下,Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
Spring Security 为基于J2EE企业应用软件提供了全面安全服务。特别是使用领先的J2EE解决方案-Spring框架开发的企业软件项目。复杂,功能比较强大,能无缝的整合spring;springboot的底层也是使用的security作为安全框架的;
1)、导入相应的依赖
org.springframework.boot
spring-boot-starter-security
2)编写config配置类
package com.atguigu.security.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.crypto.bcrypt.BCryptPasswordEncoder;
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
//定义授权的规则
@Override
protected void configure(HttpSecurity http) throws Exception {
//定制请求的授权的规则
http.authorizeRequests().antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("VIP1")
.antMatchers("/level2/**").hasRole("VIP2")
.antMatchers("/level3/**").hasRole("VIP3");
//开启自动配置的登录页
http.formLogin();
//1./login来到登录页
//2.重定向/login?error表示登录失败
// super.configure(http);
}
//定义认证规则
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//super.configure(auth);
//注意:在
/**
*
* protected void configure(AuthenticationManagerBuilder auth) throws Exception {
* //inMemoryAuthentication 从内存中获取
* auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).withUser("user1").password(new BCryptPasswordEncoder().encode("123456")).roles("USER");
* }
* */
//注意 这是在spring security 5.x前是可以这样写
/*auth.inMemoryAuthentication()
.withUser("zhangsan").password("123456").roles("VIP1","VIP2")
.and()
.withUser("lisi").password("123456").roles("VIP2","VIP3")
.and()
.withUser("wangwu").password("123456").roles("VIP1","VIP3");*/
/***
现如今Spring Security中密码的存储格式是“{id}…………”。前面的id是加密方式,id可以是bcrypt、sha256等,后面跟着的是加密后的密码。也就是说,程序拿到传过来的密码的时候,会首先查找被“{”和“}”包括起来的id,来确定后面的密码是被怎么样加密的,如果找不到就认为id是null。这也就是为什么我们的程序会报错:There is no PasswordEncoder mapped for the id “null”。官方文档举的例子中是各种加密方式针对同一密码加密后的存储形式,原始密码都是“password”。
*/
//新版本要这样
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("zhangsan").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP1","VIP2")
.and()
.withUser("lisi").password("123456").roles("VIP2","VIP3")
.and()
.withUser("wangwu").password("123456").roles("VIP1","VIP3");
}
}
### 如果要使用 登录后才显示能操作功能
1.引入pom文件 Tymelaf 与 spring security 的整和依赖
org.thymeleaf.extras
thymeleaf-extras-springsecurity4
这里补充一下,thymeleaf-extras-springsecurity到底用 4 还是 5 是根据spring security的版本。 如果: spring security版本是4以上,thymeleaf-extras-springsecurity就用4 spring security版本是5以上,thymeleaf-extras-springsecurity就用5 这里是官方给出的说明: This is a Thymeleaf Extras module, not a part of the Thymeleaf core (and as such following its own versioning schema), but fully supported by the Thymeleaf team. This repository contains 3 projects: thymeleaf-extras-springsecurity3 for integration with Spring Security 3.x thymeleaf-extras-springsecurity4 for integration with Spring Security 4.x thymeleaf-extras-springsecurity5 for integration with Spring Security 5.x
这就是为什么,SpringSecurity4+thymeleaf sec:authorize 标签使用无效 最后终于解决了我的问题了
修改pom文件
org.thymeleaf.extras
thymeleaf-extras-springsecurity5
页面修改
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title heretitle>
head>
<body>
<h1 align="center">欢迎光临武林秘籍管理系统h1>
<div sec:authorize="!isAuthenticated()">
<h2 align="center">游客您好,如果想查看武林秘籍 <a th:href="@{/login}">请登录a>h2>
div>
<div sec:authorize="isAuthenticated()">
<h2><span sec:authentication="name">span>,您好,您的角色有:
<span sec:authentication="principal.authorities">span>h2>
<form th:action="@{/logout}" method="post">
<input type="submit" value="注销"/>
form>
div>
<hr>
<div sec:authorize="hasRole('VIP1')">
<h3>普通武功秘籍h3>
<ul>
<li><a th:href="@{/level1/1}">罗汉拳a>li>
<li><a th:href="@{/level1/2}">武当长拳a>li>
<li><a th:href="@{/level1/3}">全真剑法a>li>
ul>
div>
<div sec:authorize="hasRole('VIP2')">
<h3>高级武功秘籍h3>
<ul>
<li><a th:href="@{/level2/1}">太极拳a>li>
<li><a th:href="@{/level2/2}">七伤拳a>li>
<li><a th:href="@{/level2/3}">梯云纵a>li>
ul>
div>
<div sec:authorize="hasRole('VIP3')">
<h3>绝世武功秘籍h3>
<ul>
<li><a th:href="@{/level3/1}">葵花宝典a>li>
<li><a th:href="@{/level3/2}">龟派气功a>li>
<li><a th:href="@{/level3/3}">独孤九剑a>li>
ul>
div>
body>
html>
上面使用的默认的登录页
//开启记住我功能
http.rememberMe().rememberMeParameter(“remeber”);
//.rememberMeParameter(“remeber”);这里是子定义页面
//登陆成功以后,将cookie发给浏览器保存,以后访问页面带上这个cookie,只要通过检查就可以免登录
//点击注销会删除cookie
3定制登录页
//开启自动配置的登陆功能,效果,如果没有登陆,没有权限就会来到登陆页面
http.formLogin().usernameParameter(“user”).passwordParameter(“pwd”)
.loginPage("/userlogin");
//1、/login来到登陆页
//2、重定向到/login?error表示登陆失败
//3、更多详细规定
//4、默认post形式的 /login代表处理登陆
//5、一但定制loginPage;那么 loginPage的post请求就是登陆
1. Dubbo是什么?
dubbo就是个服务框架,如果没有分布式的需求,其实是不需要用的,只有在分布式的时候,才有dubbo这样的分布式服务框架的需求,并且本质上是个服务调用的东东,说白了就是个远程服务调用的分布式框架(告别Web Service模式中的WSdl,以服务者与消费者的方式在dubbo上注册)
2. Dubbo能做什么?
1.透明化的远程方法调用,就像调用本地方法一样调用远程方法,只需简单配置,没有任何API侵入。
2.软负载均衡及容错机制,可在内网替代F5等硬件负载均衡器,降低成本,减少单点。
3.服务自动注册与发现,不再需要写死服务提供方地址,注册中心基于接口名查询服务提供者的IP地址,并且能够平滑添加或删除服务提供者。
3、Dubbo的原理
[外链图片转存失败(img-OKjqmcST-1566554304108)(images/QQ截图20190823001737.png)]
安装Zookeeper
#安装zookeeper镜像
docker pull docker.io/zookeeper
#运行zookeeper
docker run --name zk01 --restart always -d -p 2181:2181 bf5cbc9d5cac
目的:完成服务消费者从注册中心查询调用服务生产者
步骤:
1、将服务提供者注册到注册中心
1、引入dubbo和zkclient相关依赖
2、配置dubbo的扫描包和注册中心地址
3、使用@Service发布服务
1、将服务提供者注册到注册中心
1)、引入dubbo和zkclient的相关依赖
<dependency>
<groupId>com.alibaba.bootgroupId>
<artifactId>dubbo-spring-boot-starterartifactId>
<version>0.2.0version>
dependency>
<dependency>
<groupId>com.github.sgroschupfgroupId>
<artifactId>zkclientartifactId>
<version>0.1version>
dependency>
2)、配置service服务,新建service.TicketService 和service.TicketServiceImp
public interface TicketService {
public String getTicket();
}
import org.springframework.stereotype.Component;
import com.alibaba.dubbo.config.annotation.Service;
@Component //加在spring的容器中
@Service //dubbo下的service;将服务发布出去; 通过propertis扫描后
public class TicketServiceImp implements TicketService {
@Override
public String getTicket() {
return "《厉害了,我的国》";
}
}
3)、配置文件application.yml
#当前应用的名字
dubbo.application.name=provider-ticket
#dubbo的注册中心
dubbo.registry.address=zookeeper://192.168.1.106:2181
#要将那个包下的内容发布出去
dubbo.scan.base-packages=com.atguigu.ticket.service
4)、启动服务提供者
在服务启动类上要加@EnableDubbo springboot2.x后 默认没有启用Dubbo;
如果不加,在服务就注册不进 zookeeper注册中心
[外链图片转存失败(img-fZjIz1OB-1566554304109)(images/QQ截图20190823020103.png)]
2、启动服务消费者
1)、引入Dubbo和Zookeeper的依赖
<dependency>
<groupId>com.alibaba.bootgroupId>
<artifactId>dubbo-spring-boot-starterartifactId>
<version>0.2.0version>
dependency>
<dependency>
<groupId>com.github.sgroschupfgroupId>
<artifactId>zkclientartifactId>
<version>0.1version>
dependency>
2)、新建一个service.userService,并将TicketService的接口调用过来【全类名相同-包相同】
[外链图片转存失败(img-4bKYSvIN-1566554304111)(images/QQ截图20190823020521.png)]
package com.atguigu.user.server;
import com.alibaba.dubbo.config.annotation.Reference;
import com.atguigu.ticket.service.TicketService;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Reference
TicketService ticketService;
public void hello(){
String ticket = ticketService.getTicket();
System.out.println("买到票了:"+ticket);
}
}
3)、配置文件application.yml
#当前应用的名字
dubbo.application.name=consumer-user
#dubbo的注册中心
dubbo.registry.address=zookeeper://192.168.1.106:2181
4)、编写测试类测试
package com.atguigu.user;
import com.atguigu.user.server.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ConsumerUserApplicationTests {
@Autowired
UserService userService;
@Test
public void contextLoads() {
userService.hello();
}
}
结果展示: 切记,消费者在消费时候,服务提供者,必须开启
[外链图片转存失败(img-Lj7CE5L0-1566554304114)(images/QQ截图20190823020753.png)]
SpringCloud是一个分布式的整体解决方案,Spring Cloud为开发者提供了在分布式系统(配置管理,服务器发现,熔断,路由,微代理,控制总线,一次性token,全局锁,leader选举,分布式session,集群状态)中快速构建的工具,使用SpringCloud的开发者可以快速的驱动服务或者构建应用,同时能够和云平台资源进行对接。
SpringCloud分布式开发的五大常用组件
Eureka:找到
目的:
多个A服务调用多个B服务,负载均衡
注册中心+服务提供者+服务消费者
1、新建Spring项目 ,SpringBoot1.5+Eureka Server
2、编写application.yml
server:
port: 8761
eureka:
instance:
hostname: eureka-server #实例的主机名
client:
register-with-eureka: false #不把自己注册到euraka上
fetch-registry: false #不从euraka上来获取服务的注册信息
service-url:
defaultZone: http://localhost:8761/eureka/
12345678910
3、编写主程序
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
12345678
1、新建Spring项目,SpringBoot1.5+Eureka Discovery
2、编写配置文件application.yml
server:
port: 8002
spring:
application:
name: provider-ticket
eureka:
instance:
prefer-ip-address: true #注册是服务使用IP地址
client:
service-url:
defaultZone: http://localhost:8761/eureka/
12345678910111213
3、创建一个售票的service
@Service
public class TicketService {
public String getTicket(){
System.out.println("8001");
return "《厉害了,我的国》";
}
}
12345678
4、创建一个用于访问的controller
@RestController
public class TicketController {
@Autowired
TicketService ticketService;
@GetMapping("/ticket")
public String getTicket(){
return ticketService.getTicket();
}
}
1234567891011
5、完毕
1、新建Spring项目,SpringBoot1.5+Eureka Discovery
2、编写application.yml文件
spring:
application:
name: consumer-user
server:
port: 9001
eureka:
instance:
prefer-ip-address: true
client:
service-url:
defaultZone: http://localhost:8761/eureka/
1234567891011
3、编写一个controller
@RestController
public class UserController {
@Autowired
RestTemplate restTemplate;
@GetMapping("/buy")
public String buyTicket(String name){
String s = restTemplate.getForObject("http://PROVIDER-TICKET/ticket", String.class);
return name+"购买了"+" "+s;
}
}
1234567891011
4、编写主程序
@EnableDiscoveryClient //开启发现服务功能
@SpringBootApplication
public class ConsumerUserApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerUserApplication.class, args);
}
@LoadBalanced //使用负载均衡机制
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
5、完毕
1、运行Eureka-server,provider-ticket【8002执行】(端口改为8001打成jar包,执行),consumer-user
2、provider-ticket
3、consumer-user
访问是以负载均衡的方式,所以每次都是 8001 。8002.轮询访问
org.springframework.boot
spring-boot-devtools
true
idea中 按 Ctrl+f9
eclipse 中 按 Ctrl+s 保存
ps: IDEA使用ctrl+F9
– 或做一些小调整
Intellij IEDA和Eclipse不同,Eclipse设置了自动编译之后,修改类它会自动编译,而IDEA在非RUN或DEBUG情况下
才会自动编译(前提是你已经设置了Auto-Compile)。
• 设置自动编译(settings-compiler-make project automatically)
• ctrl+shift+alt+/(maintenance)
• 勾选compiler.automake.allow.when.app.running
通过引入spring-boot-starter-actuator,可以使用Spring Boot为我们提供的准
生产环境下的应用监控和管理功能。我们可以通过HTTP,JMX,SSH协议来进
行操作,自动得到审计、健康及指标信息等
• 步骤:
– 引入spring-boot-starter-actuator
– 通过http方式访问监控端点
– 可进行shutdown(POST 提交,此端点默认关闭)
• 监控和管理端点
端点名 | 描述 |
---|---|
autoconfig | 所有自动配置信息 |
auditevents | 审计事件 |
beans | 所有Bean的信息 |
configprops | 所有配置属性 |
dump | 线程状态信息 |
env | 当前环境信息 |
health | 应用健康状况 |
info | 当前应用信息 |
metrics | 应用的各项指标 |
mappings | 应用@RequestMapping映射路径 |
shutdown | 关闭当前应用(默认关闭) |
trace | 追踪信息(最新的http请求) |
定制端点一般通过endpoints+端点名+属性名来设置。
修改端点id(endpoints.beans.id=mybeans)
开启远程应用关闭功能(endpoints.shutdown.enabled=true)
关闭端点(endpoints.beans.enabled=false)
开启所需端点 endpoints.enabled=false endpoints.beans.enabled=true
定制端点访问根路径 management.context-path=/manage
关闭http端点 management.port=-1
其中配置文件报错
a global security auto-configuration is now provided
问题原因是springBoot1.XXX升级为2以后的版本问题:
从Spring Boot 1.5升级到2.0
Spring Data的一些方法进行了重命名:
findOne(id) -> findById(id) (返回的是Optional)
delete(id) -> deleteById(id)
exists(id) -> existsById(id)
findAll(ids) -> findAllById(ids)
在升级时,可以在pom中增加spring-boot-properties-migrator,这样在启动时会提示需要更改的配置:
org.springframework.boot
spring-boot-properties-migrator
runtime
其中改动较大的是security(The security auto-configuration is no longer customizable,A global security auto-configuration is now provided)、management、banner、server等。
security
security开头的配置及management.security均已过期,如以下的配置不再支持,需要调整到代码中:
security:
ignored: /api-docs,/swagger-resources/,/swagger-ui.html,/webjars/**
management
management:
security:
enabled: false
port: 8090
修改为:
management:
server:
port: 8090
datasource
datasource:
initialize: false
修改为:
datasource:
initialization-mode: never
如使用PostgreSQL,可能会报错:Method org.postgresql.jdbc.PgConnection.createClob() is not yet implemented hibernate,需修改配置:
jpa:
database-platform: org.hibernate.dialect.PostgreSQLDialect
properties:
hibernate:
default_schema: test
jdbc:
lob:
non_contextual_creation: true
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api-docs", "/swagger-resources/", "/swagger-ui.html", "/webjars/**");
}
如在代码中注入了AuthenticationManager,
@Autowired
private AuthenticationManager authenticationManager;
在启动时会报错:Field authenticationManager required a bean of type ‘org.springframework.security.authentication.AuthenticationManager’ that could not be found.请在WebSecurityConfigurerAdapter中增加以下代码:
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
Old property | New property |
---|---|
endpoints.id.* | management.endpoint.id.* |
endpoints.cors.* | management.endpoints.web.cors.* |
endpoints.jmx.* | management.endpoints.jmx.* |
management.address | management.server.address |
management.context-path | management.server.servlet.context-path |
management.ssl.* | management.server.ssl.* |
management.port | management.server.port |
management.endpoints.web.base-path的默认值为/actuator,即Actuator访问路径前部增加了
actuator([/actuator/health],[/actuator/info]),这可以在启动日志中看到。
因management.security不再支持,权限配置需添加到WebSecurityConfigurerAdapter中:
.authorizeRequests()
.requestMatchers(EndpointRequest.to("health", "info")).permitAll()
Endpoint | Changes |
---|---|
/actuator | No longer available. There is, however, a mapping available at the root of management.endpoints.web.base-path that provides links to all the exposed endpoints. |
/auditevents | The after parameter is no longer required |
/autoconfig | Renamed to /conditions |
/docs | No longer available (the API documentation is part of the published documentation now) |
/health | Rather than relying on the sensitive flag to figure out if the health endpoint had to show full details or not, there is now a management.endpoint.health.show-details option: never, always, when-authorized. By default, /actuator/health is exposed and details are not shown. |
/trace | Renamed to /httptrace |
可以在代码中直接使用Binder API从配置文件中读取内容:
public class Person implements EnvironmentAware {
private Environment environment;
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
public void bind() {
List<PersonName> people = Binder.get(environment)
.bind("my.property", Bindable.listOf(PersonName.class))
.orElseThrow(IllegalStateException::new);
}
}
YAML配置
my:
property:
- first-name: Jane
last-name: Doe
- first-name: John
last-name: Doe
新增spring.data.web配置来设置分页和排序:
#DATA WEB (SpringDataWebProperties)
spring.data.web.pageable.default-page-size=20 # Default page size.
spring.data.web.pageable.max-page-size=2000 # Maximum page size to be accepted.
spring.data.web.pageable.one-indexed-parameters=false # Whether to expose and assume 1-based page number indexes.
spring.data.web.pageable.page-parameter=page # Page index parameter name.
spring.data.web.pageable.prefix= # General prefix to be prepended to the page number and page size parameters.
spring.data.web.pageable.qualifier-delimiter=_ # Delimiter to be used between the qualifier and the actual page number and size properties.
spring.data.web.pageable.size-parameter=size # Page size parameter name.
spring.data.web.sort.sort-parameter=sort # Sort parameter name.
#JDBC (JdbcProperties)
spring.jdbc.template.fetch-size=-1 # Number of rows that should be fetched from the database when more rows are needed.
spring.jdbc.template.max-rows=-1 # Maximum number of rows.
spring.jdbc.template.query-timeout= # Query timeout. Default is to use the JDBC driver's default configuration. If a duration suffix is not specified, seconds will be used.
hibernate自定义命名策略
Ractive
更多新特性请查看Release Notes。
io.springfox
springfox-swagger2
2.9.2
io.springfox
springfox-swagger-ui
2.9.2
Swagger 2.9版本,增加了对@ApiParam属性example的处理,在Swagger UI和文档中会显示,因此要注意设置合适的example值:
@ApiOperation(value = "Delete airline by id")
@GetMapping("/airlines/delete/{airlineId}")
public void deleteAirline(@ApiParam(required = true, example = "123") @PathVariable Long airlineId)
否则你会看到Warn日志:
AbstractSerializableParameter
@JsonProperty("x-example")
public Object getExample() {
if (example == null) {
return null;
}
try {
if (BaseIntegerProperty.TYPE.equals(type)) {
return Long.valueOf(example);
} else if (DecimalProperty.TYPE.equals(type)) {
return Double.valueOf(example);
} else if (BooleanProperty.TYPE.equals(type)) {
if ("true".equalsIgnoreCase(example) || "false".equalsIgnoreCase(defaultValue)) {
return Boolean.valueOf(example);
}
}
} catch (NumberFormatException e) {
LOGGER.warn(String.format("Illegal DefaultValue %s for parameter type %s", defaultValue, type), e);
}
return example;
}
另外Swagger 2.9.2会为org.springframework.data.domain.Pageable自动增加@ApiImplicitParams。
详细内容看博客 地址