JavaWeb开发(三)3.7——Spring AOP(面向切面编程) 详解

一、Spring AOP(面向切面编程)概念

1、概述

AOP(Aspect Oriented Programming面向切面编程):说简单点就是我们可以在不修改源代码的情况下,对程序的方法进行增强。说复杂点就是将涉及多业务流程的通用功能抽取并单独封装,形成独立的切面,在合适的时机将这些切面横向切入到业务流程指定的位置中。即系统级的服务从代码中解耦出来。例如:将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来。允许你把遍布应用各处的功能分离出来形成可重用组件。提高程序的可重用性,同时提高了开发的效率。

在面向切面编程的思想里面,把功能分为核心业务功能,和周边功能。
所谓核心业务,工作中做的最多的就是增删改查,增删改查都叫核心业务;
所谓周边功能,比如性能统计,日志记录,事务管理等等。
周边功能在 Spring 的面向切面编程AOP思想里,即被定义为切面。在面向切面编程AOP的思想里面,核心业务功能和切面功能分别独立进行开发,然后把切面功能和核心业务功能 “编织” 在一起,这就叫AOP。

AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

2、AOP的常见术语

  • 切面 (Aspect):类是对物体特征的抽象,切面就是对横切关注点的抽象。可以理解成,就是一个特殊的类(包含的都是增强核心业务的代码),切面 = 切入点 + 通知,通俗点就是:在什么时机,什么地方,做什么增强!
  • 连接点(join point):指那些被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器。整个系统的所有方法都可以称为连接点。
  • 切入点(Pointcut):对连接点进行拦截的定义。就是被选中的连接点,可以通过execution来确定选中的连接点有哪些。
  • 通知(Advice):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类。在方法执行的什么实际(when:方法前/方法后/方法前后)做什么(what:增强的功能),就是切面这个类中的代码块。
  • 目标对象:代理的目标对象。
  • 引入(Introduction):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段。
  • 织入(Weaving) 把切面加入到对象,并创建出代理对象的过程。(由 Spring 来完成)。

3、Spring Aop中的通知类型

  • 前置通知(Before Advice)::在目标方法被调用前调用通知功能;相关的类 org.springframework.aop.MethodBeforeAdvice。
  • 后置通知(After Advice):在目标方法被调用之后调用通知功能;相关的类 org.springframework.aop.AfterReturningAdvice。
  • 返回通知(After-returning):在目标方法成功执行之后调用通知功能。
  • 异常通知(After-throwing):在目标方法抛出异常之后调用通知功能;相关的类org.springframework.aop.ThrowsAdvice。
  • 环绕通知(Around):把整个目标方法包裹起来,在被调用前和调用之后分别调用通知功能相关的类org.aopalliance.intercept.MethodInterceptor

4、Spring AOP的实现原理

Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:

(1)JDK动态代理:Spring AOP的首选方法。 每当目标对象实现一个接口时,就会使用JDK动态代理。目标对象必须实现接口。
(2)CGLIB代理:如果目标对象没有实现接口,则可以使用CGLIB代理。

AOP编程其实是很简单的事情,纵观AOP编程,程序员只需要参与三个部分:

  • 定义普通业务组件;
  • 定义切入点,一个切入点可能横切多个业务组件;
  • 定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作。

所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法。

spring aop实现是通过动态代理的方式实现的,动态代理避免了静态代理需要定义冗余的代理类,实现类,动态代理分为两种,第一种就是 jdk 动态代理,第二种就是cglib 动态代理,aop 实现同时采用两种代理模式。

两种动态代理的区别:

  • jdk动态代理模式 :采用反射的方式,只能对实现接口的类生成代理,具有加载速度快,执行效率低的特点。
  • cglib动态代理模式:采用的asm,通过字节码形式实现,是针对类实现代理,具有加载速度慢,执行效率高的特点。

二、基于Aspectj实现切面

2.1、什么是AspectJ

AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件,AspectJ不是Spring组成部分,独立AOP框架,一般把AspectJ和Spring框架一起使用,进行AOP操作。

2.2、基于AspectJ实现AOP

2.2.1、添加maven依赖

 <!-- aspectj支持 -->
 <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
      <version>1.8.9</version>
  </dependency>
  <dependency>
      <groupId>org.apache.geronimo.bundles</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.6.8_2</version>
  </dependency>
   <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.2.1.RELEASE</version>
  </dependency>

2.2.2、切入点表达式

execution ,匹配方法的执行(常用),让spring知道对哪个类中的哪个方法进行增强。

  • execution(表达式)
execution([权限修饰符] [返回值类型] 包名.类名.方法名(参数列表))
  • 全匹配方式
public void com.arbor.service.impl.AccountServiceImpl.saveAccount(com.arbor.domain.Account)
  • 访问修饰符可以省略
