Spring 5 设计模式 - 使用代理和装饰模式的Spring AOP

Spring中的代理模式

代理模式,就是向外界提供一个类,代表另一个类的功能。
Spring提供两种代理

JDK proxy CGLIB proxy
也叫动态代理 不是JDK内建的
API在JDK内 在Spring JARs内
要实现接口 没有接口时使用
可代理接口 不能用于final类和方法(因为不能被覆盖)

both the proxies

什么是AOP

Aspect-Oriented Programming (AOP)能够模块化cross-cutting concerns。它是另一种编程范式,补充了OOP。
OOP的关键要素是类和对象,AOP的关键要素则是aspect。Aspects允许你在程序的多个地方模块化一些功能(cross-cutting concerns)。
比如,安全是程序里的一个cross-cutting concerns,因为我们不得不在程序的许多需要安全的方法中应用它。类似的,事务和日志也是cross-cutting concerns。
cross-cutting concerns

AOP要解决的问题

如果不使用AOP,cross-cutting功能和业务逻辑就会混杂在一起。一般会导致两个问题:代码纠缠和代码分散

代码纠缠

public class TransferServiceImpl implements TransferService {
    public void transfer(Account a, Account b, Double amount) {
        //Security concern start here
        if (!hasPermission(SecurityContext.getPrincipal()) {
            throw new AccessDeniedException();
        }
        //Security concern end here
        //Business logic start here
        Account aAct = accountRepository.findByAccountId(a);
        Account bAct = accountRepository.findByAccountId(b);
        accountRepository.transferAmount(aAct, bAct, amount);
        //
    }
}

上面的代码只涉及安全,再加上事务和日志,代码结构就像是这个样子的:
tangling

代码分散

public class TransferServiceImpl implements TransferService {
    public void transfer(Account a, Account b, Double amount) {
        //Security concern start here
        if (!hasPermission(SecurityContext.getPrincipal()) {
            throw new AccessDeniedException();
        }
        //Security concern end here
        //Business logic start here

    }
}
public class AccountServiceImpl implements AccountService {
    public void withdrawl(Account a, Double amount) {
        //Security concern start here
        if (!hasPermission(SecurityContext.getPrincipal()) {
            throw new AccessDeniedException();
        }
        //Security concern end here
        //Business logic start here

    }
}

可以看到,安全相关的代码分散在各个功能里。
scattering

解决

AOP的核心术语和概念

AOP concepts

Advices实现在多点。这些点叫Joint Points,他们使用表达式定义。这些表达式叫pointcuts。

Advice

每个aspect有它的任务和目的。aspect的任务就是advice。
advice是一个任务,aspect执行该任务。这就带来一个问题,什么时候执行任务,任务做什么。任务可以在业务方法调用前执行,也可以在业务方法执行完再执行,或者业务方法执行前后都执行,或者业务方法抛了异常才执行。业务方法有时候也叫advised method。

  • Before:在advised method执行前,调用advice的任务
  • After:在advised method执行完成(不能抛异常),调用advice的任务
  • After-returning:在advised method执行完成,返回结果后(不能抛异常),调用advice的任务
  • After-throwing:在advised method抛异常退出后,调用advice的任务
  • Around:最常用。在advised method执行前后,调用advice的任务

Join Point

是程序执行的一个点,比如方法调用或者异常抛出。在这些点,Spring aspect插入concern功能。

Pointcut

可以定义表达式,选择一个或者多个Join Points。这个表达式就是pointcut。

Aspect

aspect是封装pointcuts和advice的模块。Aspects知道它要做什么,和在哪儿、在什么时候做。

Weaving

Weaving是aspects被组合进业务代码的技术。这是一个把aspects应用于目标对象的过程(通过增加新的代理对象)。
Weaving可以在编译时、类加载时或者运行时执行。

定义pointcuts

pointcuts被用来定义advice作用的点。Spring AOP可以使用表达式(AspectJ的表达式语言的子集)定义pointcuts。

Spring支持的 描述
execution 匹配方法执行的join points
within 匹配的join points限定在一定的类型内
this 匹配的join points作用于给定类型的一个实例
target 匹配的join points作用于给定类型
args 匹配的join points作用于参数是给定类型的一个实例
@target 匹配的join points作用于有给定类型的注解的目标对象
@args 在运行时匹配join points,传递的实际参数有给定类型的注解
@within 匹配的join points作用于目标对象所声明的类型有给定的注解
@annotation 匹配的join points作用于给定注解

写pointcuts

可以这样使用execution写一个pointcuts:

  • execution(): 方法必须匹配pattern
  • 可以使用下列操作符链式组合:&& (and) , || (or) , ! (not)
  • Method pattern
    • [Modifiers] ReturnType [ClassType]
    • MethodName ([Arguments]) [throws ExceptionType]

[ ]内的参数和表达式都是可选的。

比如这样一个接口:

package com.github.ls.test.service;

public interface TransferService {
    void transfer(String accountA, String accountB, Long amount);
}

它的实现类:

package com.github.ls.test.service.impl;

public class TransferServiceImpl {
    public void transfer(String accountA, String accountB, Long amount) {
        ///
    }
}

如果我们想在执行transfer()方法的时候,应用一个advice,可以这样配置pointcut表达式:

  • 任何类或者包:
    • execution(void transfer*(String)):以transfer开始的任何方法,接受一个字符串参数,没有返回值
    • execution(* transfer(*)):任何叫transfer()的,接受一个参数的方法
    • execution(* transfer(int, …)):任何叫transfer的方法,其中第一个参数类型是int,后面可以跟0个或者多个参数
  • 由类限制:
    • execution(void com.github.ls.test.service.impl.TransferServiceImpl.*(…)):TransferServiceImpl类的任何void方法,包括任何子类
  • 由接口限制:
    • execution(void com.github.ls.test.service.TransferService.transfer(*)):任何带一个参数的void transfer()方法,可以是实现TransferService的任何类
  • 使用注解:
    • execution(@javax.annotation.security.RolesAllowed void transfer*(…)):以transfer开始的任何方法,该方法还使用了@RolesAllowed注解
  • 由包限制:
    • execution(* com…test..(…)):com和test之间的一个目录
    • execution(* com..test..*(…)):com和test之间的几个目录
    • execution(* …test..*(…)):任何叫test的子目录

增加aspects

aspects是AOP最重要的一个术语。把pointcuts和advices组合在一起。

@Aspect
public class Auditing {
    //Before transfer service
    @Before("execution(* com.github.ls.test.service.TransferService.transfer(..))")
    public void validate(){
        System.out.println("bank validate your credentials before amount transferring");
    }
    //Before transfer service
    @Before("execution(* com.github.ls.test.service.TransferService.transfer(..))")
    public void transferInstantiate(){
        System.out.println("bank instantiate your amount transferring");
    }
    //After transfer service
    @AfterReturning("execution(* com.github.ls.test.service.TransferService.transfer(..))")
    public void success(){
        System.out.println("bank successfully transferred amount");
    }
    //After failed transfer service
    @AfterThrowing("execution(* com.github.ls.test.service.TransferService.transfer(..))")
    public void rollback() {
        System.out.println("bank rolled back your transferred amount");
    }
}

已经看到Auditing类如何使用@Aspect注解了。该类不只是一个Spring bean,也是一个aspect。
Auditing类的一些方法,是advices,定义了下面的逻辑:转账前,使用validate()做用户认证;然后使用transferInstantiate()实例化;转账成功,调用success()方法,如果转账失败,使用rollback()回滚。

Spring AOP支持五种advice注解:

Annotation Advice
@Before 使用before advice,advice的方法在advised method被调用前执行
@After 使用after advice,advice的方法在advised method正常执行完,或者不在乎异常后执行
@AfterReturning 使用after returning advice,advice的方法在advised method成功执行后执行
@AfterThrowing 使用after throwing advice,advice的方法在advised method抛异常退出后执行
@Around 使用around advice,advice的方法在advised method调用前后执行

实现Advice

Before

在这里插入图片描述

After Returning

在这里插入图片描述

After Throwing

在这里插入图片描述

After

在这里插入图片描述

Around

在这里插入图片描述

理解AOP代理

Spring AOP是基于代理的。Spring增加代理,在目标对象的业务逻辑之间weave aspect。
比如TransferServiceImpl类,调用者通过对象引用调用transfer()方法:
invokes

你看到了,调用者可以直接调用该service。但是,你把TransferService声明为aspect的目标。现在,这个类被代理包装了,调用者实际上不能直接调用该service,调用被路由到代理:
AOP-proxy

AOP代理是这样作用的:

  • Spring增加代理weaving aspect和目标
  • 代理也实现了目标接口
  • 所有的transfer()调用都被拦截了
  • 执行匹配的advice
  • 执行目标方法

猜你喜欢

转载自blog.csdn.net/weixin_43364172/article/details/84636092