spring5.1.3第五章-使用spring面向切面编程

5. Aspect Oriented Programming with Spring

       AOP提供另一种思想程序结构的方式对OOP进行补充;AOP框架是spring中的一个关键组件,但spring不依赖AOP,可以使用,AOP补充了IOC提供非常强大的中间件解决方案

5.1. AOP Concepts

       关键词定义:

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

       Spring AOP包含advice的类型:

  1. Before advice:切点之前运行,但无法阻止执行流程进入切点(除非异常)
  2. After returning advice:切点之后执行(切点不能有异常)
  3. After throwing advice:程序执行异常时执行
  4. After (finally) advice:无论切点退出的方式如何(正常/异常),都执行
  5. Around advice:切点之前、之后执行自定义的行为,

在Spring 2.0中,所有通知参数都是静态类型的,因此您可以使用相应类型的Advice参数(例如,方法执行的返回值的类型)而不是Object数组。

5.2. Spring AOP Capabilities and Goals

       功能及目标

       Spring AOP目前仅支持方法执行连接点(建议在Spring bean上执行方法)。虽然可以在不破坏核心Spring AOP API的情况下添加对字段拦截的支持,但未实现字段拦截。如果您需要建议字段访问和更新连接点,请考虑使用AspectJ等语言。

       Spring AOP的AOP方法与大多数其他AOP框架的方法不同。目的不是提供最完整的AOP实现(尽管Spring AOP非常强大)。相反,目标是在AOP实现和Spring IoC之间提供紧密集成,以帮助解决企业应用程序中的常见问题。Spring Framework的AOP功能通常与Spring IoC容器一起使用,通过使用普通bean定义语法来配置方面(尽管这允许强大的“自动代理”功能)。

Spring将Spring AOP和IoC与AspectJ无缝集成,以在一致的基于Spring的应用程序架构中实现AOP的所有使用

5.3. AOP Proxies

       Spring AOP默认使用AOP代理的标准JDK动态代理。这使得任何接口(或接口集)都可以被代理。

       Spring AOP也可以使用CGLIB代理。这是代理类而不是接口所必需的。默认情况下,如果业务对象未实现接口,则使用CGLIB。也可以强制使用这种

5.4. @AspectJ support

       Spring使用AspectJ提供的库来解释与AspectJ 5相同的注释,用于切入点分析和匹配。但是,AOP运行时仍然是纯Spring AOP,并且不依赖于AspectJ编译器或weaver。

5.4.1. Enabling @AspectJ Support

       可以使用XML或Java样式配置启用@AspectJ支持。在任何一种情况下,您还需要确保AspectJ的aspectjweaver.jar库位于应用程序的类路径中(版本1.8或更高版本)

       两种方式开启@AspectJ支持

//注解配置
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}


//xml配置

5.4.2. Declaring an Aspect

       声明切面

       如果bean中存在@Aspect注解,Spring会自动检测并用于配置Spring AOP

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

@Aspect
public class NotVeryUsefulAspect {

}

       切面(就是带有注释的类)与正常的类一样,还包含切点,advice,Introduction(inter-type)声明。

5.4.3. Declaring a Pointcut

       声明切点

//@Pointcut注释值的切入点表达式是常规的AspectJ 5切入点表达式。
@Pointcut("execution(* transfer(..))")// the pointcut expression
private void anyOldTransfer() {}// the pointcut signature

Spring AOP仅限制与方法执行连接点的匹配

Spring AOP支持以下AspectJ切入点指示符(PCD)用于切入点表达式:

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

完整的AspectJ切入点语言支持未在Spring支持额外的切入点指示符:call,get,set,preinitialization, staticinitialization,initialization,handler,adviceexecution,withincode,cflow, cflowbelow,if,@this,和@withincode。在Spring AOP解释的切入点表达式中使用这些切入点指示符会导致IllegalArgumentException被抛出。

       Spring AOP还支持另一个名为的PCD bean。此PCD允许您将连接点的匹配限制为特定的命名Spring bean或一组命名的Spring bean(使用通配符时)。该beanPCD具有下列形式:
