Spring:面向切面编程(AOP)

1,AOP概述 

AOP,也就是面向切面编程,作为面向对象编程的一种补充,已经成为一种比较成熟的编程方式。AOP与OOP互相补充,面向对象编程将程序分解成各个层次的对象,而面向切面编程将程序的运行过程分解成各个切面。可以这么理解:面向对象编程是从静态角度考虑程序结构,而面向切面编程则是从动态角度考虑程序的运行过程。

AOP 就是在运行时通过动态代理技术对目标方法进行增强,可以在目标方法的调用前后或者调用过程中执行其他额外的逻辑。

1.1,为什么需要AOP

在传统的OOP编程里以对象为核心,整个软件由一系列相互依赖对象组成,而这些对象将被抽象成一个个类,并允许使用类继承来管理类与类之间一般到特殊的关系。随着软件规模的增大,应用软件逐渐升级,慢慢出现了一些OOP很难解决的问题。

面向对象可以通过分析抽象出一系列具有一定属性与行为的类,并通过这些类之间的协作来形成一个完整的软件功能。由于软件可以继承,因此可以把具有相同的功能或相同特性的属性抽象到一个层次分明的类结构体系中。随着软件规范的不断扩大,专业化分工越来越细致,以及OOP应用实践的不断增多,随之也暴露了一些OOP无法很好解决的问题。

OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系。例如日志功能:日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码。如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

现在假设系中有三段完全相同的代码,这些代码通常会采用“复制”、“粘贴”的方式来完成的,通过这种方法开发出来的软件存在严重的不足。如果有一天需要修改,将需要将三个地方的代码都进行修改,如果包含更多的地方,这将是一个很大的问题。为了解决上面的问题,通常采用下图所示方法将相同代码段定义成一个方法,然后在三个代码段中调用该方法即可。这样不管整个系统中有多少个地方调用了该方法,程序无须修改这些地方,只需要修改被调用的方法即可——大大降低了软件后期维护的复杂度。

希望实现更好的解耦:希望方法1,方法2,方法3彻底与相同代码段分离,换而言之方法1,方法2,方法3无须直接调用深色的方法,应该如何解决?

希望做到:只要实现新的方法,然后无须在方法1,方法2,方法3中显示调用它,系统会“自动”(无需开发者担心,由系统来驱动)在方法1,方法2,方法3中调用这个特殊的方法。

如何做到:实现这个需求的技术就是AOP。AOP专门用于处理系统中分布于各个模块(不同方法)中的交叉关注点的问题,在JavaEE应用中,常常通过AOP来处理一些具有横切性质的系统服务,如事务管理、安全检查、缓存、对象池管理等,AOP已经成为一种非常常用的解决方案。

AOP是一种编程范式,它可以提高模块化并允许分离横切关注点。横切关注点指在系统的多个部分中出现的功能,AOP 将其与应用程序的核心分离,从而提高关注点的分离并避免代码重复。同时,AOP 也可以实现一些常见的横切关注点,如日志记录、异常处理、事务管理等,从而减轻开发人员的负担。

1.2,AOP的基本概念

AOP技术利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

AOP和切面的关系:(1)类比于OOP和对象,AOP和切面就是这样的一种关系。(2)切面是AOP的一个工具。

使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

AOP可以理解为一个拦截器框架,但是这个拦截器会非常武断,如果它拦截一个类,那么它就会拦截这个类中的所有方法。如对一个目标列的代理,增强了目标类的所有方法。

AOP不与特定的代码耦合,AOP框架能处理程序中执行特定的切入点(Pointcut),而不与某个具体的类耦合。AOP具有两个特征:各步骤之间的良好隔离性。源代码无关性。

核心概念

切面(aspect):切面用于组织多个Advice,Advice放在切面中定义。

连接点(joinpoint):被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器。

