翻译 aop

spring boot 源码学习

identityHashCode 与 hashCode区别
1. identityHashCode会返回对象的hashCode,而不管对象是否重写了hashCode方法。
2. identityHashCode 与地址有关,hashCode 没有。
如:String str1 = new String("abc"); String str2 = new String("abc"); 的hashcode 一样identityHashCode 是不一样的。

AOP 在spring应用

介绍aop

11.1
Introduction

aop 提供了另一方式思考编程架构 完善了oop 。OOP中模块化的关键单元是类,而在AOP中,模块化的单元是方面。方面支持关注点的模块化,例如跨多个类型和对象的事务管理。(在AOP文献中,这种关注通常被称为横切关注。)Spring的关键组件之一是AOP框架。虽然Spring IoC容器不依赖于AOP,这意味着如果你不想使用AOP,就不需要引入AOP,但是AOP补充了springioc,提供了一个非常有能力的中间件解决方案。


11.1.1 AOP concepts
让我们首先定义一些核心AOP概念和术语。这些术语不是特定于Spring的……不幸的是,AOP术语不是特别直观;但是,如果Spring使用自己的术语,则会更加混乱。

  • Aspect: 跨越多个类的关注点模块化。事务管理是企业Java应用程序中横切关注点的一个很好的例子。在Spring AOP中,方面是使用常规类(基于模式的方法)或使用@Aspect注释的常规类(@AspectJ样式)实现的。
  • Join point: 程序执行过程中的一点,如方法的执行或异常的处理。在Spring AOP中,连接点总是表示方法的执行。
  • Advice: 一个方面在特定连接点上采取的行动。不同类型的Advice包括“around”、“before”和“after”Advice。(Advice类型在下面讨论)许多AOP框架,包括Spring,将Advice建模为拦截器,在连接点周围维护拦截器链。
    +Pointcut: 匹配连接点的谓词。通知与切入点表达式相关联,并在与切入点匹配的任何连接点处运行(例如,执行具有特定名称的方法)。由切入点表达式匹配的连接点的概念是AOP的核心,Spring默认使用AspectJ切入点表达式语言。
  • Introduction:代表类型声明其他方法或字段。Spring AOP允许您向任何建议的对象引入新接口(和相应的实现)。例如,可以使用一个简介使bean实现一个IsModified接口,以简化缓存。(简介在AspectJ社区中称为类型间声明。)
  • Traget object: 由一个或多个方面建议的对象。也称为建议对象。由于Spring AOP是使用运行时代理实现的,所以这个对象将始终是一个代理对象。
  • AOP proxy: AOP框架创建的一个对象,用于实现方面契约(advise method executions等)。在Spring框架中,AOP代理将是JDK动态代理或CGLIB代理。
  • Weaving: 将方面与其他应用程序类型或对象链接以创建建议对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。Spring AOP和其他纯javaaop框架一样,在运行时执行编织。

advice的类型:

  • Before advice:在连接点之前执行但不能阻止执行流继续到连接点的通知(除非它引发异常)。
  • After returning advice: 连接点正常(无异常)执行返回后,会执行通知 不会改变返回结果。
  • After throwing advice: 连接点执行发生异常,则会执行通知。
  • After (finally) advice: 连接点无论发生什么,都会执行通知。
  • Around advice: 包围连接点(如方法调用)的建议。这是最有力的通知。Around通知可以在方法调用前后执行自定义行为。它还负责通过返回自己的返回值或引发异常来选择是继续到连接点还是缩短通知方法的执行

围绕建议是最普遍的一种建议。由于Spring AOP和AspectJ一样,提供了一系列的建议类型,我们建议您使用最不强大的建议类型来实现所需的行为。例如,如果只需要使用方法的返回值更新缓存,那么最好实现after-return建议,而不是around建议,尽管around建议可以完成相同的任务。使用最具体的建议类型提供了一个更简单的编程模型,减少了出错的可能性。例如,您不需要对around建议使用的JoinPoint调用proceed()方法,因此不能调用失败。
在Spring2.0中,所有的通知参数都是静态类型的,因此您可以使用适当类型的通知参数(例如方法执行返回值的类型),而不是OBJECT数组。
与切入点匹配的连接点概念是AOP的关键,AOP将其与仅提供拦截的旧技术区分开来。切入点使建议能够独立于面向对象的层次结构而成为目标。例如,提供声明性事务管理的around通知可以应用于跨越多个对象(例如服务层中的所有业务操作)的一组方法。