bean(idOrNameOfBean)
idOrNameOfBean令牌可以是任何Spring bean的名字。提供了使用该*字符的有限通配符支持,因此,如果为Spring bean建立了一些命名约定,则可以编写beanPCD表达式来选择它们。与其他切入点指示符一样,beanPCD也可以与&&(和),||(或)和!(否定)运算符一起使用。

       切点表达式

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

//指定包下的方法
@Pointcut("within(com.xyz.someapp.trading..*)")
private void inTrading() {} 

//同时符合这两个方法规则
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {} 

示例:

//service包中定义的所有方法
execution(* com.xyz.service.*.*(..))

//service包及其子包中定义的所有方法
execution(* com.xyz.service..*.*(..))

//servie包中的切点(仅在spring Aop中)
within(com.xyz.service.*)
//servie包及其子包(仅在spring Aop中)
within(com.xyz.service..*)

//实现这个接口的代理
this(com.xyz.service.AccountService)

//实现这个接口的目标对象的切点
target(com.xyz.service.AccountService)

//单个参数,并且参数类型是Serializable
args(java.io.Serializable)


//目标对象带这个注解,应该是指类上
@target(org.springframework.transaction.annotation.Transactional)
//目标对象的声明类型据用注解
@within(org.springframework.transaction.annotation.Transactional)
//方法上有这个注解
@annotation(org.springframework.transaction.annotation.Transactional)
//有一个参数,并且参数类有这个注解
@args(com.xyz.security.Classified)

//按bean名称
bean(tradeService)
bean(*Service)

切入点编写建议:
动态匹配切入点是一个代价高昂的过程,所以在写切入点是需要尽可能缩小匹配的范围,故使用切点匹配时,至少应该包含kinded和scoping的过滤条件

匹配的范围:
kinded:execution,get,set,call,和handler。
scoping:within和withincode
contextual:this,target和@annotation

5.4.4. Declaring Advice

       

Before Advice
//在advice之前声明
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

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

}


//返回advice后,匹配的方法执行正常返回
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

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

}

//-------------------------------------------------
//After Returning Advice
//绑定返回值类型;returning属性中使用的名称必须与advice方法中的参数名称相对应;当方法执行返回时,返回值作为相应的参数值传递给advice方法
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

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

}

//-------------------------------------------------
//After Throwing Advice
//抛出异常时执行
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

    @AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doRecoveryActions() {
        // ...
    }

}

//指定异常类型
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

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

}

//-------------------------------------------------
//After (Finally) Advice
//一定会执行的方法,类似于finally代码块

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

@Aspect
public class AfterFinallyExample {

    @After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doReleaseLock() {
        // ...
    }

}

//-------------------------------------------------
//Around Advice

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;

@Aspect
public class AroundExample {

    //方法参数的第一个参数必须是ProceedingJoinPoint
    @Around("com.xyz.myapp.SystemArchitecture.businessService()")
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        // start stopwatch
        Object retVal = pjp.proceed();
        // stop stopwatch
        return retVal;
    }

}

       advice 参数
org.aspectj.lang.JoinPoint

//参数传递:
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
public void validateAccount(Account account) {
    // ...
}
//它将匹配仅限于那些方法至少采用一个参数的方法执行,而传递给该参数的参数是一个实例Account。其次,它Account通过account 参数使实际对象可用于advice


//第二种方法
@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
private void accountDataAccessOperation(Account account) {}

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

代理对象(this),目标对象(target),和说明(@within, @target,@annotation,和@args)都可以以类似的方式结合

@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
    // Advice implementation
}

确定参数名:

//1.如果参数已知
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
        argNames="bean,auditable")
public void audit(Object bean, Auditable auditable) {
    AuditCode code = auditable.value();
    // ... use code and bean
}

//2.如果第一个参数是的JoinPoint,ProceedingJoinPoint或 JoinPoint.StaticPart类型;argNames可以不包含它
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
        argNames="bean,auditable")
public void audit(JoinPoint jp, Object bean, Auditable auditable) {
    AuditCode code = auditable.value();
    // ... use code, bean, and jp
}

