面向切面编程–AOP(Aspect Oriented Programming)

导读:本篇文章讲解 面向切面编程–AOP(Aspect Oriented Programming),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

目录

XML文件配置

Java代码配置


AOP(Aspect Oriented Programming),即面向切面编程,利用一种成为“横切”的技术,抛开封装的对象内部,并将那些影响了多个类的公共行为封装为一个可重用模块,并将其命名为“Aspect”,即切面。所谓切面,简单的说,和代理设计模式有些相似,但是AOP的功能比代理模式更加的全面,利用AOP的环绕通知也可以达到代理模式的效果。

Spring AOP一共有五种通知:前置通知、后置通知、返回通知、异常通知和环绕通知。前置通是在目标方法执行前执行,后置通知在目标方法执行后执行,返回通知是当目标方法有返回值的时候执行,同理异常通知是目标方法发生异常的时候通知,而环绕通知则是其他四个通知的集合。

以下是测试AOP五个通知的代码

XML文件配置

首先添加两个依赖,spring-aspects + spring-context

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>myaop01</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.18</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.18</version>
        </dependency>
    </dependencies>
</project>
/**
 * 为了便于测试,分别定义有返回值和无返回值的方法
 */
public interface Calculator {
    Integer add(int a, int b);

    void minus(int a, int b);
}
/**
 * 接口的实现类
 */
public class CalculatorImp implements Calculator {
    @Override
    public Integer add(int a, int b) {
        return a + b;
    }

    @Override
    public void minus(int a, int b) {
        System.out.println(a + "-" + b + "=" + (a - b));
    }
}
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

/**
 * 定义一个通知类,测试AOP的五种通知
 * 
 * 前置通知:MethodBeforeAdvice 目标方法执行前通知
 * 后置通知:AfterAdvice 目标方法执行后通知
 * 返回通知:AfterReturningAdvice 当目标方法有返回值的时候触发
 * 异常通知:ThrowsAdvice 当目标方法抛出异常的时候通知
 * 环绕通知:MethodInterceptor 以上四个通知的集大成者
 */
public class LogAspect {
    /**
     * @param jp 连接点是程序类中客观存在的方法,可被Spring拦截并切入内容
     */
    public void before(JoinPoint jp) {
        //获取目标方法名称
        String name = jp.getSignature().getName();
        System.out.println(name + "方法开始执行。。。");
    }

    public void after(JoinPoint jp) {
        String name = jp.getSignature().getName();
        System.out.println(name + "方法执行结束。。。");
    }

    /**
     * 参数的类型必须匹配,目前只有当返回值类型为int时,才会进入当前方法,int改为Object则所有方法都匹配
     *
     * @param result 目标方法返回值
     */
    public void returning(JoinPoint jp, int result) {
        System.out.println("返回通知。。。" + result);
    }

    /**
     * @param e 只有异常匹配时,才会拦截
     */
    public void exception(JoinPoint jp, Exception e) {
        System.out.println(jp.getSignature().getName() + "方法抛出了异常" + e.getMessage());
    }

    /**
     * 环绕通知
     */
    public Object around(ProceedingJoinPoint pjp) {
        try {
            //调用目标方法
            long startTime = System.nanoTime();
            Object proceed = pjp.proceed();
            long endTime = System.nanoTime();
            System.out.println(pjp.getSignature().getName() + "方法执行耗时" + (endTime - startTime));
            return proceed;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return null;
    }

}

applicationContext.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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

     <--! 配置实现类,但是在获取的时候是获取接口 -->
    <bean class="com.qfedu.demo.CalculatorImp" id="calculatorImp"/>
    <bean class="com.qfedu.demo.LogAspect" id="logAspect"/>
    <!--    开始AOP的配置-->
    <aop:config>
        <!--        配置切点,即要拦截哪些方法?id就是切点名称,expression='execution(int com.qfedu.demo.CalculatorImp.add(int,int))'就是切点表达式,表示拦截add方法
        * com.qfedu.demo.*.*(..)) 表示任意返回值类型,com.qfedu.demo包下的任意类,任意方法名,任意参数 ,都拦截-->
        <aop:pointcut id="pc01" expression="execution(* com.qfedu.demo.*.*(..))"/>
        <!--        配置一个拦截类-->
        <aop:aspect ref="logAspect">
            <!--            定义一个前置通知,目标方法拦截下来之后触发的方法-->
            <aop:before method="before" pointcut-ref="pc01"/>
            <aop:after method="after" pointcut-ref="pc01"/>
            <!--            返回通知,returning表示目标方法返回值的映射-->
            <aop:after-returning method="returning" pointcut-ref="pc01" returning="result"/>
            <aop:after-throwing method="exception" pointcut-ref="pc01" throwing="e"/>
            <aop:around method="around" pointcut-ref="pc01"/>
        </aop:aspect>
    </aop:config>
