自定义注解 + 切面 实现预览量添加


前言

自定义注解加上切面的编程可以使业务代码和系统代码分开,在不改动基础源码的情况下,在它前后写一些功能,十分优雅

我个人还是比较喜欢使用这种方式写代码的。

一、Aop的五大通知

1. 前置通知 @Before

在目标方法执行之前执行执行的通知,无论何时都第一个执行。
方法可以无参,也可以有参。
参数为:JoinPoint

2. 后置通知 @AfterReturning

在目标方法执行之后执行的通知,正常执行时第三个执行。
方法可以无参,也可以有参。
参数为:JoinPoint

3. 异常通知 @AfterThrowing

在目标方法抛出异常时执行的通知,出现异常时第三个执行。

4. 最终通知 @After

是在目标方法执行之后执行的通知,无论何时都第二个执行。
和后置通知不同之处在于,后置通知是在方法正常返回后执行的通知,如果方法没有正常返回,例如抛出异常,则后置通知不会执行。
而最终通知无论如何都会在目标方法调用过后执行,即使目标方法没有正常的执行完成。
还有,后置通知可以通过配置得到返回值,而最终通知无法得到。

5. 环绕通知 @Around

环绕通知是囊括了以上四种通知的结合体
只有环绕通知可以接收ProceedingJoinPoint,而其他通知只能接收JoinPoint
环绕通知需要返回返回值,否则真正调用者将拿不到返回值,只能得到一个null。
环绕通知有控制目标方法是否执行、有控制是否返回值、有改变返回值的能力。(慎用)

二、练习思路

像平常的视频预览、小说预览量、解决接口的幂等性、缓存等诸多问题都可以通过自定义注解加切面的方式实现

三、使用

1. 依赖

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</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-data-redis</artifactId>
        </dependency>

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

2. 配置

spring:
  redis:
    port: 6379
    host: 127.0.0.1
logging:
  level:
    com.lss.like: debug
server:
  port: 8000

3. 注解

import java.lang.annotation.*;

/**
 * ClassName: MyLike
 *
 * @author Jay01is
 * @version 1.0
 * @date 2023/3/8 20:17
 */
@Target(ElementType.METHOD) // 这个注解只能加到方法上面
@Documented // 使用类名生成javadoc文档就需要添加这个注解去了
@Retention(value = RetentionPolicy.RUNTIME) // 注解的范围(生命周期)  *.java  *.class  打包  运行
public @interface MyLike {

}

4. 切面

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * ClassName: CaCheAspect
 *
 * @author Jay01is
 * @version 1.0
 * @date 2023/3/8 20:15
 */
@Aspect
@Component
public class CaCheAspect {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 前置通知 记录用户预览量
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Before("@annotation(com.lss.like.annotation.MyLike)")
    public void doBefore(JoinPoint joinPoint) {
        // 取出参数 为该帖子的id
        Object[] args = joinPoint.getArgs();
        // 判断参数是否存在redis 如果存在则为用户已一分钟访问过
        if (stringRedisTemplate.opsForValue().setIfAbsent(String.valueOf(args[1]), "记录用户预览", 60, TimeUnit.SECONDS)) {
            // 为null代表用户在过期时间之内未访问过 记录用户访问量
            stringRedisTemplate.opsForZSet().incrementScore("likes", String.valueOf(args[0]), 1);
            // 记录用户访问记录 并设置一分钟过期
            stringRedisTemplate.opsForValue().setIfAbsent(String.valueOf(args[1]), "记录用户预览", 60, TimeUnit.SECONDS);
        }
    }
}

5. 调用接口

import com.lss.like.annotation.MyLike;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author Jay01is
 * @since 2023-03-08
 */
@Slf4j
@RestController
@RequestMapping("/like")
public class LikeController {

    @MyLike
    @GetMapping("/find/{lid}/{uid}")
    @ApiOperation(value = "查询", notes = "查询")
    public String find(@PathVariable Integer lid, @PathVariable String uid){
        log.info("lid:{},uid:{}",lid,uid);
        return "ok";
    }
}

总结

补充:

1. 因为Aop是同步的原因,如果需要效率可以把切面中的代码跑一个线程池或使用MQ

2. 因为Redis是缓存,这里没有持久化到Mysql,所以需要跑一个定时任务持久化到Mysql中,保证数据安全

好的,你的问题是关于如何在Spring Boot应用中实现多数据源动态切换,使用的技术包括Spring Boot、MyBatis、MySQL、Oracle、Druid数据源连接池、自定义注解切面,并且配置文件使用application.yml格式。 首先,需要在pom.xml文件中添加相应的依赖: ```xml <!--Spring Boot--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!--MyBatis--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency> <!--MySQL--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.23</version> </dependency> <!--Oracle--> <dependency> <groupId>com.oracle.database.jdbc</groupId> <artifactId>ojdbc8</artifactId> <version>19.3.0.0</version> </dependency> <!--Druid--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.6</version> </dependency> ``` 接下来,需要在application.yml文件中配置数据源和MyBatis相关的属性,例如: ```yaml spring: datasource: druid: # 数据源1 db1: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: root # 数据源2 db2: driver-class-name: oracle.jdbc.OracleDriver url: jdbc:oracle:thin:@localhost:1521:ORCL username: scott password: tiger # 默认数据源 url: jdbc:mysql://localhost:3306/db1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: root mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.demo.entity ``` 然后,需要定义一个自定义注解,用于标识哪些方法需要使用哪个数据源: ```java @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DataSource { String value() default "db1"; } ``` 在数据源切换的时候,我们需要获取注解上指定的数据源名称,因此需要定义一个切面: ```java @Aspect @Component public class DataSourceAspect { @Around("@annotation(ds)") public Object around(ProceedingJoinPoint point, DataSource ds) throws Throwable { String dataSourceName = ds.value(); DynamicDataSource.setDataSource(dataSourceName); try { return point.proceed(); } finally { DynamicDataSource.clearDataSource(); } } } ``` 最后,需要定义一个动态数据源,用于实现数据源的切换: ```java public class DynamicDataSource extends AbstractRoutingDataSource { private static final ThreadLocal<String> dataSourceHolder = new ThreadLocal<>(); @Override protected Object determineCurrentLookupKey() { return dataSourceHolder.get(); } public static void setDataSource(String dataSourceName) { dataSourceHolder.set(dataSourceName); } public static void clearDataSource() { dataSourceHolder.remove(); } } ``` 至此,多数据源动态切换的配置就完成了。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值