Java中运用SpirngAOP的来思想实现自定义日志注解并使用

1.AOP的思想

面向切面编程。它是对OOP(面向对象编程)的一种补充,OOP往往是继承或者是实现都是纵向进行的,而AOP是切面编程,是横向的,它出现的目的是将我们某些与业务无关的公共代码提取出来,找到一个切点进行切入形成切面,在切面进行公共代码的插入,以此来进行增强的操作。这样我们就不用在每一个业务中都写一遍与业务无关的代码。

上面这句话可以跳过,我们还是要从问题出发,什么问题导致出现了AOP的思想及其解决方案。

问题:我们系统中一定会有日志记录吧。例如每一个Controller的增删改操作,我们都会记录一下,记录哪个用户干了什么事,如果没有AOP,可能会这样去做,封装一个统一的日志工具类,然后在每个Controller的方法中调用这个日志的方法。可能是一行代码就解决了,这里可以先透露一下,我们使用aop,也是通过一个注解的方式进行增强,那问题就来了?同样是一行代码,为什么还用aop呢而不用日志工具类呢?

解答:我举个例子,假如说我某个修改操作发生了异常,那此时你封装日志工具类就没办法处理了吧,它的关注点就只是方法内部的某一个小点,而AOP此时就可以解决这个问题,它能获取到方法的所有东西,包括返回的结果,并且通过注解的方式我就不用侵入你的业务逻辑代码,并且能在你的方法的执行前后都去做一些操作,并且使我们的代码更容易维护,因为我的方法里面就只是我的业务逻辑,我是通过注解来实现日志记录的,其次呢,如果将来你封装的工具类方法需要新添加一个参数,你自己想想吧,你是不是调用的每个地方都需要修改,而使用注解呢,我参数可传可不传,不会影响我其他的代码,可扩展性也是非常好的。
 

总结一句话:AOP就是将我们代码中的横切关注点(日志记录、事物管理)从核心业务逻辑中分离出来, 通过定义切面来统一管理这些横切关注点,在需要使用的地方,将切面进行织入即可。

AOP的优点总结:
1.横切关注点的集中管理:可以将与业务逻辑无关的功能集中到切面中,提高代码的可维护性。
2.降低代码重复和分散:将通用功能抽离出来,避免在多个对象和方法中重复实现。
3.提高代码的可读性和可维护性:将横切关注点与核心业务逻辑分离,使代码更加清晰、简洁。
4.支持横向扩展:可以在不修改原有代码的情况下,通过定义新的切面来增加功能。

2.AOP的组成

2.1连接点(Join Point)

连接点是程序执行过程中的实际事件点,可以被拦截和增强,简单说就是某个方法,要跟下面的切点明确区分开。

2.2切点(Point Cut)

切点是一个表达式或规则,用来匹配连接点,确定哪些连接点应该被AOP拦截和应用切面。

2.3通知(Advice)

就是我们需要进行的增强的操作,例如日志的记录操作,并且我们可以定义不同的通知来实现不同时机进行的增强操作,

前置通知使用 @Before:通知方法会在目标方法(即要访问的具体业务方法)调用之前执行
后置通知使用 @After:通知方法会在目标方法返回或者抛出异常后调用
返回之后通知@AfterReturning:通知方法会在目标方法返回后调用
抛异常后通知 @AfterThrowing:通知方法会在目标方法抛出异常之后调用
环绕通知@Around:通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行相应代码

2.4切面(Aspect)

切点+通知=切面。

而SpringAOP就是对AOP的一个具体实现。

3.实现自定义的日志注解

3.1 日志注解SysOperationLog

首先定义一个注解,起名@SysOperationLog,考虑一下需要哪些字段,一般就是记录某个操作的名字和类型就可。代码:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysOperationLog {
    /**
     * 业务标题 title
     * @return
     */
    String value() default "操作日志";
    /**
     *  业务类型
     * @return
     */
    BusinessTypeEnum businessType() default BusinessTypeEnum.OTHER;
}
3.2 业务类型枚举BusinessTypeEnum
@Getter
@AllArgsConstructor
public enum BusinessTypeEnum {

