JavaEE——Spring AOP(面向切面编程)

1.面向切面编程(AOP)

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

通常情况下,我们会根据业务使用 OOP(面向对象)思想,将应用划分为多个不同的业务模块,每个模块的核心功能都只为特定的业务领域提供服务,例如电商系统中的订单模块、商品模块、库存模块就分别是为维护电商系统的订单信息、商品信息以及库存信息而服务的。

但除此之外,应用中往往还存在一些非业务的通用功能,例如日志管理、权限管理、事务管理、异常管理等。这些通用功能虽然与应用的业务无关,但几乎所有的业务模块都会使用到它们,因此这些通用功能代码就只能横向散布式地嵌入到多个不同的业务模块之中。这无疑会产生大量重复性代码,不利于各个模块的复用。

与 OOP 中纵向的父子继承关系不同,AOP 是通过横向的抽取机制实现的。它将应用中的一些非业务的通用功能抽取出来单独维护,并通过声明的方式(例如配置文件、注解等)定义这些功能要以何种方式作用在那个应用中,而不是在业务模块的代码中直接调用。

AOP 编程和 OOP 编程的目标是一致的,都是为了减少程序中的重复性代码,让开发人员有更多的精力专注于业务逻辑的开发,只不过两者的实现方式大不相同。

AOP 不是用来替换 OOP 的,而是 OOP 的一种延伸,用来解决 OOP 编程中遇到的问题。
在这里插入图片描述

2.AOP术语

在这里插入图片描述
Advice 直译为通知,也有人将其翻译为“增强处理”,共有 5 种类型,如下表所示。
在这里插入图片描述

3.AOP类型

AOP 可以被分为以下 2 个不同的类型。
动态 AOP
动态 AOP 的织入过程是在运行时动态执行的。其中最具代表性的动态 AOP 实现就是 Spring AOP,它会为所有被通知的对象创建代理对象,并通过代理对象对被原对象进行增强。

相较于静态 AOP 而言,动态 AOP 的性能通常较差,但随着技术的不断发展,它的性能也在不断的稳步提升。

动态 AOP 的优点是它可以轻松地对应用程序的所有切面进行修改,而无须对主程序代码进行重新编译。

静态 AOP
静态 AOP 是通过修改应用程序的实际 Java 字节码,根据需要修改和扩展程序代码来实现织入过程的。最具代表性的静态 AOP 实现是 AspectJ。

相较于动态 AOP 来说,性能较好。但它也有一个明显的缺点,那就是对切面的任何修改都需要重新编译整个应用程序。

4.AOP 的优势

AOP 是 Spring 的核心之一,在 Spring 中经常会使用 AOP 来简化编程。

在 Spring 框架中使用 AOP 主要有以下优势。

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

5.Spring AOP 的代理机制

Spring 在运行期会为目标对象生成一个动态代理对象,并在代理对象中实现对目标对象的增强。

Spring AOP 的底层是通过以下 2 种动态代理机制,为目标对象(Target Bean)执行横向织入的。

在这里插入图片描述

注意:由于被标记为 final 的方法是无法进行覆盖的,因此这类方法不管是通过 JDK 动态代理机制还是 CGLIB 动态代理机制都是无法完成代理的。

6.Spring AOP 连接点

Spring AOP 并没有像其他 AOP 框架(例如 AspectJ)一样提供了完成的 AOP 功能,它是 Spring 提供的一种简化版的 AOP 组件。其中最明显的简化就是,Spring AOP 只支持一种连接点类型:方法调用。您可能会认为这是一个严重的限制,但实际上 Spring AOP 这样设计的原因是为了让 Spring 更易于访问。

方法调用连接点是迄今为止最有用的连接点,通过它可以实现日常编程中绝大多数与 AOP 相关的有用的功能。如果需要使用其他类型的连接点(例如成员变量连接点),我们可以将 Spring AOP 与其他的 AOP 实现一起使用,最常见的组合就是 Spring AOP + ApectJ。

7.Spring AOP 通知类型

Spring AOP 按照通知(Advice)织入到目标类方法的连接点位置,为 Advice 接口提供了 6 个子接口,如下表。
在这里插入图片描述
Spring AOP 切面类型
Spring 使用 org.springframework.aop.Advisor 接口表示切面的概念,实现对通知(Adivce)和连接点(Joinpoint)的管理。

在 Spring AOP 中,切面可以分为三类:一般切面、切点切面和引介切面。

在这里插入图片描述
一般切面的 AOP 开发
当我们在使用 Spring AOP 开发时,若没有对切面进行具体定义,Spring AOP 会通过 Advisor 为我们定义一个一般切面(不带切点的切面),然后对目标对象(Target)中的所有方法连接点进行拦截,并织入增强代码。

