【Spring】面向切面编程详解(AOP)

时间:2022-10-19 08:03:32

????博客x主页:己不由心王道长????!
????文章说明:spring????
✅系列专栏:spring
????本篇内容:对面向切面编程即AOP进行一个详细讲解(对所需知识点进行选择阅读呀~)????
☕️每日一语:在人生的道路上,即使一切都失去了,只要一息尚存,你就没有丝毫理由绝望。因为失去的一切,又可能在新的层次上复得。☕️
????作者详情:作者是一名双非大三在校生,喜欢Java,欢迎大家探讨学习,喜欢的话请给博主一个三连鼓励。????

一、AOP概述

什么是AOP

AOP(Aspect Oriented Program)即面向切面编程。我们先回顾以下三层架构,三层架构式垂直架构,即一层对另一层提供服务,不能越级访问,分级进行运作。
【Spring】面向切面编程详解(AOP)
上述架构是垂直分布的,而面向切面则是一个切面:
到底是什么切面呢?
就是把各种类中冗余的代码提取出来,在需要用的时候就横向切入,就像一个切面一样。
到底是什么冗余代码呢?事务处理、异常处理等等!突然来一大堆听不懂。
举个栗子:
【Spring】面向切面编程详解(AOP)

比如说事务控制,在我们的业务中,虽然查询是最多的,但是添加、修改、删除也不少。查询不用提交事务,而后面的都得提交事务,在JDBC中,我们一般都手动提交事务。
【Spring】面向切面编程详解(AOP)
都得提交事务,难道要在调用每一个方法执行完之后都编写一段提交事务的代码吗?这当然可以,不过太傻了!

④解决办法
上面看到了事务提交每次都编写代码会造成代码冗余,复用性不好,也不利于维护(事务提交与代码耦合死了)。
这是我们可以把上面重复的代码提取出来,制成一个切面,需要用的地方就声明它

⑤ 实现先行剧透:通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。

AOP应用场景

日志记录、性能统计、安全控制、事务处理、异常处理

二、AOP的基本术语

术语介绍

Joint point(连接点): 连接点就是可以被拦截的点,在程序中,通常指的是方法,在Spring中只支持方法类型的连接点。在其他的地方可能支持类,这里记住方法就行了。
Pointcut(切点): 切入点就是对连接点中哪些点进行拦截的定义,对连接点(一般所有方法都可做连接点)进行条件筛选。
Advice(通知/增强): 通知就是我们拦截到切点之后,对它做的事情叫做通知或者增强,比如上面的事务,我们拦截到addUser之后,要对它增强,使其在完成方法之后增强提交事务,异常的时候发生回滚。
Aspect(切面): 即切入点和通知的结合(多个切入点共有一个通知,连在一起像不像一个面?)
Weaving(织入): 指的是在把增强的方法加入到目标对象(切点方法的拥有者)来创建新的对象的过程,spring采用的是动态代理织入(jdk动态代理和子类动态代理都有),AspectJ采用编译期织入和类装载织入。

术语举例详解

先说一个故事吧:
有一天,小吉和他的小伙伴(连接点)去后山的山洞冒险,发现山洞里有一个shiti,小吉和其他一部分小伙伴(切点)就把这个shiti抬了出去,回家后其他抬了的小伙伴都die了,这时候小吉的身上开始发绿,爷爷看到就说:“不好,是绿shi寒警告”。

Joint point(连接点): 在上面的故事里,小吉和他的伙伴都能够被绿,就是都能被增强,可以看到绿shi寒警告是可以作用在上面所以人的身上。在Spring中,这些人就等于可以被增强的方法。
Pointcut(切点): 在上面的故事里,只有小吉和其他几名抬了这个东西的小伙伴收到了绿shi寒,其他没有抬的小伙伴则没有事。可以看出来Pointcut(切点)是有条件的Joint point(连接点),抬了的人被增强了。
Advice(通知/增强): 上面的小伙伴在抬了以后,那个东西就对他们进行了增强。
Aspect(切面): 切点和通知的结合,上面的切点就是抬了的小伙伴和小吉,通知是抬之后的作用,切点有很多,连载一起就像一个面一样。

三、AOP实例说明

一、创建maven工程
二、导入相关配置文件

<dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.5.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.2.5.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.2.5.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.6</version>
            <scope>runtime</scope>
        </dependency>

三、添加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 http://www.springframework.org/schema/aop/spring-aop.xsd">

注意、上面给了Aop的命名空间。

四、定义接口和实现类
这里的实现类里面定义的方法就是连接点,至于要增强哪个方法,由程序员根据自己的业务要求,自己指定,要增强的方法就是切点
【Spring】面向切面编程详解(AOP)

package com.bipt.ServiceAop;

/**
 * @author 不止于梦想
 * @date 2022/10/17 13:08
 */
public interface UserService {
    public void save();
    public void addUser();
}


package com.bipt.ServiceAop.impl;

import com.bipt.ServiceAop.UserService;

/**
 * @author 不止于梦想
 * @date 2022/10/17 13:09
 */
public class ImplUserService implements UserService {
    @Override
    public void save() {
        System.out.println("save......");
    }

    @Override
    public void addUser() {
        System.out.println("AddUser.......");
    }
}


在Spring配置文件中添加bean,把ImplUserService作为连接点
【Spring】面向切面编程详解(AOP)

四、写通知类和在配置文件中引入