通知|增强处理(advice):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类。
切入点(pointcut):可以插入增强处理的连接点。简而言之,当某个连接点满足指定要求时,该连接点将被添加增强处理,该连接点也就变成了切入点。

目标对象:被AOP框架进行增强处理的对象,也被称为增强的对象。如果AOP框架采用的是动态AOP实现,那么该对象就是一个被代理的对象。

AOP代理:AOP框架创建的对象,简单地说,代理就是目标对象的加强。Spring中的AOP代理可以是JDK动态代理,也可以是cglib代理。前者为实现接口的目标对象的代理,后者为不实现接口的目标对象的代理。

引入:在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段【对类的增强】。

织入(weave):将切面应用到目标对象并导致代理对象创建的过程【对方法的增强】。

(1)前置增强(BeforeAdvice):在目标方法前调用。

(2)后置增强(AfterAdvice):在目标方法后调用。

(3)环绕增强(AroundAdvice):将 Before 和 After ,甚至抛出增强和返回增强合到一起。

(4)返回增强(AfterReturningAdvice):在方法返回结果后执行,该增强可以接收到目标方法返回结果。

(5)抛出增强(AfterThrowingAdvice):在目标方法抛出对应的类型后执行,可以接收到对应的异常信息。

1.3,AOP的代理规则

Spring中的AOP代理由Spring的IoC容器负责生成、管理,其依赖关系也由IoC容器负责管理。因此,AOP代理可以直接使用容器中的其他Bean作为目标,这种关系可由IoC容器的依赖注入提供。

Spring的代理规则、实现方式:

  • 默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了。如果针对接口做代理默认使用的是JDK自带的Proxy+InvocationHandler。
  • 当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB。如果针对类做代理使用的是Cglib。即使针对接口做代理,也可以将代理方式配置成走Cglib的。

JDK动态代理和CGLIB的区别:

  • JDK动态代理:这是Java提供的动态代理技术,可以在运行时创建接口的代理实例。Spring AOP默认采用这种方式,在接口的代理实例中织入代码。
  • CGLIB动态代理:采用底层的字节码技术,在运行时创建子类代理的实例。当目标对象不存在接口时,Spring AOP就会采用这种方式,在子类实例中织入代码。

在性能方面,CGLib创建的代理对象比JDK动态代理创建的代理对象高很多。但是,CGLib在创建代理对象时所花费的时间比JDK动态代理多很多。所以,对于单例的对象因为无需频繁创建代理对象,采用 CGLib动态代理比较合适。反之,对于多例的对象因为需要频繁的创建代理对象,则JDK动态代理更合适。

静态代理:静态代理

1.4,Spring对AOP的支持 

SpringAOP使用纯Java实现。它不需要特定的编译工具,也不需要控制类装载器层次,因此它可以在所有的JavaWeb容器或应用服务器中运行良好。

SpringAOP目前仅支持将方法调用作为连接点(Joinpoint),如果需要把对成员变量的访问和更新也作为增强处理的连接点,则可以考虑使用AspectJ。

Spring实现AOP的方式跟其他框架不同。Spring并不是要提供最完整的AOP实现(尽管SpringAOP有这个能力),Spring侧重于AOP实现和Spring IoC容器之间的整合用于帮助解决企业级开发中常见的问题。因此,Spring的AOP通常和SpringIoC容器一起使用,SpringAOP从来没有打算通过提供一种全面的AOP解决解决方案来与AspectJ竞争。SpringAOP采用基于代理的AOP解决方案,而AspectJ则采用编译时增强的解决方案。

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

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

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

Spring提供了如下两种选择来定义切入点和增强处理:

  • 基于注解的“零配置”方式:使用@Aspect、@Pointcut等注解来标注切入点和增强处理。
  • 基于XML配置文件的管理方式:使用Spring配置文件来定义切入点好增强处理。

2,基于注解的“零配置”方式

AspectJ允许使用注解定义切面、切入点和增强处理(通知),而Spring框架则可识别并根据这些注解来生成AOP代理。Spring只是使用了和AspectJ5一样的注解,但并没有使用AspectJ的编译器或者织入器,底层依然使用的是Spring AOP,依然是在运行时动态生成AOP代理,并不依赖于AspectJ的编译器或者织入器。

为了启动Spring对@AspectJ切面配置的支持,并保证Spring容器中的目标Bean被一个或多个切面自动增强,必须在Spring配置文件中配置:

<aop:aspectj-autoproxy/>

所谓自动增强,指的是Spring会判断一个或多个切面是否需要对指定Bean进行增强,并据此自动生成相应代理,从而使得增强处理在合适的时候被调用。

2.1,定义切面Bean

定义切面Bean:当启动了@AspectJ支持后,只要在Spring容器中配置一个带@Aspect注解的Bean,Spring将会自动注入该Bean,并将该Bean作为切面处理。

在Spring容器中配置切面Bean(即带@Aspect注解的Bean),与配置普通Bean没有任何区别一样使用<bean.../>元素进行配置,一样支持使用依赖注入来配置属性值;如果启动了Spring的“零配置”特性,一样可以让Spring自动搜索,并加载指定路径下的切面Bean。

package Bean;

import org.aspectj.lang.annotation.Aspect;

@Aspect
public class LogAspect {
    //其他内容
}

切面类(用@Aspect修饰的类)和其他类一样可以有方法、成员变量定义,还可能包括切入点、增强处理定义。

当使用@Aspect来修饰一个Java类后,Spring将不会把该Bean当成组件Bean处理,因此负责自动增强的后处理Bean将会略过该Bean,不会对该Bean进行任何增强处理。

开发时无须担心使用@Aspect定义的切面类被增强处理,当Spring容器检测到某个Bean类使用了@Aspect修饰之后,Spring容器不会对该Bean类进行增强。

2.2,定义Before增强处理

在一个切面里使用@Before来修饰一个方法时,该方法将作为Before增强处理。使用@Before修饰时,通常需要指定一个value属性值,该属性值指定一个切入点表达式(既可以是一个已有的切入点,也可以直接定义切入点表达式),用于指定该增强处理将被织入那些切入点。

package Bean;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class AuthAspect {
    //匹配所有Bean包下的所有类的所有方法作为切入点
    @Before("excution(* Bean.*.*(..))")
    public void authority(){
        System.out.println("模拟执行权限操作!!!");
    }
}

上面程序使用@Aspect修饰了AuthAspect类,这表明该类是一个切面类,在该切面里定义了一个authority()方法——这个方法本来没有任何特殊之处,但因为使用了@Before来标注该方法,这就将该方法转换成了一个Before增强处理。

@Before注解中直接指定了切入点表达式,指明匹配Bean包下所有类的所有方法的执行作为切入点。

package Bean;
import org.springframework.stereotype.Component;
@Component
public class HelloWord {
    public void foo(){
        System.out.println("执行Hello组件的foo()方法");
    }
    public void addUser(String name){
        System.out.println("执行Hello组件的addUser添加用户:"+name);
    }
}

上面这个纯Java类,不知道将被谁来进行增强,也不知道将被怎样的增强——但是正式因为HelloWord的这种无知,才是AOP的最大魅力;目标可以被无限的增强。

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        HelloWord helloWord = context.getBean("helloWord",HelloWord.class);
        helloWord.foo();
    }
}
=======================================
模拟执行权限操作!!!
执行Hello组件的foo()方法
模拟执行权限操作!!!
执行Hello组件的addUser添加用户:燕双嘤

2.3,定义AfterReturning增强

类似于使用@Before注解可修饰Before增强处理,使用@AfterReturning可修饰AfterReturning增强处理,AfterRetruning增强处理在目标方法正常完成后被织入。

使用@AfterReturning注解可指定如下两个属性:

  • pointcut/value:两个属性作用一样,用于指定该切入点对应的切入点表达式。一样即可是一个已有的切入点,也可以直接定义切入点表达式。当指定了pointcut属性值后,value属性值将会被覆盖。
  • returning:该属性值指定一个形式名,用于表示Advice方法中可定义与此同时的形参,该形参可用于访问目标方法的返回值。除此之外,在Advice方法中定义该形参(代表目标方法的返回值)时指定的类型,会限制目标方法必须返回指定类型的值或没有返回值。
public String add(String name){
    System.out.println("执行Hello组件的addUser添加用户:"+name);
    return name;
}
package Bean;

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class AuthAspect {
    //匹配所有Bean包下的所有类的所有方法作为切入点
    @AfterReturning(pointcut = "execution(* Bean.*.*(..))",returning = "str")
    public void afterReturning(Object str){
        System.out.println("获取目标方法返回值:"+str);
        System.out.println("模拟记录日志功能......");
    }
}

程序中使用@AfterReturning注解时,指定了一个returning属性,该属性值为str,这表明程序可通过str形参来访问目标方法的返回值。 @AfterReturning注解returning属性所指定的形参必须对应于增强处理中的一个形参名,当目标方法执行返回后,返回值作为相应的参数值传入增强处理方法。

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
HelloWord helloWord = context.getBean("helloWord",HelloWord.class);
helloWord.foo();
helloWord.add("燕双嘤");
===================================
执行Hello组件的foo()方法
获取目标方法返回值:null
模拟记录日志功能......
执行Hello组件的addUser添加用户:燕双嘤
获取目标方法返回值:燕双嘤
模拟记录日志功能......

使用returning属性还有一个额外的作用:它可用于限定切入点只匹配具有对应返回值类型的方法——假设上面的afterReturning()方法中定义str形参的类型是String,则该切入点只匹配Bean包下返回值类型为String。不能匹配无返回值的方法。当然,如果形参类型为Object,这表明该切入点可以匹配任何返回值的类型。虽然AfterReturning增强处理可以访问到目标方法的返回值,但不能改变目标方法的返回值。

2.4,定义AfterThrowing增强处理

使用@AfterThrowing注解可修饰AfterThrowing增强处理,AfterThrowing增强处理主要用于处理程序中未处理的异常。

使用@AfterReturning注解可指定如下两个属性:

  • pointcut/value:两个属性作用一样,用于指定该切入点对应的切入点表达式。一样即可是一个已有的切入点,也可以直接定义切入点表达式。当指定了pointcut属性值后,value属性值将会被覆盖。
  • throwing:该属性值指定一个形参名,用于表示Advice方法中可定义与同名的形参,该形参可用于访问目标访问目标方法抛出的一次。除此之外,在Advice方法中定义该形参(代表目标方法抛出的异常)时指定的类型,会限制目标方法必须抛出指定类型的异常。
@Component("helloWord")
public class HelloWord {
    public void foo(){
        System.out.println("执行Hello组件的foo()方法"+1/0);
    }
    public String add(String name){
        System.out.println("执行Hello组件的addUser添加用户:"+name);
        return name;
    }
}

程序中使用@AfterThrowing注解时指定了一个throwing属性,该属性值为ex,这允许在增强处理方法(doRecoveryActions()方法)中定义名为ex形参,程序可通过该形参访问目标方法所抛出的异常。  

@Aspect
public class AuthAspect {
    //匹配所有Bean包下的所有类的所有方法作为切入点
    @AfterThrowing(pointcut = "execution(* Bean.*.*(..))",throwing = "ex")
    public void doRecoveryActions(Throwable ex){
        System.out.println("目标方法中抛出异常:"+ex);
        System.out.println("模拟Advice对异常的修复...");
    }
}
目标方法中抛出异常:java.lang.ArithmeticException: / by zero
模拟Advice对异常的修复...
Exception in thread "main" java.lang.ArithmeticException: / by zero
......

使用throwing属性还有一个额外的作用:它可以用于限定切入点只匹配指定类型的异常。

2.5,After增强处理

Spring还提供了一个After增强处理,它与AfterReturning增强处理有点相似。

  • AfterReturning增强处理只有在目标方法成功完成后才会被织入。
  • After增强处理不管目标方法如何增强(包括成功完成和遇到异常中止两种情况),它都会被织入。

因为不论一个方法是如何结束的,After增强处理都会被织入,因此After增强处理必须准备处理正常返回和异常返回两种情况,这种增强处理通常用于释放资源。类似于异常处理中的finally快的作用——无论如何都会在方法执行结束之后被织入。

@Aspect
public class AuthAspect {
    //匹配所有Bean包下的所有类的所有方法作为切入点
    @After("execution(* Bean.*.*(..))")
    public void doRecoveryActions(){
        System.out.println("模拟方法结束后的释放资源...");
    }
}

2.6,Around增强处理

@Around注解用于修饰Around增强处理,Around增强处理是功能比较强大的增强处理,它近似等于Before增强处理和AfterReturning增强处理的总和,Around增强处理既可在执行目标方法之前织入增强处理,也可以在执行目标方法之后织入增强动作。与Before增强处理和AfterReturning增强处理不同的是,Around增强处理可以决定目标方法在什么时候执行,如何执行,甚至可以完全组织目标方法的执行。

Around增强处理可以改变执行目标方法的参数值,也可以改变目标方法之后的返回值。Around功能过于强大,通常需要在线程安全的环境下使用。因此,如果使用普通的Before增强处理、AfterReturning增强处理就能解决的问题,就没有必要使用Around增强处理了。

Around增强处理方法应该使用@Around来标注,使用@Around注解时需要指定一个value属性,该属性指定该增强处理被织入的切入点。

当定义一个Around增强处理方法时,该方法的第一个形参必须是ProceedingJoinPoint类型(至少包含一个形参),在增强处理方法体内,调用ProceedingJoinPoint参数的proceed()方法才会执行且目标方法——这就是Around增强处理可以完全控制目标方法的执行时机。如何执行的关键:如果程序没有调用ProceedingJoinPoint参数的proceed()方法,则目标方法不会执行。

调用ProceedingJoinPoint参数的proceed()方法时,还可以传入一个Object[]对象作为参数,该数组中的值将被传入目标方法作为执行方法的实参。

@Aspect
public class AuthAspect {
    //匹配所有Bean包下的所有类的所有方法作为切入点
    @Around("execution(* Bean.*.*(..))")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("执行目标方法之前,模拟开始事务...");
        Object[] args = proceedingJoinPoint.getArgs();  //获取原始的调用参数
        if (args != null && args.length>1){
            //修改目标方法调用参数
            args[0] = "增强的前缀"+args[0];
        }
        //以改变后的参数去执行目标方法,并保存目标方法执行后的返回值。
        Object rvt = proceedingJoinPoint.proceed(args);
        System.out.println("执行目标方法之后,模拟结束事务...");
        if (rvt !=null && rvt instanceof Integer){
            rvt = (Integer)rvt*(Integer)rvt;
        }
        return rvt;
    }
}

上面程序定义了一个切面,该切面包含了一个Around增强处理:doAround()方法,该方法中第二行用于回调目标方法,回调目标方法时传入一个args数组,但这个args数组是执行目标方法的原始参数被修改后的结果,这样就实现了对调用参数的修改。

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
HelloWord helloWord = context.getBean("helloWord",HelloWord.class);
helloWord.foo();
System.out.println(helloWord.add(30));
=============================================
执行目标方法之前,模拟开始事务...
执行Hello组件的foo()方法
执行目标方法之后,模拟结束事务...
执行目标方法之前,模拟开始事务...
执行目标方法之后,模拟结束事务...
900

从结果可以看出,使用Around增强处理可以取得对目标方法的最大的控制权,既可完全控制目标方法的执行,也可以改变目标方法的参数类型,还可以改变目标方法的返回值。

当调用ProceedingJoinPoint的proceed()方法时,传入的Object[]参数值将作为目标方法的参数,如果传入的Object[]数组长度与目标方法所需要参数的个数不想等,或者Object[]数组元素与目标方法所需参数的类型不匹配,程序就会出现异常。

2.7,访问方法参数、重用切入点、切入点表达式

https://shao12138.blog.csdn.net/article/details/90710060

3,基于XML配置文件的管理方式

使用XML配置方式与前面介绍的@AspectJ方式的实质是一样的,同样需要指定相关信息:配置切面、切入点、增强处理所需要的信息完全一样,只是提供这些信息的位置不同而已。使用XML配置方式时是通过XML文件来提供这些信息的;而使用@AspectJ方式则通过Annotation来提供这些信息。

相比之下,使用XML配置方式有如下几个优点:

  • 如果应用没有使用JDK1.5以上版本,那么应用只能使用XML配置方式来管理切面、切入点和增强处理。
  • 采用XML配置方式时对早期的Spring用户来说更加习惯,而且这种方式允许使用纯粹的POJO来支持AOP。当使用AOP作为工具来配置企业服务时,XML会使一个很好的选择。

当使用XML风格时,可以在配置文件中清晰地看出系统中存在哪些切面。但XML配置方式,存在如下几个缺点:

  • 使用XML配置方式不能将切面、切入点、增强处理等封装到一个地方。如果需要查看切面、切入点、增强处理,必须同时结合Java文件和XML配置文件来查看;但使用@AspectJ时,则只需一个单独的类文件计科看到切面、切入点和增强处理的全部信息。
  • XML配置文件比@AspectJ方式有更多的限制:仅支持“singleton”切面Bean,不能在XML中组合多个命名连接点的生命。

使用<aop:aspect.../>方式进行配置时,可能与Spring的自动代理方式相冲突,例如使用<aop:aspect-autoprox/>或类似的方式显示启用了自动代理,则可能会导致出现问题(比如有些增强处理没有被织入)。因此,要么全部使用<aop:aspect.../>配置方式,要么全部使用自动代理方式,不要两者混合使用。

3.1,配置切面

定义切面使用中所示的<aop:aspect.../>元素,使用该元素来定义切面时,其实质是将一个已有的Spring Bean转换成切面Bean,所以需求先定义一个普通的Spring Bean。

因为切面Bean可以当成一个普通的Spring Bean来配置,所以完全可以为该切面Bean配置依赖注入。当切面Bean定义完成后,通过在<aop:aspect.../>元素中使用ref属性来引用该Bean,就可将该Bean转换成一个切面Bean了。

<aop:aspect.../>元素可以指定如下三个属性:

  • id:定义该切面的标识名。
  • ref:用于将ref属性所引用的普通bean转换为切面bean。
  • order:指定该切面Bean的优先级,与@Order注解相同,属性值越小,该切面对应的优先级越高。
<aop:config>
    <!--将fourAdviceAspect指定切入点表达式,指定切面的优先级为2-->
    <aop:aspect id="fourAdviceAspect" ref="fourAdviceBean" order="2">

    </aop:aspect>
</aop:config>
<bean id="fourAdviceBean" class="Bean.FourAdviceTest"/>

上面配置文件中,将Spring容器中的fourAdviceBean Bean转换成一个切面Bean,该切面Bean的id为afterAdviceAspect。由于Spring支持将切面Bean当成普通Bean来管理,所以完全可以利用依赖注入来管理切面Bean,管理切面Bean的属性值、依赖关系等。

3.2,配置增强处理

