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.class
package ccom.ly.ybg.school;
import org.springframework.stereotype.Component;
@Component
public class Boy implements IBuy {
@Override
public String buy() {
System.out.println("男孩买了一个游戏机");
return "游戏机";
}
}
// Girl.class
package com.ly.ybg.school;
import org.springframework.stereotype.Component;
@Component
public class Girl implements IBuy {
@Override
public String buy() {
System.out.println("女孩买了一件漂亮的衣服");
return "衣服";
}
}
// 配置文件 AppConfig.class
package 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
@Component
public 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
@Component
public 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
@Component
public 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;
@Component
public class Girl implements IBuy {
@Override
public String buy(double price) {
System.out.println(String.format("女孩花了%s元买了一件漂亮的衣服", price));
return "衣服";
}
}
package com.sharpcj.aopdemo.test1;
import org.springframework.stereotype.Component;
@Component
public class Boy implements IBuy {
@Override
public 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
@Component
public 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
的基于接口的动态代理方式,这个时候,代理会生成一个接口对象,将这个接口对象强制转换为实现该接口的一个类。