测试项目结构:
目前是测试两个日志记录和 代码的性能测试 后面如果有其他的应用场景了在添加.其实一中就包括了二,但是没事,多练一遍
1. 日志记录
比如说对service层中的所有增加,删除,修改方法添加日志, 记录内容包括操作的时间 操作的方法, 方法的参数, 方法所在的类, 方法执行的时间. 等进行日志记录.后面可以把数据存到数据库中,也可以把数据存到日志文件中
这里就简单的做一个演示,直接输出到控制台了.
对于AOP有两种方式可以实现, 由于注解较为灵活,所以这里就使用注解的方式来实现.如果不太会使用注解的方式,可以看一下我之前的笔记.
SpringAop中的五种常见的通知的注解及@annotation 切入点表达式_yfs1024的博客-CSDN博客
第一步: 定义注解
这里因为是对方法切入,所以创建了一个MethodLog注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodLog {
}
第二步: 在对应的方法上添加注解 这里是对Service层所以就加到Service上了
类如下: 其中包括增删改查, 因为我们要求的是对增删改进行日志记录,所以就把注解加到增删改的方法上即可
@Service
public class EmployeeServiceImpl implements EmployeeService {
@Override
@MethodLog
public void insertEmployee(Employee employee) {
System.out.println("新增");
}
@Override
@MethodLog
public void deleteEmployee(Long EmployeeId) {
System.out.println("删除");
}
@Override
public List<Employee> list() {
ArrayList<Employee> employees = new ArrayList<>();
employees.add(new Employee());
return employees;
}
@Override
@MethodLog
public void updateEmployee(Employee employee) {
System.out.println("更新员工信息");
}
}
第三步: 定义切面 定义切入点, 定义通知
@Aspect
@Component
@Slf4j
public class MyAspect {
// 定义切入点 对那些方法进行切入
// com.yfs1024.service任意子包下的任意类中任意参数,任意返回值 并且 使用@MethodLog注解的方法
@Pointcut("execution(* com.yfs1024.service..*.*(..)) && @annotation(com.yfs1024.annocation.MethodLog)")
public void pointCutForMethodLog() {
}
// 定义通知 做什么
@Around("pointCutForMethodLog()")
public Object methodLog(ProceedingJoinPoint joinPoint) throws Throwable {
//操作时间
LocalDateTime operateTime = LocalDateTime.now();
//操作类名
String className = joinPoint.getTarget().getClass().getName();
//操作方法名
String methodName = joinPoint.getSignature().getName();
//操作方法参数
Object[] args = joinPoint.getArgs();
String methodParams = Arrays.toString(args);
long begin = System.currentTimeMillis();
// 为了防止时间太短,睡会
Thread.sleep(3);
//调用原始目标方法运行
Object result = joinPoint.proceed();
long end = System.currentTimeMillis();
//操作耗时
Long costTime = end - begin;
log.info("方法操作时间{},方法所在类{},方法名{},方法的参数{},方法执行耗时 {}",operateTime,className,methodName,methodParams,costTime);
// 注意一定要返回
return result;
}
}
第四步: 测试
在测试类中进行测试, 注意因为我们使用了Spring的注解所以一定要在测试类上加上@SpringBootTest
默认会加但是还是要注意一下.
测试方法如下:
@SpringBootTest
class SpringAopApplicationTests {
@Autowired
private EmployeeService employeeService;
@Test
void methodLog() {
Employee employee = new Employee();
employeeService.insertEmployee(employee);
employeeService.deleteEmployee(10001L);
employeeService.list();
employeeService.updateEmployee(employee);
}
}
控制台结果:
嗯,真香 其实想想如果使用动态代理让我们自己写应该也能写出来这样的效果. 在之前的笔记中也有记录感兴趣的话可以看一下,在笔记的最后的案例中
试着让动态代理变得通俗易懂,通过三个案例_yfs1024的博客-CSDN博客
2. 性能测试.
需求: 现在有两个工具包, 分别是AliyunOssUtils 和 AliyunOssUtilsOld ,我们现在要通过AOP来对这两个工具包进行测试
你可能会问,就两个我还用AOP? 没错确实麻烦,但是这里就是举个例子. 后面如果多个的话我们只需要,修改切入点即可.
依然是上面的四步
第一步: 定义注解
// 性能测试日志记录
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface PTLog {
}
第二步: 在对应的方法上添加注解 (也可以添加到,类上,但是其实还是执行方法的时候记录,因为有多个吗. 也可以是接口上,这样接口的实现类的所有的方法就都被切入了)
AliyunOssUtils
@Component
public class AliyunOssUtils {
/**
* 公共方法
* @throws InterruptedException
*/
@PTLog
public void load(MultipartFile multipartFile) throws InterruptedException {
Thread.sleep(10);
}
}
AliyunOssUtilsOld
@Component
public class AliyunOssUtilsOld {
/**
* 公共方法
* @throws InterruptedException
*/
@PTLog
public void load(MultipartFile multipartFile) throws InterruptedException {
Thread.sleep(10);
}
}
第三步: 定义切面 定义切入点, 定义通知
@Aspect
@Component
@Slf4j
public class MyAspect {
// com.yfs1024.utils任意子包下的任意类中任意参数,任意返回值 并且 使用@PTLog注解的方法
@Pointcut("execution(* com.yfs1024.utils..*.*(..)) && @annotation(com.yfs1024.annocation.PTLog)")
public void pointCutForPTLog() {}
@Around("pointCutForPTLog()")
public Object ptLog(ProceedingJoinPoint joinPoint) throws Throwable {
String className = joinPoint.getTarget().getClass().getName();
long begin = System.currentTimeMillis();
// 为了防止时间太短,睡会
Thread.sleep(3);
//调用原始目标方法运行
Object result = joinPoint.proceed();
long end = System.currentTimeMillis();
Long costTime = end - begin;
log.info("{}类中方法执行耗时{}",className,costTime);
return result;
}
}
第四步: 测试
@SpringBootTest
class SpringAopApplicationTests {
// 这里因为测试用, 所以上面的工具类上就加了@Component注解, 一般不加哈,其实也可以通过一个配置类配合@Bean注入到容器这里懒了,嘿
@Autowired
private AliyunOssUtils aliyunOssUtils;
@Autowired
private AliyunOssUtilsOld aliyunOssUtilsOld;
@Test
void myPTLog() throws InterruptedException {
aliyunOssUtils.load();
aliyunOssUtilsOld.load();
}
}
控制台结果:
3.公共字段的填充
首先介绍一下什么是公共字段的填充, 我们在对数据进行更新操作的时候,或者插入操作的时候往往都需要记录一下操作的时间, 基本所有的表都会有这两个字段,即 更新日期, 创建日期, 另外我们的每张表还需要记录创建该条记录的人是谁,以及修改这条记录的是谁,等这些就叫做公共字段. 对于不同的表可能要求不同视情况而定, 可见我们对于公共字段的操作往往是在 对数据库的更新操作和插入操作. 所以我们可以定义一个枚举来表示这两个操作,以供后期的使用.
第0步:
public enum OperationType {
/**
* 更新操作
*/
UPDATE,
/**
* 插入操作
*/
INSERT
}
第一步: 定义注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AutoFill {
OperationType value() default OperationType.INSERT; // 设置枚举的参数,默认为插入操作
}
第二步: 在对应的方法上添加注解
这个时候就该想了, 我们把这些注解添在哪里合适?
答案是Mapper层, 因为Mapper层是和数据库交互的最后一个步骤, 我不管你之前怎么处理,知道在插入数据库之前把公共字段填充即可
代码如下:
@Mapper
public interface EmployeeMapper {
/**
* 根据用户名查询员工
*
* @param username
* @return
*/
@Select("select * from employee where username = #{username}")
Employee getByUsername(String username);
/**
* @param employee
*/
// @Options(useGeneratedKeys = true,keyProperty = "id")
@AutoFill()
void insertEmployee(Employee employee);
/**
* 根据条件查询用户列表
*
* @param employee
* @return
*/
Page<Employee> select(Employee employee);
/**
* 更新员工信息
*/
@AutoFill(OperationType.UPDATE)
void updateEmployee(Employee employee);
}
此时对插入和更新操作添加注解:
第三步: 定义切面 定义切入点, 定义通知
此时AOP的强大就体现的淋漓尽致
@Component
@Aspect
@Slf4j
public class AutoFillAspect {
// 设置通知的连接点所在的位置
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annocation.AutoFill)")
public void insertOrUpdateCommonsMeg() {
}
// 当前切入位置是mapper层,所以是在方法执行之前,即Service层执行之后,再给公共的属性赋值
@Before("insertOrUpdateCommonsMeg()")
public void beforeAutoFillCommonsField(JoinPoint joinPoint) throws Exception {
// 获取连接点的方法对象
Object[] args = joinPoint.getArgs();
// 判断如果不为空,再执行操作,如果为空直接返回
if (args == null || args.length == 0) {
return;
}
// 此时就可以获取参数列表中的第一个对象,即为当前需要添加数据的对象
Object NowMethodObject = args[0];
Class<?> obj = NowMethodObject.getClass();
// 判断当前的方法是Insert还是Update那些方法进行切入
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
AutoFill annotation = signature.getMethod().getAnnotation(AutoFill.class);
// 获取当前的注解对象,通过value值进行判断
if (annotation.value() == OperationType.INSERT) {
// 获取创建时间方法这个对象
Method setCreateTime = obj.getMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
// 获取设置创建者id的方法对象
Method setCreateUser = obj.getMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
// 调用传入参数
setCreateTime.invoke(NowMethodObject, LocalDateTime.now());
setCreateUser.invoke(NowMethodObject, BaseContext.getCurrentId());
}
// 其次我要知道对谁切入
Method setUpdateTime = obj.getMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
// 获取设置创建者id的方法对象
Method setUpdateTUser = obj.getMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
// 调用传入参数
setUpdateTime.invoke(NowMethodObject, LocalDateTime.now());
setUpdateTUser.invoke(NowMethodObject, BaseContext.getCurrentId());
}
}
第四步: 测试
此时运行SpringBoot项目,进行更新和插入操作, 此时我们就可以看到数据库中的数据已经自动的填充进去