Spring AOP详解

什么是AOP?

AOP 的全称是“Aspect Oriented Programming”,即面向切面编程,和 OOP(面向对象编程)类似,也是一种编程思想


AOP的作用

AOP 采取横向抽取机制(动态代理),取代了传统纵向继承机制的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。

主要作用是分离功能性需求和非功能性需求,使开发人员可以集中处理某一个关注点或者横切逻辑,减少对业务代码的侵入增强代码的可读性和可维护性

简单的说,AOP 的作用就是保证开发者在不修改源代码的前提下,为系统中的业务组件添加某种通用功能。AOP 就是代理模式的典型应用


AOP框架

目前最流行的 AOP 框架有两个

  • Spring AOP
    • Spring AOP 是基于 AOP 编程模式的一个框架,它能够有效的减少系统间的重复代码,达到松耦合的目的。
    • Spring AOP 使用纯 Java 实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。
    • 有两种实现方式:基于接口的 JDK 动态代理和基于继承的 CGLIB 动态代理。
  • AspectJ
    • AspectJ 是一个基于 Java 语言的 AOP 框架,从 Spring 2.0 开始,Spring AOP 引入了对 AspectJ 的支持。AspectJ 扩展了 Java 语言,提供了一个专门的编译器,在编译时提供横向代码的植入。

AOP术语

为了更好地理解 AOP,我们需要了解一些它的相关术语。这些专业术语并不是 Spring 特有的,有些也同样适用于其它 AOP 框架,如 AspectJ。

名称说明
Joinpoint(连接点)指那些被拦截到的点,在 Spring 中,指可以被动态代理拦截目标类的方法。
Pointcut(切入点)指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。
Advice(通知)指拦截到 Joinpoint 之后要做的事情,即对切入点增强的内容。
Target(目标)指代理的目标对象。
Weaving(植入)指把增强代码应用到目标上,生成代理对象的过程。
Proxy(代理)指生成的代理对象。
Aspect(切面)切入点和通知的结合。

Advice 直译为通知,也有的资料翻译为“增强处理”,共有 5 种类型

通知说明
before(前置通知)通知方法在目标方法调用之前执行
after(后置通知)通知方法在目标方法返回或异常后调用
after-returning(返回后通知)通知方法会在目标方法返回后调用
after-throwing(抛出异常通知)通知方法会在目标方法抛出异常后调用
around(环绕通知)通知方法会将目标方法封装起来

AOP的优点

AOP 是 Spring 的核心之一,在 Spring 中经常会使用 AOP 来简化编程。在 Spring 框架中使用 AOP 主要有以下优势。

  • 提供声明式企业服务,特别是作为 EJB 声明式服务的替代品。最重要的是,这种服务是声明式事务管理。
  • 允许用户实现自定义切面。在某些不适合用 OOP 编程的场景中,采用 AOP 来补充。
  • 可以对业务逻辑的各个部分进行隔离,从而使业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时也提高了开发效率。

SpringAOP AspectJ开发AOP

基于XML开发

基于 XML 的声明式是指通过 Spring 配置文件的方式来定义切面、切入点及通知,而所有的切面和通知都必须定义在 <aop:config> 元素中。

使用XML开发需要注意以下两点:

  • 需要导入一个依赖包
<dependencies>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>x.x.x</version>
    </dependency>
</dependencies>
  • 使用 <aop:config> 元素时,我们需要先导入 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:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
</beans>

基于XML开发有两种方式:自定义类开发使用Spring的API接口开发


1、自定义类开发AOP
  • 定义切面<aop:aspect>
    在 Spring 配置文件中,使用 <aop:aspect> 元素定义切面,该元素可以将定义好的 Bean 转换为切面 Bean,所以使用 <aop:aspect> 之前需要先定义一个普通的 Spring Bean
<!--    自定义类实现-->
    <bean id="diyPoint" class="xxx.xxx"/>
    <aop:config>
