Spring--AOP面向切面编程

什么是 AOP

AOP:全称 Aspect Oriented Programming 即:面向切面编程。通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
AOP是OOP(Object-Oriented Programing,面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容。AOP技术将那些影响了多个类的公共行为封装到一个可重用模块,将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,减少了系统的重复代码,降低模块间的耦合度,并有利于以后的操作和维护。简单的说它就是抽取程序中的重复代码,当我们用到的时候,使用动态代理技术,在不修改源码的基础上,对已有方法进行增强。

关于AOP的相关概念:

  • 连接点(JoinPoint)程序执行的某个特定位置,如方法调用前后、方法抛出异常后,在spring中只支持方法类型的连接点。
  • 切入点(Pointcut)程序中有着多个连接点,切入点用来匹配相对应的带有通知的连接点,一个切入点可以匹配多个连接点。
  • 增强\通知(Advice)对特定的切入点进行增强处理。
  • 切面(Aspect)通常是一个类,由切点和增强组成。
  • 代理(Proxy)对目标对象的加强

spring中基于XML的AOP配置步骤:

  • 1. 把通知Bean交给spring来管理
  • 2. 使用aop:config标签表明开始AOP配置
  • 3. 使用aop:aspect标签表明配置切面
    属性:id:给切面提供一个唯一标识,ref:通知类bean的Id
  • 4. 在aop:aspect标签的内部使用对应标签来配置通知的类型,aop:before表示配置前置通知
    属性:method:指定通知类中哪个方法作为通知,pointcut:指定切入点表达式,用于对某些方法增强
<!--配置AOP-->
    <aop:config>
        <!--配置切面-->
        <aop:aspect id="...." ref="通知类">
            <!--配置通知的类型,并且建立通知方法和切入点方法的关联-->
            <aop:before method="通知类中的方法" pointcut="execution(切入点表达式)"></aop:before>
        </aop:aspect>
    </aop:config>

切入点表达式的格式:

  • 关键字:execution(表达式)
  • 表达式写法:
    访问修饰符 返回值 包名.包名.包名...类名.方法名(参数列表)
    public void com.ly.service.impl.AccountServiceImpl.saveAccount()
    访问修饰符可以省略:void com.ly.service.impl.AccountServiceImpl.saveAccount()
    返回值和包名可以使用通配符表示,有几级包就需要写几个*.或者使用*..表示当前包及其子包: * *..AccountServiceImpl.saveAccount()
    类名和方法名都可以使用*来实现通配:* *..*.*()
  • 参数列表:
    可以直接写数据类型:基本类型直接写名称,引用类型使用:.类名的方式,类型可以使用通配符来表示任意类型,但必须有参数。
    可以使用..表示有无参数均可,有参数可以是任意类型
  • 全通配写法:* *..*.*(..)

通知的类型: 前置通知、后置通知、异常通知、最终通知、环绕通知。

  • 前置通知:在目标方法运行之前运行
  • 后置通知:在我们的目标方法正常返回值后运行
  • 异常通知:在我们的目标方法出现异常后运行
  • 最终通知:在目标方法运行结束之后 ,不管有没有异常
  • 环绕通知:是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方法

AOP案例:对增删改方法进行增强

项目结构:
在这里插入图片描述
在pom.xml中导入相关依赖:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.7</version>
</dependency>

新建业务层接口和实现类:

/**
 * @Author: Ly
 * @Date: 2020-08-02 22:14
 */
public interface IAccountService {
    /**
     * 模拟保存账户
     */
    void saveAccount();
    /**
     * 模拟修改账户
     * @param i
     */
    void updateAccount(int i);
    /**
     * 模拟删除账户
     * @return
     */
    int deleteAccount();
}

/**
 * @Author: Ly
 * @Date: 2020-08-02 22:17
 */
public class AccountServiceImpl implements IAccountService {

    public void saveAccount() {
        System.out.println("执行了保存");
    }

    public void updateAccount(int i) {
        System.out.println("执行力修改");
    }

    public int deleteAccount() {
        System.out.println("执行了删除");
        return 0;
    }
}

新建通知类:

/**
 * @Author: Ly
 * @Date: 2020-08-02 22:19
 */
public class Logger {

    /**
     * 前置通知
     */
    public void beforePrintLog(){
        System.out.println("前置Logger类中的printLog方法开始记录日志了");
    }

    /**
     * 后置通知
     */
    public void afterReturningPrintLog(){
        System.out.println("后置Logger类中的printLog方法开始记录日志了");
    }
    /**
     * 异常通知
     */
    public void afterThrowPrintLog(){
        System.out.println("异常Logger类中的printLog方法开始记录日志了");
    }
    /**
     * 最终通知
     */
    public void afterPrintLog(){
        System.out.println("最终Logger类中的printLog方法开始记录日志了");
    }


    public Object aroundPrintLog(ProceedingJoinPoint pjp){
        Object rtValue = null;
        try {
            Object[] args=pjp.getArgs();//得到方法执行所需的参数

            System.out.println("前Logger类中的aroundPrintLog方法开始记录日志了");

            rtValue=pjp.proceed(args);//明确调用业务层方法(切入点方法)

            System.out.println("后Logger类中的aroundPrintLog方法开始记录日志了");

            return rtValue;
        } catch (Throwable e) {
            System.out.println("异常Logger类中的aroundPrintLog方法开始记录日志了");
            throw new RuntimeException(e);
        }finally {
            System.out.println("最终Logger类中的aroundPrintLog方法开始记录日志了");
        }

    }

}

配置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: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
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--配置spring的Ioc,把service对象配置进来-->
    <bean id="accountService" class="com.ly.service.impl.AccountServiceImpl"></bean>

    <!--配置Logger类-->
    <bean id="logger" class="com.ly.utils.Logger"></bean>

    <!--配置AOP-->
    <aop:config>
        <!--只能在配置切面上方-->
        <aop:pointcut id="pt2" expression="execution(public void com.ly.service.impl.*.*(..))"/>

        <!--配置切面-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--配置前置通知:在切入点方法执行之前执行-->
            <aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>

            <!--配置后置通知:在切入点方法正常执行之后执行-->
            <aop:after-returning method="afterReturningPrintLog" pointcut="execution(public void com.ly.service.impl.*.*(..))"></aop:after-returning>

            <!--配置异常通知:在切入点方法执行产生异常之后执行-->
            <aop:after-throwing method="afterThrowPrintLog" pointcut="execution(public void com.ly.service.impl.*.*(..))"></aop:after-throwing>

            <!--最终通知:无论切入点方法是否正常执行都会在其后面执行-->
            <aop:after method="afterPrintLog" pointcut="execution(public void com.ly.service.impl.*.*(..))"></aop:after>

            <!--配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
                此标签卸载aop:aspect标签内部只能在当前切面使用
                它还可以写在aop:aspect外面,此时就变成了所有切面可用
                -->
            <aop:pointcut id="pt1" expression="execution(public void com.ly.service.impl.*.*(..))"/>

            <!--配置环绕通知-->
            <aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
        </aop:aspect>

    </aop:config>

</beans>

测试类:

/**
 * @Author: Ly
 * @Date: 2020-08-03 16:57
 * 测试AOP的配置
 */
public class AOPTest {
    public static void main(String[] args) {
        //1.获取容器
        ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
        //2.获取对象
        IAccountService as=(IAccountService)ac.getBean("accountService");
        //3.执行方法
        as.saveAccount();
        //as.updateAccount(1);
        //as.deleteAccount();
    }
}

运行结果:
在这里插入图片描述

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

龙源lll

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

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

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

打赏作者

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

抵扣说明:

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

余额充值