在本栏中,我们之前已经完成了: 【SpringBoot实战系列】之发送短信验证码 【SpringBoot实战系列】之从Async组件应用实战到ThreadPoolTaskExecutor?定义线程池 【SpringBoot实战系列】之图形验证码开发并池化Redis6存储 【SpringBoot实战系列】阿里云OSS接入上传图片实战 【SpringBoot实战系列】Sharding-Jdbc实现分库分表到分布式ID生成器Snowflake自定义wrokId实战 【SpringBoot实战系列】RabbitMQ实现消息发送并实现邮箱发送异常监控报警实战
本片速览: 1.AOP简介及好处 2.Spring??的AOP常?概念 3.java核心知识-?定义注解 4.防重提交自定义注解实战 5.分布式锁 6.切面开发 7.测试结果
AOP简介及好处
Aspect Oriented Program ?向切?编程, 在不改变原有逻辑上增加额外的功能AOP思想把功能分两个部分,分离系统中的各种关注点 好处
- 减少代码侵?,解耦
- 可以统?处理横切逻辑
- ?便添加和删除横切逻辑
Spring??的AOP常?概念
- 横切关注点
对哪些?法进?拦截,拦截后怎么处理,这些就叫横切关注点 ?如 权限认证、?志、事物 - 通知 Advice
在特定的切?点上执?的增强处理 做啥? ?如你需要记录?志,控制事务 ,提前编写好通?的模块,需要的地?直接调? ?如重复提交判断逻辑 类型
- @Before前置通知
在执??标?法之前运? - @After后置通知
在?标?法运?结束之后 - @AfterReturning返回通知
在?标?法正常返回值后运? - @AfterThrowing异常通知
在?标?法出现异常后运? - @Around环绕通知
在?标?法完成前、后做增强处理 ,环绕通知是最重要的通知类型 ,像事务,?志等都是环绕通知,注意编程中核?是?个ProceedingJoinPoint,需要?动执? joinPoint.procced()
- 连接点 JointPoint
要?通知的地?,业务流程在运?过程中需要插?切?的 具体位置,?般是?法的调?前后,全部?法都可以是连接点只是概念,没啥特殊 - 切?点 Pointcut
不能全部?法都是连接点,通过特定的规则来筛选连接点,就是Pointcut,选中那?个你想要的?法在程序中主要体现为书写切?点表达式(通过通配、正则 表达式)过滤出特定的?组 JointPoint连接点过滤出相应的 Advice 将要发?的joinpoint地? - 切? Aspect
通常是?个类,??定义 切?点+通知 , 定义在什么地?; 什么时间点、做什么事情 通知 advice指明了时间和做的事情(前置、后置等)切?点 pointcut 指定在什么地??这个事情web接?设计中,web层->?关层->服务层->数据层,每?层之间也是?个切?,对象和对象,?法和?法之间都是?个个切? - ?标 target
?标类,真正的业务逻辑,可以在?标类不知情的条件下,增加新的功能到?标类的链路上 - 织? Weaving
把切?(某个类)应?到?标函数的过程称为织?
java核心知识-?定义注解
- Annotation(注解) 从JDK 1.5开始, Java增加了对元数据(MetaData)的?持,也就是 Annotation(注解)。
注解其实就是代码?的特殊标记,它?于替代配置?件常?的很多 @Override、@Deprecated等 - 什么是元注解
注解的注解,?如当我们需要?定义注解时会需要?些元注解(meta-annotation),如@Target和@Retention - java内置4种元注解
@Target 表示该注解?于什么地?
- ElementType.CONSTRUCTOR ?在构造器
- ElementType.FIELD ?于描述域-属性上
- ElementType.METHOD ?在?法上
- ElementType.TYPE ?在类或接?上
- ElementType.PACKAGE ?于描述包
- @Retention 表示在什么级别保存该注解信息
- RetentionPolicy.SOURCE 保留到源码上
- RetentionPolicy.CLASS 保留到字节码上
- RetentionPolicy.RUNTIME 保留到虚拟机运?时(最多,可通过反射获取)
- @Documented 将此注解包含在 javadoc 中
- @Inherited 是否允许?类继承?类中的注解
- @interface
?来声明?个注解,可以通过default来声明参数的默认值?定义注解时,?动继承了java.lang.annotation.Annotation接?通过反射可以获取?定义注解
防重提交自定义注解实战
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RepeatSubmit {
enum Type {PARAM,TOKEN}
Type limitType() default Type.PARAM;
long lockTime() default 5;
}
分布式锁 redission依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.10.1</version>
</dependency>
配置类
@Configuration
public class RedissionConfiguration {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private String redisPort;
@Value("${spring.redis.password}")
private String redisPwd;
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer().setPassword(redisPwd).setAddress("redis://" + redisHost + ":" + redisPort);
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
}
切面开发:
@Aspect
@Component
@Slf4j
public class RepeatSubmitAspect {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private RedissonClient redissonClient;
@Pointcut("@annotation(repeatSubmit)")
public void pointCutNoRepeatSubmit(RepeatSubmit repeatSubmit) {
}
@Around("pointCutNoRepeatSubmit(repeatSubmit)")
public Object around(ProceedingJoinPoint joinPoint, RepeatSubmit repeatSubmit) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
boolean res = false;
String type = repeatSubmit.limitType().name();
if (type.equalsIgnoreCase(RepeatSubmit.Type.PARAM.name())) {
long lockTime = repeatSubmit.lockTime();
String ippAddr = CommonUtil.getIpAddr(request);
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
String className = method.getDeclaringClass().getName();
String key ="order-server-repeat-submit:"+CommonUtil.MD5(String.format("%s-%s-%s-%s", ippAddr, className, method, accountNo)) ;
RLock lock = redissonClient.getLock(key);
res = lock.tryLock(0, lockTime, TimeUnit.SECONDS);
} else {
String requestToken = request.getHeader("request-token");
if (StringUtils.isBlank(requestToken)) {
throw new BizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_EQUAL_FAIL);
}
String key = String.format(RedisKey.SUBMIT_ORDER_TOKEN_KEY, accountNo, requestToken);
res = redisTemplate.delete(key);
}
if (!res) {
log.error("订单重复提交");
return null;
}
log.info("环绕通知前:{}", CommonUtil.getCurrentTimestamp());
Object obj = joinPoint.proceed();
log.info("环绕通知后:{}", CommonUtil.getCurrentTimestamp());
return obj;
}
}
将自定义的注解加在对应想要防重提交的方法上即可
@PostMapping("page")
@RepeatSubmit
public JsonData page(@RequestBody OrderPageRequest orderPageRequest){
Map<String,Object>pageResult = productOrderService.page(orderPageRequest);
return JsonData.buildSuccess(pageResult);
}
访问对应接口 本篇完!
|