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: 织入,把切面应用在链接点的过程,能够是:函数
|
- Introduction: 引入,为被织入对象定义新的方法或字段
1.3 Spring AOP
- Spring提供了4种方式的AOP支持:
|
- 若是织入的目标对象是接口,Spring AOP基于JDK动态代理进行运行时织入;不然使用CGLIB。
- Spring AOP的链接点只能是public方法;而经过Spring集成AspectJ能够是private/protected方法或构造函数等。
1.3.1 切点
1.3.1.1 切点类型
- Spring AOP支持部分AspectJ的切点类型,且有本身扩展的类型
- Spring自动优化切点顺序,但定义时尽可能缩小Spring的搜索范围
|
- 例子
|
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>