Spring笔记 - 面向切面编程AOP

1. 面向切面编程AOP

1.1 概述

- AOP是解耦的重要手段,让业务实体专一于业务逻辑,其余诸如安全验证、日志记录等辅助功能经过切面的方式,切入到业务实体的执行过程。java

- 主流的AOP框架有:AspectJ、JBoss AOP、Spring AOPgweb

- Spring有本身实现的AOP,能够覆盖不少场景;也能够和AspectJ集成,得到更强的功能。Spring号称永远不会提供一个强大的解决方案和AspectJ竞争。spring

[参考]
express


1.2 AOP术语

- Aspect: 切面,是切入目标对象后执行的方法以及所属的对象,一般是非业务的辅助功能,能够说是切点、通知、织入对象的组合
编程

- Joint Point: 链接点,是被织入目标能够被织入的地方,一般是业务对象的方法、字段、异常等安全

- Advice: 通知,是切面应用在链接点的时机,包括around、before、afterapp

- Pointcut: 切点,符合AspectJ切点表达式的链接点集合,一般一个切面能够做用于多个链接点
框架

- Weaving: 织入,把切面应用在链接点的过程,能够是:函数

  • 编译期compile time,例如AspectJ的织入编译器测试

  • 加载器load time,例如AspectJ5的LTW

  • 运行时run time,例如Spring AOP等纯Java AOP框架

- Introduction: 引入,为被织入对象定义新的方法或字段


1.3 Spring AOP

- Spring提供了4种方式的AOP支持:

  • 基于代理的经典AOP,因为繁琐不多使用

  • 纯POJO做为切面,经过xml的spring aop schema定义

  • @AspectJ注解驱动,功能基本等同于aop schema;Spring支持部分AspectJ注解;注意:@Aspect注解加上component-scan机制,才会被Spring容器发现并注册

  • 注入式AspectJ切面,将AspectJ本地方法加载到Spring容器

- 若是织入的目标对象是接口,Spring AOP基于JDK动态代理进行运行时织入;不然使用CGLIB。

- Spring AOP的链接点只能是public方法;而经过Spring集成AspectJ能够是private/protected方法或构造函数等。

1.3.1 切点

1.3.1.1 切点类型

- Spring AOP支持部分AspectJ的切点类型,且有本身扩展的类型

- Spring自动优化切点顺序,但定义时尽可能缩小Spring的搜索范围

- 切点类型[参考1][参考2][参考3]

  • execution:用于匹配符合表达式的链接点;execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)

  • within:用于匹配指定类型(指定类型或类型模式)内的方法执行;

  • target:用于匹配目标对象(指定类型)的方法;注意是目标对象的类型匹配,这样就不包括类型匹配的引入接口;

  • this:用于匹配AOP代理对象的方法;注意是AOP代理对象的类型匹配,这样就能够包括类型匹配的引入接口;

  • args:用于匹配当前执行的方法传入的参数为指定类型的方法;

  • @within:用于匹配因此持有指定注解类型内的方法,静态匹配

  • @target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解,运行时匹配,在Spring容器中,和@within同样,在AspectJ中,区别很大;

  • @args:用于匹配当前执行的方法传入的参数持有指定注解的执行;

  • @annotation:用于匹配当前执行方法持有指定注解的方法;

  • bean:Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法;

  • reference pointcut:表示引用其余命名切入点,只有@ApectJ风格支持,Schema风格不支持。

- 例子

  • execution(public * *(..)) 全部公共方法

  • execution(* set*(..)) 全部名字以set开头的方法

  • execution(* com.xyz.service.AccountService.*(..)) 全部AccountService接口方法的实现

  • execution(* com.xyz.service.*.*(..)) 全部service包里的方法

  • execution(* com.xyz.service..*.*(..)) 全部service包及子包里的方法

  • execution(* *(java.io.Serializable)) 参数类型可序列号,与args不一样

  • within(com.xyz.service.*) 全部service包的链接点

  • this(com.xyz.service.AccountService) 链接点的代理实现了AccountService接口

  • target(com.xyz.service.AccountService) 目标对象实现了AccountService接口

  • args(java.io.Serializable) 运行时传递的参数可序列化

  • @target(org.springframework.transaction.annotation.Transactional) 目标对象加了@Transactional注解

  • @within(org.springframework.transaction.annotation.Transactional) 目标对象的声明类型加了@Transactional注解

  • @annotation(org.springframework.transaction.annotation.Transactional) 方法加了@Transactional注解

  • @args(com.xyz.security.Classified) 运行时传入的参数加了@Classified注解

  • bean(*Service)  Bean Id需以Service结尾

1.3.1.2 切点组合

@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}
@Pointcut("within(x.y.z.trading..*)")
private void inTrading() {}
//and方式组合切点,还能够用||、!
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}
<!-- 基于schema的定义没法实现切点组合,只能组合表达式 -->
<aop:pointcut id="tradingOperation" expression="execution(public * *(..)) **and** within(x.y.z.trading..*)"/>

1.3.1.3 切点重用

