说说 Spring AOP 中 Aspect 的高级用法

1 切点复合运算

支持在切点定义中加入以下运算符进行复合运算:

运算符说明&&与运算。!非运算。||或运算。

2 切点命名

一般情况下,切点是直接声明在需要增强方法处,这种切点的声明方式称为匿名切点,匿名切点只能在声明处被使用 。 如果希望在其它地方可以重用这个切点,我们可以通过 @Pointcut 注解及切面类方法来命名它。

public class NamePointcut { /**

  • 切点被命名为 method1,且该切点只能在本类中使用 / @Pointcut("within(net.deniro.spring4.aspectj.)") private void method1() { } /**
  • 切点被命名为 method2,且该切点可以在本类或子孙类中使用 / @Pointcut("within(net.deniro.spring4.aspectj.)") protected void method2() { } /**
  • 切点被命名为 method3,且该切点可以在任何类中使用
  • 这里还使用了复合运算 */ @Pointcut("method1() && method2()") public void method3() { } } 命名切点的结构如下:

说说 Spring AOP 中 Aspect 的高级用法 切点可访问性修饰符与类可访问性修饰符的功能是相同的,它可以决定定义的切点可以在哪些类中可使用。

因为命名切点仅利用方法名及访问修饰符的信息,所以我们一般定义方法的返回类型为 void ,并且方法体为空 。

定义好切点后,就可以在切面类中引用啦:

@Aspectpublic class NamePointcutAspect { @After("NamePointcut.method2()") public void aspectMethod1() { } /**

  • 这里使用了复合运算 */ @After("NamePointcut.method2() && NamePointcut.method3()") public void aspectMethod2() { } } 3 织入顺序

一个连接点可以同时匹配多个切点,而切点所对应的增强在连接点上织入顺序的规则是这样的:

1.如果在同一个切面类中声明的增强,则按照增强在切面类中定义的顺序进行织入;

org.springframework.core.Ordered org.springframework.core.Ordered 假设有两个切面类 A 与 B,它们都实现了 Ordered 接口,A 的顺序号为 1,B 的顺序号为 2,切面类 A 与 B 都定义了 3 个增强,那么同时匹配这 6 个增强的织入顺序如下图所示:

说说 Spring AOP 中 Aspect 的高级用法 4 获取连接点信息

4.1 JoinPoint

org.aspectj.lang.JoinPoint 接口表示目标类连接点对象,它定义这些主要方法。

方法说明Object[] getArgs()获取连接点方法运行时的入参列表。Signature getSignature()获取连接点的方法签名对象。Object getTarget()获取连接点所在的目标对象。Object getThis()获取代理对象。

4.2 ProceedingJoinPoint

org.aspectj.lang.ProceedingJoinPoint 继承了 JoinPoint 接口,它新增了两个方法(它们用于执行连接点方法)。

方法说明Object proceed() throws Throwable通过反射执行目标对象连接点处的方法。Object proceed(Object[] var1) throws Throwable使用新的入参(替换掉原来的入参),通过反射执行目标对象连接点处的方法。

4.3 示例

Cook 接口:

public interface Cook { /**

  • 制作食品 / void make(); /*
  • 制作
  • @param name 食品名称 */ void make(String name); } CookA 类:

public class CookA implements Cook { public void make() { System.out.println("制作食品"); } public void make(String name) { System.out.println("制作" + name); } } 在切面类中访问连接点信息:

@Aspectpublic class JoinPointAspect { @Around("within(net.deniro.spring4.aspectj.CookA)") public void test(ProceedingJoinPoint pjp) throws Throwable { System.out.println("---------获取连接点对象【开始】---------"); System.out.println("参数:" + pjp.getArgs()[0]); System.out.println("签名对象:" + pjp.getTarget().getClass()); //执行目标对象方法 pjp.proceed(); System.out.println("---------获取连接点对象【结束】---------"); } } Spring bean 配置:

xmlns:xsi=" www.w3.org/2001/XMLSch…" xmlns:aop=" www.springframework.org/schema/aop" xsi:schemaLocation=" www.springframework.org/schema/bean… www.springframework.org/schema/bean… www.springframework.org/schema/aop www.springframework.org/schema/aop/…">

aop:aspectj-autoproxy/ 输出结果:

---------获取连接点对象【开始】---------

参数:寿司

签名对象:class net.deniro.spring4.aspectj.CookA

制作寿司

---------获取连接点对象【结束】---------

分享一些知识点给大家希望能帮助到大家,或者从中启发。

加Q君羊:821169538 都是java爱好者,大家一起讨论交流学习

5 绑定连接点的方法入参

args()、this()、target()、@args()、@within()、@target() 和 @annotation() 这些切点函数除可以指定类名外,还可以指定参数名,将目标对象连接点上的方法入参绑定到增强的方法中 。 其中 args() 用于绑定连接点方法的入参, @annotation() 用于绑定连接点方法的注解对象,而 @args() 用于绑定连接点方法入参的注解。

CookC 类:

public class CookC implements Cook { public void make() { System.out.println("制作食品"); } public void make(String name) { System.out.println("制作" + name); } public void make(String name, int num) { System.out.println("制作" + name + " " + num + " 个"); } } 切面类:

@Aspectpublic class ParamsAspect { @Before("target(net.deniro.spring4.aspectj.CookC) && args(name,num,..)") public void test(String name,int num) { System.out.println("----------绑定连接点入参【开始】----------"); System.out.println("name:" + name); System.out.println("num:" + num); System.out.println("----------绑定连接点入参【结束】----------"); } } 这里的连接点表达式 args(name,num,..) 会先找到 name 与 num 的类型,从而生成真正的表达式 args(String,int,..) 。 增强方法可以通过 name 与 num 得到连接点的方法入参。 切点匹配和参数绑定的过程是这样的:

args() args() 上述示例中的匹配过程如下:

说说 Spring AOP 中 Aspect 的高级用法 Spring 配置:

注意:这里必须通过 <aop:aspectj-autoproxy proxy-target-class="true" /> 来启用 CGLib 动态代理,这是因为 CookC 的 public void make(String name, int num) 是该类独有的方法(非接口定义的方法),所以必须使用 CGLib 生成子类的代理方法 。

单元测试:

ApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml"); CookC cookC = (CookC) context.getBean("cookC"); cookC.make("寿司", 100); 输出结果:

----------绑定连接点入参【开始】----------

name:寿司

num:100

----------绑定连接点入参【结束】----------

制作寿司 100 个

6 绑定代理对象

使用 this() 或 target() 可绑定被代理对象的实例。通过类实例名绑定对象时,依然具有原来连接点匹配的功能,只是类名是由增强方法中的同名入参类型间接决定的。

@Aspectpublic class ProxyAspect { @Before("this(cook)") public void bind(Cook cook) { System.out.println("--------绑定代理对象【开始】--------"); System.out.println(cook.getClass().getName()); System.out.println("--------绑定代理对象【结束】--------"); } } 首先通过 public void bind(Cook cook) 找出 cook 所对应的类型,接着转换切点表达式为 this(net.deniro.spring4.aspectj.Cook) 。这样就表示该切点匹配所有代理对象为 Cook 类中的所有方法。

输出结果:

--------绑定代理对象【开始】--------

net.deniro.spring4.aspectj.CookC$$EnhancerBySpringCGLIB$$217fb793

--------绑定代理对象【结束】--------

target() 绑定与 this() 相似。

7 绑定类注解对象

@within() 和 @target() 函数都可以将目标类的注解对象绑定到增强方法中。

定义一个日志注解类:

@Retention(RetentionPolicy.RUNTIME)//保留期限@Target({ElementType.METHOD,ElementType.TYPE})//目标类型public @interface Log { boolean value() default true;//声明成员变量} 把该注解类应用于 CookD:

@Logpublic class CookD implements Cook { public void make() { System.out.println("制作糕点"); } public void make(String name) { } } 绑定类注解对象:

@Aspectpublic class ClassAnnotationObjectAspect { @Before("@within(log)") public void bind(Log log){ System.out.println("----------绑定类注解对象【开始】----------"); System.out.println(log.getClass().getName()); System.out.println("----------绑定类注解对象【结束】----------"); } } Spring 配置:

单元测试:

ApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml"); CookD cook = (CookD) context.getBean("cookD"); cook.make(); 输出结果:

----------绑定类注解对象【开始】----------

com.sun.proxy.$Proxy8

----------绑定类注解对象【结束】----------

从输出结果 com.sun.proxy.$Proxy8 可以看出 ,CookD 类的注解 Log 对象也被代理咯O(∩_∩)O哈哈~

8 绑定返回值

在后置增强中,可以通过 returning 来绑定连接点方法的返回值。

切面:

@Aspectpublic class ReturnValueAspect { @AfterReturning(value = "target(net.deniro.spring4.aspectj.CookA)", returning = "value") public void bind(boolean value) { System.out.println("绑定返回值【开始】"); System.out.println("value:" + value); System.out.println("绑定返回值【结束】"); } } 注意:returning 的值必须与方法参数名相同。

CookA 新增 smell 方法:

public boolean smell(String name) { System.out.println(name + "香吗?"); return true; } 单元测试:

ApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml"); CookA cook = (CookA) context.getBean("cookA"); cook.smell("烤鸭"); 输出结果:

烤鸭香吗?

绑定返回值【开始】

value:true

绑定返回值【结束】

9 绑定异常

可以使用 AfterThrowing 注解的 throwing 成员变量来绑定连接点抛出的异常。

切面类:

@Aspectpublic class ExceptionAspect { @AfterThrowing(value = "target(net.deniro.spring4.aspectj.CookA)",throwing = "e") public void bind(CookException e){ System.out.println("绑定异常【开始】"); System.out.println("e:" + e.getMessage()); System.out.println("绑定异常【结束】"); } } 注意:throwing 的值必须与方法参数名相同。

单元测试:

ApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml"); CookA cook = (CookA) context.getBean("cookA"); cook.make(""); 输出结果:

绑定异常【开始】

e:煮啥呢???

绑定异常【结束】

转载于:https://juejin.im/post/5bacbf756fb9a05d3447c761

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值