11.1.2 spring AOP 能力和目标
Spring AOP是用纯Java实现的。不需要特殊的编译过程。Spring AOP不需要控制类装入器层次结构,因此适合在Servlet容器或应用程序服务器中使用
Spring AOP目前只支持方法执行连接点(建议在springbean上执行方法)。虽然可以在不破坏核心Spring AOP api的情况下添加对字段侦听的支持,但未实现字段侦听。如果需要建议字段访问和更新连接点,请考虑使用AspectJ这样的语言。
Spring AOP的AOP方法不同于大多数其他AOP框架。其目的不是提供最完整的AOP实现(尽管springaop非常有能力);而是提供AOP实现和Spring IoC之间的紧密集成,以帮助解决企业应用程序中的常见问题。
因此,例如,Spring框架的AOP功能通常与Spring IoC容器一起使用。方面是使用普通的bean定义语法配置的(尽管这允许强大的“自动代理”功能):这与其他AOP实现有着重要的区别。在Spring AOP中,有些事情是不容易或效率不高的,比如建议非常细粒度的对象(比如通常是域对象):在这种情况下,AspectJ是最好的选择。然而,我们的经验是,Spring AOP为企业Java应用程序中的大多数问题提供了一个很好的解决方案,这些问题都是可以接受AOP的。
Spring AOP将永远不会与AspectJ竞争,提供全面的AOP解决方案。我们相信,基于代理的框架(如Spring AOP)和成熟的框架(如AspectJ)都是有价值的,它们是互补的,而不是竞争中的。Spring将Spring AOP和IoC与AspectJ无缝集成,使AOP的所有使用都能在一个一致的基于Spring的应用程序体系结构中得到满足。这种集成不会影响Spring AOP API或AOP联盟API:springaop保持向后兼容。有关SpringAOAPIs的讨论,请参见下一章。


11.1.3 AOP 代理
Spring AOP默认为AOP代理使用标准JDK动态代理。这允许代理任何接口(或一组接口)。

spring boot 2.0 以上默认使用cglib动态代理;其它仍然是 jdk动态代理
spring.aop.proxy-target-class=true/false 来设置代理类型

Spring AOP还可以使用CGLIB代理。这是代理类而不是接口所必需的。如果业务对象未实现接口,则默认使用CGLIB。因为编程到接口而不是类是很好的实践;业务类通常会实现一个或多个业务接口。在需要建议未在接口上声明的方法,或者需要将代理对象作为具体类型传递给方法的情况下(希望很少),可以强制使用CGLIB。


11.2 @AspectJ support
@AspectJ指的是一种将方面声明为带有注解的常规Java类的样式。AspectJ项目作为AspectJ 5发行版的一部分引入了@AspectJ样式。Spring使用AspectJ提供的库进行切入点解析和匹配,解释与AspectJ 5相同的注释。AOP运行时仍然是纯springaop,并且不依赖于AspectJ编译器或weaver。

11.2.1 @AspectJ support

要在Spring配置中使用@AspectJ方面,您需要启用Spring支持,以便基于@AspectJ方面配置Spring AOP,并根据这些方面是否建议它们来自动代理bean。通过自动代理,我们的意思是如果Spring确定一个bean由一个或多个方面提供建议,它将自动为该bean生成一个代理,以拦截方法调用并确保根据需要执行建议。
@AspectJ支持可以通过XML或Java样式配置启用。在这两种情况下,您还需要确保AspectJaspectjweaver.jar文件库位于应用程序(1.6.8或更高版本)的类路径上。此库位于AspectJ发行版的“lib”目录中,或通过Maven中央存储库提供。

通过Java配置启用@AspectJ支持

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}

通过XML配置启用@AspectJ支持


11.2.2 Declaring an aspect

 
    
package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspectpublic class NotVeryUsefulAspect {

}
    

您可以在Spring XML配置中将方面类注册为常规bean,或者通过类路径扫描自动检测它们,就像其他Spring管理的bean一样。但是,请注意,@Aspect注解不足以在类路径中进行自动检测:为此,您需要添加一个单独的@Component注解(或者根据Spring组件扫描程序的规则,添加一个合格的自定义原型注释)。

