基于jdk17详解spring 的aop面向切面编程

我们在平常的编码中,有没有可能有这么一种场景,业务代码已经上线但是需要对其进行增强(比如增强其日志功能)这就用到了们Spring中的功能-springAop

1.先从静态代理说起

1.先建立一个接口Calculator 里面有几加减乘除四个方法

package com.dc.esb;

public interface Calculator {
    int add(int i, int j);

    int sub(int i, int j);

    int mul(int i, int j);

    int div(int i, int j);
}

2.在建立其实现类CalculatorImpl

package com.dc.esb;


public class CalculatorImpl implements Calculator {

    @Override
    public int add(int i, int j) {

        int result = i + j;

        System.out.println("方法内部 result = " + result);

        return result;
    }

    @Override
    public int sub(int i, int j) {

        int result = i - j;

        System.out.println("方法内部 result = " + result);

        return result;
    }

    @Override
    public int mul(int i, int j) {

        int result = i * j;

        System.out.println("方法内部 result = " + result);

        return result;
    }

    @Override
    public int div(int i, int j) {

        int result = i / j;

        System.out.println("方法内部 result = " + result);

        return result;
    }
}

3.为了增强实现类的日志功能建立其静态代理类进行增强

package com.dc.esb;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class ProxyFactory {
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxy(){

        /**
         * newProxyInstance():创建一个代理实例
         * 其中有三个参数:
         * 1、classLoader:加载动态生成的代理类的类加载器
         * 2、interfaces:目标对象实现的所有接口的class对象所组成的数组
         * 3、invocationHandler:设置代理对象实现目标对象方法的过程,即代理类中如何重写接口中的抽象方法
         */
        ClassLoader classLoader = target.getClass().getClassLoader();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                /**
                 * proxy:代理对象
                 * method:代理对象需要实现的方法,即其中需要重写的方法
                 * args:method所对应方法的参数
                 */
                Object result = null;
                try {
                    System.out.println("[动态代理][日志] "+method.getName()+",参数:"+ Arrays.toString(args));
                    result = method.invoke(target, args);
                    System.out.println("[动态代理][日志] "+method.getName()+",结果:"+ result);
                } catch (Exception e) {
                    e.printStackTrace();
                    System.out.println("[动态代理][日志] "+method.getName()+",异常:"+e.getMessage());
                } finally {
                    System.out.println("[动态代理][日志] "+method.getName()+",方法执行完毕");
                }
                return result;
            }
        };

        return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
    }
}

测试结果

 [动态代理][日志] div,参数:[1, 5]
方法内部 result = 0
[动态代理][日志] div,结果:0
[动态代理][日志] div,方法执行完毕

上面这样虽然增强了其功能,但是只能其一个类进行使用,实际的项目中业务类更多,每个类都有自己的静态代理类,这样就导致代码冗余太多-------就出现了动态代理

动态代理有两种1.jdk动态代理  2.cglib动态代理

springAOP 通过下面两个注解实现

@Component
@Aspect

 建立公共的日志增强类

package com.dc.esb;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Component
@Aspect
public class LogAspect {
    @Pointcut("execution(public int com.dc.esb.CalculatorImpl.*(..))")
    public void pointCut() {
    }

    @Before("pointCut()")
    public void beforeMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        System.out.println("Logger-->前置通知,方法名:" + methodName + ",参数:" + args);
    }

    @After("execution(public int com.dc.esb.CalculatorImpl.*(..))")
    public void afterMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->后置通知,方法名:" + methodName);
    }

    @AfterReturning(value = "execution(public int com.dc.esb.CalculatorImpl.*(..))", returning = "result")
    public void afterReturningMethod(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->返回通知,方法名:" + methodName + ",结果:" + result);
    }

    @AfterThrowing(value = "execution(public int com.dc.esb.CalculatorImpl.*(..))", throwing = "ex")
    public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->异常通知,方法名:" + methodName + ",异常:" + ex);
    }

    @Around("execution(public int com.dc.esb.CalculatorImpl.*(..))")
    public Object aroundMethod(ProceedingJoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        Object result = null;
        try {
            System.out.println("环绕通知-->目标对象方法执行之前");
            //目标对象(连接点)方法的执行
            result = joinPoint.proceed();
            System.out.println("环绕通知-->目标对象方法返回值之后");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("环绕通知-->目标对象方法出现异常时");
        } finally {
            System.out.println("环绕通知-->目标对象方法执行完毕");
        }
        return result;
    }
}