//3.如果只有JoinPoint,argNames可省
@Before("com.xyz.lib.Pointcuts.anyPublicMethod()")
public void audit(JoinPoint jp) {
    // ... use jp
}

Advice Ordering
多个advice对应一个连接点时,默认是不存在顺序的,可使用org.springframework.core.Ordered指定优先级

5.4.5. Introductions

       

给定一个名为interface的接口UsageTracked和该接口的实现DefaultUsageTracked,以下方面声明服务接口的所有实现者也实现了UsageTracked接口???

@Aspect
public class UsageTracking {

    @DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class)
    public static UsageTracked mixin;

    @Before("com.xyz.myapp.SystemArchitecture.businessService() && this(usageTracked)")
    public void recordUsage(UsageTracked usageTracked) {
        usageTracked.incrementUseCount();
    }

}

5.4.6. Aspect Instantiation Models

       您可以通过perthis在@Aspect 注释中指定子句来声明方面

@Aspect("perthis(com.xyz.myapp.SystemArchitecture.businessService())")
public class MyAspect {

    private int someState;

    @Before(com.xyz.myapp.SystemArchitecture.businessService())
    public void recordServiceUsage() {
        // ...
    }

}
//该'perthis'子句的作用是为执行业务服务的每个唯一服务对象创建一个方面实例

5.4.7. An AOP Example

       示例:对于高并发情况,服务调用可能会报错,如果重试可能会成功;针对这种需要重试的服务,可以视同around实现

@Aspect
public 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;
    }

}


///

//实现order接口,所以可以设置优先级

    
    



//优化只有Idempotent 的才走
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    // marker annotation
}

@Around("com.xyz.myapp.SystemArchitecture.businessService() && " +
        "@annotation(com.xyz.myapp.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
    ...
}

5.5. Schema-based AOP Support

       使用此功能需要spring-aop支持

基于schema的aop支持

5.5.1. Declaring an Aspect

       声明一个aspect


    
        ...
    



    ...

5.5.2. Declaring a Pointcut

       声明切点



    



//第二种写法,引用了@Aspects定义的命名切点


    




//可以在aspect里面定义,同上;也可能定义切入方法类型


    

        

        

        ...
    

5.5.3. Declaring Advice

       

//before advice


    

    ...






    

    ...



//after returnning advice


    

    ...





    

    ...



//after throwing advice


    

    ...





    

    ...



//After (Finally) Advice


    

    ...



//Around Advice


    

    ...


参数:多个参数可以使用逗号分隔


    

        

            

            

        

5.5.4. Introductions

       



    

    


5.5.5. Aspect Instantiation Models

       实例化模型目前只支持单例

5.5.6. Advisors

       



    

    




    
        
    

5.5.7. An AOP Schema Example

       

5.6. Choosing which AOP Declaration Style to Use

       

5.6.1. Spring AOP or Full AspectJ?

       Spring AOP比使用完整的AspectJ更简单,因为不需要在开发和构建过程中引入AspectJ编译器/ weaver。如果您只需要建议在Spring bean上执行操作,那么Spring AOP是正确的选择。如果需要建议不受Spring容器管理的对象(例如域对象),则需要使用AspectJ。如果您希望建议除简单方法执行之外的连接点(例如,字段获取或设置连接点等),则还需要使用AspectJ。

5.6.2. @AspectJ or XML for Spring AOP?

       
XML风格有两个缺点:1、它不能在单个地方完全封装它所处理的需求的实现。DRY原则认为系统内任何知识片段都应该有一个单一的、明确的、权威性的表示。当使用XML样式时,关于如何实现需求的知识在配置文件中的支持bean类和XML的声明之间被分割。当您使用@AspectJ样式时,这个信息被封装在一个单独的模块中:.。2、XML样式在表达内容上比AspectJ样式稍微受限制:只支持“singleton”方面实例化模型,并且不能组合在XML中声明的命名切入点。

@Pointcut("execution(* get*())")
public void propertyAccess() {}

@Pointcut("execution(org.xyz.Account+ *(..))")
public void operationReturningAnAccount() {}

