2

SpringBoot3进阶用法 - 知了一笑

 9 months ago
source link: https://www.cnblogs.com/cicada-smile/p/17613225.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

标签:切面.调度.邮件.监控;

在上篇《SpringBoot3基础》中已经完成入门案例的开发和测试,在这篇内容中再来看看进阶功能的用法;

主要涉及如下几个功能点:

调度任务:在应用中提供一定的轻量级的调度能力,比如方法按指定的定时规则执行,或者异步执行,从而完成相应的代码逻辑;

邮件发送:邮件作为消息体系中的渠道,是常用的功能;

应用监控:实时或定期监控应用的健康状态,以及各种关键的指标信息;

切面编程:通过预编译方式和运行期动态代理实现程序中部分功能统一维护的技术,可以将业务流程中的部分逻辑解耦处理,提升可复用性;

二、工程搭建

1、工程结构

1691717-20230807224537688-1224555601.png

2、依赖管理

<!-- 基础框架依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>${spring-boot.version}</version>
</dependency>

<!-- 应用监控组件 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    <version>${spring-boot.version}</version>
</dependency>

<!-- 切面编程组件 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>${spring-boot.version}</version>
</dependency>

<!-- 邮件发送组件 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
    <version>${spring-boot.version}</version>
</dependency>

这里再细致的查看一下各个功能的组件依赖体系,SpringBoot只是提供了强大的集成能力;

1691717-20230807224540772-1825777323.png

3、启动类

注意在启动类中使用注解开启了异步EnableAsync和调度EnableScheduling的能力;

@EnableAsync
@EnableScheduling
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

三、切面编程

1、定义注解

定义一个方法级的注解;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface DefAop {
    /**
     * 模块描述
     */
    String modelDesc();

    /**
     * 其他信息
     */
    String otherInfo();
}

2、注解切面

在切面中使用Around环绕通知类型,会拦截到DefAop注解标记的方法,然后解析获取各种信息,进而嵌入自定义的流程逻辑;

@Component
@Aspect
public class LogicAop {

    private static final Logger logger = LoggerFactory.getLogger(LogicAop.class) ;
    
    /**
     * 切入点
     */
    @Pointcut("@annotation(com.boot.senior.aop.DefAop)")
    public void defAopPointCut() {

    }

    /**
     * 环绕切入
     */
    @Around("defAopPointCut()")
    public Object around (ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object result = null ;
        try{
            // 执行方法
            result = proceedingJoinPoint.proceed();
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            // 处理逻辑
            buildLogicAop(proceedingJoinPoint) ;
        }
        return result ;
    }

    /**
     * 构建处理逻辑
     */
    private void buildLogicAop (ProceedingJoinPoint point){
        // 获取方法
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method reqMethod = signature.getMethod();

        // 获取注解
        DefAop defAop = reqMethod.getAnnotation(DefAop.class);
        String modelDesc = defAop.modelDesc() ;
        String otherInfo = defAop.otherInfo();
        logger.info("DefAop-modelDesc:{}",modelDesc);
        logger.info("DefAop-otherInfo:{}",otherInfo);
    }
}

四、调度任务

1、异步处理

1.1 方法定义

通过Async注解标识两个方法,方法在执行时会休眠10秒,其中一个注解指定异步执行使用asyncPool线程池;

@Service
public class AsyncService {

    private static final Logger log = LoggerFactory.getLogger(AsyncService.class);

    @Async
    public void asyncJob (){
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        log.info("async-job-01-end...");
    }

    @Async("asyncPool")
    public void asyncJobPool (){
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        log.info("async-job-02-end...");
    }
}

1.2 线程池

定义一个ThreadPoolTaskExecutor线程池对象;

@Configuration
public class PoolConfig {

    @Bean("asyncPool")
    public Executor asyncPool () {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 线程池命名前缀
        executor.setThreadNamePrefix("async-pool-");
        // 核心线程数5
        executor.setCorePoolSize(5);
        // 最大线程数10
        executor.setMaxPoolSize(10);
        // 缓冲执行任务的队列50
        executor.setQueueCapacity(50);
        // 线程的空闲时间60秒
        executor.setKeepAliveSeconds(60);
        // 线程池对拒绝任务的处理策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 线程池关闭的时等待所有任务都完成再继续销毁其他的Bean
        executor.setWaitForTasksToCompleteOnShutdown(true);
        // 设置线程池中任务的等待时间
        executor.setAwaitTerminationSeconds(300);
        return executor;
    }
}

1.3 输出信息

从输出的日志信息中可以发现,两个异步方法所使用的线程池不一样,asyncJob采用默认的cTaskExecutor线程池,asyncJobPool方法采用的是async-pool线程池;

[schedule-pool-1] c.boot.senior.schedule.ScheduleService   : async-job-02-end...
[cTaskExecutor-1] c.boot.senior.schedule.ScheduleService   : async-job-01-end...

