玩转神奇“AOP”

本文深入探讨了Spring AOP的概念,包括连接点、切入点、通知、代理等核心概念,并展示了如何在Spring Boot中集成AOP。通过切点表达式和自定义注解实现精确的切面匹配,同时详细讲解了前置、后置、环绕等不同类型的通知。此外,还阐述了AOP的动态代理机制,包括JDK和Cglib的实现方式。最后,讨论了多切面执行顺序的控制以及AOP在实际项目中的应用。

**【AOP】**为 Aspect Oriented Programming 的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

在这里插入图片描述

当这两种业务逻辑出现时,我们需要关注登录,有效期,如果每次都去检验,嵌入到逻辑代码中,会出现冗余,不易解耦,急需去优化这种情况,比如aop的,增强方法,不影响主逻辑。

1.1 AOP核心概念

- Joinpoint(连接点):所谓连接点是指那些可以被增强到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
- Pointcut(切入点):所谓切入点是指被增强的连接点(方法)
- Advice(通知/ 增强):所谓通知是指具体增强的代码
- Target(目标对象):被增强的对象就是目标对象
- Aspect(切面):是切入点和通知(引介)的结合
- Proxy (代理):一个类被 AOP 增强后,就产生一个结果代理类

1.2 在 springboot 中如何集成 AOP

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>4.3.7.RELEASE</version>
</dependency>

在这里插入图片描述
1.3 切点表达式

写法:execution([修饰符] 返回值类型 包名.类名.方法名(参数))

- 访问修饰符可以省略,大部分情况下省略
- 返回值类型、包名、类名、方法名可以使用星号*  代表任意
- 包名与类名之间一个点 . 代表当前包下的类,两个点 .. 表示当前包及其子包下的类
- 参数列表可以使用两个点 .. 表示任意个数,任意类型的参数列表

案例

execution(* com.lcc.service.*.*(..))   表示com.lcc.service包下任意类,方法名任意,参数列表任意,返回值类型任意
   
execution(* com.lcc.service..*.*(..))   表示com.lcc.service包及其子包下任意类,方法名任意,参数列表任意,返回值类型任意
    
execution(* com.lcc.service.*.*())     表示com.lcc.service包下任意类,方法名任意,要求方法不能有参数,返回值类型任意
    
execution(* com.lcc.service.*.delete*(..))     表示com.lcc.service包下任意类,要求方法不能有参数,返回值类型任意,方法名要求已delete开头

1.4 切点函数@annotation

定义注解在这里插入图片描述
aop的配置具体类,advice
在这里插入图片描述
需要增强的方法

@Service
public class PhoneService {

    @Master  
    public void deleteAll(){
        System.out.println("PhoneService中deleteAll的核心代码");
    }
}

1.5 通知分类

- @Before:前置通知,在目标方法执行前执行
- @AfterReturning: 返回后通知,在目标方法执行后执行,如果出现异常不会执行
- @After:后置通知,在目标方法之后执行,无论是否出现异常都会执行 
- @AfterThrowing:异常通知,在目标方法抛出异常后执行
- @Around:环绕通知,围绕着目标方法执行

通知执行的顺序

 public Object test() {
        before();//@Before 前置通知
        try {
            Object ret = 目标方法();//目标方法调用
            afterReturing();//@AfterReturning 返回后通知
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            afterThrowing();//@AfterThrowing 异常通知通知
        }finally {
            after();//@After 后置通知
        }
        return ret;
    }

环绕通知非常特殊,它可以对目标方法进行全方位的增强。

	@Around("pt()")
    public void around(ProceedingJoinPoint pjp){
        System.out.println("目标方法前");
        try {
            pjp.proceed();//目标方法执行
            System.out.println("目标方法后");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("目标方法出现异常");
        }finally {
            System.out.println("finally中进行增强");
        }
    }

1.6 获取被增强方法相关信息

我们实际对方法进行增强时往往还需要获取到被增强代码的相关信息,比如方法名,参数,返回值,异常对象等。
我们可以在除了环绕通知外的所有通知方法中增加一个JoinPoint类型的参数。这个参数封装了被增强方法的相关信息。我们可以通过这个参数获取到除了异常对象和返回值之外的所有信息。

案例

@Component
@Aspect
public class PrintLogAspect {

    //对哪些方法增强
    @Pointcut("execution(* com.lcc.service..*.*(..))")
    public void pt(){}

    //怎么增强
    @Before("pt()")
    public void printLog(JoinPoint joinPoint){
        //输出 被增强的方法所在的类名 方法名 调用时传入的参数   joinPoint.getSignature().getName()  joinPoint.getArgs()
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //类名
        String className = signature.getDeclaringTypeName();
        //方法名
        String methodName = signature.getName();
        //调用时传入的参数
        Object[] args = joinPoint.getArgs();

        System.out.println(className+"=="+methodName+"======"+ Arrays.toString(args));
    }
}