在Spring AOP中,不可能让方面本身成为来自其他方面的建议的目标。类上的@Aspect注解将其标记为Aspect,因此将其排除在自动代理之外。

11.2.3声明切入点

回想一下,切入点确定了关注的连接点,从而使我们能够控制何时执行通知。Spring AOP只支持springbean的方法执行连接点,因此您可以将切入点视为与springbean上方法的执行相匹配。切入点声明有两个部分:一个包含名称和任何参数的签名,一个精确确定我们感兴趣的方法执行的切入点表达式。在AOP的@AspectJ注释样式中,切入点签名由常规方法定义提供,切入点表达式使用@pointcut注释表示(用作切入点签名的方法必须具有void返回类型)。
一个例子将有助于明确切入点签名和切入点表达式之间的区别。下面的示例定义了一个名为“anyOldTransfer”的切入点,该切入点将匹配名为“transfer”的任何方法的执行:

@Pointcut("execution(* transfer(..))")// the pointcut expression
private void anyOldTransfer() {}// the pointcut signature

形成@pointcut注解值的切入点表达式是正则AspectJ 5切入点表达式。有关AspectJ切入点语言的完整讨论,请参阅AspectJ编程指南(以及AspectJ 5 Developers笔记本的扩展)或有关AspectJ的书籍之一,如Colyer等人的“Eclipse AspectJ”。或者拉姆尼瓦斯·拉德的“AspectJ在行动”。

Supported Pointcut Designators (支持的切入点标志)
Spring AOP支持在切入点表达式中使用以下AspectJ切入点指示符(PCD):

execution:对于匹配方法执行连接点,这是使用Spring AOP时将使用的主要切入点指示符
within: 将匹配限制为特定类型内的连接点(仅在使用Spring AOP时在匹配类型内声明的方法的执行)
this: 将匹配限制为连接点(使用Spring AOP时方法的执行),其中bean引用(springaop代理)是给定类型的实例
target: 将匹配限制为连接点(使用Spring AOP时方法的执行),其中目标对象(代理的应用程序对象)是给定类型的实例
args:限制对连接点的匹配(使用Spring AOP时方法的执行),其中参数是给定类型的实例
@target:限制与连接点的匹配(使用Spring AOP时方法的执行),其中执行对象的类具有给定类型的注释
@args:限制与连接点的匹配(使用Spring AOP时方法的执行),其中传递的实际参数的运行时类型具有给定类型的注释
@within:限制与具有给定注释的类型中的连接点的匹配(使用Spring AOP时,使用给定注释在类型中声明的方法的执行)
@annotation:限制与连接点的匹配,其中连接点的主题(在Spring AOP中执行的方法)具有给定的注释

由于Spring AOP只将匹配限制为方法执行连接点,因此上面对切入点指示符的讨论给出了比您在AspectJ编程指南中可以找到的更窄的定义。此外,AspectJ本身具有基于类型的语义,在执行连接点,this和target都引用同一个对象——执行方法的对象。Spring AOP是一个基于代理的系统,它区分代理对象本身(绑定到this)和代理后面的目标对象(绑定到target)。

组合切入点表达式
切入点表达式可以使用“&&”、“||”和“!”组合。也可以按名称引用切入点表达式。下面的示例显示了三个切入点表达式:anyPublicOperation(如果方法执行连接点表示任何公共方法的执行,则匹配此表达式);in trading(如果方法执行在交易模块中,则匹配此表达式),和tradingOperation(如果方法执行表示交易模块中的任何公共方法,则匹配此操作)。


@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}

@Pointcut("within(com.xyz.someapp.trading..*)")
private void inTrading() {}

@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}

最好使用上面所示的较小命名组件构建更复杂的切入点表达式。按名称引用切入点时,会应用普通的Java可见性规则(可以看到相同类型的私有切入点、层次结构中受保护的切入点、任何地方的公共切入点等等)。可见性不影响切入点匹配。

共享公共切入点定义
在使用企业应用程序时,您通常希望从多个方面引用应用程序的模块和特定的操作集。我们建议定义一个“SystemArchitecture”方面来捕获用于此目的的公共切入点表达式。典型的这种情况如下:


package com.xyz.someapp;

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

@Aspectpublic class SystemArchitecture {

    /**
     * A join point is in the web layer if the method is defined
     * in a type in the com.xyz.someapp.web package or any sub-package
     * under that.
     */
    @Pointcut("within(com.xyz.someapp.web..*)")
    public void inWebLayer() {}

