Spring 从3.1开始定义了 org.springframework.cache.Cache
和 org.springframework.cache.CacheManager
接口来统一不同的缓存技术;并支持使用JCache注解简化开发
注解 | 说明 |
---|---|
Cache | 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等 |
CacheManager | 缓存管理器,管理各种缓存组件Cache |
@Cacheable | 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存(查询) |
@CacheEvict | 清空缓存(删除) |
@CachePut | 保证方法被调用,又希望结果被缓存(修改) |
@EnableCaching | 开启基于注解的缓存 |
keyGenerator | 缓存数据时key生成策略 |
serialize | 缓存数据时value序列化策略 |
将方法的返回值进行缓存,以后如果需要相同的数据,直接从缓存中获取,不再调用方法,从而提高系统的性能!
spring-boot-starter-cache
模块修改了数据库的某个数据,同时更新缓存,运行时先调用目标方法,将目标方法的结果(返回值)缓存起来
注:更新函数的缓存 Key 值如果和查询函数的 Key 值不一致,那么更新后查询函数查询的结果依然是旧的数据
缓存清除
默认使用的是 ConcurrentMapCacheManager 和 ConcurrentMapCache ,但在实际开发中通常会使用 Redis、memcached、Ehcache
spring-boot-starter-data-redis
redis
的连接地址等信息RedisTemplate & StringRedisTemplate
在 RedisAutoConfiguration 中自动配置了 RedisTemplate 和 StringRedisTemplate,可以很方便的来操作Redis!
Redis常见的五大数据类型和RedisTemplate的相关API
RedisTemplate 默认使用的序列化类是 JdkSerializationRedisSerializer,而 StringRedisTemplate 使用的是 StringRedisSerializer
如果需要更改序列化规则,如将数据以 JSON 形式保存,可以自定义 RedisTemplate 设置序列化规则并注入容器中
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Object, Object> jsonRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 默认的Key序列化器为:JdkSerializationRedisSerializer
template.setDefaultSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
return template;
}
}
引入 Redis 的 starter 后,容器中注册的但是 RedisCacheManager,默认的SimpleCacheManager 失效。RedisManager 帮助我们创建 RedisCache 来作为缓存组件,RedisCache 通过操作 redis 来缓存数据
开源的 ElasticSearch 是目前全文搜索引擎的首选。它可以快速的存储、搜索和分析海量数据。Spring Boot通过整合Spring Data ElasticSearch为我们提供了非常便捷的检索功能支持
Elasticsearch是一个分布式搜索服务,提供Restful API,底层基于Lucene,采用多shard(分片)的方式保证数据安全,并且提供自动resharding的功能,Stack Overflow等大型的站点也是采用了ElasticSearch作为其搜索服务
Elasticsearch 是面向文档的,意味着它存储整个对象或文档。ES 不仅存储文档,而且索引每个文档的内容,使之可以被检索。在 ES 中,我们对文档进行索引、检索、排序和过滤,而不是对行列数据
Elasticsearch 使用 JavaScript Object Notation(或者 JSON)作为文档的序列化格式。JSON 序列化为大多数编程语言所支持,并且已经成为 NoSQL 领域的标准格式。 它简单、简洁、易于阅读
存储数据到 Elasticsearch 的行为叫做索引,但在索引一个文档之前,需要确定将文档存储在哪里。一个 Elasticsearch 集群可以包含多个索引 ,相应的每个索引可以包含多个类型。这些不同的类型存储着多个文档,每个文档又有多个属性
注:ES 里的 Index 可以看做一个库,而 Type 相当于表,Documents 则相当于表的行(记录)
这里 Types 的概念已经被逐渐弱化,Elasticsearch 6.X 中,一个 index 下已经只能包含一个 type,Elasticsearch 7.X 中,Type 的概念已经被删除了,type都为_doc
拉取镜像:docker pull elasticsearch:7.8.0
运行容器
# -d 后台运行
# -v 绑定一个数据卷
# -p(小写) 指定要映射的IP和端口 hostPort:containerPort
# -e "discovery.type=single-node" 单节点集群
# -e ES_JAVA_OPTS="-Xms512m -Xmx512m" 制定运行参数,不然如果机器内存太小,启动后会非常卡顿
# --name 起个别名
docker run -p 9200:9200 -p 9300:9300 --name=es-7.8.0 \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms512m -Xmx512m" \
-d elasticsearch:7.8.0
ES官方文档
拉取镜像:docker pull kibana:7.8.0
运行容器
# kibana版本必须和elasticsearch版本保持一致
# 启动容器
# YOUR_ELASTICSEARCH_CONTAINER_NAME_OR_ID 正在运行的ES容器ID或name
# docker run --link YOUR_ELASTICSEARCH_CONTAINER_NAME_OR_ID:elasticsearch -p 5601:5601 {docker-repo}:{version}
docker run --link es-7.8.0:elasticsearch -p 5601:5601 -d --name=kibana-7.8 kibana:7.8.0
导入整合依赖包
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-elasticsearchartifactId>
dependency>
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName = "product")
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
// 必须有 id,这里的 id 是全局唯一的标识,等同于 es 中的"_id"
@Id
private Long id; // 商品唯一标识
/**
* type : 字段数据类型
* analyzer : 分词器类型
* index : 是否索引(默认:true)
* Keyword : 短语,不进行分词
*/
@Field(type = FieldType.Text)
private String title; // 商品名称
@Field(type = FieldType.Keyword)
private String category; // 分类名称
@Field(type = FieldType.Double)
private Double price; // 商品价格
@Field(type = FieldType.Keyword, index = false)
private String images; // 图片地址
}
public interface ProductRepository extends ElasticsearchRepository<Product,Long> {
}
@SpringBootTest
class ElasticsearchApplicationTests {
@Autowired
private ElasticsearchRestTemplate restTemplate;
@Autowired
private ProductRepository productRepository;
// ****** 索引操作 ******
@Test
public void testCreateIndex(){
// 创建索引并增加映射配置
// 创建索引,系统初始化会自动创建索引
System.out.println("创建索引...");
}
@Test
public void testDelIndex(){
// 删除索引
boolean flag = restTemplate.indexOps(Product.class).delete();
System.out.println("flag = " + flag);
}
// ****** 文档操作 ******
@Test
public void testAdd() {
Product product = new Product(1001L, "华为mate50 Pro", "手机", 2699.00, "https://img11.360buyimg.com/n1/s450x450_jfs/t1/100178/24/22008/134447/63808261E7e8fdf63/bfc4ff46e04450b6.jpg.avif");
productRepository.save(product);
}
@Test
public void testUpdate() {
Product product = new Product(1001L, "Redmi K50", "手机", 2599.00, "https://img12.360buyimg.com/n1/s450x450_jfs/t1/211467/21/27724/73497/63842044E32d227a6/232dbbcc378d8742.jpg.avif");
productRepository.save(product);
}
@Test
public void testGetById() {
Optional<Product> product = productRepository.findById(1001L);
System.out.println("product = " + product);
}
@Test
public void testGetAll() {
Iterable<Product> products = productRepository.findAll();
products.forEach(System.out::println);
}
@Test
public void testDel() {
productRepository.deleteById(1001L);
}
@Test
public void testBatchAdd() {
List<Product> productList = new ArrayList<>();
Product p1 = new Product(1003L, "OPPO Reno8 系列", "手机", 2099.00, "https://img13.360buyimg.com/n1/s450x450_jfs/t1/166774/34/31501/126697/636cd9dfE2329529e/bce5ef0bc351e516.jpg.avif");
Product p2 = new Product(1004L, "Apple iPhone 14", "手机", 6899.00, "https://img14.360buyimg.com/n1/s450x450_jfs/t1/79861/16/22966/34933/6381b0d7Ece7f68c2/6dd5f2a3f892add8.jpg.avif");
Product p3 = new Product(1005L, "奥克斯(AUX)大3匹空调", "大家电", 5999.00, "https://img12.360buyimg.com/n1/jfs/t1/109793/27/35299/101988/637aeaf7Ede5dd640/22e59a42744809c2.jpg.avif");
productList.add(p1);
productList.add(p2);
productList.add(p3);
productRepository.saveAll(productList);
}
@Test
public void testBatchDel() {
List<Long> ids = Arrays.asList(1003L, 1004L);
productRepository.deleteAllById(ids);
}
@Test
public void testFindByPage() {
Sort sortRule = Sort.by(Sort.Direction.DESC, "price");
int currentPage = 0; // 当前页,第一页从 0 开始,1 表示第二页
int pageSize = 5; // 每页显示多少条
PageRequest pageRequest = PageRequest.of(currentPage, pageSize, sortRule);
Iterable<Product> products = productRepository.findAll(pageRequest);
products.forEach(System.out::println);
}
}
在Java应用中,绝大多数情况下都是通过同步的方式来实现交互处理的;但是在处理与第三方系统交互的时候,容易造成响应迟缓的情况,之前大部分都是使用多线程来完成此类任务,其实,在Spring 3.x之后,就已经内置了@Async来完美解决这个问题
使用方法是在主启动类上用 @EnableAsync 开启异步支持,将 @Async 标注在指定的异步方法上
// 告诉Spring这是一个异步方法
@Async
public void hello() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("处理数据中...");
}
项目开发中经常需要执行一些定时任务,比如需要在每天凌晨时候,分析一次前一天的日志信息
市面上流行的定时任务技术
相关概念
导入SpringBoot整合quartz的坐标
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-quartzartifactId>
dependency>
定义具体要执行的任务,继承QuartzJobBean,并定义工作明细与触发器,并绑定对应关系
@Configuration
public class MyQuartz extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
System.out.println("生成年度报表...");
}
@Bean
public JobDetail jobDetail() {
// 绑定具体的工作
return JobBuilder.newJob(MyQuartz.class).storeDurably().build();
}
@Bean
public Trigger trigger(){
// 绑定对应的工作明细
CronScheduleBuilder csb = CronScheduleBuilder.cronSchedule("0 59 23 L 12 ? 2022-2060");
return TriggerBuilder.newTrigger().withSchedule(csb).forJob(jobDetail()).build();
}
}
在主启动类上添加@EnableScheduling
注解开启定时任务功能,然后在需要定时的函数上标注 @Schedule 注解,并设置 cron 表达式
@Component
public class MyTask {
@Scheduled(cron = "0/6 * * * * ?") // 每隔6秒执行一次
public void recordLog(){
System.out.println("记录日志...");
}
}
在线Cron表达式生成器
导入SpringBoot整合JavaMail的坐标
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-mailartifactId>
dependency>
配置JavaMail
spring:
mail:
host: smtp.qq.com
username: ********@qq.com
password: ********
发送邮箱必备四要素
@Autowired
private JavaMailSender mailSender;
@Value(value = "${from}")
private String from;
@Value(value = "${to}")
private String to;
@Value(value = "${subject}")
private String subject;
@Value(value = "${content}")
private String content;
@Test
public void testSendSimpleEmail() {
SimpleMailMessage msg = new SimpleMailMessage();
// 邮件设置
// 发件人
msg.setFrom(from);
// 收件人
msg.setTo(to);
// 标题
msg.setSubject(subject);
// 正文
msg.setText(content);
// 发送简单邮箱
mailSender.send(msg);
}
附件与HTML文本支持
@Test
public void testSendHardMail() {
try {
// 创建一个复杂的消息邮箱
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
// 邮箱设置
helper.setFrom(from);
helper.setTo(to);
helper.setSubject("测试发送复杂邮箱");
helper.setText("", true);
// 添加附件
File img1 = new File("D:\\DesktopBackground\\mobile\\73.png");
File img2 = new File("D:\\DesktopBackground\\mobile\\68.png");
helper.addAttachment(img1.getName(), img1);
helper.addAttachment(img2.getName(), img2);
mailSender.send(mimeMessage);
} catch (MessagingException e) {
throw new RuntimeException(e);
}
}
消息发送方
消息接收方
MQTT
应用场景
JMS(Java Message Service)JAVA 消息服务:一个规范,等同于JDBC规范,提供了与消息服务相关的API接口
JMS消息模型
JMS消息种类
JMS实现:ActiveMQ、Redis、HornetMQ、RabbitMQ、RocketMQ(没有完全遵守JMS规范)
AMQP(advanced message queuing protocol):一种协议(高级消息队列协议,也是消息代理规范),规范了网络交换的数据格式,兼容JMS
优点:具有跨平台性,服务器供应商,生产者,消费者可以使用不同的语言来实现
AMQP消息模型
AMQP消息种类:byte[]
AMQP实现:RabbitMQ、StormMQ、RocketMQ
MQTT(Message Queueing Telemetry Transport)消息队列遥测传输,专为小设备设计,是物联网(IOT)生态系统中主要成分之一
docker pull rabbitmq:3-management
docker run -d -e RABBITMQ_DEFAULT_USER=root -e RABBITMQ_DEFAULT_PASS=root -p 5672:5672 -p 15672:15672 --name=rabbitmq-3 rabbitmq:3-management
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
<dependency>
<groupId>org.springframework.amqpgroupId>
<artifactId>spring-rabbit-testartifactId>
<scope>testscope>
dependency>
RabbitTemplate 默认使用 SimpleConverter (即JDK反序列规则),要将序列化规则设置为 JSON 形式,则可以使用如下方法:
@Configuration
public class RabbitMQConfig {
public static final String EXCHANGE_NAME = "boot_topic_exchange";
public static final String QUEUE_NAME = "boot_queue";
// #:匹配一个或多个词 item.# = item.insert.abc.xxx.../item.insert
// *:只能匹配一个次 item.* = item.insert
public static final String ROUTING_KEY = "boot.#";
// 1.交换机
@Bean
public Exchange exchange() {
return ExchangeBuilder.topicExchange(EXCHANGE_NAME).build();
}
// 2.队列
@Bean
public Queue queue() {
return QueueBuilder.durable(QUEUE_NAME).build();
}
// 3.队列与交换机的绑定
/* 1.知道那个队列
2.知道那个交换机
3.routing key
*/
@Bean
public Binding bindingQueueExchange(Queue queue, Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY).noargs();
}
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
发送数据
@Test
public void testTopicModel() {
// 只需要传入要发送的对象,自动序列化发送到 RabbitMQ
HashMap<String, Object> map = new HashMap<>();
map.put("name", "小白");
map.put("data", Arrays.asList("苹果", "甜橙", "葡萄"));
rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, "boot.rabbit.hello", map);
}
接收数据
@Test
public void testReceive() {
Object o = rabbitTemplate.receiveAndConvert(RabbitMQConfig.QUEUE_NAME);
System.out.println(o.getClass());
System.out.println(o);
}
在消费者应用中,通常需要监听队列,以获取消息。在 Spring Boot 中如何要监听队列,需要在主启动类上开启 @EnableRabbit,然后在指定方法上使用 @RabbitListener 进行监听
@Service
public class UserServiceImpl {
@RabbitListener(queues = "boot_queue")
public void receive(User u) {
System.out.println("收到消息:" + u);
}
@RabbitListener(queues = "boot_queue")
public void receiveMsg(Message message){
System.out.println(message.getBody());
System.out.println(message.getMessageProperties());
}
}
AmqpAdmin 可以帮助我们创建和删除 Queue,Exchange,Binding规则
@Test
public void testAmqpAdmin() {
// 创建交换机
amqpAdmin.declareExchange(new DirectExchange("amqpAdmin.exchange"));
// 创建队列
amqpAdmin.declareQueue(new Queue("amqpAdmin.queue", true));
// 绑定关系
amqpAdmin.declareBinding(new Binding("amqpAdmin.queue",
Binding.DestinationType.QUEUE,
"amqpAdmin.exchange",
"amqp.hello", null));
}
在 SpringBoot 中常用的安全框架有 Spring Security、Shiro
Spring Security 是针对 Spring 项目的安全框架,也是 Spring Boot 底层安全模块默认的技术选型。它可以实现强大的 web 安全控制。对于安全控制,只需要引入 spring-boot-starter-security
模块,进行少量的配置,即可实现强大的安全管理
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
@EnableWebSecurity
public class SecurityConfig 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");
// 开启自动配置的登录功能,/login来到登录页,登录失败重定向到/login?error,支持更多详细定制
http.formLogin().usernameParameter("uname").passwordParameter("upwd").loginPage("/userLogin");
// 开启自动配置的注销功能,/logout发起注销请求,并清空session,注销成功默认会返回 /login?logout页面
http.logout().logoutSuccessUrl("/userLogin");
// 开启记住我功能(登录成功之后,将cookie发送给浏览器进行保存,下次访问页面的时候带上这个cookie,只要通过检查就会自动登录,点击注销也会删除该cookie)
http.rememberMe();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 定义认证规则
// 从spring security 5.X开始,需要使用密码编码器,也就是需要对你的明文密码进行加密
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("admin").password(new BCryptPasswordEncoder().encode("admin")).roles("VIP1", "VIP2", "VIP3")
.and()
.withUser("xiaobai").password(new BCryptPasswordEncoder().encode("666666")).roles("VIP1", "VIP2");
}
}