springAop使用
springAop使用
1.使用背景
开发中在多个模块间有某段重复的代码,在传统的面向过程编程中,将这段代码抽象成一个方法,
在需要的地方分别调用这个方法,当需要修改功能时,只改变这个方法就可以了。然而,需求总是变化的,
有一天,新增了一个需求,需要需要在多处做修改,我们需要再抽象出一个方法,
然后再在需要的地方分别调用这个方法,又或者我们不需要这个方法了,我们得删除掉每一处调用该方法的地方。
实际上涉及到多个地方具有相同的修改问题我们通过 AOP 来解决。2.AOP术语
AOP 领域中的特性术语:
通知(Advice): AOP 框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。
连接点(join point): 连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在 Spring AOP 中,连接点总是方法的调用。
切点(PointCut): 可以插入增强处理的连接点。
切面(Aspect): 切面是通知和切点的结合。
引入(Introduction):引入允许我们向现有的类添加新的方法或者属性。
织入(Weaving): 将增强处理添加到目标对象中,并创建一个被增强的对象,这个过程就是织入。3.SpringAop
3.1 特点
Spring AOP 不能拦截对对象字段的修改,也不支持构造器连接点,我们无法在 Bean 创建时应用通知。3.2 Spring AOP 的简单例子
第一步:创建接口
public interface Buy {
void buy();
}
第二步:接口实现
@Component
public class BoyBuy implements Buy{
public void buy() {
System.out.println("boy buy");
}
}
@Component
public class GirlBuy implements Buy{
public void buy() {
System.out.println("girl buy");
}
}
第三步:配置文件,开启aop
@Configurable
@ComponentScan(basePackages="com.test.aop")
@EnableAspectJAutoProxy(proxyTargetClass=true)
public class BuyConfig {
}
第四步:定义一个切面类
@Aspect
@Component
public class BuyAspects {
@Before("execution(* com.test.aop.Buy.buy(..))")
public void buyAspect() {
System.out.println("buy aspect");
}
}
注:@Aspect 表示该类是一个切面
@Before 注解,表示buyAspect方法在方法执行之前执行
("execution(* com.test.aop.Buy.buy(..))")声明了切点,
表明在该切面的切点是com.test.aop.Buy.buy(..)这个接口中的buy方法
第五步:测试
ApplicationContext aContext =new AnnotationConfigApplicationContext(BuyConfig.class);
BoyBuy boyBuy = aContext.getBean("boyBuy",BoyBuy.class);
boyBuy.buy();
GirlBuy girlBuy = aContext.getBean("girlBuy",GirlBuy.class);
girlBuy.buy();
输出:
buy aspect
boy buy
buy aspect
girl buy3.3 注解配置 Spring AOP
3.3.1 Spring AOP 所支持的 AspectJ 切点指示器
arg() 限定连接点方法参数
@args() 通过连接点方法参数上的注解进行限定
execution() 用于匹配是连接点的执行方法
this() 限制连接点匹配AOP代理Bean引用为指定的类型
target 目标对象(即被代理对象)
@target() 限制目标对象的配置了指定的注解
within 限制连接点匹配执行的类型
@windin() 限定连接点带有匹配注解类型
@annotation 限定带有指定注解的连接点
注:
(1).在spring中尝试使用AspectJ其他指示器时,将会抛出IllegalArgumentException异常
(2).execution指示器是唯一的执行匹配,其他的指示器都是用于限制所匹配的切点。3.3.2 execution指示器说明
(1).使用execution指示器选择Instrument的buy方法,
(2).方法表达式以 * 号开始,标识不关心方法的返回值类型,指定了全限定类名和方法名。
(3).对于方法参数列表,使用 .. 标识切点选择任意的buy方法,无论该方法的入参是什么。
多个匹配之间我们可以使用链接符 &&、||、!来表示 “且”、“或”、“非”的关系。
但在使用 XML 文件配置时,这些符号有特殊的含义,使用 “and”、“or”、“not”来表示。用例一:限定该切点仅匹配的包是 com.test.aop,可以使用
execution(* com.test.aop.Buy.buy(..)) && within(com.test.aop.*)用例二:在切点中选择 bean,可以使用
execution(* com.test.aop.Buy.buy(..)) && bean(girl)用例三:切面只会对 GirlBuy.java 这个类生效
@Aspect
@Component
public class BuyAspects {
@Before("execution(* com.test.aop.Buy.buy(..)) && within(com.test.aop.*) && bean(girlBuy)")
public void buyAspect() {
System.out.println("buy aspect");
}
}
输出:
boy buy
buy aspect
girl buy
备注:within(com.test.aop.*) && bean(girlBuy) 代表:com.test.aop.GirlBuy,其中girlBuy是该类的bean定义3.4 注解声明 5 种通知类型
Before 前置通知,方法在目标方法调用前执行
AfterReturning 后置通知,方法在目标方法返回后执行
AfterThrowing 异常通知,方法在目标方法抛出异常后执行
Around 环绕通知,方法将目标方法封装起来
After 最终通知,方法在目标方法返回或异常后执行用例:修改切面类
@Aspect
@Component
public class BuyAspects {
@Before("execution(* com.test.aop.Buy.buy(..))")
public void before() {
System.out.println("before");
}
@After("execution(* com.test.aop.Buy.buy(..))")
public void after() {
System.out.println("after");
}
@AfterReturning("execution(* com.test.aop.Buy.buy(..))")
public void afterReturning() {
System.out.println("afterReturning");
}
@AfterThrowing("execution(* com.test.aop.Buy.buy(..))")
public void afterThrowing() {
System.out.println("afterThrowing");
}
@Around("execution(* com.test.aop.Buy.buy(..))")
public void around(ProceedingJoinPoint point) {
try {
System.out.println("around AAA");
point.proceed();
System.out.println("around BBB");
} catch (Throwable e) {
e.printStackTrace();
}
}
}
main 方法:
public static void main(String[] args) {
ApplicationContext aContext =new AnnotationConfigApplicationContext(BuyConfig.class);
BoyBuy boyBuy = aContext.getBean("boyBuy",BoyBuy.class);
boyBuy.buy();
}
输出:
around AAA
before
boy buy
around BBB
after
afterReturning
备注:
@Around 修饰的环绕通知类型,是将整个目标方法封装起来,
在使用时,必须传入了 ProceedingJoinPoint类型的参数且需要调用 ProceedingJoinPoint 的 proceed() 方法。
若没有调用 该方法,原目标方法被阻塞调用,执行结果为 :
around AAA
around BBB
after
afterReturning3.5 注解声明切点表达式
上面多个通知使用了相同的切点表达式,
对于像这样频繁出现的相同的表达式,我们可以使用 @Pointcut注解声明切点表达式,然后使用表达式,
修改代码如下:
@Aspect
@Component
public class BuyAspects {
/**
* (1).声明了一个切点表达式,
* (2).该方法 point 的内容并不重要,方法名也不重要,
* (3).实际上它只是作为一个标识,供通知使用
*/
@Pointcut("execution(* com.test.aop.Buy.buy(..))")
public void point() {}
@Before("point()")
public void before() {
System.out.println("before");
}
@After("point()")
public void after() {
System.out.println("after");
}
@AfterReturning("point()")
public void afterReturning() {
System.out.println("afterReturning");
}
@AfterThrowing("point()")
public void afterThrowing() {
System.out.println("afterThrowing");
}
@Around("point()")
public void around(ProceedingJoinPoint point) {
try {
System.out.println("around AAA");
point.proceed();
System.out.println("around BBB");
} catch (Throwable e) {
e.printStackTrace();
}
}
}3.6 注解处理通知中的参数
下面我们给接口增加一个参数,表示购买所花的金钱。通过AOP 增强处理,如果女孩买衣服超过了 68 元,就可以赠送一双袜子
第一步:编写接口
public interface Buy {
String buy(double price);
}
第二步:实现接口
@Component
public class BoyBuy implements Buy{
public String buy(double price) {
System.out.println(String.format("男孩花了%s买了游戏机",price));
return "游戏机";
}
}
@Component
public class GirlBuy implements Buy{
public String buy(double price) {
System.out.println(String.format("女孩花了%s买了鞋子",price));
return "鞋子";
}
}
第三步:配置文件,开启aop
@Configurable
@ComponentScan(basePackages="com.test.aop")
@EnableAspectJAutoProxy(proxyTargetClass=true)
public class BuyConfig {
}
第四步:编写切点
@Aspect
@Component
public class BuyAspects {
/**
* (1).声明了一个切点表达式,
* (2).该方法 point 的内容并不重要,方法名也不重要,
* (3).实际上它只是作为一个标识,供通知使用
*/
@Pointcut("execution(* com.test.aop.Buy.buy(double)) && args(price) && bean(girlBuy)")
public void point(double price) {}
@Around("point(price)")
public String around(ProceedingJoinPoint point,double price) {
try {
point.proceed();
if(price>60) {
System.out.println("女孩买衣服超过了68元,赠送一双袜子");
}
return "衣服和袜子";
} catch (Throwable e) {
e.printStackTrace();
}
return "衣服";
}
}
第五步:main方法
public static void main(String[] args) {
ApplicationContext aContext =new AnnotationConfigApplicationContext(BuyConfig.class);
BoyBuy boyBuy = aContext.getBean("boyBuy",BoyBuy.class);
String boyString = boyBuy.buy(40);
GirlBuy girlBuy = aContext.getBean("girlBuy",GirlBuy.class);
String girlString = girlBuy.buy(70);
System.out.println("男孩买到了"+boyString);
System.out.println("女孩买到了"+girlString);
}
输出:
男孩花了40.0买了游戏机
女孩花了70.0买了鞋子
女孩买衣服超过了68元,赠送一双袜子
男孩买到了游戏机
女孩买到了衣服和袜子
备注:
当不关心方法返回值的时候,编写切点指示器时候使用了 * ,
不关心方法参数的时候,我们使用了 ..。
需要传入参数且有返回值的时候,则需要使用对应的类型。
在编写通知的时候,需要声明对应的返回值类型和参数类型。3.7 注解配置织入的方式
Bean named 'girlBuy' is expected to be of type 'com.test.aop.GirlBuy' but was actually of type 'com.sun.proxy.$Proxy19'
注解 @EnableAspectJAutoProxy() 启用Spring AOP 的时候,proxyTargetClass 参数决定了代理的机制。
proxyTargetClass 为 false:,通过jdk的基于接口的方式,
代理生成的是一个接口对象,将这个接口对象强制转换为实现该接口的一个类,自然就抛出了上述类型转换异常。
proxyTargetClass 为 true:使用 cglib 动态代理方式。缺点是拓展类的方法被final修饰时,无法进行织入。
文章标题:springAop使用
发布时间:2021-08-23, 20:11:40
最后更新:2021-08-23, 20:10:22