    // 其它
    OTHER(1,"其它"),
    // 查找
    SELECT(2,"查找"),
    // 新增
    INSERT(3,"新增"),
    // 修改
    UPDATE(4,"修改"),
    // 删除
    DELETE(5,"删除"),
    //详情
    DETAIL(6,"详情");

    private Integer code;
    private String name;

    public static String  getNameByCode(Integer code){
        for (BusinessTypeEnum value : BusinessTypeEnum.values()) {
            if(value.getCode().equals(code)){
                return value.getName();
            }
        }
        return "";
    }
}
3.3定义日志切面LogAspect

引入AOP的依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
@Component
@Aspect
public class LogAspect {

    private final String logPointcut = "execution(* com.daz.controller..*.*(..))";//切点表达式

    //切点
    @Pointcut(logPointcut)
    public void logPointcut() {
    }

    @Before(value = "logPointcut()")
    public void doBefore(JoinPoint joinPoint) {
        System.out.println("执行了前置通知");
    }

    @Around(value = "logPointcut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("执行了环绕通知");
        Object proceed =null;
        try{
            proceed = proceedingJoinPoint.proceed();
            MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
            SysOperationLog sysOperationLog = signature.getMethod().getAnnotation(SysOperationLog.class);
            System.out.println(sysOperationLog.value());
            //接着你就可以在这去将注解中的日志操作保存到数据库中即可。
        }catch (Exception e){
        }
        return proceed;
    }

    @After(value = "logPointcut()")
    public void doAfter(JoinPoint joinPoint) {
        System.out.println("执行了后置通知");
    }

    @AfterReturning(value = "logPointcut()", returning = "result")
    public void afterReturn(JoinPoint joinPoint, Object result) {
        System.out.println("执行了返回通知");

    }

    @AfterThrowing(value = "logPointcut()", throwing = "exception")
    public void doAfterThrowing(JoinPoint joinPoint, Exception exception) {
        System.out.println("执行了异常通知");
    }

}
4.在对应的方法上加上我们自定义的注解@SysOperatonLog
@RequestMapping("/aaa/bbb")
    @SysOperationLog("编辑")
    public String testA() throws Exception {
        return "hello";
    }
5.测试执行结果分析

4.步骤三如何利用AOP思想进行实现的,及其相关类的讲解

大体流程总结:首先通过@Aspect和@Component来定义一个切面,在切面中定义切点(拦截规则),拦截到对应的方法以后,跳到我们定义的对应的通知方法中,在通知方法中能够获取到自定义注解的相关信息,从而取出注解,保存注解信息操作到数据库中。

首先由上述的执行结果我们知道,执行顺序,此处主要讲解环绕通知这个方法。我们主要的操作都写到这个方法中。介绍一下其中相关的类。

4.1. ProceedingJoinPoint

可以看出它继承JoinPoint,这个又称连接点的意思,那肯定就是连接点的相关信息,就像我们此时的连接点是一个方法,那肯定就是获取方法相关的信息了。

我们主要关注以上我圈出的方法。
proceed():这个就是获取我们方法的返回结果的。
getArgs():这个是获取我们方法的参数列表的。
getSignature():这个是获取我们原始的Method类的,通过该类,可以获取注解。只不过需要强转一下成为MethodSignature,再调用getMethod方法就可以获取到。

4.2 MethodSignature

首先查看关系,鼠标放到类上,快捷键ctrl+alt+u,可以看出它的父接口就是Signature,所以可以转,因为MethodSignature中提供了获取方法类Method的方法,进而获取方法上相关的信息,例如我们自定义的注解。

4.3. Method

这个就最原始的方法,它提供了我们获取方法的相关信息的各种方法,例如获取方法的注解就是其中一个。

总结:以上我们讲了SpringAOP的思想及其运用,但是它底层实际上是通过JDK动态代理或者CGLIB动态代理的方式去实现的。后续继续介绍。

  • 25
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值