vlambda博客
学习文章列表

读书笔记《hands-on-high-performance-with-spring-5》调优面向方面编程

Tuning Aspect-Oriented Programming

在上一章中,我们深入探讨了 Spring 的 关键特性之一:依赖注入(IoC 容器)。 DI 是一种企业设计模式,它使对象与其所需的依赖项松散耦合。我们了解了 Spring 的 bean 布线配置和最佳实践,以实现最佳结果。

根据 Spring 的核心特性,在本章中,我们将讨论 面向方面的编程(AOP)。我们已经了解到,DI 促进了对接口的编程和应用程序对象的解耦,而 AOP 则有助于实现业务逻辑和横切关注点的解耦。 横切关注点是适用于部分应用程序或整个应用程序的关注点,例如,安全性、日志记录和缓存,几乎在应用程序的每个模块中都需要这些关注点。 AOP 和 AspectJ 有助于实现这些横切关注点。在本章中,我们将讨论以下主题:

  • AOP concepts
  • AOP proxies
  • Spring AOP method for profiling
  • AOP versus AspectJ comparison
  • AOP best programming practices

AOP concepts

在本节中,我们将看看如果我们只使用 面向对象编程 (OOP) 范式会面临哪些问题。然后我们将了解 AOP 是如何解决这些问题的。我们将介绍 AOP 的概念以及实现 AOP 概念的方法。

Limitations of OOP

在 OOP 基础和设计模式的帮助下,应用程序开发被划分为功能组。 OOP 协议使许多事情变得简单而有用,例如引入一个可以实现松散耦合设计的接口,可以隐藏对象数据的封装,以及可以通过类重用工作的继承扩展功能。

随着系统的增长,OOP 的这些优点也会增加复杂性。随着复杂性的增加,维护成本和失败的可能性也会增加。为了解决这个问题,将功能模块化为更简单、更易于管理的模块有助于降低复杂性。

为了模块化系统,我们开始遵循将应用程序划分为不同逻辑层的做法,例如表示层、服务层和数据层。然而,即使将功能划分为不同的层,所有层都需要某些功能,例如安全性、日志记录、缓存和性能监控。这些功能称为横切关注点

如果我们使用继承来实现这些横切关注点,就会违反 SOLID 原则的单一职责,并增加对象层次结构。如果我们使用组合来实现它们,它会更加复杂。所以使用 OOP 实现横切关注点会导致两个问题:

  • Code tangling
  • Code scattering

让我们更多地讨论这些问题。

Code tangling

代码纠缠意味着混合横切关注点和业务逻辑,这反过来又导致紧密耦合。让我们看下图来理解代码纠结:

读书笔记《hands-on-high-performance-with-spring-5》调优面向方面编程
Code tangling

上图说明了我们如何在服务实现中混合事务和安全代码以及业务逻辑。通过这样的实现,降低了代码的可重用性,降低了维护性,并且违反了单一职责原则。

Code scattering

代码分散意味着横切关注点在应用程序的所有模块中重复。让我们看下面的例子来理解代码散射:

