说说 Spring AOP 中 @Aspect 的高级用法

栏目: Java · 发布时间: 6年前

内容简介:支持在切点定义中加入以下运算符进行复合运算:一般情况下,切点是直接声明在需要增强方法处,这种切点的声明方式称为匿名切点,匿名切点只能在声明处被使用 。  如果希望在其它地方可以重用这个切点,我们可以通过 @Pointcut 注解及切面类方法来命名它。命名切点的结构如下:

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

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

    }
}
  • 这里的连接点表达式 args(name,num,..) 会先找到 name 与 num 的类型,从而生成真正的表达式 args(String,int,..)

  • 增强方法可以通过 name 与 num 得到连接点的方法入参。

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

args()
args()

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

说说 Spring AOP 中 @Aspect 的高级用法

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:煮啥呢???

绑定异常【结束】


以上所述就是小编给大家介绍的《说说 Spring AOP 中 @Aspect 的高级用法》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Introduction to Tornado

Introduction to Tornado

Michael Dory、Adam Parrish、Brendan Berg / O'Reilly Media / 2012-3-28 / USD 23.99

Tornado is a scalable, non-blocking web server and web application framework written in Python. It is also light-weight to deploy, fun to write for, and incredibly powerful. Tornado was written with p......一起来看看 《Introduction to Tornado》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具