SpringAOP入门

简介: AOP:面向切面编程。通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术

动态代理

特点:字节码随用随创建,随用随加载(在内存中形成代理类)

作用:不修改源码的基础上对方法增强

分类:

  1. 基于接口的动态代理
  2. 基于子类的动态代理

基于接口的动态代理

创建代理类实例的要求:真实类至少要实现一个接口

通过动态代理创建代理类实例,对外表现为实现了接口,可以调用接口的方法,但实际上是内部通过反射调用了真实类对象的方法,而且在不修改源码的情况下,对真实类对象实现了增强

  • 涉及的类:Proxy
  • 提供者:JDK官方
  • 如何创建代理类实例:使用Proxy类中的静态方法newProxyInstance()newProxyInstance方法的参数:
  1. ClassLoader:类加载器
    用于加载代理对象字节码的,和真实对象使用相同的类加载器

真实对象.getClass().getClassLoader()

  1. Class []:字节码数组
    代理对象和真实对象实现同一套接口,那么就有相同的方法。固定写法

真实对象.getClass().getInterfaces()

  1. InvocationHandler:调用处理器,通过代理对象对方法的调用, 会实际触发 InvocationHandler.invoke() 函数,在invoke()函数内部提供增强的代码,从而在不修改源码的情况下对真实对象方法实现增强
    一般写一个该接口的实现类,通常情况下都是匿名内部类(直接new 接口InvocationHandler),但不必须

