目录
一、SpringBoot实用篇—开发。
(1)整合第三方技术。
(1.1)缓存。
(1.1.1)自定义缓存。
(1.1.2)使用springboot提供的缓存技术(simple缓存)。
(1.1.2.1) 默认缓存的使用。
(1.1.2.2) 缓存使用案例—手机验证码案例。
(1.1.3)ehcache缓存技术。
(1.1.4)redis缓存技术。
(1.1.5)memcached缓存技术。
(1.1.6)jetcache缓存技术。
(1.1.6.1)使用缓存对象方式。
(1.1.6.2)使用方法注解方式。
(1.1.7)j2cache缓存整合框架。
(1.1.8)总结。
(1.1.9)补充知识:数据淘汰策略。
(1.2)(定时)任务。
(1.2.1)整合quartz。
(1.2.2)springboot中的task。
(1.3)邮件。
(1.3.1)简单邮件。
(1.3.2)复杂邮件。
(1.4)消息(MQ,即消息队列)。
(1.4.1)消息的了解。
(1.4.2)消息案例。
(1.4.3)activeMQ。
(1.4.4)RabbitMQ。
(1.4.4.1)RabbitMQ下载与启动。
(1.4.4.2)SpringBoot整合RabbitMQ直连交换机模式。
(1.4.4.3)SpringBoot整合RabbitMQ主题交换机模式。
(1.4.5)RocketMQ。
(1.4.5.1)RocketMQ下载与启动。
(1.4.5.2)SpringBoot整合RocketMQ。
(1.4.6)Kafka。
(1.4.6.1)kafka下载与启动。
(1.4.6.2)SpringBoot整合Kafka。
(2)监控。
(2.1)监控的意义与实施方式。
(2.2)可视化监控平台。
(2.2.1)服务端。
(2.2.2)客户端(即需要被监控的应用程序)。
(2.3)监控原理。
(2.4)自定义info端点信息。
(2.5)自定义health端点信息。
(2.6)自定义性能指标。
(2.7)自定义端点。
(2.8)总结。
以下是一些springboot版本的字母代表的意思。
注意:虽然下面很多缓存技术,但是接口是统一的,即导入坐标、配置好配置文件属性就行,注解不需要改动。
如:使用HashMap集合当作缓存使用。
@Service
public class MsgServiceImpl implements MsgService {
private HashMap cache = new HashMap<>();
@Override
public String get(String tele) {
String code = tele.substring(tele.length() - 6);
cache.put(tele,code);
return code;
}
@Override
public boolean check(String tele, String code) {
String queryCode = cache.get(tele);
return code.equals(queryCode);
}
}
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
private HashMap cache = new HashMap<>();
@Override
public Book getById(Integer id) {
//如果当前缓冲中没有本次要查询的数据,则进行查询,否则直接从缓存中获取数据返回
Book book = cache.get(id);
if (book == null){
book = bookDao.selectById(id);
cache.put(book.getId(),book);
}
return book;
}
}
@EnableCaching
注解是 Spring Framework 提供的一个通用注解,用于启用方法级别的缓存功能。无论你使用哪种缓存实现,包括 Ehcache、Redis、Caffeine 等,都可以通过 @EnableCaching
注解来开启 Spring 的缓存功能。 (不包含不被springboot整合的)
解析:当调用这个方法时,key = "#id" 获取参数id的值,然后从名为cacheSpace的缓存中缓存key=“#id”的值,如果有,则直接返回得到的值(即return bookDao.selectById(id);语句不执行);若是没有,则就会执行return bookDao.selectById(id);语句返回,并且将该方法的参数id为键,查询结果为值,存到缓存中去。
@Cacheable
注解中的 cacheNames
和 value
参数是等效的,它们用于指定缓存区域的名称。
@Override
//value属性指定了要使用的缓存空间(Cache Space)的名称。就是缓存的名称
@Cacheable(value = "cacheSpace",key = "#id")
public Book getById(Integer id) {
return bookDao.selectById(id);
}
注意:如果在这个类中直接调用get(tele)方法,那么只是普通调用,没有通过bean调用该方法,则该注解不起作用(因为spring没有参与),要用注入的对象调用(例如@Autowired)该方法,则get方法的注解就会被spring处理。(下面的代码不是全部都是在一个类的)
@Autowired
private CodeUtils codeUtils;
@Override
// @Cacheable(value = "smsCode",key = "#tele")
@CachePut(value = "smsCode",key = "#tele")
public String sendCodeToSMS(String tele) {
String code = codeUtils.generator(tele);
return code;
}
----------------------------------------------------------------------------------------
@Cacheable(value = "smsCode",key="#tele")
public String get(String tele){
return null;
}
注意:我使用的是springboot3.1.0版本,配置这里没有ehcache,所以没有完成这些步骤。
接下来就是跟springboot的简单缓存技术一样的使用方法:
@Cacheable
注解中的 cacheNames
和 value
参数是等效的,它们用于指定缓存区域的名称。
@Service
public class MyService {
@Cacheable(cacheNames = "exampleCache")
public String getFromCache(String key) {
// 从数据库或其他数据源获取数据
// ...
return data;
}
@CachePut(cacheNames = "exampleCache", key = "#key")
public void saveToCache(String key, String value) {
// 存储数据到数据库或其他数据源
}
// 其他业务方法...
}
提示:基本上属性名没变,可能前面多了个名字,如spring.redis变成spring.data.redis等等。
spring:
cache:
type: redis
redis:
cache-null-values: true
key-prefix: false
use-key-prefix: true
time-to-live: 10s
data:
redis:
host: localhost
port: 6379
接下来就是跟springboot的简单缓存技术一样的使用方法:
@Service
public class MyService {
@Cacheable(cacheNames = "exampleCache")
public String getFromCache(String key) {
// 从数据库或其他数据源获取数据
// ...
return data;
}
@CachePut(cacheNames = "exampleCache", key = "#key")
public void saveToCache(String key, String value) {
// 存储数据到数据库或其他数据源
}
// 其他业务方法...
}
这里注意fastjson,因为版本不同,发生了变化变成fastjson2。
jetcache:
statIntervalMinutes: 1
local:
default:
type: linkedhashmap
keyConvertor: fastjson2 # 如果key为对象,则需要指定转换为字符串的技术
sms:
type: linkedhashmap
keyConvertor: fastjson2
remote:
default:
type: redis
host: localhost
port: 6379
keyConvertor: fastjson2
valueEncode: java
valueDecode: java
poolConfig: # 至少写一条,不然报错
maxTotal: 50
sms:
type: redis
host: localhost
port: 6379
poolConfig:
maxTotal: 50
springboot是3.1.1版本,使用的是2.7.3(这里整整浪费了我很多时间)。
com.alicp.jetcache
jetcache-starter-redis
2.7.3
这种方式也是需要配置的,配置使用上面的。(name其实是缓存名,key是键)
@CacheInvalidate:这个注解是先删除数据库在删除缓存。
@CacheUpdate :这个注解是先修改数据库在修改缓存。
keyConvertor
表示缓存键的转换器。在将对象用作缓存键时,需要将其序列化为字符串表示。
valueEncode
表示缓存值的编码方式。当将对象存储到缓存中时,需要将对象序列化为字节数组进行存储。
valueDecode
表示缓存值的解码方式。当从缓存中获取对象时,需要将存储的字节数组反序列化为真正的对象。
注意:因为org.slf4j出现错误,那么可以试着排除看一下。
net.oschina.j2cache
j2cache-core
2.8.5-release
org.slf4j
slf4j-simple
JobBuilder.newJob(QuartzTaskBean.class)
:创建一个新的 JobBuilder 实例,并指定要执行的任务类(QuartzTaskBean.class)。这里的 QuartzTaskBean.class 是你自定义的实现了 Quartz Job 接口的任务类。storeDurably()
:此方法用于设置任务的持久化标志。当任务被设为持久化时,即使没有与该任务相关联的触发器,该任务仍然存在于调度器中。这样可以避免任务在没有触发器的情况下被自动删除。build()
:此方法用于构建最终的 JobDetail 对象,该对象包含了设置的任务类和其他属性。
@Scheduled
注解有以下常用的参数:
fixedRate
:固定频率执行任务,以毫秒为单位。例如,@Scheduled(fixedRate = 5000)
表示每隔 5 秒执行一次任务。
fixedDelay
:固定延迟执行任务,以毫秒为单位。例如,@Scheduled(fixedDelay = 5000)
表示当任务执行完成后,延迟 5 秒再次执行。
initialDelay
:初始延迟时间,以毫秒为单位。例如,@Scheduled(initialDelay = 10000, fixedRate = 5000)
表示首次执行任务前延迟 10 秒,然后以 5 秒的间隔重复执行。
cron
:使用 Cron 表达式指定执行时间。例如,@Scheduled(cron = "0 0 12 * * ?")
表示在每天中午 12 点执行任务。
zone
:指定时区,用于计算 Cron 表达式。
Cron 表达式是一种用于指定定时任务执行时间的字符串表达式,它由六个或七个字段组成,每个字段代表一个时间单位。Cron 表达式的格式如下:
second minute hour dayOfMonth month dayOfWeek [year]
其中各个字段的含义如下:
second
(秒):可选值为 0-59 的整数。
minute
(分钟):可选值为 0-59 的整数。
hour
(小时):可选值为 0-23 的整数。
dayOfMonth
(某月的第几天):可选值为 1-31 的整数。
month
(月份):可选值为 1-12 或 JAN-DEC 的字符串。
dayOfWeek
(星期几):可选值为 0-7 或 SUN-SAT 的字符串,其中星期日可以用 0 或 7 表示,星期一到星期六分别用 1-6 表示。
year
(年份):可选值为四位的整数。上述字段可以使用以下特殊字符来指定特定的时间:
星号 (
*
):匹配任意值。例如,*
在month
字段表示每个月,而在dayOfWeek
字段表示每个星期。问号 (
?
):只适用于dayOfMonth
和dayOfWeek
字段,表示不指定具体的值。例如,?
在dayOfMonth
字段表示不指定具体日期,而在dayOfWeek
字段表示任意星期。斜线 (
/
):用于指定增量。例如,0/15
在second
字段表示每隔 15 秒,而3/20
在minute
字段表示从第 3 分钟开始,每隔 20 分钟。逗号 (
,
):用于指定多个值。例如,MON,WED,FRI
在dayOfWeek
字段表示周一、周三和周五。连接符 (
-
):用于指定范围。例如,10-20
在dayOfMonth
字段表示从第 10 天到第 20 天。下面是一些 Cron 表达式的示例:
0 0 7 * * ?
:表示每天早上 7 点触发。
0 30 23 ? * MON-FRI
:表示每个工作日的晚上 11:30 触发。
0 0 12 1 1/3 ?
:表示每年的 1 月 1 日和 4 月 1 日的中午 12 点触发。"0/5" 表示每隔 5 秒执行一次任务。其中 "0" 表示起始秒数为 0,"/5" 表示间隔为 5 秒(0该位置只有第一次执行的效果,代表分钟内的第几秒,如果还没够5秒就已经到了分钟内的第0秒,则执行,然后再每隔五秒执行。否则就是先够5秒,然后分钟内的第0秒还没到,则执行,则0已经失效,每隔5秒就执行)(举例:项目启动后是第5秒,则0肯定不起作用了;如果项目启动后是第58秒,过2秒后就是第0秒,则执行一次(0执行后就失效了),然后每过5秒再循环执行)
"5" 表示在每分钟的第 5 秒执行任务,即每分钟的第 5 秒时触发任务
请注意,在
year
字段是可选项,可以省略。
不需要导入坐标就能使用,如果不需要配置,则可以不写配置。
SimpleMailMessage
是JavaMail API提供的简单邮件消息类。它提供了一种简化的方式来创建和发送基本的文本邮件。使用SimpleMailMessage
,你可以设置发送者、接收者、主题、内容等基本邮件属性。但是,它不支持复杂的邮件格式或附件。
MimeMessage
是JavaMail API提供的更灵活和功能更强大的邮件消息类。它允许你创建复杂的邮件,包括HTML内容、附件、多媒体资源等。
@Service
public class MessageServiceImpl implements MessageService {
private ArrayList msgList = new ArrayList<>();
@Override
public void sendMessage(String id) {
System.out.println("待发送短信的订单已纳入处理队列,id:"+id);
msgList.add(id);
}
@Override
public String doMessage() {
String id = msgList.remove(0);
System.out.println("已完成短信发送业务,id:"+id);
return id;
}
}
注意:这个是需要安装的,安装后找到安装路径的文件夹,进入命令行输入命令启动。
将队列绑定的交换机(不能反过来),交换机名称与路由键的组合需要唯一。
@Configuration
public class RabbitConfigDirect {
@Bean
public Queue directQueue(){
return new Queue("direct_queue");
}
@Bean
public DirectExchange directExchange(){
return new DirectExchange("directExchange");
}
//将名为"directQueue"的队列通过绑定关系与名为"directExchange"的交换机绑定起来,
//并且指定绑定的路由键为"direct"。(通过路由键+交换机名称 =》得到队列)
@Bean
public Binding bindingDirect(){
return BindingBuilder.bind(directQueue()).to(directExchange()).with("direct");
}
}
下面的方法可以参考得到队列的过程(需要提供交换机名称+路由键)。
@Service
public class MessageServiceRabbitmqDirectImpl implements MessageService {
@Autowired
private AmqpTemplate amqpTemplate;
@Override
public void sendMessage(String id) {
System.out.println("待发送短信的订单已纳入处理列表(rabbitmq direct),id:"+id);
amqpTemplate.convertAndSend("directExchange","direct",id);
}
}
区别: 可以将消息发送到多个队列当中。(只要是匹配上了绑定键,就发送)
注意:匹配规则只能用在绑定键的命名中(发送消息到队列的方法中用的绑定键名不会起作用的)
@Configuration
public class RabbitConfigTopit {
@Bean
public Queue topicQueue(){
return new Queue("topic_queue");
}
@Bean
public TopicExchange topicExchange(){
return new TopicExchange("topicExchange");
}
@Bean
public Binding bindingTopic() {
// return BindingBuilder.bind(topicQueue()).to(topicExchange()).with("topic.order.id");
//模糊匹配该规则
return BindingBuilder.bind(topicQueue()).to(topicExchange()).with("topic.*.id");
}
}
@Service
public class MessageServiceRabbitmqTopicImpl implements MessageService {
@Autowired
private AmqpTemplate amqpTemplate;
@Override
public void sendMessage(String id) {
System.out.println("待发送短信的订单已纳入处理列表(rabbitmq direct),id:"+id);
amqpTemplate.convertAndSend("topicExchange","topic.order.id",id);
}
}
注意:rocketmq使用的JAVA_HOME里面不能有空格,只能使用java8版本。
建议:还是使用jdk8比较好,因为我使用高版本的jdk,导致各种问题出现,而且监听器信息消费也没有起作用(不知道为什么,可能版本问题)。
启动类启动失败提示:(原因就是template没有被bean管理,因为spring没有维护rocket)
Action:
Consider defining a bean of type 'org.apache.rocketmq.spring.core.RocketMQTemplate' in your configuration.
@SpringBootApplication
public class Springboot24MqApplication {
@Bean //这里我懒得再写一个配置类,所以直接在这里写了
public RocketMQTemplate rocketMQTemplate(){
return new RocketMQTemplate();
}
public static void main(String[] args) {
SpringApplication.run(Springboot24MqApplication.class, args);
}
}
出现这个异常,我通过@Bean配置producer对象,解决了这个异常。
@SpringBootApplication
public class Springboot24MqApplication {
@Bean
public RocketMQTemplate rocketMQTemplate(){
return new RocketMQTemplate();
}
@Bean(initMethod = "start", destroyMethod = "shutdown")//这注解里面的参数很关键,注释后就报错(最低要求要有 initMethod = "start")
public DefaultMQProducer defaultMQProducer() {
DefaultMQProducer producer = new DefaultMQProducer("group_rocketmq");
producer.setNamesrvAddr("127.0.0.1:9876");
return producer;
}
public static void main(String[] args) {
SpringApplication.run(Springboot24MqApplication.class, args);
}
}
@Service
public class MessageServiceRocketmqImpl implements MessageService {
@Autowired
private RocketMQTemplate rocketMQTemplate;
@Autowired
private DefaultMQProducer producer;
@Override
public void sendMessage(String id) throws MQBrokerException, RemotingException, InterruptedException, MQClientException {
//第一种方式:
// Message message = new Message("order_id", "my_tag", id.getBytes(StandardCharsets.UTF_8));
// producer.send(message);
//第二种方式:
//使用同步
// rocketMQTemplate.setProducer(producer);
// rocketMQTemplate.convertAndSend("order_id",id);
//使用异步
rocketMQTemplate.setProducer(producer);
SendCallback callback = new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.println("message successful");
}
@Override
public void onException(Throwable throwable) {
System.out.println("message fail");
}
};
rocketMQTemplate.asyncSend("order_id",id,callback);
System.out.println("待发送短信的订单已纳入处理列表(rocketmq),id:"+id);
}
@Override
public String doMessage() {
return null;
}
}
提示:下面的消费消息不起作用(不清楚具体原因)。
测试了一下:如果使用jdk8与springboot版本2.5.4的时候,可以正常使用,只需要配置文件(而且还不需要改变其他的东西就能使用,所以使用rocketmq的时候建议使用jdk8与2.5.4):
rocketmq:
name-server: localhost:9876
producer:
group: group_rocketmq
@Component
@RocketMQMessageListener(topic = "order_id",consumerGroup = "group_rocketmq")//与生产组名称相同,它会去生产组拿
public class MessageListener implements RocketMQListener {
@Override
public void onMessage(String id) {
System.out.println("已完成短信发送业务(rocketmq),id:"+id);
}
}
注意:这里使用jdk8与springboot版本2.5.4,可以正常使用,而且一模一样(建议使用这一种,毕竟上面的只是创建新的template,给对象配置了一下属性。但是它是被spring管理的(2.5.4),而且还不确定装配的是原本的bean还是我@Bean创建的bean)。
综上所述:直接使用自动装配使用即可,不需要多此一举。
@Component
public class MessageKafkaListener {
@KafkaListener(topics = "itheima22222")
public void onMessage(ConsumerRecord record){//键与值的类型
//record.key();
System.out.println("已完成短信发送业务(rocketmq),id:"+record.value());
}
}
实施方式:需要监控的(客户端)应用程序在启动后要向服务端上报,服务端是主动拉取监控信息(不延迟,在刷新后就拉取最新信息)。
注意:启动服务端后,访问http://localhost:8080/就能查看监控信息。(根据程序路径访问)
需要加上web依赖才能一直运行(否则启动类启动就结束) 。
org.springframework.boot
spring-boot-starter-web
需要加上web依赖才能一直运行(否则启动类启动就结束) 。
org.springframework.boot
spring-boot-starter-web
注意:在应用程序中加上下面的就能被监控,这里的只是被监控需要的依赖和配置,不包含应用程序需要的。
management:
endpoint: # 这里指的是,是否对外开放(如果不开放,那么web与jmx方式访问都没用)
health:
enabled: true #默认为true,但是如果enabled-by-default为false的时候,必须配置
show-details: always #如果不配置这里,就只是健康的信息不会显示而已
endpoints:
enabled-by-default: true #这里默认为true,对访问方式不影响,就是对外开放所有端点(改为false会报错,提示至少需要health,给health配置enabled: true)
web: #这里使用的是web方式访问,当然这里默认就是health,根据需要配置
exposure:
include: health,info
# include: "*"
jmx: #这里使用的是jmx方式访问,当然这里默认就是*,不用配置(或根据需要配置)
exposure:
include: "*"
win+R进入命令行 —(输入回车)jconsole — 弹出一个java的监控平台(jmx方式)。
小结提示:第2点指的是对外开放的端点,第3点说的是web、jmx访问方式端点功能暴露配置。
@Component
public class InfoConfig implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
builder.withDetail("runTime",System.currentTimeMillis());
Map infoMap = new HashMap();
infoMap.put("buildTime","2006");
builder.withDetails(infoMap);//支持链式编程
}
}
@Component
public class HealthConfig extends AbstractHealthIndicator {//或者实现这个接口implements HealthIndicator
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
boolean condition = true;
if (condition){
// builder.up();//设置上线状态
builder.status(Status.UP);
builder.withDetail("runTime",System.currentTimeMillis());
Map infoMap = new HashMap();
infoMap.put("buildTime","2006");
builder.withDetails(infoMap);
}else {
// builder.down();
builder.status(Status.DOWN);
builder.withDetail("上线了吗?","你做梦");
}
}
}
提示:前面的都是在端点中增加一些信息,而这里是增加一个新的端点,虽然增加了,但是只能在映射中找到该端点的uri等信息。(我猜是因为该监控平台不显示该端点信息,毕竟是我新加的)