AOP
AOP 提供一种新的思考程序结构的方法来补充 OOP。OOP 中模块的关键是类,而 AOP 中模块的关键是切面。切面支持跨多个类型和对象的模块化(如事务管理)。
AOP 概念
- 切面 —— 在切入点进行通知操作的过程(包含通知和切人点的类 @Aspect)
- 连接点 —— 所有可能被织入通知的候选点(具体业务逻辑方法)
- 切入点 —— 满足匹配规则的连接点(@Pointcut)
- 目标对象 —— 被一到多个切面通知的对象
- AOP 代理 —— AOP 框架基于切面规则创建的对象
- 织入 —— 创建通知对象关联切面和其他应用
-
通知 —— 对切入点进行的操作
- 前置通知 —— 在连接点之前执行的通知(@Before)
- 后置通知 —— 在连接点正常完成后执行的通知(@AfterReturning)
- 环绕通知 —— 在方法调用前后执行的通知(@Around)
- 异常通知 —— 方法抛出异常时执行的通知(@AfterThrowing)
- 最终通知 —— 从连接点退出后执行的通知(@After)
Spring AOP 目标
- 纯 Java 实现。不需要特殊的编译过程。不需要控制类加载器层次结构,适合在 Servlet 容器或应用程序服务器中使用。
- 只支持方法执行连接点。如果需要字段拦截和更新连接点,考虑 AspectJ 之类的语言。
- Spring AOP 不是提供最完整的 AOP 实现。其目标是提供 AOP 实现和 Spring IoC 之间的紧密集成。
AOP 代理
- Spring AOP 默认使用标准 JDK 动态代理实现 AOP 代理。
- Spring AOP 还可以使用CGLIB代理。
@Aspect 风格
- 激活 @AspectJ 支持 —— 通过自动代理,Spring 自动为被通知的 Bean 生成代理来拦截方法调用,确保按需执行通知
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
- 定义一个切面 —— 在应用程序上下文中使用 @AspectJ 类定义 Bean 会被 Spring 自动检测到,并用于配置 Spring AOP
@Aspect
public class NotVeryUsefulAspect {
}
- 定义一个切入点 —— 确定感兴趣的连接点,从而使我们能够控制何时执行通知。
@Pointcut("execution(* transfer(..))")// 切入点表达式
private void anyOldTransfer() {}
- execution —— 用于匹配方法执行连接点
- within —— 将匹配限制为特定类型中的连接点
- this —— 将匹配限制为 Bean 引用(AOP 代理)是给定类型实例的连接点
- target —— 将匹配限制为目标对象是给定类型实例的连接点
- args —— 将匹配限制为参数是给定类型实例的连接点
- @target —— 将匹配限制为执行对象的类具有给定类型注解的连接点
- @args —— 将匹配限制为传递的实际参数的运行时类型具有给定类型注解的连接点
- @within —— 将匹配限制为与具有给定注解类型的连接点
- @annotation —— 将匹配限制为连接点的主题具有给定类型注释的连接点
@Aspect
public class SystemArchitecture {
@Pointcut("within(com.xyz.someapp.web..*)")
public void inWebLayer() {}
@Pointcut("within(com.xyz.someapp.service..*)")
public void inServiceLayer() {}
@Pointcut("within(com.xyz.someapp.dao..*)")
public void inDataAccessLayer() {}
@Pointcut("execution(* com.xyz.someapp..service.*.*(..))")
public void businessService() {}
@Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")
public void dataAccessOperation() {}
}
execution(修饰符? 返回类型 包名?方法名称(参数类型) 异常类型?)
//拦截任何公共方法的执行
execution(public * *(..))
//拦截任何以 set 开头的方法的执行
execution(* set*(..))
//拦截任何定义在 AccountService 接口中的方法的执行
execution(* com.xyz.service.AccountService.*(..))
//拦截任何定义在 service 包中的方法的执行
execution(* com.xyz.service.*.*(..))
//拦截任何定义在 service 包或者其一个子包中的方法的执行
execution(* com.xyz.service..*.*(..))
//拦截 service 包中的连接点
within(com.xyz.service.*)
//拦截 service 包或者其一个子包中的连接点
within(com.xyz.service..*)
//拦截实现 AccountService 接口的代理的连接点
this(com.xyz.service.AccountService)
//拦截实现 AccountService 接口的目标对象的连接点
target(com.xyz.service.AccountService)
//拦截参数只有一个 Serializable 的连接点
args(java.io.Serializable)
//拦截目标对象有 @Transactional 注解的连接点
@target(org.springframework.transaction.annotation.Transactional)
//拦截目标对象的声明类型有 @Transactional 注解的连接点
@within(org.springframework.transaction.annotation.Transactional)
//拦截执行方法有 @Transactional 注解的连接点
@annotation(org.springframework.transaction.annotation.Transactional)
//拦截参数只有一个 @Classified 注解的连接点
@args(com.xyz.security.Classified)
- 定义通知 —— 与切入点表达式相关联,并在匹配的方法执行之前、之后或前后运行
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
@AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
@AfterReturning(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
returning="retVal")
public void doAccessCheck(Object retVal) {
// ...
}
@AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doRecoveryActions() {
// ...
}
@AfterThrowing(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
throwing="ex")
public void doRecoveryActions(DataAccessException ex) {
// ...
}
@After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doReleaseLock() {
// ...
}
@Around("com.xyz.myapp.SystemArchitecture.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
-
JoinPoint 参数 —— ProceedingJoinPoint 是 JoinPoint 的子类
- getArgs(): 返回方法参数
- getThis(): 返回代理对象
- getTarget(): 返回目标对象
- getSignature(): 返回被通知方法的描述
- toString(): 打印被通知方法的描述
- 传递参数给通知
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
public void validateAccount(Account account) {
// ...
}
@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
private void accountDataAccessOperation(Account account) {}
@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
// ...
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
AuditCode value();
}
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
public void audit(Auditable auditable) {
AuditCode code = auditable.value();
// ...
}
public interface Sample<T> {
void sampleGenericMethod(T param);
void sampleGenericCollectionMethod(Collection<T> param);
}
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
// Advice implementation
}
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
public void beforeSampleMethod(Collection<MyType> param) {
// Advice implementation
}
@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
}
//第一参数是 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
}
@Before("com.xyz.lib.Pointcuts.anyPublicMethod()")
public void audit(JoinPoint jp) {
// ... use jp
}
@Around("execution(List<Account> find*(..)) && " +
"com.xyz.myapp.SystemArchitecture.inDataAccessLayer() && " +
"args(accountHolderNamePattern)")
public Object preProcessQueryPattern(ProceedingJoinPoint pjp,
String accountHolderNamePattern) throws Throwable {
String newPattern = preProcess(accountHolderNamePattern);
return pjp.proceed(new Object[] {newPattern});
}
代理机制
- Spring AOP 使用 JDK 动态代理或 CGLIB 为给定的目标对象创建代理。
- 如目标对象实现至少一个接口,则使用 JDK 动态代理。如目标对象没有实现任何接口,则创建一个 CGLIB 代理。
public class SimplePojo implements Pojo {
public void foo() {
// this next method invocation is a direct call on the 'this' reference
this.bar();
}
public void bar() {
// some logic...
}
}
public class Main {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new SimplePojo());
factory.addInterface(Pojo.class);
factory.addAdvice(new RetryAdvice());
Pojo pojo = (Pojo) factory.getProxy();
// this is a method call on the proxy!
pojo.foo();
}
}
API
Pointcut
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
}
public interface ClassFilter {
boolean matches(Class clazz);
}
public interface MethodMatcher {
boolean matches(Method m, Class targetClass);
boolean isRuntime();
boolean matches(Method m, Class targetClass, Object[] args);
}