newInvocationHandler() {

                   @Override

                   publicObjectinvoke(Objecto, Methodmethod, Object[] objects) throwsThrowable {

                   

                   }

案例

下面的代码中,真实类Producer实现了接口IProducer,那么可以使用基于接口的动态代理

  1. 首先创建真实对象,因为在后面的代理对象中,需要使用真实对象来调用方法
  2. 使用Proxy的静态方法newProxyInstance,返回的是一个代理对象 ,他实现了真实对象producerIProducer接口,并且要向下转型为IProducernewProxyInstance方法的参数是固定的
  1. producer.getClass().getClassLoader()
  2. producer.getClass().getInterfaces()
  3. new InvocationHandler(){提供增强代码}

//对象的向上转型

IProducerproducer=newProducer();

//对象的向下转型

IProducerproxyProduce=(IProducer)Proxy.newProxyInstance(producer.getClass().getClassLoader(),

               producer.getClass().getInterfaces(),

               newInvocationHandler() {

                   /**

                    * 作用:执行真实对象的任何接口方法都会经过该方法(相当于过滤器的作用)

                    * 方法参数的含义:

                    * @param o:代理对象的引用(一般不用)

                    * @param method:当前执行的方法

                    * @param objects:当前执行方法所需的参数

                    * @return 和真实对象方法有相同的返回值

                    * @throws Throwable

                    */

                   @Override

                   publicObjectinvoke(Objecto, Methodmethod, Object[] objects) throwsThrowable {

                       //提供增强的代码

                       ObjectreturnValue=null;

                       //1.获取方法执行的参数

                       floatmoney=(float)objects[0];

                       //2.判断当前方法是否为销售(如果是,则回扣20%利润,这就是黑心代理,用户不再实现与生产厂商的联系,而是交由代理来实现)

                       if(method.getName().equals("saleProduct")){

                           returnValue=method.invoke(producer, money*0.8f);

                       }

                       returnreturnValue;

                   }

               });

java 内置实现的动态代理本质是通过一种手段, 创建一个虚拟实现了某 Interface 的代理实例 proxyInstance, 对于该 proxyInstance 直接可以调用 Interface 中的任意方法, 最终会触发对应的 InvocationHandler 中的 invoke() 函数。 至于 InvocationHandler 最终是否进一步调用真正的 Interface 实现类, 完全取决于自定义的 InvocationHandler 逻辑。

基于子类的动态代理

创建代理对象的要求:真实类不能是最终类,需要有子类

需要第三方依赖

       <dependency>

           <groupId>cglib</groupId>

           <artifactId>cglib-parent</artifactId>

           <version>3.2.12</version>

       </dependency>

  • 涉及的类:Enhancer
  • 提供者:第三方cglib库
  • 如何创建代理对象:使用Enhancer类中的静态方法createcreate方法的参数:
  1. Class:字节码
    用于指定真实对象的字节码

真实对象.getClass().getClassLoader()

  1. Callback:让我们写如何代理。一般写Callback接口的子接口实现类:MethodInterceptor
    执行真实对象的任何方法都会经过该方法

Producerproducer=newProducer();

ProducercglibProducer=(Producer) Enhancer.create(producer.getClass(), newMethodInterceptor() {

           /**

            *

            * @param o

            * @param method

            * @param objects

            * 以上三个参数和基于接口的动态代理中invoke方法参数一样

            * @param methodProxy:当前执行方法的代理对象

            * @return

            * @throws Throwable

            */

           @Override

           publicObjectintercept(Objecto, Methodmethod, Object[] objects, MethodProxymethodProxy) throwsThrowable {

               //提供增强的代码

               ObjectreturnValue=null;

               //1.获取方法执行的参数

               floatmoney=(float)objects[0];

               //2.判断当前方法是否是销售

               if(method.getName().equals("saleProduct")){

                   returnValue=method.invoke(producer, money*0.8f);

               }

               returnreturnValue;

           }

       });

AOP

AOP:Aspect Oriented Programming 面向切面编程

通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

spring中的AOP就是通过配置的方式实现过滤器功能

spring会根据目标类是否实现了接口来决定采用哪种动态代理方式

AOP相关术语

Joinpoint(连接点)

所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的

连接点。(接口中的所有方法都可以被拦截到,因此接口中所有方法都是连接点

Pointcut(切入点)

所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。(要对接口中哪些方法进行增强,哪些方法就是切入点

切入点一定是连接点,连接点不一定是切入点

Advice(增强\通知)

所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。(invoke() 函数)

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

Introduction(引介)

引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方

法或 Field。

Target(目标对象)

真实对象

Weaving(织入)

是指把增强应用到目标对象来创建新的代理对象的过程。

spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。

Proxy(代理)

一个类被 AOP 织入增强后,就产生一个结果代理类。(代理对象)

Aspect(切面)

是切入点(要进行增强的方法)和通知(增强)的结合

切入目标方法,扩展目标方法功能,却不修改目标方法代码,将扩展功能代码从目标方法代码中分离出来

spring基于XML的AOP配置

1. 导入aop相关配置

spring任何一段内容的运行都依赖于IOC

<?xmlversion="1.0" encoding="UTF-8"?>

<beansxmlns="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. 把要增强的对象添加进容器

<!--    把service对象添加进spring容器-->

   <beanid="accountService"class="cn.upeveryday.service.impl.AccountServiceImpl"></bean>

3. 把含有增强方法的bean添加进容器

<!--    添加Logger类-->

   <beanid="logger"class="cn.upeveryday.utils.Logger"></bean>

4. aop:config标签开始aop的配置

<!--    配置aop-->

   <aop:config>

       ...

   </aop:config>

下面的标签都是在<aop:config>标签的内部

4.1 aop:aspect标签用来配置切面

切面(aspect):切入点(要进行增强的方法)+通知(增强方法)

  1. id属性:给切面提供一个唯一标识
  2. ref属性:含有增强方法的bean的id

<aop:aspectid="logAdvice"ref="logger">

   ...

</aop:aspect>

4.1.1 在 aop:aspect内部使用对应标签来配置通知(增强方法)的类型
1. aop:before:表示配置前置通知(前置增强方法)
  1. method属性:用于指定Logger类中哪个方法是前置通知
  2. pointcut属性:用于指定切入点表达式,该表达式指出要进行增强的方法

<aop:beforemethod="printLog"pointcut="execution(public void cn.upeveryday.service.impl.AccountServiceImpl.saveAccount())"></aop:before>

  1. 切入点表达式的写法关键字:execution(表达式)表达式:访问修饰符 返回值 包名..类名.方法名(参数列表)
  1. 标准的表达式写法:public void cn.upeveryday.service.impl.AccountServiceImpl.saveAccount()
  2. 访问修饰符可以省略:void cn.upeveryday.service.impl.AccountServiceImpl.saveAccount()
  3. 返回值可以使用通配符*,表示任意返回值:* cn.upeveryday.service.impl.AccountServiceImpl.saveAccount()
  4. 包名可以使用通配符*,表示任意包,但是有几级包,就需要写几个*.* *.*.*.*.AccountServiceImpl.saveAccount()
  5. 包名可以使用*..表示当前包及其子包:* *..AccountServiceImpl.saveAccount()
  6. 类名和方法名都可以使用通配符** *..*.*()
  7. 参数列表可以直接写数据类型
  1. 基本类型直接写名称:int
  2. 引用类型写包名.类名的方式:java.lang.String
  1. 可以写通配符*,表示任意类型:* *..*.*(*)
    可以使用..表示有无参数都可以,有参数表示任意类型:* *..*.*(..)
  2. 全通配写法:* *..*.*(..)
  1. 实际开发中切入点表达式的通常写法:切入到业务层实现类下的所有方法 ——> * cn.upeveryday.service.impl.*.*(..)

bean.xml

<?xmlversion="1.0" encoding="UTF-8"?>

<beansxmlns="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">

<!--    将要进行增强的业务层对象添加进容器-->

   <beanid="accountService"class="cn.upeveryday.service.impl.AccountServiceImpl"></bean>

<!--    添加含有增强方法的logger对象-->

   <beanid="logger"class="cn.upeveryday.utils.Logger"></bean>

   

<!--    配置aop-->

   <aop:config>

<!--        配置切面:切入点(要进行增强的方法)+通知(增强方法)-->

       <aop:aspectid="logAdvice"ref="logger">

<!--            配置通知的类型,并且建立增强方法和被增强方法之间的关联-->

           <aop:beforemethod="printLog"pointcut="execution(* cn.upeveryday.service.impl.*.*(..))"></aop:before>

       </aop:aspect>

   </aop:config>

</beans>

通用化切入点表达式与四种常用通知

aop:pointcut标签

  1. id属性:指定表达式的唯一标识
  2. expression属性:指定表达式内容

此标签写在aop:aspect标签内部,只能当前切面使用;

它还可以写在aop:aspect标签外部,此时变成了所有切面可用

   <aop:config>

       <aop:aspectid="logAdvice"ref="logger">

           <!--            配置通用切入点表达式-->

           <aop:pointcutid="pt1"expression="execution(* cn.upeveryday.service.impl.*.*(..))"/>

<!--            配置增强的类型,并且建立增强方法和切入点方法的关联-->

           <!--1.配置前置通知:在切入点方法执行之前执行-->

           <aop:beforemethod="beforeLog"pointcut-ref="pt1"></aop:before>

           <!--2.配置后置通知:在切入点方法正常执行后执行。它和异常通知永远只能执行一个-->

           <aop:after-returningmethod="afterLog"pointcut-ref="pt1"></aop:after-returning>

           <!--3.配置异常通知:在切入点方法执行产生异常后执行。它和后置通知永远只能执行一个-->

           <aop:after-throwingmethod="exceptionLog"pointcut-ref="pt1"></aop:after-throwing>

           <!--4.配置最终通知:无论切入点方法是否正常执行它都会在其后执行-->

           <aop:aftermethod="finalLog"pointcut-ref="pt1"></aop:after>

       </aop:aspect>

   </aop:config>

环绕通知

动态代理中的环绕通知是整个invoke方法

           <!--配置环绕通知-->

           <aop:aroundmethod="aroundLog"pointcut-ref="pt1"></aop:around>

   /**

    * 环绕通知

    */

   publicvoidaroundLog(){

       System.out.println("环绕通知:Logger类中的aroundLog方法开始执行。。。");

   }

问题:当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了

分析:通过对比动态代理中的环绕通知,发现动态代理的环绕通知有明确的切入点方法调用,而我们的环绕通知代码中没有

解决:Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确切入点方法。

该接口可以作为环绕通知的方法参数,spring会自动提供接口的实现类对象。

Spring的环绕通知:它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方法

   /**

    * 环绕通知

    */

   publicObjectaroundLog(ProceedingJoinPointpjp) {

       ObjectrtValue=null;

       try {

           //得到方法执行所需的参数

           Object[] args=pjp.getArgs();

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

           //明确调用业务层方法(切入点方法)

           rtValue=pjp.proceed(args);

           System.out.println("后置通知");

           returnrtValue;

       } catch (Throwablethrowable) {

           System.out.println("异常通知");

           thrownewRuntimeException();

       }finally {

           System.out.println("最终通知");

       }

   }

之前的四种通知(增强)方法是通过XML配置来指定什么代码什么时候执行,环绕通知则使用代码的方式来指定什么代码什么时候执行

spring基于注解的AOP配置

1. 在XML中导入注解标签所需的约束

<?xmlversion="1.0" encoding="UTF-8"?>

<beansxmlns="http://www.springframework.org/schema/beans"

      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

      xmlns:aop="http://www.springframework.org/schema/aop"

     

      xmlns:context="http://www.springframework.org/schema/context"

     

      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

       

       http://www.springframework.org/schema/context

       http://www.springframework.org/schema/context/spring-context.xsd">

</beans>

2. 配置spring在创建容器时要扫描的包

<!--    配置spring创建容器时要扫描的包-->

   <context:component-scanbase-package="cn.upeveryday"></context:component-scan>

3. 配置spring开启注解AOP的支持

   <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

Aspect注解

@Component("logger")

@Aspect

publicclassLogger {...}

作用在类上,表明当前类类Logger是一个切面类

Pointcut注解

   @Pointcut("execution(* cn.upeveryday.service.impl.*.*(..))")

   privatevoidpt1(){}

作用在一个方法上,表示切入点表达式,后面的通知中可以直接使用这个表达式,例如:@Before("pt1()")

5种通知

  • 前置通知:@Before("pt1()")
  • 后置通知:@AfterReturning("pt1()"):被增强方法执行之后,即return之后执行,可以对return进行操作属性:
  1. pointcut/value:指定切入点对应的切入表达式
  2. returning:该属性指定一个形参名,用于表示Advice方法中可定义与此同名的形参,该形参可用于访问目标方法的返回值
  • 异常通知:@AfterThrowing("pt1()")
  • 最终通知:@After("pt1()")
  • 环绕通知:@Around("pt1()")

/**

* 用于记录日志的工具类(提供对业务层对象进行增强的方法)

*/

@Component("logger")

@Aspect

publicclassLogger {

   /**

    * 切入点表达式

    */

   @Pointcut("execution(* cn.upeveryday.service.impl.*.*(..))")

   privatevoidpt1(){}

   /**

    * 前置通知

    */

   @Before("pt1()")

   publicvoidbeforeLog(){

       System.out.println("前置通知:Logger类中的beforeLog方法开始执行。。。");

   }

   /**

    * 后置通知

    */

   @AfterReturning("pt1()")

   publicvoidafterLog(){

       System.out.println("后置通知:Logger类中的afterLog方法开始执行。。。");

   }

   /**

    * 异常通知

    */

   @AfterThrowing("pt1()")

   publicvoidexceptionLog(){

       System.out.println("异常通知:Logger类中的exceptionLog方法开始执行。。。");

   }

   /**

    * 最终通知

    */

   @After("pt1()")

   publicvoidfinalLog(){

       System.out.println("最终通知:Logger类中的finalLog方法开始执行。。。");

   }

   /**

    * 环绕通知

    */

   @Around("pt1()")

   publicObjectaroundLog(ProceedingJoinPointpjp) {

       ObjectrtValue=null;

       try {

           //得到方法执行所需的参数

           Object[] args=pjp.getArgs();

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

           //明确调用业务层方法(切入点方法)

           rtValue=pjp.proceed(args);

           System.out.println("后置通知");

           returnrtValue;

       } catch (Throwablethrowable) {

           System.out.println("异常通知");

           thrownewRuntimeException();

       }finally {

           System.out.println("最终通知");

       }

   }

}

不使用XML的配置方式

@Configuration//指定当前类是一个配置类

@ComponentScan(basePackages="cn.upeveryday")//指定spring在创建容器时要扫描的包

@EnableAspectJAutoProxy//配置spring开启注解AOP的支持

publicclassSpringConfiguration {

}


目录
相关文章
|
20天前
|
XML 安全 Java
使用 Spring 的 @Aspect 和 @Pointcut 注解简化面向方面的编程 (AOP)
面向方面编程(AOP)通过分离横切关注点,如日志、安全和事务,提升代码模块化与可维护性。Spring 提供了对 AOP 的强大支持,核心注解 `@Aspect` 和 `@Pointcut` 使得定义切面与切入点变得简洁直观。`@Aspect` 标记切面类,集中处理通用逻辑;`@Pointcut` 则通过表达式定义通知的应用位置,提高代码可读性与复用性。二者结合,使开发者能清晰划分业务逻辑与辅助功能,简化维护并提升系统灵活性。Spring AOP 借助代理机制实现运行时织入,与 Spring 容器无缝集成,支持依赖注入与声明式配置,是构建清晰、高内聚应用的理想选择。
253 0
|
4月前
|
监控 安全 Java
Spring AOP实现原理
本内容主要介绍了Spring AOP的核心概念、实现机制及代理生成流程。涵盖切面(Aspect)、连接点(Join Point)、通知(Advice)、切点(Pointcut)等关键概念,解析了JDK动态代理与CGLIB代理的原理及对比,并深入探讨了通知执行链路和责任链模式的应用。同时,详细分析了AspectJ注解驱动的AOP解析过程,包括切面识别、切点表达式匹配及通知适配为Advice的机制,帮助理解Spring AOP的工作原理与实现细节。
|
1月前
|
人工智能 监控 安全
Spring AOP切面编程颠覆传统!3大核心注解+5种通知类型,让业务代码纯净如初
本文介绍了AOP(面向切面编程)的基本概念、优势及其在Spring Boot中的使用。AOP作为OOP的补充,通过将横切关注点(如日志、安全、事务等)与业务逻辑分离,实现代码解耦,提升模块化程度、可维护性和灵活性。文章详细讲解了Spring AOP的核心概念,包括切面、切点、通知等,并提供了在Spring Boot中实现AOP的具体步骤和代码示例。此外,还列举了AOP在日志记录、性能监控、事务管理和安全控制等场景中的实际应用。通过本文,开发者可以快速掌握AOP编程思想及其实践技巧。
|
1月前
|
人工智能 监控 安全
如何快速上手【Spring AOP】?核心应用实战(上篇)
哈喽大家好吖~欢迎来到Spring AOP系列教程的上篇 - 应用篇。在本篇,我们将专注于Spring AOP的实际应用,通过具体的代码示例和场景分析,帮助大家掌握AOP的使用方法和技巧。而在后续的下篇中,我们将深入探讨Spring AOP的实现原理和底层机制。 AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架中的核心特性之一,它能够帮助我们解决横切关注点(如日志记录、性能统计、安全控制、事务管理等)的问题,提高代码的模块化程度和复用性。
|
1月前
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
|
8月前
|
XML Java 开发者
Spring Boot中的AOP实现
Spring AOP(面向切面编程)允许开发者在不修改原有业务逻辑的情况下增强功能,基于代理模式拦截和增强方法调用。Spring Boot通过集成Spring AOP和AspectJ简化了AOP的使用,只需添加依赖并定义切面类。关键概念包括切面、通知和切点。切面类使用`@Aspect`和`@Component`注解标注,通知定义切面行为,切点定义应用位置。Spring Boot自动检测并创建代理对象,支持JDK动态代理和CGLIB代理。通过源码分析可深入了解其实现细节,优化应用功能。
415 6
|
7月前
|
XML Java 测试技术
Spring AOP—通知类型 和 切入点表达式 万字详解(通俗易懂)
Spring 第五节 AOP——切入点表达式 万字详解!
373 25
|
7月前
|
XML 安全 Java
Spring AOP—深入动态代理 万字详解(通俗易懂)
Spring 第四节 AOP——动态代理 万字详解!
276 24
|
6月前
|
Java API 微服务
微服务——SpringBoot使用归纳——Spring Boot中的切面AOP处理——Spring Boot 中的 AOP 处理
本文详细讲解了Spring Boot中的AOP(面向切面编程)处理方法。首先介绍如何引入AOP依赖,通过添加`spring-boot-starter-aop`实现。接着阐述了如何定义和实现AOP切面,包括常用注解如`@Aspect`、`@Pointcut`、`@Before`、`@After`、`@AfterReturning`和`@AfterThrowing`的使用场景与示例代码。通过这些注解,可以分别在方法执行前、后、返回时或抛出异常时插入自定义逻辑,从而实现功能增强或日志记录等操作。最后总结了AOP在实际项目中的重要作用,并提供了课程源码下载链接供进一步学习。
753 0
|
6月前
|
Java 开发者 微服务
微服务——SpringBoot使用归纳——Spring Boot中的切面AOP处理——什么是AOP
本文介绍了Spring Boot中的切面AOP处理。AOP(Aspect Oriented Programming)即面向切面编程,其核心思想是分离关注点。通过AOP,程序可以将与业务逻辑无关的代码(如日志记录、事务管理等)从主要逻辑中抽离,交由专门的“仆人”处理,从而让开发者专注于核心任务。这种机制实现了模块间的灵活组合,使程序结构更加可配置、可扩展。文中以生活化比喻生动阐释了AOP的工作原理及其优势。
355 0