强迫自己学习和写作组织语言-java注解与aop
1.引言
什么是Java注解?
Java注解(Annotations)是Java语言中的一种元数据形式,用于为代码中的类、方法、字段、变量、构造函数等元素提供附加信息。注解并不会直接影响代码的逻辑运行,而是通过编译期或运行时的工具和框架来解释和处理这些信息。
什么是AOP(面向切面编程)?
AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,旨在提高模块化程度,特别是对跨越多个模块的功能进行封装。与OOP(面向对象编程)中的类和对象划分关注点不同,AOP通过引入“切面”(Aspect)来处理程序中分散的横切关注点(Cross-Cutting Concerns),如日志记录、权限控制、事务管理等。
2.Java注解基础
Java注解的特点:
- 元数据:注解是附加在代码上的信息,可以用于描述类的行为或属性,而不会影响程序的实际运行逻辑。
- 轻量级:相比于XML配置等其他形式的元数据,注解更加简洁直接。它们直接嵌入在Java代码中,便于开发和维护。
- 多用途:注解可以用于生成文档、编译时检查、代码分析,甚至在运行时动态改变行为。
- 可继承性:有些注解可以从父类或接口中继承,影响子类或实现类。
Java注解的常见类型:
- 内置注解:Java提供了一些常用的内置注解,如
@Override
、@Deprecated
、@SuppressWarnings
等。这些注解提供了基础的功能,例如提示编译器某个方法是重写的,或者抑制编译器的警告。 - 元注解:Java提供了几个特殊的注解,用来定义和描述其他注解的行为,包括
@Retention
(定义注解的保留策略)、@Target
(定义注解的适用范围)、@Inherited
(允许注解被继承)和@Documented
(表明注解应当在Javadoc中显示)。 - 自定义注解:开发者可以根据需要创建自己的注解,通过结合反射机制、注解处理器等工具,实现在编译期或运行时对注解的处理。
3.aop基础及核心概念
切面(Aspect):
- 切面是AOP的核心模块,负责封装跨越多个类或方法的通用行为。它可以看作是一个关注点的模块化实现。
- 切面通常由一组通知(Advice)和切入点(Pointcut)组成。
通知(Advice):
- 通知是定义在特定切入点处执行的代码,也就是切面中的实际操作。根据执行时机,通知分为以下几类:
- 前置通知(Before Advice):在目标方法执行之前运行。
- 后置通知(After Advice):在目标方法执行之后运行。
- 返回通知(After Returning Advice):在目标方法成功返回结果后运行。
- 异常通知(After Throwing Advice):在目标方法抛出异常后运行。
- 环绕通知(Around Advice):可以在目标方法执行前后自定义行为,甚至可以控制是否执行目标方法。
切入点(Pointcut):
- 切入点是定义在哪些连接点上应用通知的表达式。通过切入点表达式,可以选择特定的类、方法或注解作为目标,从而定义通知的作用范围。
连接点(Join Point):
- 连接点是程序执行过程中可以插入切面的特定点。连接点通常是方法调用或异常处理过程。
织入(Weaving):
- 织入是将切面代码应用到目标对象的过程。在编译期、类加载期或运行时将切面逻辑插入到目标代码中,以生成增强的目标对象。
这里就不再说aop的好处了,对于需要使用的场景,用过的人都说好。
4.场景
aop的场景运用在各个方面:
日志记录:在方法调用前后自动记录日志。
性能监控:监控方法执行时间,识别性能瓶颈。
事务管理:在方法执行过程中自动处理事务的开启、提交或回滚。
安全检查:在方法执行前进行权限验证。
异常处理:统一处理异常,避免重复编写异常处理代码。
以我目前遇到的场景来说,现在有个B系统因某种money的原因,现在在B系统的注册页调B系统的注册接口,B系统会去调A系统的注册接口,返回的注册信息除了密码其余会存入系统用户表,系统用户表的信息有4种类型,除了系统管理员以及其他不同角色的管理员信息不用单独再存一张表,其余的三种用户类型各有一张基本信息表和及其附属的基本信息紧密相关的其他信息的表。现在时间紧迫不会去做单独的统一用户中心,两者甲方要求用户数据互通但是各自的业务又存在各自的系统中。且不能去使用A系统的从库。这个时候选择了使用线程池异步在相关接口里调用同步接口。
但是我们知道,多线程的调试与记录是麻烦的。而且是还是及其赶的项目,又涉及菜单,角色,权限,多类型用户,以及相关业务众多的表和接口。我们不光需要日志,也需要对调用的请求以及方法的全部内容进行详细记录,方便排查bug。这个时候就轮到咱们的主角出场了,aop加注解可以轻松帮助我们完成以上功能,不用谢复杂的代码。
@Configuration
@EnableAspectJAutoProxy // 启用AspectJ注解支持
public class AppConfig {
}
//定义一个我们需要的注解规定其是在方法上还是类上
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface TongbuLoggerAspectAnnotation {
}
package com.ruoyi.web.aspect;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.web.domain.TongbuInfoHis;
import com.ruoyi.web.domain.bo.TongbuInfoHisBo;
import com.ruoyi.web.mapper.TongbuInfoHisMapper;
import liquibase.pro.packaged.P;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Arrays;
@Aspect
@Component
@Slf4j
//@DependsOn("tongbuInfoHisMapper")
//定义切面类
public class TongbuLoggerAspect {
private TongbuInfoHisMapper tongbuInfoHisMapper;
public TongbuLoggerAspect() {
this.tongbuInfoHisMapper = SpringUtils.getBean(TongbuInfoHisMapper.class);
}
@Pointcut("@annotation(com.ruoyi.web.aspect.annotation.TongbuLoggerAspectAnnotation)")
public void TongbuLoggerAspectPointCut(){}
@Before("TongbuLoggerAspectPointCut()")
public void logBefore(JoinPoint joinPoint){
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
log.info("同步方法开始,方法名称:{}", method.getName() );
log.info("同步方法开始,方法参数:{}", Arrays.toString(joinPoint.getArgs()));
TongbuInfoHis bo = new TongbuInfoHis();
bo.setApi(method.getName());
bo.setMes("同步方法开始,方法名称:" + method.getName() + ",方法参数:" + Arrays.toString(joinPoint.getArgs()));
tongbuInfoHisMapper.insert(bo);
}
@AfterReturning(pointcut = "TongbuLoggerAspectPointCut()", returning = "result")
public void loggerAfter(JoinPoint joinPoint, Object result){
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
log.info("同步方法结束,方法名称:{}", method.getName() );
log.info("同步方法结束,方法参数:{}", Arrays.toString(joinPoint.getArgs()));
log.info("同步方法结束,返回结果:{}", result);
TongbuInfoHis bo = new TongbuInfoHis();
bo.setApi(method.getName());
bo.setMes("同步方法开始,方法名称:" + method.getName() + ",方法参数:" + Arrays.toString(joinPoint.getArgs()));
if (result != null) {
bo.setRes(result.toString());
}
tongbuInfoHisMapper.insert(bo);
}
@AfterThrowing(pointcut = "TongbuLoggerAspectPointCut()", throwing = "exception")
public void logAfterThrowing(JoinPoint joinPoint, Throwable exception) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
log.error("同步方法报错:{},错误信息为:{}", method.getName(),exception);
TongbuInfoHis bo = new TongbuInfoHis();
bo.setApi(method.getName());
bo.setMes("同步方法报错:{},错误信息为:{}" + method.getName() + exception);
tongbuInfoHisMapper.insert(bo);
}
}
以上我们就完成了,注解的定义与切面的定义与方法实现,接下来直接在我们需要使用的方法上去加上这个注解即可,举个例子如下:
@Async("myExecutor")
@TongbuLoggerAspectAnnotation()
public void syncUpdateTalentEducationExperience(TalentEducationExperience update, HttpServletRequest request){
}
@Configuration
@EnableAsync
@EnableAspectJAutoProxy
public class ThreadPoolConfigForListener {
@Bean("myExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
//设置线程池参数信息
taskExecutor.setCorePoolSize(8);
taskExecutor.setMaxPoolSize(20);
taskExecutor.setQueueCapacity(50);
taskExecutor.setKeepAliveSeconds(60);
taskExecutor.setThreadNamePrefix("eventHandle--");
taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
// 设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。
taskExecutor.setAwaitTerminationSeconds(60);
//修改拒绝策略为使用当前线程执行
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//初始化线程池
taskExecutor.initialize();
return taskExecutor;
}
}