基于spring boot实现aop切面编程~~~~

引言

一直就知道aop用来处理日志,具体怎么用不清楚,平时工作中用到的也比较少,但是这么一个如雷贯耳的aop知识点,不会的话始终是一个痛点,必须要研究明白了。
开搞!

代码实现

首先定义一个注解类,用来标注一些你想标记的东西,当然没有的话也没问题啦。不需要的话,此步骤略过

package com.geek45.exampleall.aspect.demo3;

import java.lang.annotation.*;

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Name {
    String name() default "老钱";
    int age() default 24;
}

实现切面

详细说一下该步骤哈

该步骤,主要是来讲,你要用切面干嘛?白话一点就是,切面嘛,就像一个球,你目前要进这个球里面,进行一顿猛如虎的操作,进行操作的时候,切面的作用就是当你碰触到球表面,切面的时候,触发一些其他操作。比如你碰到这个球的**点了,她很开心呀,给你回应回应,让你更兴奋了~~
这么一看,的确切面的作用不是很大哈,所以把切面做一个日志记录是一个比较不错而且比较常见的使用场景。例如你进入这个方法了,打印一条日志,进入方法了。结束之后,再打印一条日志,执行结束了。打印的时候还能更全面一些,比如请求参数是什么,进方法前进行一些参数校验,日志输出,方法后去校验一些数据什么的。都可以呀。

package com.geek45.exampleall.aspect.demo3;

import com.geek45.exampleall.aspect.demo2.Lingyejun;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Component
@Aspect
@Lazy(value = false)
public class RubikAspect {

    /**
     * ps:这个方法的实际作用在于头上面的注解,没有其他特殊用处。
     * 该方法标识的包路径,表名这个切点生效的范围。
     *
     * 定义切入点:对要拦截的方法进行定义与限制,如包、类
     *
     * 1、execution(public * *(..)) 任意的公共方法
     * 2、execution(* set*(..)) 以set开头的所有的方法
     * 3、execution(* com.lingyejun.annotation.LoggerApply.*(..))com.lingyejun.annotation.LoggerApply这个类里的所有的方法
     * 4、execution(* com.lingyejun.annotation.*.*(..))com.lingyejun.annotation包下的所有的类的所有的方法
     * 5、execution(* com.lingyejun.annotation..*.*(..))com.lingyejun.annotation包及子包下所有的类的所有的方法
     * 6、execution(* com.lingyejun.annotation..*.*(String,?,Long)) com.lingyejun.annotation包及子包下所有的类的有三个参数,第一个参数为String类型,第二个参数为任意类型,第三个参数为Long类型的方法
     * 7、execution(@annotation(com.lingyejun.annotation.Lingyejun))
     */
    @Pointcut("execution(* com.geek45.exampleall.aspect.demo3.*.*(..))")
    private void cutMethod() {}

    /**
     * 前置通知:在目标方法执行前调用
     */
    @Before("cutMethod()")
    public void begin() {
        System.err.println("来了老弟");
    }

    /**
     * 后置通知:在目标方法执行后调用,若目标方法出现异常,则不执行
     */
    @AfterReturning("cutMethod()")
    public void afterReturning() {
        System.err.println("欢迎下次光临");
    }

    /**
     * 后置/最终通知:无论目标方法在执行过程中出现一场都会在它之后调用
     */
    @After("cutMethod()")
    public void after() {
        System.err.println("慢走啊老弟");
    }

    /**
     * 异常通知:目标方法抛出异常时执行
     */
    @AfterThrowing(value = "cutMethod()", throwing = "ex")
    public void afterThrowing(Exception ex){
        System.err.println("非常抱歉老弟,你看这事闹的:" + ex.getMessage());
        System.out.println("你继续~");
//        joinPoint.proceed();
    }

    /**
     * 环绕通知:灵活自由的在目标方法中切入代码
     */
    @Around("cutMethod()")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取目标方法的名称
        String methodName = joinPoint.getSignature().getName();
        // 获取方法传入参数
        Object[] params = joinPoint.getArgs();
        Name name = getDeclaredAnnotation(joinPoint);
//        System.out.println("==@Around== lingyejun blog logger --》 method name " + methodName + " args " + params[0]);
        System.err.println("吱哇 开门啦:你可以干这件事:" + methodName);
        // 模拟进行验证
        if (params != null && params.length > 0 && params[0].equals("老钱")) {
            System.out.println("我老婆名字是:》 " + name.name() + "年龄是:" + name.age() + "  你最美了~");
        } else {
            System.out.println(params[0] + "说:她的名字是 --》 " + name.name() + "年龄是:" + name.age() + " 她真美");
        }
        // 执行源方法
        joinPoint.proceed();
    }

    /**
     * 获取方法中声明的注解
     *
     * @param joinPoint
     * @return
     * @throws NoSuchMethodException
     */
    public Name getDeclaredAnnotation(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
        // 获取方法名
        String methodName = joinPoint.getSignature().getName();
        // 反射获取目标类
        Class<?> targetClass = joinPoint.getTarget().getClass();
        // 拿到方法对应的参数类型
        Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
        // 根据类、方法、参数类型(重载)获取到方法的具体信息
        Method objMethod = targetClass.getMethod(methodName, parameterTypes);
        // 拿到方法定义的注解信息
        Name annotation = objMethod.getDeclaredAnnotation(Name.class);
        // 返回
        return annotation;
    }
}

异常通知获取异常信息