与使用@AspectJ一样,使用XML一样可以配置Before、After、AfterReturning、AfterThrowing和Around五种增强处理,而且完全支持和@AspectJ完全一样的语义。

  • <aop:before.../>:配置Before增强处理。
  • <aop:after.../>:配置After增强处理。
  • <aop:after-returning.../>:配置AfterReturning增强处理。
  • <aop:after-throwing.../>:配置AfterThrowing增强处理。
  • <aop:around.../>:配置Around增强处理。

上面这些元素都不支持使用子元素,但是可以指定如下属性:

  • pointcut:该属性指定一个切入表达式,Spring将在匹配该表达式的连接点时织入该增强处理。
  • pointcut-ref:该属性指定一个已经存在的切入点名称,通常pointcut和pointcut-ref两个属性,只使用其中之一。
  • method:该属性指定一个方法名,指定将切面Bean的该方法转换为增强处理。
  • throwing:该属性只对<after-throwing.../>元素有效,用于指定一个形参名,AfterThrowing增强处理方法可通过该形参访问目标方法所抛出的异常。
  • returning:该属性只对<after-returining.../>元素有效,用于指定一个形参名,AfterReturning增强处理方法可以通过该形参访问目标方法的返回值。

当定义切入点表达式时,XML配置方式完全支持切入点指示符,一样可以使用execution、within、args、this、target和bean等切入点指示符。但是XML配置方式不再使用简单的&&、||和!作为组合运算符(因为直接在XML文件中需要使用实体引用来表示它们),而是使用:and、or和not。

public class FourAdviceTest {
    public Object processTx(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("Around增强:执行目标方法之前,模拟开始事务...");
        //访问执行目标方法的参数
        Object[] args = jp.getArgs();
        //当执行目标方法的参数存在且第一个参数是字符串时
        if (args != null && args.length > 0 && args[0].getClass() == String.class) {
            //修改目标方法调用参数的第一个参数
            args[0] = "[增强的前缀]" + args[0];
        }
        //执行目标方法,并保证目标方法执行后的返回值
        Object rvt = jp.proceed(args);
        System.out.println("Around增强:执行目标方法之后,模拟结束事务...");
        //如果rvt的类型是Integer,将rvt改为它的平方
        if (rvt != null && rvt instanceof Integer) {
            rvt = (Integer) rvt * (Integer) rvt;
        }
        return rvt;
    }

    public void authority(JoinPoint jp) {
        System.out.println("Before增强:模拟执行权限检查");
        //返回被织入增强处理的目标方法
        System.out.println("Beofre增强:被织入增强处理的目标方法为:" + jp.getSignature().getName());
        //访问目标方法的参数
        System.out.println("Before增强:目标方法的参数为:" + Arrays.toString(jp.getArgs()));
        //访问被增强处理的目标对象
        System.out.println("Before增强:被织入增强处理的目标对象为:" + jp.getTarget());
    }

    public void log(JoinPoint jp, Object rvt) {
        System.out.println("AfterReturning增强:获取目标方法返回值" + rvt);
        System.out.println("AfterReturning增强:模拟记录日志功能...");
        System.out.println("AfterReturning增强:被织入增强处理的目标方法为:" + jp.getSignature().getName());
        System.out.println("AfterReturning增强:目标方法参数为:" + Arrays.toString(jp.getArgs()));
        System.out.println("AfterReturning增强:被织入增强处理的目标对象为:" + jp.getTarget());
    }