void com.arbor.service.impl.AccountServiceImpl.saveAccount(com.arbor.domain.Account)
  • 返回值可以使用*号,表示任意返回值
 com.arbor.service.impl.AccountServiceImpl.saveAccount(com.arbor.domain.Account)
  • 包名可以使用号,表示任意包,有几级包,需要写几个
 *.*.*.*.AccountServiceImpl.saveAccount(com.arbor.domain.Account)
  • 使用…来表示当前包,及其子包
 com..AccountServiceImpl.saveAccount(com.arbor.domain.Account)
  • 类名可以使用*号,表示任意类
 com..*.saveAccount(com.arbor.domain.Account)
  • 方法名可以使用*号,表示任意方法
 com..*.*( com.arbor.domain.Account)
  • 参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数
 com..*.*(*)
  • 参数列表可以使用…表示有无参数均可,有参数可以是任意类型
 com..*.*(..)
  • 全通配方式
 *..*.*(..)
  • 通常情况下,是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类
execution(* com.arbor.service.impl.*.*(..))

2.2.3、常用注解

  • @Aspect:把当前类声明为切面类;
  • @Before:把当前方法看成是前置通知;
  • @AfterReturning:把当前方法看成是后置通知;
  • @AfterThrowing:把当前方法看成是异常通知;
  • @After:把当前方法看成是最终通知。

上面四个注解的参数:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。

2.2.4、示例

  • 创建一个类和方法,对该类中的方法做增强
import org.springframework.stereotype.Component;

/**
 * @author chenqun
 * @date 2023/3/14 22:01
 */
@Component
public class QiZeKJService {
    public String addQiZeKJ() {
        System.out.println("addQiZeKJ...");
        return "ok";
    }
}
  • 创建增强类,在增强类中创建方法,不同的方法代表不同的通知类型,并配置不同类型的通知
/**
 * @author chenqun
 * @date 2023/3/14 22:02
 */

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

@Component
@Aspect// aop 代理
public class UserProxy {
    /**
     * 前置通知
     */
    @Before("execution(* com.qizekj.proxy.QiZeKJService.addQiZeKJ(..));")
    public void before() {
        System.out.println("前置通知...");
    }

    /**
     * 后置通知
     */
    @After("execution(* com.qizekj.proxy.QiZeKJService.addQiZeKJ(..));")
    public void after() {
        System.out.println("后置通知...");
    }

    /**
     * 环绕通知
     */
    @Around(value = "execution(* com.qizekj.proxy.QiZeKJService.addQiZeKJ(..));")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕通知...");
        System.out.println("目标方法之前开始执行...");
        Object result = proceedingJoinPoint.proceed();
        System.out.println("目标方法之后开始执行...");
        return result;
    }

    //@AfterReturning表达后置通知/返回通知,表达方法返回结果之后执行
    @AfterReturning(value = "execution(* com.qizekj.proxy.QiZeKJService.addQiZeKJ(..));")
    public void afterReturning() {
        System.out.println("afterReturning");
    }

    //@AfterThrowing表达异常通知
    @AfterThrowing(value = "execution(* com.qizekj.proxy.QiZeKJService.addQiZeKJ(..));")
    public void afterThrowing() {
        System.out.println("afterThrowing");
    }

}
  • 创建一个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: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/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
      ">

    <!--开启注解扫描,告知 spring,在创建容器时要扫描的包 -->
    <context:component-scan base-package="com.qizekj.proxy"></context:component-scan>
    <!--开启 spring 对注解 AOP 的支持-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
  • 创建测试类
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author chenqun
 * @date 2023/3/14 22:05
 */
public class ProxyTest {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext app =
                new ClassPathXmlApplicationContext("proxy.xml");
        QiZeKJService qiZeKJService = app.getBean("qiZeKJService", QiZeKJService.class);
        qiZeKJService.addQiZeKJ();
    }
}
  • 运行结果:

在这里插入图片描述

2.2.5、不使用XML文件开启AOP注解

定义一个配置类,使用@EnableAspectJAutoProxy注解:

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

/**
 * @author chenqun
 * @date 2023/3/14 22:54
 */
@Configuration
@ComponentScan(basePackages="com.qizekj.proxy")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}

定义一个配置类MyConfigofAOP,将切面类和业务逻辑类(目标方法所在类)都加入到容器中

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * @author chenqun
 * @date 2023/3/14 23:03
 */
@EnableAspectJAutoProxy
@Configuration
public class MyConfigofAOP {
    //将切面类和业务逻辑类(目标方法所在类)都加入到容器中;
    @Bean
    public QiZeKJService qiZeKJService(){
        return  new QiZeKJService();
    }
    //切面类
    @Bean
    public UserProxy userProxy(){
        return  new UserProxy();
    }
}

编写测试类:

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author chenqun
 * @date 2023/3/14 22:05
 */
