Spring 5 设计模式 - 使用代理和装饰模式的Spring AOP
Spring中的代理模式
代理模式,就是向外界提供一个类,代表另一个类的功能。
Spring提供两种代理
JDK proxy | CGLIB proxy |
---|---|
也叫动态代理 | 不是JDK内建的 |
API在JDK内 | 在Spring JARs内 |
要实现接口 | 没有接口时使用 |
可代理接口 | 不能用于final类和方法(因为不能被覆盖) |
什么是AOP
Aspect-Oriented Programming (AOP)能够模块化cross-cutting concerns。它是另一种编程范式,补充了OOP。
OOP的关键要素是类和对象,AOP的关键要素则是aspect。Aspects允许你在程序的多个地方模块化一些功能(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);
//
}
}
上面的代码只涉及安全,再加上事务和日志,代码结构就像是这个样子的:
代码分散
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
}
}
可以看到,安全相关的代码分散在各个功能里。
解决
AOP的核心术语和概念
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()方法:
你看到了,调用者可以直接调用该service。但是,你把TransferService声明为aspect的目标。现在,这个类被代理包装了,调用者实际上不能直接调用该service,调用被路由到代理:
AOP代理是这样作用的:
- Spring增加代理weaving aspect和目标
- 代理也实现了目标接口
- 所有的transfer()调用都被拦截了
- 执行匹配的advice
- 执行目标方法