2、调度任务

2.1 调度配置

通过实现SchedulingConfigurer接口,来修改调度任务的配置,这里重新定义任务执行的线程池;

@Configuration
public class ScheduleConfig implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        scheduledTaskRegistrar.setScheduler(Executors.newScheduledThreadPool(5));
    }
}

2.2 调度方法

通过Scheduled注解来标记方法,基于定时器的规则设定,来统一管理方法的执行时间;

@Component
public class ScheduleJob {
    private static final Logger log = LoggerFactory.getLogger(ScheduleJob.class);

    private static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") ;

    /**
     * 上一次开始执行时间点之后10秒再执行
     */
    @Scheduled(fixedRate = 10000)
    private void timerJob1(){
        log.info("timer-job-1:{}",format.format(new Date()));
    }

    /**
     * 上一次执行完毕时间点之后10秒再执行
     */
    @Scheduled(fixedDelay = 10000)
    private void timerJob2(){
        log.info("timer-job-2:{}",format.format(new Date()));
    }

    /**
     * Cron表达式:每30秒执行一次
     */
    @Scheduled(cron = "0/30 * * * * ?")
    private void timerJob3(){
        log.info("timer-job-3:{}",format.format(new Date()));
    }
}

五、邮件发送

1、邮件配置

采用QQ邮箱来模拟邮件的发送方,需要先开启smtp邮件传输协议,在QQ邮箱的设置/账户路径下,并且获取相应的授权码,在项目的配置中使用;

1691717-20230807224543757-1831226770.png
spring:
  application:
    name: boot-senior
  # 邮件配置
  mail:
    host: smtp.qq.com
    port: 465
    protocol: smtps
    username: 邮箱账号
    password: 邮箱授权码
    properties:
      mail.smtp.ssl.enable: true

2、方法封装

定义一个简单的邮件发送方法,并且可以添加附件,是常用的功能之一;另外也可以通过Html静态页渲染,再转换为邮件内容的方式;

@Service
public class SendMailService {

    @Value("${spring.mail.username}")
    private String userName ;

    @Resource
    private JavaMailSender sender;

    /**
     * 带附件的邮件发送方法
     * @param toUsers 接收人
     * @param subject 主题
     * @param content 内容
     * @param attachPath 附件地址
     * @return java.lang.String
     * @since 2023-07-10 17:03
     */
    public String sendMail (String[] toUsers,String subject,
                            String content,String attachPath) throws Exception {
        // MIME邮件类
        MimeMessage mimeMessage = sender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
        // 邮件发送方From和接收方To
        helper.setFrom(userName);
        helper.setTo(toUsers);
        // 邮件主题和内容
        helper.setSubject(subject);
        helper.setText(content);
        // 邮件中的附件
        File attachFile = ResourceUtils.getFile(attachPath);
        helper.addAttachment(attachFile.getName(), attachFile);
        // 执行邮件发送命令
        sender.send(mimeMessage);
        return "send...mail...sus" ;
    }
}
1691717-20230807224546952-1939125182.png

六、应用监控

1、监控配置

springbootactuator组件中,可以通过提供的Rest接口,来获取应用的监控信息;

# 应用监控配置
management:
  endpoints:
    web:
      exposure:
        # 打开所有的监控点
        include: "*"
      base-path: /monitor
  endpoint:
    health:
      enabled: true
      show-details: always
    beans:
      enabled: true
    shutdown:
      enabled: true

2、相关接口

2.1 Get类型接口:主机:端口/monitor/health,查看应用的健康信息,三个核心指标:status状态,diskSpace磁盘空间,ping检查;

{
    /* 状态值 */
	"status": "UP",
	"components": {
	    /* 磁盘空间 */
		"diskSpace": {
			"status": "UP",
			"details": {
				"total": 250685575168,
				"free": 112149811200,
				"threshold": 10485760,
				"path": "Path/butte-spring-parent/.",
				"exists": true
			}
		},
		/* Ping检查 */
		"ping": {
			"status": "UP"
		}
	}
}

2.2 Get类型接口:主机:端口/monitor/beans,查看bean列表;

{
	"contexts": {
		"boot-senior": {
			"beans": {
				"asyncPool": {
					"scope": "singleton",
					"type": "org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor",
					"resource": "class path resource [com/boot/senior/schedule/PoolConfig.class]"
				},
				"asyncService": {
					"scope": "singleton",
					"type": "com.boot.senior.schedule.AsyncService$$SpringCGLIB$$0"
				}
			}
		}
	}
}

2.3 Post类型接口:主机:端口/monitor/shutdown,关闭应用程序;

{
    "message": "Shutting down, bye..."
}

七、参考源码

文档仓库:
https://gitee.com/cicadasmile/butte-java-note

源码仓库:
https://gitee.com/cicadasmile/butte-spring-parent

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK