,让我们首先定义一些核心的AOP概念和术语:,Spring AOP包括以下类型的Advice:,Around advice是最通用的一种Advice。由于Spring AOP和AspectJ一样,提供了全面的Advice类型,我们建议你使用功能最弱的Advice类型来实现所需的行为。例如,如果你只需要用一个方法的返回值来更新缓存,那么你最好实现一个After returning advice,而不是一个Around advice,尽管Around advice可以完成同样的事情。使用最具体的Advice类型可以提供一个更简单的编程模型,减少错误的可能性。,所有的Advice参数都是静态类型的,因此你可以使用适当类型的Advice参数(例如,方法执行的返回值的类型),而不是Object数组。,由切点匹配的连接点的概念是AOP的关键,它区别于只提供拦截的旧技术。Pointcuts使Advice能够独立于面向对象的层次结构而成为目标。例如,你可以将提供声明性事务管理的建议应用于跨越多个对象的一组方法(如服务层的所有业务操作)。,Spring AOP默认使用标准的JDK动态代理作为AOP代理。这使得任何接口(或一组接口)都可以被代理。,Spring AOP也可以使用CGLIB代理。这对于代理类(不是接口)来说是必要的。默认情况下,如果一个业务对象没有实现一个接口,就会使用CGLIB。由于对接口而不是类进行编程是很好的做法,业务类通常实现一个或多个业务接口。在那些(希望是罕见的)需要向未在接口上声明的方法提供Advice的情况下,或者需要将代理对象作为具体类型传递给方法的情况下,可以强制使用CGLIB。,@AspectJ指的是将aspect作为带有注解的普通Java类来声明的一种风格。,要在Spring配置中使用@AspectJ切面,需要启用Spring支持,以根据@AspectJ切面配置Spring AOP,并根据Bean是否被这些切面通知而自动代理。通过自动代理,我们的意思是,如果Spring确定一个Bean被一个或多个切面所通知,它会自动为该Bean生成一个代理,以拦截方法调用,并确保建议在需要时被运行。,@AspectJ支持可以通过XML或Java风格的配置来启用。在这两种情况下,你还需要确保AspectJ的aspectjweaver.jar库在你的应用程序的classpath上(版本1.8或更高)。,用Java配置启用@AspectJ 要通过Java @Configuration启用@AspectJ支持,请添加@EnableAspectJAutoProxy注解,如下例所示:,在启用@AspectJ的情况下,任何在你的应用程序上下文中定义的Bean,其类是@AspectJ切面(具有@Aspect注解),会被Spring自动检测到,并用于配置Spring AOP。接下来的两个例子显示了一个不怎么有用的切面所需的最小定义:,Pointcuts确定连接点,从而使我们能够控制Advice的运行时间。Spring AOP只支持Spring Bean的方法执行连接点,所以你可以把pointcut看作是与Spring Bean上的方法执行相匹配。一个切点声明有两个部分:一个由名称和任何参数组成的签名,以及一个切点表达式,它决定了我们到底对哪些方法的执行感兴趣。在AOP的@AspectJ注解式中,一个pointcut签名是由一个常规的方法定义提供的,而pointcut表达式是通过使用@Pointcut注解来表示的(作为pointcut签名的方法必须有一个void返回类型)。,Spring AOP支持以下AspectJ的切点表达式,以便在切点表达式中使用:,你可以通过使用&&、||和!来组合pointcut表达式。你也可以通过名字来引用pointcut表达式。下面的例子显示了三个切点表达式:,在处理企业应用程序时,开发人员经常想从几个方面来引用应用程序的模块和特定的操作集。我们建议定义一个CommonPointcuts切面,它可以为这个目的捕获通用的pointcut表达。,Advice与一个切点表达式相关联,在切点匹配的方法执行之前、之后或around 运行。该切点表达式可以是对一个命名的切点的简单引用,也可以是一个就地声明的切点表达式。,你可以通过使用@Before注解在一个切面声明before advice:,如果我们使用pointcut表达式,我们可以把前面的例子改写成下面的例子:,当一个匹配的方法执行正常返回时,返回后的Advice会运行。你可以通过使用@AfterReturning注解来声明它:,有时,你需要访问Advice中返回的值。你可以使用绑定返回值的@AfterReturning的形式来获得这种访问权,如下例所示:,当一个匹配的方法的执行因抛出异常而退出时,After Throwing Advice会运行。你可以通过使用@AfterThrowing注解来声明它,如下例所示:,通常情况下,你希望Advice只在给定类型的异常被抛出时运行,而且你也经常需要在Advice中访问被抛出的异常。你可以使用throwing属性来限制匹配(如果需要的话–否则使用Throwable作为异常类型),并将抛出的异常绑定到advice参数上。,当一个匹配的方法执行退出时,After (Finally) Advice会运行。它是通过使用@After注解来声明的。After通知必须准备好处理正常和异常的返回条件。它通常用于释放资源和类似的目的。下面的例子展示了如何使用after finally通知:,最后一种advice是Around Advice。Around Advice是一个与匹配的方法的执行而运行。它有机会在方法运行之前和之后进行工作,并决定何时、如何、甚至是否真正运行该方法。如果你需要以线程安全的方式分享方法执行前后的状态,例如启动和停止一个定时器,那么Around Advice经常被使用。,Around Advice是通过用@Around注解来声明一个方法的。该方法应该声明Object为其返回类型,并且该方法的第一个参数必须是ProceedingJoinPoint类型。在Advice中,你必须在ProceedingJoinPoint上调用proceed(),以使底层方法运行。在没有参数的情况下调用proceed()将导致调用者的原始参数在底层方法被调用时被提供给它。对于高级用例,有一个重载的 proceed() 方法,它接受一个参数数组(Object[] )。当底层方法被调用时,数组中的值将被用作该方法的参数。,Spring提供了完全类型化的Advice,这意味着你可以在Advice签名中声明你需要的参数(就像我们在前面看到的返回和抛出的例子那样),而不是一直使用Object[]数组。我们将在本节后面看到如何使参数和其他上下文值对Advice主体可用。首先,我们看一下如何编写通用Advice,它可以找出advice当前所通知的方法。,任何Advice方法都可以声明一个org.aspectj.lang.JoinPoint类型的参数,作为它的第一个参数。请注意,aroud advice需要声明一个ProceedingJoinPoint类型的第一个参数,它是JoinPoint的一个子类。,JoinPoint接口提供了许多有用的方法:,向advice传递参数我们已经看到了如何绑定返回值或异常值(使用返回后和抛出后建议)。为了使参数值对advice主体可用,你可以使用args的绑定形式。如果你在args表达式中使用参数名来代替类型名,那么当advice被调用时,相应参数的值将作为参数值被传递。一个例子可以让我们更清楚地了解这一点。假设你想advice执行将一个帐户对象作为第一个参数的DAO操作,并且你需要在advice中访问该帐户。你可以写如下内容:,pointcut表达式args(account,..)部分有两个目的。首先,它将匹配限制在方法的执行上,即方法至少需要一个参数,并且传递给该参数的参数是一个账户的实例。其次,它使实际的账户对象通过账户参数对advice可用。,另一种写法是声明一个pointcut,当它与一个连接点匹配时 “提供 “账户对象的值,然后从advice中引用这个命名的pointcut:,Advice参数和泛型 Spring AOP可以处理类声明和方法参数中使用的泛型。假设你有一个像下面这样的泛型:,你可以将方法类型的拦截限制在某些参数类型上,办法是将advice参数与你想拦截方法的参数类型联系起来:,指定参数名称Advice调用中的参数绑定依赖于将pointcut表达式中使用的名称与advice和pointcut方法签名中声明的参数名称相匹配。参数名称无法通过Java反射获得,因此Spring AOP使用以下策略来确定参数名称。,如果用户已经明确指定了参数名,则使用指定的参数名。Advice和pointcut注解都有一个可选的argNames属性,你可以用它来指定被注解方法的参数名。这些参数名在运行时是可用的。下面的例子展示了如何使用argNames属性:,如果第一个参数是JoinPoint、ProceedingJoinPoint或JoinPoint.StaticPart类型,你可以从argNames属性的值中省略参数的名称。例如,如果你修改前面的advice以接收连接点对象,argNames属性不需要包括它:,带参数的进程 我们在前面说过,我们将描述如何编写一个在Spring AOP和AspectJ中一致运行的带参数的继续调用。解决方案是确保advice签名按顺序绑定每个方法参数。下面的例子展示了如何做到这一点:,当多个advice都想在同一个连接点运行时,会发生什么?Spring AOP遵循与AspectJ相同的优先级规则来决定advice的执行顺序。优先级最高的advice在 “进入 “时首先运行。从一个连接点 “出来 “时,优先级最高的advice最后运行。,Introductions(在AspectJ中被称为类型间声明)使一个切面能够声明advice对象实现一个给定的接口,并代表这些对象提供该接口的实现。,你可以通过使用@DeclareParents注解来做一个Introductions。这个注解被用来声明匹配的类型有一个新的父类(因此得名)。例如,给定一个名为UsageTracked的接口和一个名为DefaultUsageTracked的接口的实现,下面这个切面声明所有服务接口的实现者也实现UsageTracked接口(例如,通过JMX进行统计):,默认情况下,每个切面在应用上下文中都有一个实例。AspectJ把这称为单例实例模型。我们可以用其他的生命周期来定义方面。Spring支持AspectJ的perthis和pertarget实例化模型;目前不支持percflow、percflowbelow和pertypewithin。你可以通过在@Aspect注解中指定一个perthis子句来声明一个perthis方面。请看下面的例子:,业务服务的执行有时会因为并发性问题而失败(例如,死锁失败者)。如果该操作被重试,那么它很可能在下一次尝试中成功。对于在这种情况下适合重试的业务服务(不需要回到用户那里解决冲突的idempotent操作),我们希望透明地重试操作,以避免客户端看到PessimisticLockingFailureException。这是一个明显跨越服务层中多个服务的需求,因此,非常适合通过一个切面来实现。,因为我们想重试操作,所以我们需要使用around advice,以便我们可以多次调用Proceed。下面的列表显示了切面基本的实现。,注意,这个方面实现了Ordered接口,这样我们就可以把切面的优先级设置得比事务advice高(我们希望每次重试都是一个新的事务)。maxRetries和order属性都是由Spring配置的。主要的动作发生在around advice的doConcurrentOperation中。请注意,就目前而言,我们将重试逻辑应用于每个businessService()。我们尝试进行,如果失败了,出现PessimisticLockingFailureException,我们就再试一次,除非我们已经用尽了所有的重试尝试。,为了细化这个切面,使它只重试幂等操作,我们可以定义下面的幂等注解:,然后我们可以使用注解来注解服务操作的实现。对只重试幂等操作切面的修改涉及到细化pointcut表达式,以便只有@Idempotent操作可以匹配,如下所示:
© 版权声明
文章版权归作者所有,未经允许请勿转载。