// 定义切点
@Aspect
@Component
public class SystemArchitecture {
    @Pointcut("within(x.y.z.web..*)")
    public void inWebLayer() {}
    @Pointcut("within(x.y.z.service..*)")
    public void inServiceLayer() {}
}
// 引用切点
@Aspect
@Component
public class WebProcessor{
    @Before(pointcut="x.y.z.SystemArchitecture.inWebLayer()")
    public void inWebLayer() {}
    @Pointcut("within(x.y.z.service..*)")
    public void doSth() { /* ... */ }
}
<aop:config>
    <aop:pointcut id="inWebLayer" expression="within(x.y.z.web..*)"/>
    <aop:aspect id="webProcessorAspect" ref="webProcessor">
        <aop:before method="doSth" pointcut-ref="inWebLayer" />        
    </aop:aspect>
</aop:config>

1.3.2 通知

1.3.2.1 通知类型

@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")

@AfterReturning(
    pointcut="x.y.z.SystemArchitecture.dataAccessOperation()",
    returning="retVal")
public void doAccessCheck(Object retVal) { /* ... */ }
    
@AfterThrowing(
    pointcut="x.y.z.SystemArchitecture.dataAccessOperation()",
    throwing="ex")
public void doRecoveryActions(DataAccessException ex) { /* ... */ }

//After (finally)
@After("x.y.z.SystemArchitecture.dataAccessOperation()")
public void doReleaseLock() { /* ... */ }

@Around("x.y.z.SystemArchitecture.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
    // 开始计时 ...
    Object retVal = pjp.proceed();
    // 中止计时 ...
    return retVal;
}
<aop:after-throwing pointcut="..." throwing="ex" method="doRecoveryActions"/>

13.2.2 传递参数到通知

// 前面AfterReturning能够returning参数、AfterThrowing能够throwing参数,此外,还能够运用args为切点、通知和切面指定参数

@Pointcut("x.y.z.SystemArchitecture.dataAccessOperation() && args(Account,int)")
private void accountDataAccessOperation(Account account, int age) {}

@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) { /* ... */}
<aop:pointcut id="accountDataAccessOperation" expression=""x.y.z.SystemArchitecture.dataAccessOperation(Account,int) and args(account,age)" />
<aop:before pointcut="accountDataAccessOperation" method="validateAccount" arg-names="account,age"/>

13.2.3 传递参数到链接点

@Around("x.y.z.SystemArchitecture.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
    Object retVal = pjp.proceed(new Object[] {"some arg"});
    return retVal;
}

1.3.3 引入Introduction

引入是利用代理为被代理的Bean加入新的方法或字段

// 前提:被代理的Bean
public class UserService {
    public void saveUser(User user) { /* ... */ }
    // 其余方法和字段略
}
// 任务:要在saveUser以前检查user是否知足条件
// a. 定义Checker接口
public interface Checker {
    public boolean check(User user);
}
// b. 实现Checker接口
public class UserChecker implements Checker {
    public boolean check(User user) { /* ... */ }
}
// c. 定义切面,进行引入
@Aspect
@Component
public class CheckerAspect {
    @DeclareParents(
        value="x.y.z.UserService", 
        defaultImpl=x.y.z.UserChecker.class)
    public UserChecker userChecker;
}
// d. 测试
UserService userService = (UserService)ctx.getBean("userService");
UserChecker userChecker = (UserChecker)userService;
if(userChecker.check(user)
    userService.saveUser(user);
<aop:aspect id="checkerAspect" ref="checkerAspect">
    <aop:declare-parents 
        type-matching="x.y.z.UserService"
        implement-interface="x.y.z.Checker"
        default-impl="x.y.z.UserChecker" />
    <aop:before pointcut="x.y.z.UserService.saveUser()" method="check" />
</aop:aspect>

1.3.4 打开AspectJ auto-proxying自动代理

让Spring容器自动为被代理类建立代理并织入切面

/JavaConfig方式
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass=false) //默认为false,为true时使用CGLib动态代理技术织入加强。不过即便proxy-target-class设置为false,若是目标类没有声明接口,则spring将自动使用CGLib动态代理
@ComponentScan
public class ConcertConfig {
    @Bean
    public Audience audience() {
        return new Audience();
    }
}

@Aspect
public class Audience {
    // 定义pointcut和advice
}
<!-- xml方式 -->
<beans ...>
    <context:component-scan base-package="x.y.z" />
    <aop:aspectj-autoproxy poxy-target-class="false"/>
    <bean class="Audience />
</bean>

1.3.5 Advisors

Advisor是advice和pointcut的组合,只包含一个advice,advice实现了各类Advice接口,典型应用例如tx-advice、cache-advice

<aop:config>
    <aop:pointcut id="businessService"
        expression="execution(* com.xyz.myapp.service.*.*(..))"/>
    <aop:advisor
        pointcut-ref="businessService"
        advice-ref="tx-advice"/>
</aop:config>
<tx:advice id="tx-advice">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>
相关文章
相关标签/搜索