//aspectJ可以提供组合模式,xml不行
@Pointcut("propertyAccess() && operationReturningAnAccount()")
public void accountPropertyAccess() {}





只要您的方面不仅仅是简单的企业服务配置,Spring团队更喜欢@AspectJ风格。

5.7. Mixing Aspect Types

       通过使用自动代理支持,模式定义aop:aspect方面,aop:advisor声明的顾问程序,甚至在相同配置中使用Spring 1.2样式定义的代理和拦截器,完全可以混合@AspectJ样式方面

5.8. Proxying Mechanisms

       如果要代理的目标对象实现至少一个接口,则使用JDK动态代理。目标类型实现的所有接口都是代理的。如果目标对象未实现任何接口,则会创建CGLIB代理。

如果要强制使用CGLIB代理(例如,代理为目标对象定义的每个方法,而不仅仅是那些由其接口实现的方法),您可以这样做。但是,您应该考虑以下问题:

  • final 方法无法建议,因为它们无法被覆盖。
  • 从Spring 3.2开始,不再需要将CGLIB添加到项目类路径中,因为CGLIB类被重新打包org.springframework并直接包含在spring-core JAR中。这意味着基于CGLIB的代理支持“正常工作”,与JDK动态代理始终具有相同的方式。
  • 从Spring 4.0开始,代理对象的构造函数不再被调用两次,因为CGLIB代理实例是通过Objenesis创建的。只有当您的JVM不允许构造函数绕过时,您才会看到Spring的AOP支持中的双重调用和相应的调试日志条目。
//强制使用cglib

    

5.8.1. Understanding AOP Proxies

       spring aop是基于代理实现的,自我方法调用时,比如用this调用另外一个方法,这个时候并不是使用代理取调的;AspectJ不是基于代理实现的aop框架

这种做法的意义:
It means that self-invocation is not going to result in the advice associated with a method invocation getting a chance to execute.

public class SimplePojo implements Pojo {

    public void foo() {
        // this works, but... gah!
        // 使用this调用,是使用当前类的实例调用;使用这种方式是使用代理去调用的bar方法
        ((Pojo) AopContext.currentProxy()).bar();
    }

    public void bar() {
        // some logic...
    }
}


public class Main {

    public static void main(String[] args) {

        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.adddInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());
        factory.setExposeProxy(true);

        Pojo pojo = (Pojo) factory.getProxy();

        // this is a method call on the proxy!
        pojo.foo();
    }
}

5.9. Programmatic Creation of @AspectJ Proxies

       
除了使用aop:config与aop:aspectj-autoproxy还可以以编程的方式创建通知目标对象的代理

org.springframework.aop.aspectj.annotation.AspectJProxyFactory接口可以为一个或多个@AspectJ创建代理

// create a factory that can generate a proxy for the given target object
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);

// add an aspect, the class must be an @AspectJ aspect
// you can call this as many times as you need with different aspects
factory.addAspect(SecurityManager.class);

// you can also add existing aspect instances, the type of the object supplied must be an @AspectJ aspect
factory.addAspect(usageTracker);

// now get the proxy object...
MyInterfaceType proxy = factory.getProxy();

5.10. Using AspectJ with Spring Applications

       需要spring-aspects.jar包; 使用AspectJ编译器或weaver代替Spring AOP,如果您的需求超出了Spring AOP提供的功能

5.10.1. Using AspectJ to Dependency Inject Domain Objects with Spring

        spring-aspects.jar包含一个注释驱动的方面,利用此功能允许依赖注入任何对象。该支持旨在用于在任何容器控制之外创建的对象。域对象通常属于此类别,因为它们通常由new运算符以编程方式创建,或者由于数据库查询而由ORM工具创建 。

后续章节为介绍如何使用aspects

5.10.2. Other Spring aspects for AspectJ

       

5.10.3. Configuring AspectJ Aspects by Using Spring IoC

       

5.10.4. Load-time Weaving with AspectJ in the Spring Framework

       

5.11. Further Resources

       

你可能感兴趣的:(Spring)