/**
* 增强代码
* MethodBeforeAdvice 前置增强
*/
public class UserDaoBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("正在执行前置增强操作…………");
    }
}
    <?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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
                http://www.springframework.org/schema/context/spring-context.xsd">
        <!--******Advisor : 代表一般切面,Advice 本身就是一个切面,对目标类所有方法进行拦截(* 不带有切点的切面.针对所有方法进行拦截)*******-->
        <!-- 定义目标(target)对象 -->
        <bean id="userDao" class="net.biancheng.c.dao.impl.UserDaoImpl"></bean>
        <!-- 定义增强 -->
        <bean id="beforeAdvice" class="net.biancheng.c.advice.UserDaoBeforeAdvice"></bean>
        <!--通过配置生成代理 UserDao 的代理对象 -->
        <bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
            <!-- 设置目标对象 -->
            <property name="target" ref="userDao"/>
            <!-- 设置实现的接口 ,value 中写接口的全路径 -->
            <property name="proxyInterfaces" value="net.biancheng.c.dao.UserDao"/>
            <!-- 需要使用value:增强 Bean 的名称 -->
            <property name="interceptorNames" value="beforeAdvice"/>
        </bean>
    </beans>

Spring 能够基于 org.springframework.aop.framework.ProxyFactoryBean 类,根据目标对象的类型(是否实现了接口)自动选择使用 JDK 动态代理或 CGLIB 动态代理机制,为目标对象(Target Bean)生成对应的代理对象(Proxy Bean)。

ProxyFactoryBean 的常用属性如下表所示。
在这里插入图片描述

8.基于 PointcutAdvisor 的 AOP 开发

PointCutAdvisor 是 Adivsor 接口的子接口,用来表示带切点的切面。使用它,我们可以通过包名、类名、方法名等信息更加灵活的定义切面中的切入点,提供更具有适用性的切面。

Spring 提供了多个 PointCutAdvisor 的实现,其中常用实现类如如下。

  • NameMatchMethodPointcutAdvisor:指定 Advice 所要应用到的目标方法名称,例如 hello* 代表所有以 hello 开头的所有方法。
  • RegExpMethodPointcutAdvisor:使用正则表达式来定义切点(PointCut),RegExpMethodPointcutAdvisor 包含一个 pattern 属性,该属性使用正则表达式描述需要拦截的方法。

创建一个名为 OrderDaoAroundAdvice 的环绕增强类

/**
* 增强代码
* 环绕增强
*/
public class OrderDaoAroundAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("环绕增强前********");
        //执行被代理对象中的逻辑
        Object result = methodInvocation.proceed();
        System.out.println("环绕增强后********");
        return result;
    }
}
    <!--带切点的切面-->
    <!-- 定义目标(target)对象 -->
    <bean id="orderDao" class="com.xawl.dao.OrderDao"></bean>
    <!-- 定义增强 -->
    <bean id="aroundAdvice" class="com.xawl.advice.OrderDaoAroundAdvice"></bean>
    <!--定义切面-->
    <bean id="myPointCutAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <!--定义表达式,规定哪些方法进行拦截 .* 表示所有方法-->
        <!--<property name="pattern" value=".*"></property>-->
        <property name="patterns" value="com.xawl.dao.OrderDao.add.*,com.xawl.dao.OrderDao.delete.*"></property>
        <property name="advice" ref="aroundAdvice"></property>
    </bean>
    <!--Spring 通过配置生成代理-->
    <bean id="orderDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 配置目标 -->
        <property name="target" ref="orderDao"></property>
        <!-- 针对类的代理,该属性默认取值为 false(可省略), 表示使用 JDK 动态代理;取值为 true,表示使用 CGlib 动态代理-->
        <property name="proxyTargetClass" value="true"></property>
        <!-- 在目标上应用增强 -->
        <property name="interceptorNames" value="myPointCutAdvisor"></property>
    </bean>

9.自动代理

所有目标对象(Target Bean)的代理对象(Proxy Bean)都是在 XML 配置中通过 ProxyFactoryBean 创建的。但在实际开发中,一个项目中往往包含非常多的 Bean, 如果每个 Bean 都通过 ProxyFactoryBean 创建,那么开发和维护成本会十分巨大。为了解决这个问题,Spring 为我们提供了自动代理机制。

Spring 提供的自动代理方案,都是基于后处理 Bean 实现的,即在 Bean 创建的过程中完成增强,并将目标对象替换为自动生成的代理对象。通过 Spring 的自动代理,我们在程序中直接拿到的 Bean 就已经是 Spring 自动生成的代理对象了。

Spring 为我们提供了 3 种自动代理方案:

  • BeanNameAutoProxyCreator:根据 Bean 名称创建代理对象。
  • DefaultAdvisorAutoProxyCreator:根据 Advisor 本身包含信息创建代理对象。
  • AnnotationAwareAspectJAutoProxyCreator:基于 Bean 中的 AspectJ 注解进行自动代理对象。

根据 Bean 名称创建代理对象

    <?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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
                http://www.springframework.org/schema/context/spring-context.xsd">
        <!-- 定义目标(target)对象 -->
        <bean id="userDao" class="com.xawl.dao.impl.UserDaoImpl"></bean>
        <bean id="orderDao" class="com.xawldao.OrderDao"></bean>
        <!-- 定义增强 -->
        <bean id="beforeAdvice" class="com.xawl.advice.UserDaoBeforeAdvice"></bean>
        <bean id="aroundAdvice" class="com.xawl.advice.OrderDaoAroundAdvice"></bean>
        <!--Spring 自动代理:根据 Bean 名称创建代理独享-->
        <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
            <property name="beanNames" value="*Dao"></property>
            <property name="interceptorNames" value="beforeAdvice,aroundAdvice"></property>
        </bean>
    </beans>