如果需要获取被增强方法中的异常对象或者返回值则需要在方法参数上增加一个对应类型的参数,并且使用注解的属性进行配置。这样Spring会把你想获取的数据赋值给对应的方法参数。

    @AfterReturning(value = "pt()",returning = "ret")//使用returning属性指定了把目标方法返回值赋值给下面方法的参数ret
    public void AfterReturning(JoinPoint jp,Object ret){
        System.out.println("AfterReturning方法被调用了");
    }
    @AfterThrowing(value = "pt()",throwing = "t")//使用throwing属性指定了把出现的异常对象赋值给下面方法的参数t
    public void AfterThrowing(JoinPoint jp,Throwable t){
        System.out.println("AfterReturning方法被调用了");
    

proceed()方法被调用相当于被增强方法被执行,调用后的返回值就相当于被增强方法的返回值。

案例

    @Around(value = "pt()")
    public Object around(ProceedingJoinPoint pjp) {
        Object[] args = pjp.getArgs();//方法调用时传入的参数
        Object target = pjp.getTarget();//被代理对象
        MethodSignature signature = (MethodSignature) pjp.getSignature();//获取被被增强方法签名封装的对象
        Object ret = null;
        try {
            ret = pjp.proceed();//ret就是目标方法执行后的返回值
        } catch (Throwable throwable) {
            throwable.printStackTrace();//throwable就是出现异常时的异常对象
        }
        return ret;
    }

1.7 多切面顺序问题

在实际项目中我们可能会存在配置了多个切面的情况。这种情况下我们很可能需要控制切面的顺序。

我们在默认情况下Spring有它自己的排序规则。(按照类名排序)

默认排序规则往往不符合我们的要求,我们需要进行特殊控制。

如果是注解方式配置的AOP可以在切面类上加**@Order**注解来控制顺序。@Order中的属性越小优先级越高。

如果是XML方式配置的AOP,可以通过调整配置顺序来控制。

案例

@Component
@Aspect
@Order(2)
public class APrintLogAspect {
    //省略无关代码
}
@Component
@Aspect
@Order(1)
public class CryptAspect {
    //省略无关代码
}

1.8 AOP原理-动态代理

实际上Spring的AOP其实底层就是使用动态代理来完成的。并且使用了两种动态代理分别是JDK的动态代理和Cglib动态代理。

1.8.1 JDK动态代理

JDK的动态代理使用的java.lang.reflect.Proxy这个类来进行实现的。要求被代理(被增强)的类需要实现了接口。并且JDK动态代理也只能对接口中的方法进行增强。

public static void main(String[] args) {
        AIControllerImpl aiController = new AIControllerImpl();
        //使用动态代理增强getAnswer方法
        //1.JDK动态代理
        //获取类加载器
        ClassLoader cl = Demo.class.getClassLoader();
        //被代理类所实现接口的字节码对象数组
        Class<?>[] interfaces = AIControllerImpl.class.getInterfaces();
        AIController proxy = (AIController) Proxy.newProxyInstance(cl, interfaces, new InvocationHandler() {
            //使用代理对象的方法时 会调用到invoke
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //proxy   是代理对象
                //method 是当前被调用的方法封装的Method对象
                //args   是调用方法时传入的参数
                //调用被代理对象的对应方法
                //判断 当前调用的是否是getAnswer方法
                if(method.getName().equals("getAnswer")){
                    System.out.println("增强");
                }
                Object ret = method.invoke(aiController, args);
                return ret;
            }
        });
        String answer = proxy.getAnswer("三连了吗?");
		System.out.println(answer);
    }

1.8.2 Cglib动态代理

使用的是org.springframework.cglib.proxy.Enhancer类进行实现的。

public class CglibDemo {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        //设置父类的字节码对象
        enhancer.setSuperclass(AIControllerImpl.class);
        enhancer.setCallback(new MethodInterceptor() {
            //使用代理对象执行方法是都会调用到intercept方法
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                //判断当前调用的方法是不是getAnswer方法 如果是进行增强
                if ("getAnswer".equals(method.getName())){
                    System.out.println("被增强了");
                }
                //调用父类中对应的方法
                Object ret = methodProxy.invokeSuper(o, objects);
                return ret;
            }
        });
        //生成代理对象
        AIControllerImpl proxy = (AIControllerImpl) enhancer.create();
//        System.out.println(proxy.getAnswer("你好吗?"));
        System.out.println(proxy.fortuneTelling("你好吗?"));
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值