public class TransferServiceImpl implements TransferService {
  public void transfer(Account source, Account dest, Double amount) {
    //permission check
    if (!hasPermission(user) {
      throw new AuthorizationException();
    }
  }
}

public class AccountServiceImpl implements AccountService {
  public void withdraw(Account userAccount, Double amount) {
    //Permission check
    if (!hasPermission(user) {
      throw new AuthorizationException();
    }
}

正如我们在前面的代码示例中看到的,权限检查(安全)是我们在所有服务中重复的横切关注点。

这些代码缠结和代码分散的问题都被 AOP 解决了,但是如何解决呢?我们很快就会看到。

AOP – problem solver

我们在上一节中已经看到,使用 OOP,会发生代码缠结分散。使用 AOP,我们可以实现以下目标/好处:

  • Modularizing crosscutting concerns
  • Decoupling of modules
  • Removing crosscutting concerns regarding module dependency

Spring AOP 允许我们将横切关注点逻辑与业务逻辑分开,这样我们就可以专注于应用程序的主要逻辑。为了帮助我们执行这种分离,Spring 提供了 Aspects,一个普通的类,我们将在其中实现横切关注点逻辑。 Spring 提供了将这些 Aspects 注入应用程序中正确位置的方法,而无需将它们与业务逻辑混合。我们将在接下来的部分中看到更多关于 Aspects、如何实现它以及如何应用它的信息。

此图说明了 Spring AOP:

读书笔记《hands-on-high-performance-with-spring-5》调优面向方面编程
How AOP solves code tangling

Spring AOP terminology and concepts

AOP 和每一种技术一样,都有自己的术语。它有自己的词汇。 Spring 在其 Spring AOP 模块中使用 AOP 范例。但是,Spring AOP 有自己的特定于 Spring 的术语。要理解 Spring AOP 术语,我们看下图:

读书笔记《hands-on-high-performance-with-spring-5》调优面向方面编程
Spring AOP terminologies and concepts

让我们理解上图中提到的 Spring AOP 的各个概念:

  • Join Point: A point defined in the execution of our program. This execution could be method invocation, exception handling, class initialization, or object instantiation. Spring AOP supports method invocation only. In case we want a join point for anything other than method invocation, we can use Spring and AspectJ together. We will walk through AspectJ later in this chapter.
  • Advice: A definition of what exactly needs to be done at the join point. Different types of advice are @Before, @After, @Around, @AfterThrowing, and @AfterReturning. We will see them in action in the Types of advice section.
  • Pointcut: A collection of join point used to define an advice that has to be executed. An advice is not necessarily applied to all join points, so pointcut gives fine-grained control over an advice that is to be executed on components in our application. Pointcuts are defined using an expression and Spring uses the AspectJ pointcut expression language. We will shortly see how this is done.
  • Aspect: The combination of advice and pointcuts that defines logic in an application and where it should execute. Aspect is implemented using the regular class annotated with the @Aspect annotation. This annotation is from Spring AspectJ support.

这也太理论了吧?现在,让我们深入探讨如何在实际编程中应用这些 Spring AOP 概念。您可能已经在您的项目中实现了这些 AOP 概念;但是,您知道为什么需要它的背景吗?不,所以现在您知道我们为什么需要 Spring AOP。

从 Spring 2.0 开始,使用在基于模式的方法 (XML) 或注释中定义的 AspectJ 切入点语言使 AOP 实现变得更简单。我们将在本章中进一步讨论带有注释的 Spring 2.0 AspectJ 支持。

Defining pointcuts

正如我们之前所了解的,切入点定义了应该应用建议的点。 Spring AOP 使用 AspectJ 的表达式语言来定义应该应用通知的点。以下是 Spring AOP 中支持的切入点指示符集:

代号

Description
execution It restricts matching to join points by a method execution.
within

它将匹配限制为仅在某些类型内的连接点。

示例:within(com.packt.springhighperformance.ch3.TransferService)

args

它将匹配限制为参数属于给定类型的连接点。

示例:args(account,..)

this

它将匹配限制在 bean 引用或 Spring 代理对象是给定类型的实例的连接点。

示例:this(com.packt.springhighperformance.ch3.TransferService)

target

它将匹配限制在目标对象是给定类型的实例的连接点。

示例:target(com.packt.springhighperformance.ch3.TransferService)

@within

它将匹配限制在声明类型具有给定类型注释的连接点。

示例:@within(org.springframework.transaction.annotation.Transactional)

@target

它将匹配限制在目标对象具有给定类型注释的连接点。

示例:@target(org.springframework.transaction.annotation.Transactional)

@args

它将匹配限制为连接点,其中传递的实际参数的类型具有给定类型的注释。

示例:@args(com.packt.springhighperformance.ch3.Lockable)

@annotation

它将匹配限制在执行方法具有给定注释的连接点。

示例:@annotation(org.springframework.transaction.annotation.Transactional)

让我们看看如何使用 execution 指示符编写点表达式:

  • Using execution(<method-pattern>): Method matching to the pattern would be advised. The following is the method pattern:
[Modifiers] ReturnType [ClassType]
MethodName ([Arguments]) [throws ExceptionType]
  • To create composite pointcuts by joining other pointcuts, we can use the &&, ||, and ! operators (these mean AND, OR, and NOT, respectively).

在前面的方法模式中,[ ] 中定义的任何内容都是可选的。没有 [ ] 的值是强制定义的。

下图将说明点表达式使用 execution 指示符在执行 findAccountById() 方法时应用建议:

读书笔记《hands-on-high-performance-with-spring-5》调优面向方面编程
Execution join point pattern

Types of advice

在上一节中,我们学习了 AOP 的不同术语以及如何定义切入点表达式。在本节中,我们将了解 Spring AOP 中不同类型的通知:

  • @Before: This advice is executed before the join point and it is defined in aspect using the @Before annotation. The declaration is shown in the following code:
@Pointcut("execution(* com.packt.springhighperformance.ch03.bankingapp.service.TransferService.transfer(..))")
public void transfer() {}

@Before("transfer()")
public void beforeTransfer(JoinPoint joinPoint){
  LOGGGER.info("validate account balance before transferring amount");
}
如果 @Before 方法抛出异常, transfer 目标方法不会被调用。这是一个有效的使用 @Before 建议。
  • @After: This advice is executed after the join point (method) exits/returns either normally or with any exception. To declare this advice, use the @After annotation. The declaration is shown in the following code:
@Pointcut("execution(* com.packt.springhighperformance.ch03.bankingapp.service.TransferService.transfer(..))")
public void transfer() {}

@After("transfer()")
public void afterTransfer(JoinPoint joinPoint){
  LOGGGER.info("Successfully transferred from source account to dest     
  account");
}
  • @AfterReturning: As we know in @After advice, the advice is executed in any case where the join point exits normally or with an exception. Now, if we want to run an advice only after a matched method returns normally, then what? Then we need @AfterReturning. Sometimes we need to perform some operation based on the value returned by the method. In those cases, we can use the @AfterReturning annotation. The declaration is shown in the following code:
@Pointcut("execution(* com.packt.springhighperformance.ch03.bankingapp.service.TransferService.transfer(..))")
public void transfer() {}
  

@AfterReturning(pointcut="transfer() and args(source, dest, amount)", returning="isTransferSuccessful" )
public void afterTransferReturns(JoinPoint joinPoint, Account source, Account dest, Double amount, boolean isTransferSuccessful){
  if(isTransferSuccessful){
    LOGGGER.info("Amount transferred successfully ");
    //find remaining balance of source account
  }
}
  • @AfterThrowing: This advice is called when an exception is thrown by a matched method in an expression. This is useful when we want to take some action when any particular type of exception is thrown or we want to track method execution to correct errors. It is declared using the @AfterThrowing annotation, as shown in the following code:
@Pointcut("execution(* com.packt.springhighperformance.ch03.bankingapp.service.TransferService.transfer(..))")
public void transfer() {}

@AfterThrowing(pointcut = "transfer()", throwing = "minimumAmountException")
public void exceptionFromTransfer(JoinPoint joinPoint, MinimumAmountException minimumAmountException) {
  LOGGGER.info("Exception thrown from transfer method: " +         
  minimumAmountException.getMessage());
}

@AfterThrowing returning 属性类似,@AfterThrowing 通知中的 throwing 属性必须与参数名称匹配在建议方法中。 throwing 属性限制匹配那些抛出指定类型异常的方法执行。

  • @Around: The last and final advice that is applied around the matched method. This means that it is a combination of the @Before and @After advice we saw earlier. However, the @Around advice is more powerful than @Before and @After combined. It is powerful because it can decide whether to proceed to the join point method or return its own value or throw an exception. The @Around advice can be used with the @Around annotation. The first parameter of the advice method in @Around advice should be ProceedingJoinPoint. The following is the code sample of how to use the @Around advice:
@Pointcut("execution(* com.packt.springhighperformance.ch03.bankingapp.service.TransferService.transfer(..))")
public void transfer() {}
  

@Around("transfer()")
public boolean aroundTransfer(ProceedingJoinPoint proceedingJoinPoint){
  LOGGER.info("Inside Around advice, before calling transfer method ");
  boolean isTransferSuccessful = false;
  try {
    isTransferSuccessful = (Boolean)proceedingJoinPoint.proceed();
  } catch (Throwable e) {
    LOGGER.error(e.getMessage(), e);
  }
  LOGGER.info("Inside Around advice, after returning from transfer 
  method");
  return isTransferSuccessful;
}
我们可以调用 proceed 一次,多次,或者根本不在 @Around 建议。

Aspect instantiation models

默认情况下,声明的 aspectsingleton,因此每个类加载器(而不是每个JVM)。我们的 aspect 实例只有在类加载器是垃圾时才会被销毁。

如果我们需要我们的 aspect 具有私有属性来保存与类实例相关的数据,那么 aspect 需要是有状态的。为此,Spring 及其 AspectJ 支持提供了一种使用 perthispertarget 实例化模型的方法。 AspectJ是一个独立的库,除了perthispertarget还有其他的实例化模型,如 percflow percflowbelowpertypewithin,它们在 Spring 的 AspectJ 支持中不受支持。

要使用 perthis 创建有状态的 aspect,我们需要在 @Aspect 声明中声明 perthis,如下所示:

@Aspect("perthis(com.packt.springhighperformance.ch03.bankingapp.service.TransferService.transfer())")
public class TransferAspect {
//Add your per instance attributes holding private data
//Define your advice methods
}

一旦我们用 perthis 子句声明了我们的@Aspect,就会为每个子句创建一个aspect实例执行 transfer 方法的唯一 TransferService 对象(在与切入点表达式匹配的连接点处绑定到 this 的每个唯一对象)。当 TransferService 对象超出范围时,方面的实例超出范围。

pertargetperthis 工作方式相同;但是,在 pertarget 中,它会在与切入点表达式匹配的连接点处为每个唯一目标对象创建一个 aspect 实例。

现在您可能想知道 Spring 如何在不从业务逻辑类调用横切关注类 (Aspects) 的情况下应用建议。所以答案是,Spring 使用代理模式来做到这一点。它通过创建代理对象将您的 Aspects 编织到目标对象。让我们在下一节中详细了解 Spring AOP 代理。

AOP proxies

正是代理模式使 Spring AOP 能够将横切关注点与核心应用程序的业务逻辑或功能分离。代理模式是 Gang of Four (GoF) 的一本书中包含的一种结构设计模式。在实践中,代理模式在不改变原始对象的行为以允许拦截其方法调用的情况下从原始对象创建不同的对象,并且外部世界会觉得它们是在与原始对象而不是代理进行交互。

JDK dynamic proxies and CGLIB proxies

Spring AOP中的代理可以通过两种方式创建:

  • JDK proxy (dynamic proxy): The JDK proxy creates a new proxy object by implementing interfaces of the target object and delegating method calls
  • CGLIB proxy: The CGLIB proxy creates a new proxy object by extending the target object and delegating method calls

让我们看看这些代理机制以及它们在下表中的不同之处:

JDK proxy CGLIB proxy

它内置在 JDK 中。

它是一个定制开发的库。

JDK 代理在接口上工作。

CGLIB 代理适用于子类化。这在接口不存在时使用。

它将代理所有接口。

当方法和类是最终的时,它不能工作。

从 Spring 3.2 开始,CGLIB 库与 Spring Core 一起打包,因此无需在我们的应用程序中单独包含该库。
从 Spring 4.0 开始,被代理对象的构造函数不会被调用两次,因为 CGLIB 代理实例将通过 Objenesis 创建。

默认情况下,如果目标对象的类实现了接口,Spring会尝试使用JDK动态代理;如果目标对象的类没有实现任何接口,那么 Spring 将使用 CGLIB 库创建一个代理。

如果目标对象的类实现了一个接口并且它作为一个具体的类被注入到另一个bean中,那么Spring会抛出一个异常:NoSuchBeanDefinitionException。这个问题的解决方案是要么通过接口注入(这是一种最佳实践),要么用 Scope(proxyMode=ScopedProxyMode.TARGET_CLASS) 对注入进行注解。然后 Spring 将使用 CGLIB 代理创建一个代理对象。此配置禁用 Spring 对 JDK 代理的使用。然后 Spring 将始终扩展具体类,即使注入了接口。 CGLIB 代理使用装饰器模式通过创建代理将通知编织到目标对象:

读书笔记《hands-on-high-performance-with-spring-5》调优面向方面编程
JDK dynamic proxy and CGLIB proxy

创建代理将能够将所有对方法的调用委托给拦截器(建议)。但是,一旦方法调用到达目标对象,在该目标对象内进行的任何内部方法调用都不会被拦截。因此,对象引用中的任何方法调用都不会导致任何通知执行。为了解决这个问题,要么重构代码以便不会发生直接自调用,要么使用 AspectJ 编织。为了在 Spring 中解决这个问题,我们需要将暴露代理属性设置为 true 并使用 AopContext.currentProxy() 进行自调用。

Spring recommends using the JDK proxy wherever possible. Hence, try to implement the abstraction layer almost everywhere in your application so that the JDK proxy will be applied when the interface is available and we have not explicitly set it to use the CGLIB proxy only.

ProxyFactoryBean

Spring 提供了一种使用 ProxyFactoryBean 手动创建对象代理的经典方法,它将创建一个包装目标对象的 AOP 代理。 ProxyFactoryBean 提供了一种设置最终合并到 AOP 代理中的建议和顾问的方法。 Spring中所有AOP代理工厂继承自org.springframework.aop.framework.ProxyConfig超类的关键属性如下:

  • proxyTargetClass: If it's true, then the proxy is created using CGLIB only. If it's not set, the proxy will be created using the JDK proxy if the target class implements the interface; otherwise, the proxy will be created using CGLIB.
  • optimize: For the CGLIB proxy, this instructs the proxy to apply some aggressive optimizations. Currently, it is not supported by the JDK proxy. This needs to be used wisely.
  • frozen: If a proxy is set as frozen, then changes to the configuration are not allowed. This is useful when we don't want callers to modify the proxy after the proxy has been created. This is used for optimization. The default value of this property is false.
  • exposeProxy: Setting this property to true determines whether the current proxy should be exposed to ThreadLocal or not. If it's exposed to ThreadLocal, then the target can use the AopContext.currentProxy() method for self-invocation of the method.

ProxyFactoryBean in action

我们将定义一个常规的 Spring bean 作为目标 bean,比如 TransferService,然后,使用 ProxyFactoryBean,我们将创建一个可以被我们的应用程序访问的代理。为了通知 TransferServicetransfer 方法,我们将使用 AspectJExpressionPointcut 设置点表达式,我们将创建拦截器,我们将其设置为 < kbd>DefaultPointcutAdvisor 创建顾问。

目标对象或bean如下:

public class TransferServiceImpl implements TransferService {
  private static final Logger LOGGER =     
  Logger.getLogger(TransferServiceImpl.class);

  @Override
  public boolean transfer(Account source, Account dest, Double amount) {
    // transfer amount from source account to dest account
    LOGGER.info("Transferring " + amount + " from " + 
    source.getAccountName() + " 
    to " +   dest.getAccountName());
    ((TransferService)
    (AopContext.currentProxy())).checkBalance(source);
    return true;
  }

  @Override
  public double checkBalance(Account a) {
    return 0;
  }
}

以下代码用于方法拦截器或通知:

public class TransferInterceptor implements MethodBeforeAdvice{

   private static final Logger LOGGER =  
   Logger.getLogger(TransferInterceptor.class);

 @Override
 public void before(Method arg0, Object[] arg1, Object arg2) throws   
 Throwable {
    LOGGER.info("transfer intercepted");
 }
}

Spring配置如下:

@Configuration
public class ProxyFactoryBeanConfig {
  
  @Bean
  public Advisor transferServiceAdvisor() {
      AspectJExpressionPointcut pointcut = new 
      AspectJExpressionPointcut();
      pointcut.setExpression("execution(* 
      com.packt.springhighperformance.ch03.bankingapp.service
      .TransferService.checkBalance(..))");
      return new DefaultPointcutAdvisor(pointcut, new 
      TransferInterceptor());
  }
  
  @Bean
  public ProxyFactoryBean transferService(){
    ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
    proxyFactoryBean.setTarget(new TransferServiceImpl());
    proxyFactoryBean.addAdvisor(transferServiceAdvisor());
    proxyFactoryBean.setExposeProxy(true);
    return proxyFactoryBean;
  }
}

在前面的代码示例中,我们没有单独TransferService 定义为 Spring bean。我们创建了一个 TransferService 的匿名 bean,然后使用 ProxyFactoryBean 创建了它的代理。这样做的好处是只有一个 TransferService 类型的对象,没有人可以得到一个不建议的对象。如果我们想使用 Spring IoC 将此 bean 连接到任何其他 bean,这也减少了歧义。

使用 ProxyFactoryBean,我们可以配置 AOP 代理,提供编程方法的所有灵活性,而无需我们的应用程序进行 AOP 配置。

It is best to use the declarative method of proxy configuration over the programmatic method unless we need to perform a manipulative action at runtime or we want to gain fine-grained control.

Performance JDK dynamic proxy versus CGLIB proxy

我们了解了代理的用途。根据 GoF 的书,Design Patterns: Elements of Reusable Object-Oriented Software,代理是另一个对象控制对其访问的占位符。由于代理位于对象的调用者和真实对象之间,它可以决定是阻止调用真实(或目标)对象还是在调用目标对象之前执行某些操作。

许多对象关系映射器使用代理模式来实现阻止数据在实际需要之前被加载的行为。有时这称为延迟加载。 Spring 还使用代理来开发它的一些功能,例如它的事务管理、安全性、缓存和 AOP 框架。

由于代理对象是在运行时由 JDK 代理或 CGLIB 库创建的附加对象,并且位于调用者对象和目标对象之间,因此它将为普通方法调用增加一些开销。

让我们看看在普通方法调用中增加了多少开销代理。

以下片段显示了 CGLIB 代理的基于 Spring Java 的配置类:

@EnableAspectJAutoProxy
@Configuration
public class CGLIBProxyAppConfig {

  @Bean
  @Scope(proxyMode=ScopedProxyMode.TARGET_CLASS)
  public TransferService transferService(){
    return new TransferServiceImpl();
  }
}

JDK代理的基于Spring Java的配置类如下:

@Configuration
@EnableAspectJAutoProxy
public class JDKProxyAppConfig {

 @Bean
 @Scope(proxyMode=ScopedProxyMode.INTERFACES)
 public TransferService transferService(){
 return new TransferServiceImpl();
 }
}

JUnit 类如下:

public class TestSpringProxyOverhead {
  private static final Logger LOGGER = 
  Logger.getLogger(TestSpringProxyOverhead.class);

  @Test
  public void checkProxyPerformance() {
    int countofObjects = 3000;
    TransferServiceImpl[] unproxiedClasses = new 
    TransferServiceImpl[countofObjects];
    for (int i = 0; i < countofObjects; i++) {
      unproxiedClasses[i] = new TransferServiceImpl();
    }

    TransferService[] cglibProxyClasses = new     
    TransferService[countofObjects];
    TransferService transferService = null;
    for (int i = 0; i < countofObjects; i++) {
      transferService = new 
      AnnotationConfigApplicationContext(CGLIBProxyAppConfig.class)
      .getBean(TransferService.class);
      cglibProxyClasses[i] = transferService;
    }

    TransferService[] jdkProxyClasses = new 
    TransferService[countofObjects];
    for (int i = 0; i < countofObjects; i++) {
      transferService = new 
      AnnotationConfigApplicationContext(JDKProxyAppConfig.class)
      .getBean(TransferService.class);
      jdkProxyClasses[i] = transferService;
    }

    long timeTookForUnproxiedObjects = 
    invokeTargetObjects(countofObjects, 
    unproxiedClasses);
    displayResults("Unproxied", timeTookForUnproxiedObjects);

    long timeTookForJdkProxiedObjects = 
    invokeTargetObjects(countofObjects, 
    jdkProxyClasses);
    displayResults("Proxy", timeTookForJdkProxiedObjects);

    long timeTookForCglibProxiedObjects = 
    invokeTargetObjects(countofObjects, 
    cglibProxyClasses);
    displayResults("cglib", timeTookForCglibProxiedObjects);

  }

  private void displayResults(String label, long timeTook) {
  LOGGER.info(label + ": " + timeTook + "(ns) " + (timeTook / 1000000) 
  + "(ms)");
  }

  private long invokeTargetObjects(int countofObjects, 
  TransferService[] classes) {
    long start = System.nanoTime();
    Account source = new Account(123456, "Account1");
    Account dest = new Account(987654, "Account2");
    for (int i = 0; i < countofObjects; i++) {
      classes[i].transfer(source, dest, 100);
    }
    long end = System.nanoTime();
    long execution = end - start;
    return execution;
  }
}

开销时间因硬件工具而异,例如 CPU 和内存。以下是我们将获得的输出类型:

2018-02-06 22:05:01 INFO TestSpringProxyOverhead:52 - Unproxied: 155897(ns) 0(ms)
2018-02-06 22:05:01 INFO TestSpringProxyOverhead:52 - Proxy: 23215161(ns) 23(ms)
2018-02-06 22:05:01 INFO TestSpringProxyOverhead:52 - cglib: 30276077(ns) 30(ms)

我们可以使用 Google 的 Caliper 等工具进行基准测试,该工具位于 https://github.com/google/caliperJava Microbenchmark Harness (JMH),位于 http://openjdk.java.net/projects/code-tools/jmh/。许多性能测试,使用不同的工具和场景,提供了不同的结果。一些测试表明 CGLIB 比 JDK 代理更快,还有一些得到了其他结果。如果我们测试 AspectJ(我们将在本章后面讨论),性能仍然优于 JDK 代理和 CGLIB 代理,因为它的字节码编织机制而不是代理对象。

这里的问题是我们真的需要担心我们看到的开销吗?答案是肯定的和否定的。我们将讨论这两个答案。

我们不必真正担心开销,因为添加代理的时间可以忽略不计,而 AOP 或代理模式提供的好处量很大。我们已经在本章前面几节中看到了 AOP 的好处,例如事务管理、安全性、延迟加载或任何横切但具有代码简化、集中管理或代码维护的东西。

此外,当我们的应用程序有 服务水平协议 (SLA) 以毫秒为单位交付或我们的应用程序有大量并发请求或负载时,我们需要担心开销.在这种情况下,花费的每一毫秒对我们的应用程序都很重要。但是,我们仍然需要在我们的应用程序中使用 AOP 来实现横切关注点。所以,这里我们需要注意的是正确的 AOP 配置,避免不必要地扫描对象以获取通知,配置我们想要通知的确切连接点,并避免通过 AOP 实现细粒度的需求。对于细粒度的需求,用户 AspectJ(字节码编织方法)。

所以经验法则是,使用 AOP 来实现横切关注点并利用其优势。但是,通过对每个操作应用建议或代理,谨慎地实施它,并使用不会降低系统性能的正确配置。

Caching

为了提高应用程序的性能,缓存繁重的操作是不可避免的。 Spring 3.1 添加了一个很棒的抽象层,称为 caching,它有助于放弃所有自定义实现的 aspects、装饰器和注入到与缓存相关的业务逻辑中的代码。

Spring 使用 AOP 概念将缓存应用于 Spring bean 的方法;我们在本章的AOP 概念部分了解了它。 Spring 创建 Spring bean 的代理,其中方法被注释为缓存。

为了利用 Spring 缓存抽象层的优势,只需使用 @Cacheable 注释重方法。此外,我们需要通过使用 @EnableCaching 注释我们的配置类来通知我们的应用程序已缓存方法。以下是缓存方法的示例:

@Cacheable("accounts")
public Account findAccountById(int accountId){

@Cacheable 注释具有以下属性:

  • value: Name of cache
  • key: Caching key for each cached item
  • condition: Defines whether to apply caching or not based on the evaluation of the Spring Expression Language (SpEL) expression
  • unless: This is another condition written in SpEL, and if it is true, it prevents the return value from being cached

以下是 Spring 提供的与缓存相关的附加注解:

  • @CachePut: It will let the method execute and update the cache
  • @CacheEvict: It will remove stale data from the cache
  • @Caching: It allows you to group multiple annotations @Cacheable, @CachePut, and @CacheEvict on the same method
  • @CacheConfig: It will allow us to annotate our entire class instead of repeating on each method

我们可以在检索数据的方法上使用 @Cacheable 并在执行插入以更新缓存的方法上使用 @CachePut。代码示例如下:

@Cacheable("accounts" key="#accountId")
public Account findAccountById(int accountId){
  
@CachePut("accounts" key="#account.accountId")
public Account createAccount(Account account){

缓存数据的注释方法不会存储数据;为此,我们需要实现或提供 CacheManager。 Spring 默认在 org.springframework.cache 包中提供了一些缓存管理器,其中之一是 SimpleCacheManagerCacheManager 代码示例如下所示:

@Bean
public CacheManager cacheManager() {
  CacheManager cacheManager = new SimpleCacheManager();
  cacheManager.setCaches(Arrays.asList(new     
  ConcurrentMapCache("accounts"));
  return cacheManager;
}

Spring 还提供了对集成以下第三方缓存管理器的支持:

  • EhCache
  • Guava
  • Caffeine
  • Redis
  • Hazelcast
  • Your custom cache

AOP method profiling

应用程序可以有许多业务方法。由于一些实现问题,一些方法需要时间,我们想测量这些方法花费了多少时间,我们可能还想分析方法参数。 Spring AOP 提供了一种在不涉及业务方法的情况下执行方法分析的方法。让我们看看如何。

PerformanceMonitorInterceptor

让我们看看如何对我们的方法执行进行分析或监控。这是在 Spring AOP 使用 PerformanceMonitorInterceptor 类提供的一个简单选项的帮助下完成的。

正如我们所了解的,Spring AOP 允许在应用程序中定义横切关注点,方法是拦截一个或多个方法的执行以添加额外的功能,而无需触及核心业务类。

Spring AOP 的 PerformanceMonitorInterceptor 类是一个拦截器,可以绑定到任何自定义方法同时执行。此类使用 StopWatch 实例来记录方法执行的开始和结束时间。

让我们监控TransferServicetransfer方法。以下是TransferService代码:

public class TransferServiceImpl implements TransferService {

  private static final Logger LOGGER = 
  LogManager.getLogger(TransferServiceImpl.class);

  @Override
  public boolean transfer(Account source, Account dest, int amount) {
    // transfer amount from source account to dest account
    LOGGER.info("Transferring " + amount + " from " + 
    source.getAccountName() + " 
    to " + dest.getAccountName());
    try {
      Thread.sleep(5000);
    } catch (InterruptedException e) {
      LOGGER.error(e);
    }
    return true;
  }
}

以下代码是 @Pointcut 使用 Spring 拦截器监控通知方法:

@Aspect 
public class TransferMonitoringAspect {
  
    @Pointcut("execution(*          
    com.packt.springhighperformance.ch03.bankingapp.service
    .TransferService.transfer(..))")
    public void transfer() { }
}

下面的代码是advisor类:

public class PerformanceMonitorAdvisor extends DefaultPointcutAdvisor {

 private static final long serialVersionUID = -3049371771366224728L;

 public PerformanceMonitorAdvisor(PerformanceMonitorInterceptor 
 performanceMonitorInterceptor) {
 AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
 pointcut.setExpression(
 "com.packt.springhighperformance.ch03.bankingapp.aspect.TransferMonito  ringAspect.transfer()");
 this.setPointcut(pointcut);
 this.setAdvice(performanceMonitorInterceptor);
 }
}

以下代码为Spring Java配置类:

@EnableAspectJAutoProxy
@Configuration
public class PerformanceInterceptorAppConfig {
  @Bean
  public TransferService transferService() {
    return new TransferServiceImpl();
  }

  @Bean
  public PerformanceMonitorInterceptor performanceMonitorInterceptor() {
    return new PerformanceMonitorInterceptor(true);
  }
  
  @Bean
  public TransferMonitoringAspect transferAspect() {
    return new TransferMonitoringAspect();
  }

  @Bean
  public PerformanceMonitorAdvisor performanceMonitorAdvisor() {
    return new 
    PerformanceMonitorAdvisor(performanceMonitorInterceptor());
  }
}

切入点中的表达式标识了我们要拦截的方法。我们将 PerformanceMonitorInterceptor 定义为一个 bean,然后创建 PerformanceMonitorAdvisor 以将切入点与拦截器相关联。

在我们的 Appconfig 中,我们使用 @EnableAspectJAutoProxy 注释来启用 AspectJ 支持 让我们的 bean 自动创建代理。

要让 PerformanceMonitorInterceptor 工作,我们需要将目标对象的日志级别 TransferServiceImpl 设置为 TRACE 级别,因为这是在它记录消息。

对于 transfer 方法的每次执行,我们将在控制台日志中看到 TRACE 消息:

2018-02-07 22:14:53 TRACE TransferServiceImpl:222 - StopWatch 'com.packt.springhighperformance.ch03.bankingapp.service.TransferService.transfer': running time (millis) = 5000

Custom monitoring interceptor

PerformanceMonitorInterceptor 是一种非常基本且简单的方法来监控我们方法的执行时间。然而,大多数时候我们需要更多的受控方式来监控方法及其参数。为此,我们可以通过扩展 AbstractMonitoringInterceptor 或编写通知或自定义注释来实现我们的自定义拦截器。在这里,我们将编写一个扩展 AbstractMonitoringInterceptor 的自定义拦截器。

让我们扩展 AbstractMonitoringInterceptor 类并覆盖 invokeUnderTrace 方法来记录start end 和方法的持续时间。如果方法执行持续时间超过 5 毫秒,我们也可以记录警告。以下是自定义监控拦截器的代码示例:

public class CustomPerformanceMonitorInterceptor extends AbstractMonitoringInterceptor {
    
    private static final long serialVersionUID = -4060921270422590121L;
    public CustomPerformanceMonitorInterceptor() {
    }
 
    public CustomPerformanceMonitorInterceptor(boolean 
    useDynamicLogger) {
            setUseDynamicLogger(useDynamicLogger);
    }
 
    @Override
    protected Object invokeUnderTrace(MethodInvocation invocation, Log 
    log) 
      throws Throwable {
        String name = createInvocationTraceName(invocation);
        long start = System.currentTimeMillis();
        log.info("Method " + name + " execution started at:" + new 
        Date());
        try {
            return invocation.proceed();
        }
        finally {
            long end = System.currentTimeMillis();
            long time = end - start;
            log.info("Method "+name+" execution lasted:"+time+" ms");
            log.info("Method "+name+" execution ended at:"+new Date());
             
            if (time > 5){
                log.warn("Method execution took longer than 5 ms!");
            } 
        }
    }
}

我们在基本的 PerformanceMonitorInterceptor 中看到的所有其他步骤都是相同的,只需将 PerformanceMonitorInterceptor 替换为 CustomPerformanceMonitorInterceptor

生成以下输出:

2018-02-07 22:23:44 INFO TransferServiceImpl:32 - Method com.packt.springhighperformance.ch03.bankingapp.service.TransferService.transfer execution lasted:5001 ms
2018-02-07 22:23:44 INFO TransferServiceImpl:33 - Method com.packt.springhighperformance.ch03.bankingapp.service.TransferService.transfer execution ended at:Wed Feb 07 22:23:44 EST 2018
2018-02-07 22:23:44 WARN TransferServiceImpl:36 - Method execution took longer than 5 ms!

Spring AOP versus AspectJ

到目前为止,我们已经看到了使用代理模式和运行时编织的 AOP。现在让我们看看编译时和加载时编织的 AOP。

What is AspectJ?

从本章开始我们就知道,AOP 是一种编程范式,它通过分离横切关注点的实现来帮助解耦我们的代码。 AspectJ 是 AOP 的原始实现,它使用 Java 编程语言的扩展实现了关注点和横切关注点的编织。

要在我们的项目中启用 AspectJ,我们需要 AspectJ 库,并且 AspectJ 根据其用途提供不同的库。可以在 https://mvnrepository.com/artifact/org.aspectj 找到它的所有库.

在 AspectJ 中,Aspects 将在扩展名为 .aj. 的文件中创建。以下是示例 TransferAspect.aj 文件:

public aspect TransferAspect {
    pointcut callTransfer(Account acc1, Account acc2, int amount) : 
     call(public * TransferService.transfer(..));
 
    boolean around(Account acc1, Account acc2, int amount) : 
      callTransfer(acc1, acc2,amount) {
        if (acc1.balance < amount) {
            return false;
        }
        return proceed(acc1, acc2,amount);
    }
}

要启用编译时编织,当我们同时拥有 aspect 代码和要编织 aspects 的代码时,请使用 Maven 插件,如下所示:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.11</version>
    <configuration>
        <complianceLevel>1.8</complianceLevel>
        <source>1.8</source>
        <target>1.8</target>
        <showWeaveInfo>true</showWeaveInfo>
        <verbose>true</verbose>
        <Xlint>ignore</Xlint>
        <encoding>UTF-8 </encoding>
    </configuration>
    <executions>
        <execution>
            <goals>
                <!-- use this goal to weave all your main classes -->
                <goal>compile</goal>
                <!-- use this goal to weave all your test classes -->
                <goal>test-compile</goal>
            </goals>
        </execution>
    </executions>
</plugin>

要执行编译后编织,当我们想要编织现有的类文件和 JAR 文件时,请使用 Mojo 的 AspectJ Maven 插件,如下所示。 我们引用weave的工件或JAR文件必须在Maven项目中列为<dependencies/>并列为 <weaveDependencies/> 在 AspectJ Maven 插件的 <configuration> 中。以下是如何定义编织依赖的 Maven 示例:

<configuration>
    <weaveDependencies>
        <weaveDependency> 
            <groupId>org.agroup</groupId>
            <artifactId>to-weave</artifactId>
        </weaveDependency>
        <weaveDependency>
            <groupId>org.anothergroup</groupId>
            <artifactId>gen</artifactId>
        </weaveDependency>
    </weaveDependencies>
</configuration>

为了执行Load-time weaving (LTW),当我们想推迟我们的编织直到类加载器加载一个类文件时,我们需要一个编织代理;使用 Maven 插件如下:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.20.1</version>
    <configuration>
        <argLine>
            -javaagent:"${settings.localRepository}"/org/aspectj/
            aspectjweaver/${aspectj.version}/
            aspectjweaver-${aspectj.version}.jar
        </argLine>
        <useSystemClassLoader>true</useSystemClassLoader>
        <forkMode>always</forkMode>
    </configuration>
</plugin>

对于 LTW,它会在 META-INF 文件夹下的类路径中查找 aop.xml。该文件包含 aspectweaver 标记,如下所示:

<aspectj>
    <aspects>
        <aspect name="com.packt.springhighperformance.ch3.bankingapp. aspectj.TransferAspect"/>
        <weaver options="-verbose -showWeaveInfo">
            <include within="com.packt.springhighperformance.ch3.bankingapp .service.impl.TransferServiceImpl"/>
        </weaver>
    </aspects>
</aspectj>

所以这只是介绍如何在我们的项目中启用 AspectJ。

Differences between Spring AOP and AspectJ

让我们看看 Spring AOP(运行时编织)和 AspectJ(编译时和 LTW)之间的区别。

Capabilities and goals

Spring AOP 提供了一个简单的 AOP 实现来使用代理模式和装饰器模式来实现横切关注点。它不被认为是一个完整的 AOP 解决方案,Spring 可以应用于由 Spring 容器管理的 bean

AspectJ 是独创的 AOP 技术,旨在提供完整的 AOP 解决方案。然而,它比 Spring AOP 更健壮,也更复杂。 AspectJ 的好处是它可以应用于所有领域对象。

Weaving

Spring AOP AspectJ都使用不同类型的编织,并且基于它们的编织机制,它们在性能和易用性方面的行为是不同的.

为了在应用程序执行期间执行我们的 aspects 的运行时编织,Spring 使用我们之前讨论过的 JDK 动态代理或 CGLIB 代理来创建目标对象的代理。

与 Spring AOP 的 运行时编织相反,AspectJ 在编译时或类加载时执行编织。在上一节中,我们已经看到了不同类型的 AspectJ 编织。

Join points

由于 Spring AOP 创建目标类或对象的代理以应用横切关注点 (Aspects),它需要执行目标类或对象的子类化。正如我们已经知道的那样,通过子类化,Spring AOP 不能将横切关注点应用于最终或静态的类或方法。

另一方面,AspectJ 使用字节码编织将横切关注点编织到实际代码中,因此它不需要子类化目标类或对象。

Simplicity

在 Spring AOP 中,Aspects 的运行时编织将由容器在启动时执行,因此它与我们的构建过程无缝集成。

另一方面,在 AspectJ 中,我们必须使用额外的编译器 (ajc) 来执行此操作,除非我们正在执行此后编译或在 LTW 中。因此,Spring 比 AspectJ 更简单、更易于管理。

使用 Spring AOP,我们无法使用或应用 AOP 的全部功能,因为 Spring AOP 是基于代理的,并且只能应用于 Spring 管理的 bean。

AspectJ 基于字节码编织,这意味着它修改了我们的代码,因此它使我们能够在应用程序的任何 bean 上使用 AOP 的全部功能。

Performance

从性能的角度来看,编译时编织比运行时编织要快。 Spring AOP 是一个基于代理的框架,因此它会在运行时为代理创建额外的对象,并且每个 aspect 有更多的方法调用,这会对性能产生负面影响。

另一方面,AspectJ 在应用程序启动之前将 aspects 编织到主代码中,因此没有额外的运行时开销。互联网上的基准测试表明 AspectJ 比 Spring AOP 快得多。

这并不像一个框架比另一个更好。将根据需求和许多不同的因素做出选择,例如开销、简单性、可管理性/可维护性、复杂性和学习曲线。如果我们使用较少的 aspects 并且除了 Spring bean 或方法执行之外不需要应用 aspect,那么 Spring AOP 和 AspectJ 之间的性能差异是微不足道的。我们也可以同时使用 AspectJ 和 Spring AOP 来实现我们的要求。

AspectJ with Spring

Spring 提供了一些小型库来使 AspectJ aspects 进入 Spring 项目。这个库被命名为 spring-aspects.jar。从前面的讨论中我们知道,Spring 只允许在 Spring bean 上进行依赖注入或 AOP 建议。通过使用这个小型库的 Spring 的 AspectJ 支持,我们可以启用在容器外部创建的任何对象以进行 Spring 驱动的配置。只需使用 @Configurable 注释外部对象。使用 @Configurable 注释非 Spring bean 需要 spring-aspects.jar 中的 AnnotationBeanConfigurerAspect。 Spring需要的AnnotationBeanConfigurerAspect配置可以通过使用@EnableSpringConfigured注解我们的配置Java配置类来完成。

Spring 提供了一种更好的方法来启用 Load-time weaving (LTW),方法是启用基于每个类的加载器。这提供了更细粒度的控制,尤其是当我们将大型或多个应用程序部署到单个 JVM 环境中时。

要将 LTW 与 Spring 一起使用,我们需要实现我们的 aspect 或建议,就像我们之前在 AOP 概念 部分中实现的那样,并且根据 AspectJ 概念,我们需要创建META-INF文件夹下的aop.xml,如下:

<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
    <weaver>
        <!-- only weave classes in our application-specific packages -- > <include within="com.packt.springhighperformance.ch3.bankingapp .service.impl.TransferServiceImpl"/> <include within="com.packt.springhighperformance.ch3.bankingapp .aspects.TransferServiceAspect"/> </weaver> <aspects> <!-- weave in just this aspect -->
        <aspect name="com.packt.springhighperformance.ch3.bankingapp .aspects.TransferServiceAspect"/>
    </aspects>
</aspectj>

我们需要做的最后一件事是使用 @EnableLoadTimeWeaving 注释我们基于 Java 的 Spring 配置。我们需要在我们的服务器启动脚本中添加-javaagent:path/to/org.springframework.instrument-{version}.jar

AOP best programming practices

我们已经了解了为什么我们的应用程序需要 AOP。我们详细了解了它的概念以及如何使用它。让我们看看在我们的应用程序中使用 AOP 时应该遵循哪些最佳实践。

Pointcut expressions

我们从 AOP 的角度了解了切入点。现在让我们看看在使用切入点时我们应该注意什么:

  • Spring with AspectJ processes the pointcuts during compilation and tries to match and optimize matching performance. However, examining code and matching (statically or dynamically) would be a costly process. So, for optimal performance, think twice about what we want to achieve and narrow down our search or matching criteria as much as possible.
  • All the designators we learned earlier in this chapter are divided into three categories:
    • Method signature pattern: execution, get, set, call, handler
    • Type signature pattern: within, withincode
    • Contextual signature pattern: this, target, @annotation
  • In order to achieve good performance, write pointcut that includes the method at least and type signature pattern. It is not like matching would not work if we use only the method or type pattern; however, it is always recommended to join the method and type signature together. The type signature is very fast as it narrows down the search space by quickly opting out join points that could not be further processed.
  • Declare pointcuts on empty methods and refer to those pointcuts by their empty method name (named pointcut), so in case of any change to an expression, we have to change only at one place.
  • It is also recommended to declare small-named pointcuts and combine them by name to build complex pointcuts. Referring to pointcuts by name would follow the default Java method visibility rules. The following is the code sample of defining small pointcuts and joining them:
@Pointcut("execution(public * *(..))")
private void anyPublicMethod() {}

@Pointcut("within(com.packt.springhighperformance.ch3.bankingapp.TransferService..*)")
private void transfer() {}

@Pointcut("anyPublicMethod() && transfer()")
private void transferOperation() {}

  • Try to create anonymous beans for pointcuts when they are not shared to avoid direct access by an application.
  • Try to use static pointcuts where arguments need not be matched. These are faster and cached by Spring when the method is invoked first. Dynamic pointcuts are costly because they are evaluated on every method invocation because caching cannot be done as the argument would differ.

Advice ordering

现在我们知道如何编写建议以及如何创建 Aspect。让我们看看当我们在同一个连接点上有多个建议时,建议排序如何帮助我们确定建议的优先级:

  • Let's say we wrote two before or after advice in different aspects and both want to run at the same join point. In this case, the order of execution of the advice would be based on the which aspect comes first in the execution of classes. To avoid this and apply our advice one after the other, Spring provides a way to specify the order of execution by either implementing the Ordered interface by an aspect or applying the @Order annotation. The lower the value of the order, the higher the precedence.
  • While declaring an advice, always use the least powerful form of advice; for example, if a simple before advice would achieve our requirement, we should not use the around advice.

Best practices of AOP proxies

我们了解了 AOP 代理以及 AOP 代理如何工作。我们了解了 Spring AOP 中不同类型的代理。以下是在 Spring 中实现 AOP 代理时要遵循的最佳实践:

  • Unless we need to perform a manipulative action at runtime or we want to gain fine-grained control of our proxies, use the declarative method of proxy configuration over the programmatic method.
  • Spring recommends JDK dynamic proxy over the CGLIB proxy wherever possible. If we are building our application from scratch and there is no requirement to create proxies of third-party APIs, implement the abstraction layer to loosely-couple the implementation using the interface and let Spring use the JDK dynamic proxy mechanism to create proxies.
  • In case of CGLIB proxies, make sure the methods are not final, as final methods cannot be overridden and hence they cannot be advised.
  • According to Spring, it is not possible to have aspects themselves be the target of advice from other aspects. There are workarounds for this situation; move the aspect method to a new Spring bean annotated with @Component, @Autowire this new Spring bean to aspect, and just call the advised method. MethodProfilingAspect is aspect defining a pointcut on all join points under com.packt.springhighperformance.ch3.bankingapp:
@Aspect
public class MethodProfilingAspect {
  
  @Around("execution(* 
  com.packt.springhighperformance.ch3.bankingapp.*.*(..))")
  public Object log(ProceedingJoinPoint joinPoint){
    System.out.println("Before 
    Around"+joinPoint.getTarget().getClass().getName());
    Object retVal = null;
    try {
       retVal = joinPoint.proceed();
    } catch (Throwable e) {
      e.printStackTrace();
    }
    System.out.println("After 
    Around"+joinPoint.getTarget().getClass().getName());
    return retVal;
  }
  • The following ValidatingAspect is aspect defined under the com.packt.springhighperformance.ch3.bankapp package, however a call to the validate method would not be advised by MethodProfilingAspect:
@Aspect
public class ValidatingAspect {

 @Autowired
 private ValidateService validateService;
 
 @Before("execution(*   
 com.packt.springhighperformance.ch3.bankingapp.TransferService.tran
 sfe  r(..))")
 public void validate(JoinPoint jp){
 validateService.validateAccountNumber();
 }
}
  • The following is the solution by creating a separate class with the @Component annotation and implementing the validate method. This class will be a Spring-managed bean and it will be advised:
@Component
public class ValidateDefault{

  @Autowired
  private ValidateService validateService;
  public void validate(JoinPoint jp){
        validateService.validateAccountNumber();
    }
}
  • The following code of ValidatingAspect injects the ValidateDefault Spring bean and calls the validate method:
@Aspect
public class ValidatingAspect {

 @Autowired
 private ValidateDefault validateDefault;
 
 @Before("execution(* com.packt.springhighperformance.ch3.bankingapp.TransferService.transfer(..))")
 public void validate(JoinPoint jp){
 validateDefault.validate(jp);
 }
}

永远不要通过 AOP 实现细粒度的需求,或者使用 AspectJ 来满足这些需求。不要在 Spring 管理的 bean 类上使用 @Configurable ,否则它会进行双重初始化,一次通过容器,一次通过方面。

Caching

我们已经看到了如何使用缓存来提高应用程序的性能。以下是在 Spring 中实现缓存时要遵循的最佳实践:

  • Spring cache annotations should be used on concrete classes and not on interfaces. If we choose to use proxy-target-class="true", caching will not work because the Java annotation from the interfaces cannot be inherited.
  • Try not use @Cacheable and @CachePut together on the same method.
  • Do not cache very low-level methods close, such as CPU-intensive and in-memory computations. Spring's caching might be overkill in those scenarios.

Summary

在本章中,我们研究了 Spring AOP 模块。 AOP 是一种强大的编程范式,它补充了面向对象的编程。 AOP 有助于将横切关注点与业务逻辑分离,并帮助我们在处理业务需求时只关注业务逻辑。解耦的横切关注点有助于实现可重用的代码。

我们了解了 AOP 的概念、术语以及如何实现建议。我们了解了代理以及如何使用代理模式实现 Spring AOP。我们学习了在使用 Spring AOP 以实现更好的质量和性能时要遵循的最佳实践。

在下一章中,我们将学习 Spring MVC。 Spring Web MVC 提供了一个基于 MVC 的 Web 框架。使用 Spring Web MVC 作为 Web 框架使我们能够开发松散耦合的 Web 应用程序,并且可以在不使用请求和响应对象的情况下编写测试用例。我们将看到如何优化 Spring MVC 实现以使用异步方法特性、多线程和身份验证缓存来获得更好的结果。