这边是通过spring配置文件的形式进行测试,所以在resources目录下建立bean.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--
        基于注解的AOP的实现:
        1、将目标对象和切面交给IOC容器管理(注解+扫描)
        2、开启AspectJ的自动代理,为目标对象自动生成代理
        3、将切面类通过注解@Aspect标识
    -->
    <context:component-scan base-package="com.dc"></context:component-scan>

    <aop:aspectj-autoproxy />
</beans>

最后进行测试结果

环绕通知-->目标对象方法执行之前
Logger-->前置通知,方法名:add,参数:[1, 1]
方法内部 result = 2
Logger-->返回通知,方法名:add,结果:2
Logger-->后置通知,方法名:add
环绕通知-->目标对象方法返回值之后
环绕通知-->目标对象方法执行完毕

Process finished with exit code 0

 重要:对日志增强类LogAspect 进行分析

- 前置通知:使用@Before注解标识,在被代理的目标方法**前**执行
- 返回通知:使用@AfterReturning注解标识,在被代理的目标方法**成功结束**后执行(**寿终正寝**)
- 异常通知:使用@AfterThrowing注解标识,在被代理的目标方法**异常结束**后执行(**死  于非命**)
- 后置通知:使用@After注解标识,在被代理的目标方法**最终结束**后执行(**盖棺定论**)
- 环绕通知:使用@Around注解标识,使用try...catch...finally结构围绕**整个**被代理的目标方法,包括上面四种通知对应的所有位置

 切面的优先级

相同目标方法上同时存在多个切面时,切面的优先级控制切面的**内外嵌套**顺序。

- 优先级高的切面:外面
- 优先级低的切面:里面

使用@Order注解可以控制切面的优先级:

- @Order(较小的数):优先级高
- @Order(较大的数):优先级低

 以上就是用Spring注解的形式实现了Aop,其实也可以通过配置文件的形式实现,只需要早bean.xml添加如下代码(两种选择其一不要重复使用)

<context:component-scan base-package="com.atguigu.aop.xml"></context:component-scan>

<aop:config>
        <!--配置切面类-->
        <aop:aspect ref="loggerAspect">
            <aop:pointcut id="pointCut"
                          expression="execution(public int com.dc.esb.CalculatorImpl.*(..))"/>
            <aop:before method="beforeMethod" pointcut-ref="pointCut"></aop:before>
            <aop:after method="afterMethod" pointcut-ref="pointCut"></aop:after>
            <aop:after-returning method="afterReturningMethod" returning="result" pointcut-ref="pointCut"></aop:after-returning>
            <aop:after-throwing method="afterThrowingMethod" throwing="ex" pointcut-ref="pointCut"></aop:after-throwing>
            <aop:around method="aroundMethod" pointcut-ref="pointCut"></aop:around>
        </aop:aspect>
    </aop:config>

最后把上面用到的maven贴一下

<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.2</version>
        </dependency>

        <!--spring aop依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>6.0.2</version>
        </dependency>
        <!--spring aspects依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>6.0.2</version>
        </dependency>

        <!--junit5测试-->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.3.1</version>
        </dependency>

        <!--log4j2的依赖-->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.19.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j2-impl</artifactId>
            <version>2.19.0</version>
        </dependency>

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

一人荡江湖@

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值