    public void relaese(JoinPoint jp) {
        System.out.println("AfterReturning增强:模拟方法结束后的释放资源...");
        //返回被织入增强处理的目标方法
        System.out.println("AfterReturning增强:被织入增强处理的目标方法为:" + jp.getSignature().getName());
        //访问执行目标方法的参数
        System.out.println("AfterReturning增强:目标方法的参数为:" + Arrays.toString(jp.getArgs()));
        //访问被增强处理的目标对象
        System.out.println("AfterReturning增强:被织入增强处理的目标对象为:" + jp.getTarget());
    }
}
<?xml version="1.0" encoding="GBK"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       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-4.0.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <aop:config>
        <!--将fourAdviceAspect指定切入点表达式,指定切面的优先级为2-->
        <aop:aspect id="fourAdviceAspect" ref="fourAdviceBean" order="2">
            <aop:after method="relaese" pointcut="execution(* Bean.*.*(..))"/>
            <aop:before method="authority" pointcut="execution(* Bean.*.*(..))"/>
            <aop:after-returning method="log" pointcut="execution(* Bean.*.*(..))" returning="rvt"/>
            <aop:around method="processTx" pointcut="execution(* Bean.*.*(..))"/>
        </aop:aspect>
    </aop:config>
    <bean id="fourAdviceBean" class="Bean.FourAdviceTest"/>
</beans>

3.3,配置切入点

允许重用切入点表达式,XML配置方式也可以通过定义切入点来重用切入点表达式,Spring提供了<aop:pointcut.../>元素来定义切入点。当把<aop:pointcut.../>元素作为<aop:config.../>的子元素定义时,表明该切入点可被多个切面共享;当把<aop:pointcut.../>元素作为<aop:aspect.../>的子元素定义时,表明该切入点只能在该切面中有效。

配置<aop:pointcut.../>元素时通常需要指定如下两个属性:

  • id:指定该切入点的标识名。
  • expression:指定该切入点关联的切入点表达式。
<aop:config>
    <aop:pointcut id="myPointcut" expression="execution(* Bean.*.*(..))"/>
    <!--将fourAdviceAspect指定切入点表达式,指定切面的优先级为2-->
    <aop:aspect id="fourAdviceAspect" ref="fourAdviceBean" order="2">
        <aop:after method="relaese" pointcut-ref="myPointcut"/>
    </aop:aspect>
</aop:config>
### 动态代理概述 动态代理提供了一种机制,在程序运行期间可以动态地创建代理类及其实例,并拦截对这些实例的方法调用。这种方式广泛应用于各种高级功能的实现,例如远程过程调用(RPC)[^1]。 ### 动态代理的实现原理 在Java环境中,动态代理主要通过`java.lang.reflect.Proxy`类和`InvocationHandler`接口来完成。当客户端请求访问某个目标对象时,实际上会先经过由`Proxy.newProxyInstance()`方法生成的一个代理对象;该代理负责截获所有针对被代理对象的操作并转发给指定处理器——即实现了`InvocationHandler`接口的具体类处理。后者则定义了一个通用模板用于捕获所有的方法调用事件,并允许开发者自定义逻辑去响应它们[^2]。 对于C#而言,则可以通过.NET Framework所提供的`RealProxy`基类或者第三方库如Castle DynamicProxy等工具包来进行类似的开发工作。 ### 应用场景分析 #### 远程服务调用 (RPC) 借助于动态代理技术,可以在不改变原有业务代码的基础上轻松集成跨平台通信能力。每当应用程序尝试调用远端服务器上的某项操作时,本地就会自动触发相应的代理函数执行序列化参数、发起网络请求等一系列必要的准备工作,待收到回复后再反序列化数据返回给调用方作为最终结果呈现出来[^1]。 #### 面向切面编程(AOP) 这是另一个典型的应用领域,特别是在Spring框架下更是如此。AOP旨在将那些散布在整个系统的横切关注点分离出去单独管理,从而提高模块间的解耦合度。而要达成这一点的关键就在于能够灵活操控各个连接点的行为模式—而这正是动态代理所擅长之处:它能帮助我们轻易实现在特定条件下环绕通知等功能需求[^3]。 ```csharp // C#示例: 创建一个简单的动态代理 public class MyInvocationHandler : RealProxy { public override IMessage Invoke(IMessage msg){ Console.WriteLine("Method called."); return ChannelServices.CreateMessage(null, null); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

燕双嘤

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

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

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

打赏作者

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

抵扣说明:

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

余额充值