<!--        自定义切面-->
        <aop:aspect ref="diyPoint">
            ...
        </aop:aspect>
    </aop:config>
  • 定义切入点<aop:pointcut>
    <aop:pointcut> 用来定义一个切入点,当 <aop:pointcut>元素作为 <aop:config> 元素的子元素定义时,表示该切入点是全局切入点,它可被多个切面所共享;当 <aop:pointcut> 元素作为 <aop:aspect> 元素的子元素时,表示该切入点只对当前切面有效。
<aop:config>
        <aop:pointcut id="point" expression="execution(* service.UserServiceImpl.*(..))"/>   
</aop:config>

其中,id 用于指定切入点的唯一标识名称,execution 用于指定切入点关联的切入点表达式。

execution 格式解析:

符号含义
execution()表达式的主体;
第一个*符号表示返回值的类型任意;
serviceAOP所切的服务的包名,即我们的业务部分
UserServiceImpl表示UserServiceImpl类
.*(…)表示任何方法名,括号表示参数,两个点表示任何参数类型
  • 定义通知

    AspectJ 支持 5 种类型的 advice

<aop:aspect ref="diyPoint">
  <aop:pointcut id="point" expression="execution(* service.UserServiceImpl.*(..))"/>
    <!-- 前置通知 -->
    <aop:before pointcut-ref="point" method="..."/>
    <!-- 后置通知 -->
    <aop:after-returning pointcut-ref="point" method="..."/>
    <!-- 环绕通知 -->
    <aop:around pointcut-ref="point" method="..."/>
    <!-- 异常通知 -->
    <aop:after-throwing pointcut-ref="point" method="..."/>
    <!-- 最终通知 -->
    <aop:after pointcut-ref="point" method="..."/>
</aop:aspect>

下面使用IDEA演示自定义类开发AOP

如图所示创建如下包、类、接口、配置文件
在这里插入图片描述
UserService接口代码如下:

public interface UserService {
    public void add();
}

UserServiceImpl类代码如下:

public class UserServiceImpl implements UserService{
    @Override
    public void add() {
        System.out.println("增加了一个用户");
    }

}

自定义类diy

public class diyPointCut {
    public void before(){
        System.out.println("==========执行前===========");
    }
    public void after(){
        System.out.println("==========执行后===========");
    }
}

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

    <bean id="userService" class="service.UserServiceImpl"/>
<!--    自定义类实现-->
    <bean id="diyPoint" class="diy.diyPointCut"/>
    <aop:config>
<!--        自定义切面,ref要引用的类-->
        <aop:aspect ref="diyPoint">
            <aop:pointcut id="point" expression="execution(* service.UserServiceImpl.*(..))"/>
            <aop:before method="before" pointcut-ref="point"/>
            <aop:after method="after" pointcut-ref="point"/>
        </aop:aspect>
    </aop:config>
</beans>

测试类代码如下:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.UserService;
public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) context.getBean("userService");
        userService.add();
    }
}

测试结果为:
在这里插入图片描述


2、使用Spring的API接口开发AOP

各接口介绍如下:

  • Advice:一个用于标识的接口,也是所有通知的最终父接口
  • BeforeAdvice:前置通知接口,指的是某些行为触发前的通知
  • MethodBeforeAdvice:方法前置通知接口,指的是方法被执行前会触发的通知
  • AfterAdvice:后置通知接口,指的是某些行为触发后的通知
  • AfterReturningAdvice:返回后置通知接口,指的是方法执行后触发的通知
  • ThrowsAdvice:异常通知接口,指的是方法出现异常时触发的通知。方法出现异常也代表方法已经返回了结果,需要注意,此接口也是一个标识接口。

这里只演示了MethodBeforeAdvice和AfterReturningAdvice接口,其他接口读者可查阅资料学习。

在这里插入图片描述
同样的,创建如上包、类、接口、配置文件