    /**
     * A join point is in the service layer if the method is defined
     * in a type in the com.xyz.someapp.service package or any sub-package
     * under that.
     */
    @Pointcut("within(com.xyz.someapp.service..*)")
    public void inServiceLayer() {}

    /**
     * A join point is in the data access layer if the method is defined
     * in a type in the com.xyz.someapp.dao package or any sub-package
     * under that.
     */
    @Pointcut("within(com.xyz.someapp.dao..*)")
    public void inDataAccessLayer() {}

    /**
     * A business service is the execution of any method defined on a service
     * interface. This definition assumes that interfaces are placed in the
     * "service" package, and that implementation types are in sub-packages.
     *
     * If you group service interfaces by functional area (for example,
     * in packages com.xyz.someapp.abc.service and com.xyz.someapp.def.service) then
     * the pointcut expression "execution(* com.xyz.someapp..service.*.*(..))"
     * could be used instead.
     *
     * Alternatively, you can write the expression using the 'bean'
     * PCD, like so "bean(*Service)". (This assumes that you have
     * named your Spring service beans in a consistent fashion.)
     */
    @Pointcut("execution(* com.xyz.someapp..service.*.*(..))")
    public void businessService() {}

    /**
     * A data access operation is the execution of any method defined on a
     * dao interface. This definition assumes that interfaces are placed in the
     * "dao" package, and that implementation types are in sub-packages.
     */
    @Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")
    public void dataAccessOperation() {}

}

在这样一个方面中定义的切入点可以引用到任何需要切入点表达式的地方。例如,要使服务层具有事务性,可以编写:


    


    
        
    

那个以及元素在第11.3节“基于模式的AOP支持”中讨论。在第17章,事务管理中讨论了事务元素。

Examples
Spring AOP用户可能最常使用 ' execution' 切入点指示符。执行表达式的格式为:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
下面给出一些常用切入点表达式的示例:
执行任何公共方法:

execution(public * *(..))

任何名称以“set”开头的方法的执行:

execution(* set*(..))

执行AccountService接口定义的任何方法:

execution(* com.xyz.service.AccountService.*(..))

执行服务包中定义的任何方法:

execution(* com.xyz.service.*.*(..))

service包或子包中定义的任何方法的执行:

execution(* com.xyz.service..*.*(..))

service包中的任何连接点(仅在Spring AOP中执行方法):

within(com.xyz.service.*)

service包或子包中的任何连接点(仅在Spring AOP中执行方法):

within(com.xyz.service..*)

代理实现AccountService接口的任何连接点(仅在Spring AOP中执行方法)

this(com.xyz.service.AccountService)

目标对象实现AccountService接口的任何连接点(仅在Spring AOP中执行方法):

target(com.xyz.service.AccountService)

目标对象具有@Transactional注解的任何连接点(仅在Spring AOP中执行方法):

@target(org.springframework.transaction.annotation.Transactional)

@within(org.springframework.transaction.annotation.Transactional)

@annotation(org.springframework.transaction.annotation.Transactional)

bean(tradeService)

bean(*Service)

Writing good pointcuts

11.2.4声明通知

Before advice

@Aspect
public class BeforeExample {

 @Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }

 @Before("execution(* com.xyz.myapp.dao.*.*(..))")
    public void doAccessCheck() {
        // ...
    }
}

After returning advice

@Aspect
public class AfterReturningExample {
  @AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }
    
@AfterReturning(
  pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
        returning="retVal")
    public void doAccessCheck(Object retVal) {
        // ...
    }

}

