说说 Spring AOP 中 @Aspect 的高级使用法
来源:deniro     阅读:697
开心源码
发布于 2018-08-19 22:22
查看主页

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() {    }}

命名切点的结构如下:

切点可访问性修饰符与类可访问性修饰符的功能是相同的,它可以决定定义的切点可以在哪些类中可用。

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

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

@Aspectpublic class NamePointcutAspect {    @After("NamePointcut.method2()")    public void aspectMethod1() {    }    /**     * 这里用了复合运算     */    @After("NamePointcut.method2() && NamePointcut.method3()")    public void aspectMethod2() {    }}

3 织入顺序

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

1.假如在同一个切面类中公告的加强,则按照加强在切面类中定义的顺序进行织入;

  1. 假如加强位于不同的切面类中,并且这些切面类都实现了org.springframework.core.Ordered 接口,则由 Ordered 方法的顺序号决定(顺序号小的先织入);
  2. 假如加强位于不同的切面类中,但这些切面类没有实现org.springframework.core.Ordered 接口,织入的顺序是不确定的 。

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

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 配置:

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xmlns:aop="http://www.springframework.org/schema/aop"       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">    <!--aspectj 驱动器 -->    <aop:aspectj-autoproxy/>    <bean id="cookA" class="net.deniro.spring4.aspectj.CookA"/>    <bean class="net.deniro.spring4.aspectj.JoinPointAspect"/></beans>

输出结果:

---------获取连接点对象【开始】---------
参数:寿司
签名对象:class net.deniro.spring4.aspectj.CookA
制作寿司
---------获取连接点对象【结束】---------

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("----------绑定连接点入参【结束】----------");    }}

切点匹配和参数绑定的过程是这样的:

  1. args()会根据参数名称在加强方法中查到名称相同的入参并取得对应参数的类型,这样就得到了匹配连接点方法的入参类型 。
  2. 连接点方法入参类型所在的位置由参数名在 args() 函数中公告的位置决定 。

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

Spring 配置:

<!--aspectj 驱动器 --><aop:aspectj-autoproxy proxy-target-class="true"/><bean id="cookC" class="net.deniro.spring4.aspectj.CookC"/><bean class="net.deniro.spring4.aspectj.ParamsAspect"/>

注意: 这里必需通过 <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 配置:

<!--aspectj 驱动器 --><aop:aspectj-autoproxy proxy-target-class="true"/><bean id="cookD" class="net.deniro.spring4.aspectj.CookD"/><bean class="net.deniro.spring4.aspectj.ClassAnnotationObjectAspect"/>

单元测试:

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:煮啥呢???
绑定异常【结束】

`

免责声明:本文为用户发表,不代表网站立场,仅供参考,不构成引导等用途。 系统环境 服务器应用
相关推荐
js实现请求缓存方法
Debian 10 正在进行桌面壁纸调查投票
PHP WebShell代码后门的一次检查
程序编译过程
JavaScript计算丢失精度问题
首页
搜索
订单
购物车
我的