AOP,面向切面编程。其实是一种编程思想。主要是对多个相同的代码逻辑的方法进行切面,分离出相同的代码,整合至一个模块里面(比如事务管理,安全管理,系统日志等)。
为什么需要AOP
如果有一个场景,开发中在多个模块间有某段重复的代码,我们通常是怎么处理的?
如果都用 复制粘贴 的方法,那势必会造成代码的冗余,后面在维护方面上也会很困难。
如果对 公共的代码 进行方法的封装,就可以节约代码的冗余,当这段代码修改修改时,也只需要改变这个方法就可以了。
但是如果有一天需求发生改变,我们可能需要再抽象出一个方法,然后再在需要的地方调用这个新的方法。又或者我们不需要这个方法了,我们还是得删除每一处调用该方法的地方。
此时,我们可以使用AOP的思想,做公共代码做横切面,整合成一个新的模块,更加方便操作和维护。
AOP的分类
AOP要达到的效果是,保证开发者在不修改源代码的前提下,去为系统中的业务组件添加某种通用功能。AOP本质是由AOP框架修改业务组件的多个方法的源代码。AOP其实就是代理模式的一种应用。可以分两类:
静态AOP实现
AOP框架在编译阶段对程序源代码进行修改,生成静态的AOP代理类。比如AspectJ;
动态AOP实现
AOP框架在运行阶段动态生成代理对象(在内存中以JDK动态代理,或CGLib动态地生成AOP代理类)比如Spring AOP。
AOP术语
通知(Advice): AOP框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。
连接点(JoinPoint):连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在Spring AOP中,连接点总是方法的调用。
切点(PointCut):可以插入增强处理的连接点。
切面(Aspect):切面是通知和切点的结合。
引入(Introduction):引入允许我们向现有的类添加新的方法或者属性。
织入(Weaving):将增强处理添加到目标对象中,并创建一个被增强的对象这个过程就是织入。
Spring AOP
基本代码
package com.ly.ybg.school;public interface IBuy {String buy();}// Boy.classpackage ccom.ly.ybg.school;import org.springframework.stereotype.Component;@Componentpublic class Boy implements IBuy {@Overridepublic String buy() {System.out.println("男孩买了一个游戏机");return "游戏机";}}// Girl.classpackage com.ly.ybg.school;import org.springframework.stereotype.Component;@Componentpublic class Girl implements IBuy {@Overridepublic String buy() {System.out.println("女孩买了一件漂亮的衣服");return "衣服";}}
// 配置文件 AppConfig.classpackage com.ly.ybg.school;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;@Configuration@ComponentScan(basePackageClasses = {com.sharpcj.aopdemo.test1.IBuy.class})public class AppConfig {}
package com.ly.ybg.school;import com.ly.ybg.school.Boy;import com.ly.ybg.school.Girl;import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class AppTest {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);Boy boy = context.getBean("boy",Boy.class);Girl girl = (Girl) context.getBean("girl");boy.buy();girl.buy();}}
AOP功能
Spring AOP支持的AspectJ指示器
| Aspect指示器 | 描述 | 
|---|---|
| arg() | 限制连接点匹配参数为指定类型的执行方法 | 
| @arg() | 限制连接点匹配参数由指定注解标注的执行方法 | 
| execution() | 用于匹配连接点的执行方法 | 
| this() | 限制连接点匹配AOP代理的Bean引用为指定类型的类 | 
| target() | 限制连接点匹配目标对象为指定类型的类 | 
| @target() | 限制连接点匹配特定的执行对象,这些对象对应的类具备指定指定类型的注解 | 
| within() | 限制连接点匹配指定的类(类型) | 
| @within() | 限制连接点匹配指定注解所标注的类型 | 
| @annotation() | 限制匹配带有指定注解连接点 | 
execution(* com.ly.ybg.school.querySchool(..))* -------------------- 返回任意类型com.ly.ybg.school ----- 方法所属的类(类型)querySchool ----------- 方法名称(..) ------------------ 使用任意参数多个匹配之间可以使用连接符 && 、 || 、 ! 来表示 且,或,非的关系。execution(* com.ly.ybg.school.querySchool(...) && within(com.ly.ybg.school.*))在切点中选择bean,可以使用execution(* com.ly.ybg.school.querySchool(...) && bean(girl))
package com.sharpcj.aopdemo.test1;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.springframework.stereotype.Component;@Aspect@Componentpublic class BuyAspectJ {@Before("execution(* com.ly.ybg.school.IBuy.buy(..)) && within(com.ly.ybg.school.*) && bean(girl)")public void hehe(){System.out.println("男孩女孩都买自己喜欢的东西");}}结果:男孩买了一个游戏机男孩女孩都买自己喜欢的东西女孩买了一件漂亮的衣服
通过注解声明的5种通知类型
| @Before | 通知方法会在目标方法调用之前执行 | 
|---|---|
| @After | 通知方法会在目标方法返回或抛出异常后调用 | 
| @AfterReturning | 通知方法会在目标方法返回后调用 | 
| @AfterThrowing | 通知方法会在目标方法抛出异常后调用 | 
| @Around | 通知方法会将目标方法封装起来 | 
package com.ly.ybg.school;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.*;import org.springframework.stereotype.Component;@Aspect@Componentpublic class BuyAspectJ {@Before("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")public void hehe() {System.out.println("before ...");}@After("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")public void haha() {System.out.println("After ...");}@AfterReturning("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")public void xixi() {System.out.println("AfterReturning ...");}@Around("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")public void xxx(ProceedingJoinPoint pj) {try {System.out.println("Around aaa ...");pj.proceed();System.out.println("Around bbb ...");} catch (Throwable throwable) {throwable.printStackTrace();}}}package com.ly.ybg.school;import com.ly.ybg.school.Boy;import com.ly.ybg.school.Girl;import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class AppTest {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);Boy boy = context.getBean("boy",Boy.class);Girl girl = (Girl) context.getBean("girl");boy.buy();// girl.buy();}}执行结果:Around aaa...before...男孩买了一个游戏机Around bbb...After...AfterReturning...
注意:
@Around修饰的环绕通知类型,是将整个目标方法封装起来,在使用时,我们传入ProceedingJoinPoint类型的参数,并且需要调用ProceedingJoinPoint的proceed()的方法,该方法其实就是执行目标对象的目标方法。
可以通过使用 @Pointcut 注解声明切点表达式,然后使用表达式简化上面的代码。
package com.sharpcj.aopdemo.test1;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.*;import org.springframework.stereotype.Component;@Aspect@Componentpublic class BuyAspectJ {@Pointcut("execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..))")public void point(){}@Before("point()")public void hehe() {System.out.println("before ...");}@After("point()")public void haha() {System.out.println("After ...");}@AfterReturning("point()")public void xixi() {System.out.println("AfterReturning ...");}@Around("point()")public void xxx(ProceedingJoinPoint pj) {try {System.out.println("Around aaa ...");pj.proceed();System.out.println("Around bbb ...");} catch (Throwable throwable) {throwable.printStackTrace();}}}
通过注解处理通知中的参数
package com.sharpcj.aopdemo.test1;public interface IBuy {String buy(double price);}package com.sharpcj.aopdemo.test1;import org.springframework.stereotype.Component;@Componentpublic class Girl implements IBuy {@Overridepublic String buy(double price) {System.out.println(String.format("女孩花了%s元买了一件漂亮的衣服", price));return "衣服";}}package com.sharpcj.aopdemo.test1;import org.springframework.stereotype.Component;@Componentpublic class Boy implements IBuy {@Overridepublic String buy(double price) {System.out.println(String.format("男孩花了%s元买了一个游戏机", price));return "游戏机";}}
package com.sharpcj.aopdemo.test1;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.*;import org.springframework.stereotype.Component;@Aspect@Componentpublic class BuyAspectJ {@Pointcut("execution(String com.sharpcj.aopdemo.test1.IBuy.buy(double)) && args(price) && bean(girl)")public void gif(double price) {}@Around("gif(price)")public String hehe(ProceedingJoinPoint pj, double price){try {pj.proceed();if (price > 68) {System.out.println("女孩买衣服超过了68元,赠送一双袜子");return "衣服和袜子";}} catch (Throwable throwable) {throwable.printStackTrace();}return "衣服";}}
通过注解配置织入方式
注解 @EnableAspectJAutoProxy() 。
启用Spring AOP的时候,该注解的参数 proxyTargetClass 默认为false。
当 proxyTargetClass=true 使用的是 cglib 的动态代理方式,这种代理的缺点是如果扩展类的方法被 final 修饰,无法进行织入。
当 proxyTargetClass=false ,使用的是 jdk 的基于接口的动态代理方式,这个时候,代理会生成一个接口对象,将这个接口对象强制转换为实现该接口的一个类。