package com.bipt.ServiceAop;

/**
 * 定义通知类
 * @author 不止于梦想
 * @date 2022/10/17 13:14
 */
public class MyAdvice {
    public void advice1(){
        System.out.println("吉尼抬没?");
    }
}

【Spring】面向切面编程详解(AOP)

五、定义切入点和制作切面

<aop:config>
        <!--配置切点,即在哪些方法用到增强,后面是表达式-->
        <aop:pointcut id="mypointCut" expression="execution( void com.bipt.ServiceAop.impl.ImplUserService.save())"/>
        <!--配置切面,切面就是通知加上增强-->
        <aop:aspect id="myadvice" ref="Advice">
            <!--before表示前置通知,在切点执行之前执行,method则是通知里的一种方法-->
            <aop:before method="advice1" pointcut-ref="mypointCut"></aop:before>
        </aop:aspect>
    </aop:config>

六、测试
测试要先导入测试的依赖,这里不用Spring的依赖,用junit。

 <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>

然后编写测试类

package com.bipt;

import com.bipt.ServiceAop.UserService;
import com.bipt.ServiceAop.impl.ImplUserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import javax.annotation.processing.SupportedAnnotationTypes;

/**
 * @author 不止于梦想
 * @date 2022/10/17 13:47
 */
public class MyTest {

    @Test
    public void AopTest(){
        ApplicationContext App = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) App.getBean("JointPoint");
        userService.save();
    }
}

测试结果如下:
【Spring】面向切面编程详解(AOP)
可以看到结果正确,我们完成了一个入门测试,至于为什么通知在切入点之前执行是因为我们在上面配置切面的时候使用的是before,即前置增强。

四、通知类型详解

概述

通知类型大致分为六类: 分别是前置通知、后置通知、环绕通知、最终通知、异常抛出通知和引介通知。
上述通知中的引介通知我们是用不到的,这里不做介绍。

前置通知

即before,在上面的例子中我们可以清楚的看到,前置通知在切入点方法执行之前执行了。这就是前置通知的作用范围。前置通知的方式一把也可以用作权限验证。

后置通知

即after—running 在目标方法执行之后执行

<aop:pointcut id="after" expression="execution(* com.bipt.ServiceAop.impl.ImplUserService.save())"/>
        <!--配置切面,切面就是通知加上增强-->
        <aop:aspect  ref="Advice">
            <!--before表示后置通知,在切点执行之前执行,method则是通知里的一种方法-->
<!--            <aop:around method="advice2" pointcut-ref="mypointCut2" ></aop:around>-->
            <!--后置通知-->
            <aop:after-returning method="after_running" pointcut-ref="after" />

【Spring】面向切面编程详解(AOP)

环绕通知

环绕通知是重点,是最重要的一个通知。环绕通知功能比较强大,它可以追加功能到方法执行的前后,这也是比较常用的方式,它可以实现其他四种通知类型的功能。
环绕通知的怎么做到在方法执行前后追加功能呢? 不难,做一个演示
【Spring】面向切面编程详解(AOP)

最终通知

即after。不管前面发生什么,最终通知都会执行。这里进行演示

<aop:aspect id="myadvice" ref="Advice">
            <!--before表示后置通知,在切点执行之前执行,method则是通知里的一种方法-->
            <aop:after method="advice1" pointcut-ref="mypointCut"></aop:after>
        </aop:aspect>

结果:
【Spring】面向切面编程详解(AOP)
可以看到两个方法执行循序与前置通知相反了。

六、AOP实现声明式事务

二、添加事务管理

事务分为声明式事务和编程式事务,编程式事务就是JDBC的事务提交、回滚等等,这样不太方便,我们有很多的方法都要用到事务,不能需要一个就编写一个,这样太重复了。我们可以把事务抽取出来,利用Spring提高的声明式事务管理来处理。
Spring声明式事务是基于AOP(面向切面)实现的
① 配置事务管理器
事务管理器有很多种,不同的数据访问层框架有不同的实现,而Mybatis的事务管理器为:DataSourceTransactionManager
【Spring】面向切面编程详解(AOP)
②配置事务增强/通知(Advice)
【Spring】面向切面编程详解(AOP)
③ 配置切面
【Spring】面向切面编程详解(AOP)

配置代码:

<?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:tx="http://www.springframework.org/schema/tx"
       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/tx  http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop expression="com.bipt.controller"/>
    </context:component-scan>
    <!--导入配置文件-->
    <context:property-placeholder location=" classpath:config.properties"></context:property-placeholder>
    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}">
    </bean>
    <!--配置事务管理器-->
    <bean id="transactionManager" class="
    org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--配置事务增强-->
    <tx:advice id="transactionAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="login*"  read-only="true"/>
            <tx:method name="register*" propagation="REQUIRED" rollback-for="Exception,RuntimeException" read-only="false"/>
        </tx:attributes>
    </tx:advice>
    <!--配置切面-->
    <aop:config>
        <aop:advisor advice-ref="transactionAdvice" pointcut-ref="transactionPointcut"></aop:advisor>
        <aop:aspect ref="dataSource">
            <aop:pointcut id="transactionPointcut" expression="execution(* com.bipt.service.*.*(..)) "/>
        </aop:aspect>
    </aop:config>
</beans>

结语

码字不易、觉得有帮助的小伙伴动动小手给个三连,无比感谢。