AOP面向切面编程

7 篇文章 0 订阅

AOP面向切面编程

静态代理和动态代理

​ 静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。

动态代理

​ 将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现,JDK本身就支持动态代理,这是反射技术的一部分。

public class TestStaticProxy {
    @Test
    public void testProxy(){
        //1. 创建一个CalculatorPureImpl对象:被代理者对象
        Calculator calculator = new CalculatorPureImpl();
        //2. 创建代理者对象:使用动态代理技术创建
        //动态代理技术分为两种:
        //1. JDK内置的动态代理技术:要求被代理者必须实现接口,它的底层其实就是使用反射创建接口的实现类对象
        //2. CGLIB的动态代理技术(需要引入依赖):被代理者可以不实现接口,它的底层其实是创建被代理类的子类对象


        //我们现在使用JDK的动态代理技术
        Class<? extends Calculator> clazz = calculator.getClass();
        //参数一:用于定义代理对象的类加载器:和被代理者的类加载器是同一个类加载器
        ClassLoader classLoader = clazz.getClassLoader();
        //参数二:表示要被代理的接口的集合
        //获取参数二的方式一: 获取被代理者实现的所有接口
        Class<?>[] interfaces = clazz.getInterfaces();
        //获取参数二的方式二: 自己编写一个数组,将你想要代理的接口放里面new Class[]{Calculator.class}
        //参数三:InvocationHandler接口的实现类对象或者是匿名内部类对象,在它里面真正编写代理逻辑
        Calculator calculatorProxy = (Calculator) Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //这个方法中就编写具体的代理逻辑
                //invoke()方法什么时候会执行:当代理对象调用任何方法的时候都会执行invoke()
                //proxy参数:就是调用方法的代理对象
                //method参数:代理对象调用的那个方法
                //args参数:代理对象调用的那个方法的参数

                //目标:在被代理者的add、sub、mul、div方法前后添加日志打印
                //1. 判断方法名是否是add、sub、mul、div
                String methodName = method.getName();
                if (methodName.equals("add") || methodName.equals("sub") || methodName.equals("mul") || methodName.equals("div")) {
                    //先执行前置打印日志
                    System.out.println("[日志] "+methodName+" 方法开始了,参数是:" + args[0] + "," + args[1]);
                    //执行被代理者的核心逻辑
                    Object result = method.invoke(calculator, args);
                    //执行后置打印日志
                    System.out.println("[日志] "+methodName+" 方法结束了,结果是:" + result);
                    return result;
                }
                //返回值是返回给代理对象调用的那个方法
                //对于不需要进行代理的方法,就执行被代理者原本的方法
                return method.invoke(calculator,args);
            }
        });


        int result = calculatorProxy.sub(2, 3);
        System.out.println("在外面打印代理对象执行运算的结果:" + result);
    }
}

使用代理模式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KroIFJ3I-1640004690352)(./img/003.jpg)]

相关术语

①代理:又称之为代理者,用于将非核心luoji剥离出来,封装这些非核心逻辑类、方法、对象

②目标:用于核心逻辑,并把这些代理者非核心逻辑用在类、对象方法上

AOP

简化代码:把固定重复的代码抽取出来,让其方法更专注于自己的核心功能,提高内聚性

代码增强:抽取出来的方法,放在切面类里面,看哪里需要就往哪里套,被套用的切面楼价就被切面类增强了

AOP的核心思路

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hRh3PdmJ-1640004690360)(./img/004.jpg)]

相关术语
横切关注点

从每个方法中抽取同一类非核心业务

通知

每个横向切点都需写一个方法来实现

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wDBspPJD-1640004690363)(.\img\005.png)]

切面

封装通知的方法类

目标

被代理目标对象

代理

向目标对象应用通知之后创建的代理对象

连接点

各个方法中可以被增强或修改的点

切入点

方法中真正要去配置增强或者配置修改的地方

注解方式配置AOP

1、加入依赖
<dependencies>
    <!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.1</version>
    </dependency>
    <!-- junit测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!-- spring-aspects会帮我们传递过来aspectjweaver -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.3.1</version>
    </dependency>
    <!--spring整合Junit-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.3.1</version>
    </dependency>
</dependencies>
2、准备接口
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);
    
}
3、接口的实现类
import org.springframework.stereotype.Component;

@Component
public class CalculatorPureImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        int result = i + j;
        //int num = 10 / 0;
        return result;
    }

    @Override
    public int sub(int i, int j) {
        int result = i - j;
        return result;
    }

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

        int result = i * j;
        return result;
    }

    @Override
    public int div(int i, int j) {
        int result = i / j;
        return result;
    }
}
4、创建切面类
/**
 * Aspect注解:指定一个切面类
 * Component注解: 对这个切面类进行IOC
 *
 * 注解AOP的关键点:
 * 1. 一定要在配置文件中加上<aop:aspectj-autoproxy />表示允许自动代理
 * 2. 切面类一定要加上Aspect注解,并且切面类一定要进行IOC
 * 3. 其它的类该进行IOC和依赖注入的就一定要进行IOC和依赖注入
 * 4. 通知上一定要指定切入点(怎么使用切入点表达式描述切入点又是一个难点)
 */
@Aspect
@Component
public class LogAspect {
    @Before("execution(int com.wwb.component.CalculatorPureImpl.*(int,int))")
    public void printLogBeforeCore(){
        System.out.println("[前置通知]在方法执行之前打印日志...");
    }