</beans>
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 测试类
 */
class LogAspectTest {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        //这个地方拿到的对象实际上是Spring AOP 利用JDK动态给Calculator接口自动生成的一个对象
        //配置的是实现类,获取的是实现类的代理对象
        Calculator calculator = ctx.getBean(Calculator.class);
        Integer add = calculator.add(3, 4);
        System.out.println("add = " + add);
        System.out.println("-----------------------");
        calculator.minus(4, 3);
    }
}

运行结果 

add方法开始执行。。。
add方法执行耗时35600
返回通知。。。7
add方法执行结束。。。
add = 7
-----------------------
minus方法开始执行。。。
4-3=1
minus方法执行耗时54300
minus方法执行结束。。。

从运行结果可知,前置通知before、后置通知after和环绕通知arounding均已执行,因为minus方法没有返回值,所以调用minus()没有执行返回通知returning,因为add()和minus()两个方法都没有出现异常,所以异常通知没有执行。

Java代码配置

package com.qfedu.demo01.service;

/**
 * 接口
 */
public interface CalculatorImp {
    Integer add(int a, int b);

    void minus(int a, int b);
}
package com.qfedu.demo01.service;

import org.springframework.stereotype.Component;

/**
 * 实现类
 */
@Component      //注入到Spring容器
public class Calculator implements CalculatorImp {
    @Override
    public Integer add(int a, int b) {
        return a + b;
    }

    @Override
    public void minus(int a, int b) {
        //定义一个异常,测试AOP的异常通知
        int i = 1 / 0;
        System.out.println(a + "+" + b + "=" + (a + b));
    }
}
package com.qfedu.demo01;

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

/**
 * 通知类
 * 
 * @Component 表示将 LogAspect 注入到 Spring 容器中
 * @Aspect 表示当前类是一个切面
 * @EnableAspectJAutoProxy 开启切面的自动代理
 */
@Component
@Aspect
@EnableAspectJAutoProxy
public class LogAspect {

    /**
     * 定义统一的切点
     * * com.qfedu.demo01.service.*.*(..))表示 com.qfedu.demo01.service下的 任意类名,任意方法,任意参数,任意返回值类型的方法
     */
    @Pointcut("execution(* com.qfedu.demo01.service.*.*(..))")
    public void pc() {

    }

    @Before("pc()")
    public void before(JoinPoint jp) {
        //获取目标方法名称
        String name = jp.getSignature().getName();
        System.out.println(name + "方法开始执行。。。");
    }

    @After("pc()")
    public void after(JoinPoint jp) {
        String name = jp.getSignature().getName();
        System.out.println(name + "方法执行结束。。。");
    }

    @AfterReturning(pointcut = "pc()", returning = "result")
    public void returning(JoinPoint jp, Object result) {
        System.out.println("返回通知。。。" + result);
    }

    @AfterThrowing(pointcut = "pc()", throwing = "e")
    public void throwing(JoinPoint jp, Exception e) {
        System.out.println(jp.getSignature().getName() + "方法抛出了异常" + e.getMessage());
    }

    @Around("pc()")
    public Object around(ProceedingJoinPoint pjp) {
        try {
            //调用目标方法
            long startTime = System.nanoTime();
            Object proceed = pjp.proceed();
            long endTime = System.nanoTime();
            System.out.println(pjp.getSignature().getName() + "方法执行耗时" + (endTime - startTime));
            return proceed;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return null;
    }

}
package com.qfedu.demo01;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration    //声明当前类是配置类
@ComponentScan    //spring自动扫描并装入bean容器
public class JavaConfig {
}
package com.qfedu.demo01;

import com.qfedu.demo01.service.CalculatorImp;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * 测试类
 */
class LogAspectTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
        CalculatorImp calculatorImp = ctx.getBean(CalculatorImp.class);
        Integer add = calculatorImp.add(3, 4);
        System.out.println("add = " + add);
        System.out.println("-----------------");
        calculatorImp.minus(4, 3);
    }
}

运行结果 

add方法开始执行。。。
返回通知。。。7
add方法执行结束。。。
add方法执行耗时1885800
add = 7
-----------------
minis方法开始执行。。。
minis方法抛出了异常/ by zero
minis方法执行结束。。。

由于在com.qfedu.demo01.service.Calculator#minus()方法中有意编写了一个异常 by zero,所以在调用minus()方法时,异常通知执行了。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/14615.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!