返回结果属性中使用的名称必须与通知方法中的参数名称相同。当方法执行返回时,返回值将作为相应的参数值传递给advice方法。returning子句还将匹配限制为仅返回指定类型的值(在本例中为Object,它将匹配任何返回值)的方法执行。
注意,在返回device后的参数值不能使用完全不同的引用。
After throwing advice
异常抛出后,advice匹配的方法抛出异常退出时运行。advice执行,通过使用@AfterThrowing注释声明:

 @AfterThrowing(   pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()", throwing="ex")
    public void doRecoveryActions(DataAccessException ex) {
         
    }

抛出异常属性中使用的名称必须与通知方法中的参数名称相同。当通过抛出异常退出执行方法时,异常将作为相应的参数值传递给advice方法。throwing还将匹配限制为只执行引发指定类型异常(本例中为DataAccessException)的方法。
After (finally) advice

在(最后)通知运行之后,但是存在匹配的方法执行。它是使用@After注释声明的。After通知必须准备好处理正常和异常返回条件。它通常用于释放资源等

Around advice
最后一种建议是围绕着建议。Around建议运行“Around”匹配的方法执行。它有机会在方法执行之前和之后进行工作,并确定方法何时、如何、甚至是否真正执行。如果需要以线程安全的方式(例如启动和停止计时器)共享方法执行前后的状态,则经常使用Around建议。始终使用最不强大的建议形式,以满足您的要求(即,不要使用周围的建议,如果简单的建议之前会做)。

Around建议是使用@Around注释声明的。通知方法的第一个参数必须是ProceedingJoinPoint类型。在通知正文中,对ProceedingJoinPoint调用proceed()将执行底层方法。proceed方法也可以调用传入一个object[ ]-当它继续执行时,数组中的值将用作方法执行的参数。
Around通知返回的值将是方法调用方看到的返回值。例如,一个简单的缓存方面可以从缓存返回一个值(如果它有一个值),如果没有,则调用proceed()。注意,proceed可以调用一次、多次,或者根本不在around建议的主体内调用,所有这些都是合法的。
Advice parameters
Spring提供了完整类型的通知——这意味着您在advice签名中声明所需的参数(正如我们在上面的返回和抛出示例中看到的那样),而不是一直使用Object[]数组。我们稍后将看到如何使参数和其他上下文值可供咨询机构使用。首先,让我们看看如何编写通用的advice,以了解当前advice的方法。
访问当前连接点

任何advice方法都可以声明为其第一个参数,即org.aspectj.lang网站.JoinPoint(请注意,around通知需要声明ProceedingJoinPoint类型的第一个参数,它是JoinPoint的子类。JoinPoint接口提供了许多有用的方法,如getArgs()(返回方法参数)、getThis()(返回代理对象)、getTarget()(返回目标对象)、getSignature()(返回被建议方法的描述)和toString()
将参数传递给通知
我们已经看到了如何绑定返回值或异常值(advice 的使用)。要使参数值对通知正文可用,可以使用args的绑定形式。如果在args表达式中使用参数名代替类型名,则在调用通知时,相应参数的值将作为参数值传递。举个例子可以让这一点更清楚。假设您希望建议以Account对象作为第一个参数的dao操作的执行,并且需要访问通知正文中的帐户。

@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")public void validateAccount(Account account) {
    // ...
}

切入点表达式的args(account,…)部分有两个用途:首先,它将匹配限制为只执行那些方法,其中该方法至少接受一个参数,并且传递给该参数的参数是account的实例;其次,它通过account参数使通知可以使用实际的account对象。

另一种编写方法是声明一个切入点,该切入点在与连接点匹配时“提供”Account对象值,然后从通知中引用指定的切入点。如下所示:

@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")private void accountDataAccessOperation(Account account) {}

@Before("accountDataAccessOperation(account)")public void validateAccount(Account account) {
    // ...
}

感兴趣的读者将再次参阅AspectJ编程指南以了解更多详细信息。
代理对象(this)、目标对象(target)和批注(@within、@target、@annotation、@args)都可以以类似的方式绑定。下面的示例演示如何匹配使用@Auditable注释注释的方法的执行,并提取审计代码。
首先定义@Auditable注释:

example

一种锁的重试机制


@Aspectpublic class ConcurrentOperationExecutor implements Ordered {

    private static final int DEFAULT_MAX_RETRIES = 2;

    private int maxRetries = DEFAULT_MAX_RETRIES;
    private int order = 1;

    public void setMaxRetries(int maxRetries) {
        this.maxRetries = maxRetries;
    }

    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    @Around("com.xyz.myapp.SystemArchitecture.businessService()")
    public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
        int numAttempts = 0;
        PessimisticLockingFailureException lockFailureException;
        do {
            numAttempts++;
            try {
                return pjp.proceed();
            }
            catch(PessimisticLockingFailureException ex) {
                lockFailureException = ex;
            }
        } while(numAttempts <= this.maxRetries);
        throw lockFailureException;
    }

}

你可能感兴趣的:(翻译 aop)