http://www.cnblogs.com/larryzeal/tag/spring/
先说说为什么需要AOP
最简单的一个例子就是日志记录,如果想记录一些方法的执行情况,最笨的办法就是修改每一个需要记录的方法。但这,真的很笨。。。
好的方法,应该是通过反射获取方法,然后去匹配,如果需要记录日志,那就调用日志方法即可。
这就是AOP 的Weaving,俗称编织、织入,就是将需要添加的功能编织到现有功能中,而不需要修改现有代码。
另一个例子,不那么大众的需求:我想给一个对象添加方法,怎么实现?
如果有学过js、Python等动态语言,你肯定知道它们支持给对象添加方法,直接添加即可。
但是Java不行,因为Java的类型是封闭的。
Spring给出办法就是通过代理,拦截请求,然后去调用实际拥有该方法的对象的该方法!(略绕) 这就是Introduction,俗称引入。
如图:
这是书中自带的图片,很形象。
如图所示,如果调用Advised bean的Existing method,那就是Weaving(织入);如果调用introduced method,那就是Introduction。
但是,无论那种,Spring都是通过其代理功能实现的。(如果你已经知道Spring的代理功能仅限于method,那你也可以想到Spring AOP仅限于method --- 稍后讨论)
以上,记住一点就行:Spring AOP中,给方法添加功能就是织入,给对象添加功能就是引入。
(至于为什么强调是Spring AOP,这是因为还有其他的AOP框架,稍后讨论。)
再列一下其他概念:
Weaving织入部分:
Advice : 需要添加的功能,例如日志功能、权限功能等,以及什么时候添加(目标方法执行前、后、异常等时候)。
Join-point : 目标类中能够添加功能的地方!
Pointcut : 指定目标类中添加功能的地方!因为不可能给所有Join-point织入Advice!(Spring AOP仅限于方法,因为它基于代理实现。其他的框架还可以针对字段添加功能!了解就行。)
需要注意的是,Advice定义了什么时候做、做什么,而Pointcut则定义了在哪里做。
Aspect = Advices + Pointcuts // Aspect可以认为是一堆Advice的类,且其中指定了每个Advice执行的Pointcut。
Introduction引入部分: 暂无
以上,Pointcut是关键,它决定了一个AOP框架的行为。
因为Pointcut意味着where(field?method?exception?)和when(编译时?加载时?运行时?)。
【】通常,使用class和method name来定义Pointcut,或者使用正则表达式匹配class和method name来定义Pointcut!!!
Weaving应用部分
Spring AOP和AspectJ有很多协同。Spring AOP借鉴了AspectJ很多理念。
Spring对AOP的支持有四种形式: ① 经典的Spring基于代理的AOP。 ② 纯POJO aspect。 ③ @AspectJ注解驱动的aspect。 ④ 注入的AspectJ aspect。 以上,前三种是Spring自有AOP的变体,由于都是基于代理,所以,仅限于方法拦截!!!
Spring AOP引用了AspectJ EL。
AspectJ EL表达式:核心就是execution,其他的都是用于限制各种参数的。【】【】
例如:
execution(* concert.Performance.perform(..)) && within(concert.*) // 这里就定义了一个pointcut,而且仅限于被concert包下的aspect使用。
上面的AspectJ EL是由两部分组成:execution定义切入点,within限定切入点。见下图:
上面,可以使用&&或and、||或or、!或not。 类似EL或JSTL。
Spring还增加一个bean(),意思是仅限于该bean的Pointcut。
例如:execution(* concert.Performance.perform()) and bean('woodstock') 这里就定义了一个woodstock的pointcut。 例如:execution(* concert.Performance.perform()) and !bean('woodstock') 注意这里!!!很有意思的用法。
AspectJ 注解开发:
AspectJ 从 5 开始引入了注解开发,Spring AOP同样引入AspectJ的注解。
但是,Spring AOP仅仅是利用AspectJ的注解名词,底层仍然是Spring AOP的代理实现。
注解开发过程:
@Aspect注解到aspect所在的类上,然后@Before等注解到advice(aspect对应的方法)上。如下:
@Component // 这个是必须的!! @Aspect public class Audience { @Before("execution(** concert.Performance.perform(..))") // 该注解声明了silenceCellPhones()需要应用到的Pointcut。 public void silenceCellPhones() { System.out.println("Silencing cell phones"); } @Before("execution(** concert.Performance.perform(..))") public void takeSeats() { System.out.println("Taking seats"); } @AfterReturning("execution(** concert.Performance.perform(..))") public void applause() { System.out.println("CLAP CLAP CLAP!!!"); } @AfterThrowing("execution(** concert.Performance.perform(..))") public void demandRefund() { System.out.println("Demanding a refund"); } }
但是,上面这种写法很不方便,因为Pointcut是重复的。
解决办法:使用@Pointcut一次性定义好一个Pointcut。如下:
@Component // 这个是必须的!!! @Aspect public class Audience { @Pointcut("execution(** concert.Performance.perform(..))") public void perform(){}; // 必须要定义一个方法,用于承载pointcut! // 其他的正常代码,略 }
但是,到目前为止,AOP仍然是无法执行的,因为Spring AOP不知道这些注解代表什么,所以需要先开启AspectJ自动代理。
开启方法:@EnableAspectJAutoProxy注解到JavaConfig上面。或者,如果使用XML,<aop:aspectj-autoproxy> 。注意导入名称空间。
现在,上面的内容可以直接进行测试了:
package aop.performance; /** * 用这个演示join-point和pointcut。 * perform()就是join-point! * * @author Larry */ public interface Performance { void perform(); }
package aop.performance; import org.springframework.stereotype.Component; @Component public class PerformanceImpl implements Performance{ @Override public void perform() { System.out.println(this.getClass()+"正在演出~~~"); } }
package aop; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; /** * 用Audience类来掩饰AspectJ 5的注解用法。 * * @author Larry * */ @Component @Aspect public class Audience { @Before("execution(** aop.performance.Performance.perform(..))") public void takeSeat() { System.out.println("演出之前要入座~"); } @Before("execution(** aop.performance.Performance.perform(..))") public void silenceCellPhones() { System.out.println("演出之前要静音~"); } @After("execution(** aop.performance.Performance.perform(..))") public void applause() { System.out.println("演出之后要鼓掌!"); } // TODO: 貌似不能这样用??而且会导致大BUG!!!阻止访问Pointcut!!!见下面 //@Around("execution(** aop.performance.Performance.perform(..))") public void greet() { System.out.println("演出前后要致意~"); } @AfterReturning("execution(** aop.performance.Performance.perform(..))") public void leave() { System.out.println("结束后,goodbye~"); } @AfterThrowing("execution(** aop.performance.Performance.perform(..))") public void demandRefund(){ System.out.println("退钱!!!"); } //上面,不好的地方是每次都要写相同的pointcut!解决办法如下: @Pointcut("execution(** aop.performance.Performance.perform(..))") public void perform(){} // 这样就定义了一个pointcut:performance(),然后就可以直接使用了!如下: @Before("perform()") public void wave(){ System.out.println("挥挥手~"); } // TODO: 务必注意,@Around必须手动调用Pointcut,否则会阻止对Pointcut的访问!!! @Around("perform()") public void greet2(ProceedingJoinPoint jp) { try { System.out.println("演出前后要致意~A"); jp.proceed();//TODO:这里还可以调用带参数的! System.out.println("演出前后要致意~B"); } catch (Throwable e) { e.printStackTrace(); } } }
package aop; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; import aop.performance.PerformanceImpl; @Configuration @ComponentScan(basePackageClasses={ Audience.class,PerformanceImpl.class,AudienceB.class,IntroductionEncoreable.class }) @EnableAspectJAutoProxy //激活AspectJ public class JavaConfig { }
package aop.test; import java.util.Arrays; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.core.env.Environment; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import aop.JavaConfig; import aop.performance.Performance; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = { JavaConfig.class }) public class PerformanceAOPTest { @Autowired Environment env; @Autowired ApplicationContext ac; @Autowired Performance p; @Test public void run() { String[] activeProfiles = env.getActiveProfiles(); System.out.println("activeProfiles的长度"+activeProfiles.length); for (String string : activeProfiles) { System.out.println("activeProfiles:" + string); } System.out.println("-------------------------------------"); String applicationName = ac.getApplicationName(); System.out.println("applicationName:"+applicationName); String[] beanDefinitionNames = ac.getBeanDefinitionNames(); String beans = Arrays.toString(beanDefinitionNames); System.out.println("applicationContext中的beans:"+beans); } @Test public void run1() { p.perform(); // 注意有没有激活AspectJ! } }
上面的代码就是一个测试的全过程,其中遇到的一个问题就是环绕通知@Around,这个注解要求必须手动调用Pointcut(方法),否则Spring代理会丢失该方法!
丢失该方法,就意味着后面的代理无法继续!!!(类似拦截器拦截请求,拦截之后还要手动放行,否则后面的程序无法接收到该请求,也就是 丢失请求!)
需要注意的是,还可以多次调用该方法!!!应用场景:异常后重新执行。
@Around("performance()") public void watchPerformance(ProceedingJoinPoint jp) { try { System.out.println("Silencing cell phones"); // 相当于@Before System.out.println("Taking seats"); // 相当于@Before jp.proceed(); // 【】【】这个,就是调用pointcut。可能忘记调用,也可能重复调用。。。 System.out.println("CLAP CLAP CLAP!!!"); // 相当于@After 【奇怪,那@AfterReturning呢】 } catch (Throwable e) { System.out.println("Demanding a refund"); // 相当于@AfterThrowing } }
到目前为止,介绍的都是无参数的Pointcut(是指Advice不使用Pointcut的参数),下面开始带参数的Pointcut。
带参数的Pointcut(Advice使用Pointcut的参数)
// 样式 execution(* soundsystem.CompactDisc.playTrack(int)) && args(trackNumber)
注意,需要在Pointcut中给定参数类型,以及形参名。然后,再给Advice添加相同的形参即可(类型和形参名)。如下:
/* 注意,这里实现的功能是统计trackNumber的播放次数! */ @Aspect @Component public class TrackCounter { private Map<Integer, Integer> trackCounts = new HashMap<Integer, Integer>(); // 定义Pointcut @Pointcut("execution(* soundsystem.CompactDisc.playTrack(int)) && args(trackNumber)") public void trackPlayed(int trackNumber) {} // Advice @Before("trackPlayed(trackNumber)") // trackNumber就是pointcut方法的形参名!!! public void countTrack(int trackNumber) { int currentCount = getPlayCount(trackNumber); trackCounts.put(trackNumber, currentCount + 1); } // 普通的方法 public int getPlayCount(int trackNumber) { return trackCounts.containsKey(trackNumber) ? trackCounts.get(trackNumber) : 0; } }
Introduction应用部分
Introduction就是给对象(Bean)引入需要的功能,而不修改原有代码。(例如你拿不到源代码的情况~)
Spring AOP的实现方法就是拦截请求,再转而调用实现了所需方法的对象即可。
示例:
现在需要给Performance引入一个performEncore功能(再来一个、加演、额外演出 的意思)。
根据Spring AOP的原理,我们需要一个拥有该方法的Bean,所以我们先定义一个接口,再去实现它。
package aop.performance; /** * Encore,加演。延长演出的意思。 * @author Larry * */ public interface Encoreable { void performEncore(); }
package aop.performance; import org.springframework.stereotype.Component; @Component public class EncoreableImpl implements Encoreable{ @Override public void performEncore() { System.out.println("加演一场~~~"); } }
package aop; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.DeclareParents; import org.springframework.stereotype.Component; import aop.performance.Encoreable; import aop.performance.EncoreableImpl; /** * AOP应用之Introduction,就是给对象(bean)添加功能,类似js之类的动态语言给对象添加方法。。 * @author Larry * */ @Component @Aspect public class IntroductionEncoreable { @DeclareParents(value="aop.performance.Performance+",defaultImpl=EncoreableImpl.class) // 稍后讲 public static Encoreable encoreable; // 先引入需要引入的方法所在的接口 }
package aop; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; import aop.performance.PerformanceImpl; @Configuration @ComponentScan(basePackageClasses={ PerformanceImpl.class,IntroductionEncoreable.class }) @EnableAspectJAutoProxy //激活AspectJ public class JavaConfig { }
package IntroductionEncoreable; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import aop.JavaConfig; import aop.performance.Encoreable; import aop.performance.Performance; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes={JavaConfig.class}) public class IntroductionAOPTest { @Autowired ApplicationContext ac; @Autowired Performance p; @Test public void run1(){ ((Encoreable)p).performEncore(); // 通过类型强转调用Introduction的方法!!! } }
上面就是测试Introduction的全部代码。
需要注意两点:
① @DeclareParents Field,其value为Pointcut所在的类(这里是接口,+表示其所有实现类或子类),defaultImpl则是接口的默认实现类,而Field则是所需方法所在的接口。
② 通过类型强转,将目标Bean转成@DeclareParents Field类型,再去调用方法!
最后,XML中配置Weaving织入,懒得弄了,直接上图吧
在XML中,一样可以定义Pointcut,然后在其他地方引用:
<aop:config> <aop:aspect ref="audience"> <aop:pointcut id="performance" expression="execution(** aop.performance.Performance.perform(..))" /> <aop:before pointcut-ref="performance" method="silenceCellPhones"/> <aop:before pointcut-ref="performance" method="takeSeats"/> <aop:after-returning pointcut-ref="performance" method="applause"/> <aop:after-throwing pointcut-ref="performance" method="demandRefund"/> </aop:aspect> </aop:config>
XML配置和注解配置类似,唯一需要注意的是环绕通知@Around,还是需要指定一个方法,该方法接收ProceedingJoinPoint对象。
就是说,实际上同@Aspect Class的@Around Method一样,只不过现在去掉@Aspect和@Around,改为XML配置。
package aop; import org.aspectj.lang.ProceedingJoinPoint; public class Audience { public void greet3(ProceedingJoinPoint jp) { try { System.out.println("演出前后要致意~A"); jp.proceed(); // TODO:这里还可以调用带参数的! System.out.println("演出前后要致意~B"); } catch (Throwable e) { e.printStackTrace(); } } }
<aop:config> <aop:aspect ref="audience"> <aop:pointcut id="performance" expression="execution(** aop.performance.Performance.perform(..))" /> <aop:around pointcut-ref="performance" method="greet3"/> </aop:aspect> </aop:config>
另外,XML配置中的带参数Pointcut,略。见Spring in Action, 4th Edition p147。
XML中Introduction引入配置
<aop:aspect> <aop:declare-parents types-matching="aop.performance.Performance+" implement-interface="aop.performance.Encoreable" default-impl="aop.performance.DefaultEncoreable" /> </aop:aspect>
或者,不使用default-impl,而使用delegate-ref。
<bean id="encoreableDelegate" class="aop.performance.DefaultEncoreable" /> <aop:aspect> <aop:declare-parents types-matching="aop.performance.Performance+" implement-interface="aop.performance.Encoreable" delegate-ref="encoreableDelegate" /> </aop:aspect>
未完待续
https://www.cnblogs.com/larryzeal/p/5423411.html
相关推荐
Spring_AOP笔记Spring_AOP笔记Spring_AOP笔记Spring_AOP笔记Spring_AOP笔记Spring_AOP笔记Spring_AOP笔记
NULL 博文链接:https://microjava.iteye.com/blog/525796
Spring Aop 学习笔记
自己学习spring课程的笔记。笔记都是根据尚硅谷的课程(spring ioc,spring aop,spring mvc,spring boot等)写的。 主要内容:spring ioc,spring aop,spring mvc,spring boot
4th》学习笔记 第一部分 Spring的核心 1. Spring之旅 依赖注入 AOP bean的初始化过程 spring容器 2. 装配Bean “initialization on demand holder”创建单例模式的理解,参考 Spring中单例的概念限于Spring上下文中,...
spring aop spring aop spring aop spring aop spring aop spring aop spring aop spring aop spring aop
When to use Spring AOP and AspectJ AOP? Expert author Ramnivas Laddad shows how to combine technologies such as Spring, Hibernate, Swing, and JDBC with AspectJ. The book fully covers the latest ...
NULL 博文链接:https://linres.iteye.com/blog/281221
Spring是掠过Java大地的一阵... 中文版.part4.rar Spring in Action. 中文版.part5.rar Spring in Action. 中文版.part6.rar Spring in Action. 中文版.part7.rar Spring in Action. 中文版.part8.rar
Spring是掠过Java大地的一阵... 中文版.part4.rar Spring in Action. 中文版.part5.rar Spring in Action. 中文版.part6.rar Spring in Action. 中文版.part7.rar Spring in Action. 中文版.part8.rar
Spring是掠过Java大地的一阵... 中文版.part4.rar Spring in Action. 中文版.part5.rar Spring in Action. 中文版.part6.rar Spring in Action. 中文版.part7.rar Spring in Action. 中文版.part8.rar
spring-aop-1.1.1.jar spring-aop-1.2.6.jar spring-aop-1.2.9.jar spring-aop-2.0.2.jar spring-aop-2.0.6.jar spring-aop-2.0.7.jar spring-aop-2.0.8.jar spring-aop-2.0.jar spring-aop-2.5.1.jar spring-aop-...
SpringAOP学习笔记以及四个可运行的Demo,涵盖经典代理模式、基于注解、基于xml配置这3方面的Demo
李君老师JavaEE笔记-SpringAop
Spring_AOP_学习小结 Spring_AOP_学习小结 Spring_AOP_学习小结 Spring_AOP_学习小结
《Spring技术内幕》学习笔记1——IoC容器体系结构 《Spring技术内幕》学习笔记2——IoC定位Bean定义资源 《Spring技术内幕》学习笔记3——IoC容器载入Bean定义资源文件 ...《Spring技术内幕》学习笔记7——AOP基础
spring之AOP(动态代理),包括jdk动态代理和CGLib动态代理
Praise for the Third Edition of Spring in Action Preface Acknowledgments About this Book 1. Core Spring Chapter 1. Springing into action 1.1. Simplifying Java development 1.1.1. Unleashing the power ...
对Spring框架中的AOP的介绍,能够全面了解Spring中AOP的功能和使用方法。