public class ProxyTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfigofAOP.class);
        QiZeKJService qiZeKJService = applicationContext.getBean(QiZeKJService.class);
        qiZeKJService.addQiZeKJ();
        applicationContext.close();
    }
}

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

2.3、基于XML配置文件方式

2.3.1、常用配置

  • aop:config:声明开始aop的配置
  • aop:aspect:用于配置切面

参数:
id:给切面提供一个唯一标识
ref:引用配置好的通知类bean的id

  • aop:pointcut:配置切入点表达式(指定对哪些类的哪些方法进行增强)

参数:
expression:定义切入点表达式
id:给切入点表达式提供一个唯一标识

  • aop:before:配置前置通知(指定增强的方法在切入点方法之前执行)
  • aop:after-returning:用于配置后置通知
    aop:after-throwing:用于配置异常通知
    aop:after:用于配置最终通知

上面四个配置的参数:
method:指定通知类中的增强方法名称
poinitcut:指定切入点表达式
ponitcut-ref:指定切入点的表达式的引用

  • 配置文件的约束:
<?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">
</beans>

2.3.2、使用示例

  • 创建一个类和方法,对该类中的方法做增强
public class Book {
    public void buy() {
        System.out.println("buy....");
    }
}
  • 创建增强类,用于增强对应的方法
public class BookProxy {

    public void before() {
        System.out.println("before....");
    }

    public void afterReturning() {
        System.out.println("afterReturning....");
    }

    public void afterThrowing() {
        System.out.println("afterThrowing....");
    }

    public void after() {
        System.out.println("after....");
    }
}
  • 创建Spring的配置文件,proxy2.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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">

    <!-- 创建Book对象 -->
    <bean id="book" class="com.qizekj.proxy.Book"/>
    <bean id="bookProxy" class="com.qizekj.proxy.BookProxy"/>

    <!-- 配置AOP的增强 -->
    <aop:config>

        <!-- 配置切入点 -->
        <aop:pointcut expression="execution(* com.qizekj.proxy.Book.buy(..))" id="pt1"/>

        <!-- 配置切面 -->
        <aop:aspect ref="bookProxy">
            <!--配置通知的类型要写在此处-->
            <aop:before method="before" pointcut-ref="pt1"/>
            <aop:after method="after" pointcut-ref="pt1"/>
            <aop:after-returning method="afterReturning" pointcut-ref="pt1"/>
            <aop:after-throwing method="afterThrowing" pointcut-ref="pt1"/>
        </aop:aspect>

    </aop:config>
</beans>
  • 创建测试方法
@Test
public void testProxyXml() {
    ApplicationContext context = new ClassPathXmlApplicationContext("proxy2.xml");
    Book book = context.getBean("book", Book.class);
    book.buy();
}
  • 运行结果
    在这里插入图片描述

2.3.3、环绕通知

  • aop:around:用于配置环绕通知

参数:
method:通知中方法的名称
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用

spring提供的一种可以在代码中手动控制增强代码什么时候执行的方式,通常情况下,环绕通知都是独立使用的。

eg:

  • 创建环绕通知类BookProxy2:
public class BookProxy2 {
    public Object around(ProceedingJoinPoint pjp) {
        // 定义返回值,被增强的方法的返回值
        Object rtVal = null;

        try {
            // 获取被增强方法的参数列表
            Object[] args = pjp.getArgs();

            System.out.println("环绕通知的前置通知....");

            // 执行被增强的方法,并传入参数,如果确定没有返回值或者没有参数可以不写
            rtVal = pjp.proceed(args);

            System.out.println("环绕通知的后置通知....");
        } catch (Throwable e) {
            System.out.println("环绕通知的异常通知....");
            e.printStackTrace();
        } finally {
            System.out.println("环绕通知的最终通知....");
        }
        // 将被增强方法的返回值返回
        return rtVal;
    }
}
  • 创建Spring的配置文件,proxy3.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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">

    <!-- 创建Book对象 -->
    <bean id="book" class="com.qizekj.proxy.Book"/>
    <bean id="bookProxy" class="com.qizekj.proxy.BookProxy2"/>

    <!-- 配置AOP的增强 -->
    <aop:config>

        <!-- 配置切入点 -->
        <aop:pointcut expression="execution(* com.qizekj.proxy.Book.buy(..))" id="pt1"/>

        <!-- 配置切面 -->
        <aop:aspect ref="bookProxy">
            <!--配置通知的类型要写在此处-->
            <aop:around method="around" pointcut-ref="pt1"/>
        </aop:aspect>

    </aop:config>
</beans>
  • 创建测试方法
@Test
public void testProxyXml() {
    ApplicationContext context = new ClassPathXmlApplicationContext("proxy3.xml");
    Book book = context.getBean("book", Book.class);
    book.buy();
}
  • 运行结果
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

吟诗作对歌一曲

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

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

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

打赏作者

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

抵扣说明:

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

余额充值