/**
      * 在方法出现异常时会执行的代码
      * 可以访问到异常对象,可以指定在出现特定异常时在执行通知代码
      */
     @AfterThrowing(value="execution(public int com.yl.spring.aop.ArithmeticCalculator.*(..))", throwing="ex")
     public void afterThrowing(JoinPoint joinPoint, Exception ex) {
         String methodName = joinPoint.getSignature().getName();
         System.out.println("The method " + methodName + " occurs exception: " + ex);
     }

测试类

@Resource
    private RubikApply rubikApply;

    @Test
    public void rubikTest() {
        try {
            rubikApply.say("老钱");
        } catch (Exception e) {
            System.out.println("ssss");
        }
    }

执行结果

吱哇 开门啦:你可以干这件事:say
我老婆名字是:》 晗晗 年龄是:18 你最美了~
来了老弟
你看我美吗?老钱
非常抱歉老弟,你看这事闹的:害!
你继续~
慢走啊老弟
ssss

来点理论知识,以后查阅也方便不是

注解讲解

  • @Pointcut: 这个注解主要是用来标识你想要生效的切面范围,你是需要生效一个球,一个包的球,多个包的球,整个项目的球?由你顶多喽。该注解下的方法不需要做其他任何操作。这个注解才是重点。
  • @Before: 这个注解是当你执行某个方法之前,会到这个方法上面。举个例子,比如你进入一家店,进店之前,会有服务员说一嘴欢迎光临,来了老弟!
  • @AfterReturning: 这个呢就是当你在这家店消费的还算愉快,没有突发情况,走的时候呢,服务你的服务员会跟你说一句欢迎再次光临哟~当然是客套话,别当真了
  • @After: 这个也是后置通知,不过呢这个比较真诚,比上一个服务员态度好,算是老板跟你告别,给你说一嘴,慢走啊老弟。
  • @AfterThrowing: 这个也算是管理人员,一般情况下也不会出面。当你遇到不开心的事了,跟刚才那虚伪的服务员干起来了,这个时候大堂经理就过来了,对不住啊老弟,你看这事弄得,然后给你陈述一边刚才发生的事,给你解释一遍就是。然后你继续玩呗。你继续干你的事就完了。
  • @Around: 这个就是上帝视角的家伙事了,当你准备去某个店的时候,就知道你要去哪玩,也知道你要哪个店玩,还知道你带了多少钱去。根据你的需求,消费水平给你量身定做一下子。等都给你安排妥当了之后,再让你去你想去的店。一切都在神不知鬼不觉的情况下发生,可不可怕。

AOP相关术语

  • 切面(Aspect):是指横切多个对象的关注点的一个模块化,事务管理就是J2EE应用中横切关注点的很好示例。在Spring AOP中,切面通过常规类(基本模式方法)或者通过使用了注解@Aspect的常规类来实现。
  • 连接点(Joint point):是指在程序执行期间的一个点,比如某个方法的执行或者是某个异常的处理。在Spring AOP中,一个连接点往往代表的是一个方法执行。
  • 通知(Advice):是指切面在某个特殊连接点上执行的动作。通知有不同类型,包括"around","before"和"after"通知。许多AOP框架包括Spring,将通知建模成一个拦截器,并且围绕连接点维持一个拦截器链。
  • 切入点(Pointcut):是指匹配连接点的一个断言。通知是和一个切入点表达式关联的,并且在任何被切入点匹配的连接点上运行(举例,使用特定的名字执行某个方法)。AOP的核心就是切入点表达式匹配连接点的思想。Spring默认使用AspectJ切入点表达式语言
  • 引入(Introduction):代表了对一个类型额外的方法或者属性的声明。Spring AOP允许引入新接口到任何被通知对象(以及一个对应实现)。比如,可以使用一个引入去使一个bean实现IsModified接口,从而简化缓存机制。(在AspectJ社区中,一个引入也称为一个inter-type declaration类型间声明)
  • 目标对象(Target object):是指被一个或多个切面通知的那个对象。也指被通知对象("advised object"),由于Spring AOP是通过运行时代理事项的,这个目标对象往往是一个代理对象。
  • AOP 代理(AOP proxy):是指通过AOP框架创建的对象,用来实现切面合约的(执行通知方法等等)。在Spring框架中,一个AOP代理是一个JDK动态代理或者是一个CGLIB代理。
  • 织入(Weaving):将切面和其他应用类型或者对象连接起来,创骗一个被通知对象。这些可以在编译时(如使用AspectJ编译器)、加载时或者运行时完成。Spring AOP,比如其他纯Java AOP框架一般是在运行时完成织入。
  • 前置通知(Before advice):在一个连接点之前执行的通知。但这种通知不能阻止连接点的执行流程(除非它抛出一个异常)
  • 后置返回通知(After returning advice):在一个连接点正常完成后执行的通知(如,如果一个方法没有抛出异常的返回)
  • 后置异常通知(After throwing advice):在一个方法抛出一个异常退出时执行的通知。
  • 后置(最终)通知(After(finally) advice):在一个连接点退出时(不管是正常还是异常返回)执行的通知。
  • 环绕通知(Around advice):环绕一个连接点的通知,比如方法的调用。这是一个最强大的通知类型。环绕通知可以在方法调用之前和之后完成自定义的行为。也负责通过返回自己的返回值或者抛出异常这些方式,选择是否继续执行连接点或者简化被通知方法的执行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

写代码的喵o

请作者吃包辣条可好

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值