根据切面中信息创建代理对象

    <?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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
                http://www.springframework.org/schema/context/spring-context.xsd">
        <!-- 定义目标(target)对象 -->
        <bean id="userDao" class="com.xawl.dao.impl.UserDaoImpl"></bean>
        <bean id="orderDao" class="com.xawl.dao.OrderDao"></bean>
        <!-- 定义增强 -->
        <bean id="beforeAdvice" class="com.xawl.advice.UserDaoBeforeAdvice"></bean>
        <bean id="aroundAdvice" class="com.xawl.advice.OrderDaoAroundAdvice"></bean>
        <!--定义切面-->
        <bean id="myPointCutAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
            <!--定义表达式,规定哪些方法进行拦截 .* 表示所有方法-->
            <!--<property name="pattern" value=".*"></property>-->
            <property name="patterns"
                      value="com.xawl.dao.OrderDao.add.*,net.biancheng.c.dao.OrderDao.delete.*"></property>
            <property name="advice" ref="aroundAdvice"></property>
        </bean>
        <!--Spring 自动代理:根据切面 myPointCutAdvisor 中信息创建代理对象-->
        <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>
    </beans>

10.Spring使用AspectJ进行AOP开发(基于注解)

在 Spring 中,虽然我们可以使用 XML 配置文件可以实现 AOP 开发,但如果所有的配置都集中在 XML 配置文件中,就势必会造成 XML 配置文件过于臃肿,从而给维护和升级带来一定困难。

为此,AspectJ 框架为 AOP 开发提供了一套 @AspectJ 注解。它允许我们直接在 Java 类中通过注解的方式对切面(Aspect)、切入点(Pointcut)和增强(Advice)进行定义,Spring 框架可以根据这些注解生成 AOP 代理。

在这里插入图片描述
在使用 @AspectJ 注解进行 AOP 开发前,首先我们要先启用 @AspectJ 注解支持。
1)使用 Java 配置类启用
我们可以在 Java 配置类(标注了 @Configuration 注解的类)中,使用 @EnableAspectJAutoProxy 和 @ComponentScan 注解启用 @AspectJ 注解支持。

    @Configuration
    @ComponentScan(basePackages = "net.biancheng.c") //注解扫描
    @EnableAspectJAutoProxy //开启 AspectJ 的自动代理
    public class AppConfig {
    }

2)基于 XML 配置启用

    <!-- 开启注解扫描 -->
    <context:component-scan base-package="net.biancheng.c"></context:component-scan>
    <!--开启AspectJ 自动代理-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

10.1定义切面 @Aspect

在启用了 @AspectJ 注解支持的情况下,Spring 会自动将 IoC 容器(ApplicationContext)中的所有使用了 @Aspect 注解的 Bean 识别为一个切面。<bean id = "myAspect" class = "net.biancheng.c.MyAspect"> ... </bean>
在定义完 Bean 后,我们只需要在Bean 对应的 Java 类中使用一个 @Aspect 注解,将这个 Bean 定义为一个切面,代码如下。

import org.aspectj.lang.annotation.*;
@Aspect //定义为切面
public class MyAspect {
}

全注解方式定义切面

import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component // 定义成 Bean
@Aspect //定义为切面
public class MyAspect {
}

在以上代码中共使用两个注解:

  • @Component 注解:将这个类的对象定义为一个 Bean;
  • @Aspect 注解:则是将这个 Bean 定义为一个切面。

10.2.定义切点 @Pointcut

在 AspectJ 中,我们可以使用 @Pointcut 注解用来定义一个切点。需要注意的是,定义为切点的方法,它的返回值类型必须为 void,代码如下

    // 要求:方法必须是private,返回值类型为 void,名称自定义,没有参数
    @Pointcut("execution(*net.biancheng..*.*(..))")
    private void myPointCut() {
    }
/**
* 将 com.xawl.dao包下 UserDao 类中的 get() 方法定义为一个切点
*/
@Pointcut(value ="execution(* com.xawl.dao.UserDao.get(..))")
public void pointCut1(){
}
/**
* 将 net.biancheng.c.dao包下 UserDao 类中的 delete() 方法定义为一个切点
*/
@Pointcut(value ="execution(* com.xawl.dao.UserDao.delete(..))")
public void pointCut2(){
}

10.3定义通知

在这里插入图片描述
以上这些通知注解中都有一个 value 属性,这个 value 属性的取值就是这些通知(Advice)作用的切点(PointCut),它既可以是切入点表达式,也可以是切入点的引用(切入点对应的方法名称),示例代码如下。

@Pointcut(value ="execution(* com.xawl.dao.UserDao.get(..))")
public void pointCut1(){
}
@Pointcut(value ="execution(* com.xawl.dao.UserDao.delete(..))")
public void pointCut2(){
}
  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Geek Li

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

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

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

打赏作者

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

抵扣说明:

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

余额充值