    @AfterReturning("execution(int com.wwb.component.CalculatorPureImpl.*(int,int))")
    public void printLogAfterReturning(){
        System.out.println("[返回通知]在方法执行成功之后打印日志...");
    }

    @AfterThrowing("execution(int com.wwb.component.CalculatorPureImpl.*(int,int))")
    public void printLogAfterThrowing(){
        System.out.println("[AOP异常通知]在方法抛出异常之后打印日志...");
    }

    @After("execution(int com.wwb.component.CalculatorPureImpl.*(int,int))")
    public void printLogFinallyEnd(){
        System.out.println("[AOP后置通知]在方法最终结束之后打印日志...");
    }
}
5、Spring的配置文件
  <!--包扫描-->
    <context:component-scan base-package="com.wwb"/>

    <!--允许注解AOP-->
    <aop:aspectj-autoproxy />
6、测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-application.xml")
//@ContextConfiguration这个注解通常与@RunWith(SpringJUnit4ClassRunner.class)联合使用用来测试
public class TestAop {
    @Autowired
    private Calculator calculator;
    @Test
    public void testAdd(){
        //调用CalculatorPureImpl对象的add()方法
        System.out.println("返回值是:"+calculator.add(1, 2));
    }
}

[AOP前置通知] 方法开始了 方法内部 result = 12 [AOP返回通知] 方法成功返回了 [AOP后置通知] 方法最终结束了 方法外部 add = 12

在通知内部获取细节信息
JoinPoint接口
  • 要点1:JoinPoint接口通过getSignature()方法获取目标方法的签名

  • 要点2:通过目标方法签名对象获取方法名

  • 要点3:通过JoinPoint对象获取外界调用目标方法时传入的实参列表组成的数组

    	// 1.通过JoinPoint对象获取目标方法签名对象
        // 方法的签名:一个方法的全部声明信息
        Signature signature = joinPoint.getSignature();
    
         // 2.通过方法的签名对象获取目标方法的详细信息
        String methodName = signature.getName();
        System.out.println("methodName = " + methodName);
    
      // 3.通过JoinPoint对象获取外界调用目标方法时传入的实参列表
        Object[] args = joinPoint.getArgs();
        
        // 4.由于数组直接打印看不到具体数据,所以转换为List集合
        List<Object> argList = Arrays.asList(args);
    
获取目标方法的方法返回值

只有在AfterReturning返回通知中才能够获取目标方法的返回值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M4wzQwaw-1640004690366)(./img/006.jpg)]

切入点
声明切入点

易于维护,一处修改,处处生效。

@Pointcut("execution(int com.wwb.component.CalculatorPureImpl.*(int,int))")
public void calculatorPointCut(){
}
同一个类内部引用切入点

通过方法名引入

@Before("calculatorPointCut()")
public void printLogBeforeCore(JoinPoint joinPoint){
其它类中引用切入点
@Before("com.wwb.pointcut.wwbPointCut.calculatorPointCut()")
public void printLogBeforeCore(JoinPoint joinPoint){}
对项目中的所有切入点进行统一管理
public class wwbPointCut {
    @Pointcut("execution(int com.wwb.component.CalculatorPureImpl.*(int,int))")
    public void calculatorPointCut(){
    }
}
总结

在这里插入图片描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IqEevW1E-1640004690368)(.\img\007.jpg)]

环绕通知

环绕通知对应整个try…catch…finally结构,可以在目标方法的各个部位进行套用代理逻辑,它能够真正介入并改变目标方法的执行

基于XML方式配置AOP

  <!--包扫描-->
    <context:component-scan base-package="com.wwb"/>

    <!--
        使用xml方式配置AOP:
            1. 切面: 封装非核心逻辑的那个类,非核心逻辑就是封装在切面的方法中
            2. 通知: 将非核心逻辑套在核心逻辑上进行执行
            3. 切入点: 核心逻辑
    -->
    <aop:config>
        <!--
            1. 切面: ref属性就是指定作为切面的那个对象的id,order属性表示切面的优先级
        -->
        <aop:aspect id="myAspect" ref="logAspect">
            <!--2. 通知-->
            <!--配置前置通知-->
            <aop:before method="printLogBeforeCore" pointcut-ref="calculatorPoint"/>
            <!--配置返回通知-->
            <aop:after-returning method="printLogAfterReturning" pointcut-ref="calculatorPoint" returning="result"/>
            <!--配置异常通知-->
            <aop:after-throwing method="printLogAfterThrowing" pointcut-ref="calculatorPoint" throwing="throwable"/>
            <!--配置后置通知-->
            <aop:after method="printLogFinallyEnd" pointcut-ref="calculatorPoint"/>
            <!--配置环绕通知-->
            <aop:around method="printLogAround" pointcut-ref="calculatorPoint"/>
            <!--3. 切入点-->
            <aop:pointcut id="calculatorPoint"
                          expression="execution(* com.wwb.component.CalculatorPureImpl.*(..))"/>
        </aop:aspect>
    </aop:config>

测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-application.xml")
public class TestAop {
    @Autowired
    private Calculator calculator;

    @Test
    public void testAdd(){
        //调用CalculatorPureImpl对象的add()方法
        System.out.println("调用完目标方法之后获取返回值是:"+calculator.sub(5, 3));
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值