service包下的内容、测试类均与上面代码一致,这里不再赘述

Log代码如下:

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;
//在调用bean的方法前执行
public class Log implements MethodBeforeAdvice {
    //method 要执行目标对象的方法
    //object 参数
    // o 目标对象
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println(o.getClass().getName()+"的"+method.getName()+"被执行");
    }
}

AfterLog代码如下:

import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;
//接口AfterReturningAdvice为实现方法执行后通知
public class AfterLog implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("执行了"+method.getName()+"方法");
    }
}

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

    <bean id="userService" class="service.UserServiceImpl"/>
    <bean id="log" class="log.Log"/>
    <bean id="afterLog" class="log.AfterLog"/>
<!--        使用原生的API接口-->
    <aop:config>
    <!--        切入点-->
        <aop:pointcut id="pointcut" expression="execution(* service.UserServiceImpl.*(..))"/>
<!--        执行环绕-->
        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>
</beans>

advisor标签

  • 用于把一个Advice和一个Pointcut组合起来。一个advisor标签对应的就是一个Advisor接口的实现类,默认是DefaultBeanFactoryPointcutAdvisor实现。

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


基于注解开发

在 Spring 中,尽管使用 XML 配置文件可以实现 AOP 开发,但是如果所有的相关配置都集中在配置文件中,势必会导致 XML 配置文件过于臃肿,从而给维护和升级带来一定的困难。

AspectJ 允许使用注解定义切面、切入点和增强处理,Spring 框架可以根据这些注解生成 AOP 代理。

名称说明
@Aspect用于定义一个切面。
@Pointcut用于定义一个切入点。
@Before用于定义前置通知,相当于 BeforeAdvice。
@AfterReturning用于定义后置通知,相当于 AfterReturningAdvice。
@Around用于定义环绕通知,相当于MethodInterceptor。
@AfterThrowing用于定义抛出通知,相当于ThrowAdvice。
@After用于定义最终final通知,不管是否异常,该通知都会执行。
@DeclareParents用于定义引介通知,相当于IntroductionInterceptor(不要求掌握)。
  • 定义切面@Aspect的方法
    AspectJ 类和其它普通的 Bean 一样,可以有方法和字段,不同的是 AspectJ 类需要使用 @Aspect 注解
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class AspectPointCut {
}
  • 定义切入点@Pointcut
    @Pointcut 注解用来定义一个切入点
// 要求:方法必须是private,返回值类型为void,名称自定义,没有参数,id为PointCut()
@Pointcut("execution(*net.biancheng..*.*(..))")
private void PointCut() {
}
  • 定义通知advice
    @AspectJ 支持 5 种类型的 advice
@Before("PointCut()")
public void beforeAdvice(){
    ...
}

下面使用IDEA展示基于注解开发AOP

  1. 创建一个干净的Maven项目,并导入相关依赖的jar包
  2. 如图创建相应的包,类
    在这里插入图片描述
  3. 同样的service包下的类、测试类MyTest与上面代码均一致,这里不再赘述。

AnnocationPointCut类代码如下:

import org.aspectj.lang.annotation.*;


//使用注解方式实现AOP
@Aspect    //标注这个类是一个切面
public class AnnocationPointCut {
    // 要求:方法必须是private,返回值类型为void,名称自定义,没有参数
    @Pointcut("execution(* service.UserServiceImpl.*(..))")
    private void selectAll(){
    }
    @Before("selectAll()")
    public void before(){
        System.out.println("==========执行前===========");
    }
    @After("selectAll()")
    public void after(){
        System.out.println("==========执行后===========");
    }
}

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

    <bean id="userService" class="service.UserServiceImpl"/>
    <bean id="userPointCut" class="AnnocationPointCut"/>
    <!--    此时不需要在配置文件中编写切面切入点等代码,只需开启注解支持-->
    <aop:aspectj-autoproxy/>
</beans>

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值