`

Spring基础:AOP编程(4)

阅读更多
基于AspectJ的AOP编程

AspectJ的切点函数非常精妙,基本上可以覆盖我们编程中可以遇到的所有连接点,因为Spring仅支持方法级别的切点,所以Spring中的切点函数是AspectJ中的一个子集。掌握切点函数即掌握了AspectJ语法的基础。

首先是在切点函数中需要使用的通配符的概念:
*:匹配任意字符
..:匹配任意字符,表示类的时候必须和*联合使用,表示入参时可以单独使用
+:表示匹配目标类以及目标类的子类

execution(),within()可以使用全部通配符。
args(),target(),this()仅能使用+通配符,不过使用和不使用的效果是一样的。
其他的切点函数不能使用通配符。

切点函数的分类:
  • 方法切点函数
  • 方法入参切点函数
  • 目标类切点函数
  • 代理类起点函数


在AspectJ中我们使用注解来定义增强类型(即增强逻辑的织入位置)
  • @Before:前置增强
  • @AfterReturning:后置增强
  • @Around:环绕增强
  • AfterThrowing:抛出异常增强
  • After:不管是否抛出异常,都会执行,相当于Final增强
  • DeclareParents:引介增强


在使用AspectJ配置文件的时候,需要在位置文件中加入
<aop:aspectj-autoproxy proxy-target-class="true"/>

proxy-target-class默认是false,表示采用JDK动态代理生成目标类,如果设为true,则使用CGLib代理。
********这两者有很大区别:JDK动态代理仅能生成目标类的接口类型,而CGLib则生成目标的代理类型。这对于切点函数的意义重大,这点一定需要牢记!!

切点函数详解:
1.@annotation()
表示当方法标注类设定的注解,则该方法匹配次切点
//标注了NeedTes注解的方法,在返回时将被注入以下增强
@Aspect
public class TestAspect {
    @AfterReturning("@annotation(com.firethewhole.maventest08.aspectj.anno.NeedTest)")
    public void needTestFun() {
        System.out.println("needTestFun() executed");
    }
}

// 目标类
public class NaughtyWaiter implements Waiter {
    @NeedTest
    public void greetTo(String clientName) {
        System.out.println("NaughtyWaiter: greet to " + clientName);
    }
}

<aop:aspectj-autoproxy proxy-target-class="true"/>
<bean id="naiveWaiter" class="com.firethewhole.maventest08.NaiveWaiter"/>
<bean id="naughtWaiter" class="com.firethewhole.maventest08.NaughtyWaiter"/>
<bean class="com.firethewhole.maventest08.aspectj.fun.TestAspect"/>

ApplicationContext ctx = new ClassPathXmlApplicationContext("com/firethewhole/maventest08/aspectj/example/beans.xml");
NaiveWaiter naiveWaiter = (NaiveWaiter) ctx.getBean("naiveWaiter");
NaughtyWaiter naughtWaiter = (NaughtyWaiter) ctx.getBean("naughtWaiter");
naiveWaiter.greetTo("John");
naughtWaiter.greetTo("Tom");

输出:

NaiveWaiter.greet to John
NaughtyWaiter: greet to Tom
needTestFun() executed


2.execution()
表示执行方法匹配切点函数,这个切点函数是最常用的。
2.1 通过方法签名定义切点
execution(public * *(..)):匹配public方法。
execution(* *To(..)):匹配所有以To结尾的方法。

2.2 通过类定义切点
execution(com.firethewhole.maventest08.Waiter.*(..)):匹配Waiter接口的所有方法,所以NaughtyWaiter和NaiveWaiter中greetTo和serveTo方法都会被织入增强。
execution(com.firethewhole.maventest08.Waiter+.*(..)):不光匹配Waiter接口中定义的增强,还匹配NaiveWaiter的smile方法和NaughtyWaiter中的joke方法。

2.3 通过类包定义切点
execution(* com.firethewhole.maventest08.*(..)):匹配com.firethewhole.maventest08包下面所有类的所有方法。
execution(* com.firethewhole.maventest08..*(..)):不光包括上面的方法,还包括类这个包里面的子孙包中的类的所有方法。
execution(* com.firethewhole.maventest08..*.*Dao.find*(..)):匹配com.firethewhole.maventest08这个包,包括子孙包中以Dao结尾的类中,以find开头的方法。

2.4 通过方法入参定义切点
execution(* joke(String,int)):匹配joke方法,且第一个参数类型必须是String,第二个参数类型必须是int,有且只有这两个参数,比如NaughtyWaiter中的joke方法。
execution(* joke(String,*)):第二个参数可以是任意类型,但是必须是两个参数。
execution(* joke(String,..)):第一个参数必须是String类型,可以只有一个参数,也可以有多个参数。
execution(* joke(Object+)):匹配Object类型或者Object的子类,即所有的引用类型变量,是否包含基本类型,需要测试才知道。execution(* joke(Object))则仅匹配Object,其他入参类型不匹配。

3.args()
表示入参类型匹配,注意这里是实际入参类型动态匹配,所以也包括类子类。
execution(com.firethewhole.maventest08.Waiter):如果入参是Waiter或者NavieWaiter或者NaughtyWaiter,则匹配该切点。

4.@args()
如果入参标注类该切点,则匹配该切点。
这里涉及到类3个概念,切点函数所标识的注解类型,入参类型,运行时实际入参类型类型。
需要记住:标注注解的类型如果在继承关系上是入参类型的子类,则该类型及其子类都匹配切点,如果标注注解的类型在继承关系上是入参类型法父类,则都不匹配。

5.within()
属于execution()切点函数的子集,连接点仅能指定到类级别。

6.@within()
匹配标注类指定注解的类,包括该指定注解类的子类。

7.@target()
仅匹配标注类指定注解的类,不包括该标注注解类的子类!

8.target()
匹配切点函数所指定的类型及其子类的所有方法

9.this()
如果该代理对象按类型匹配目标类,则匹配该切点。这个函数和target是有区别的,在使用引介增强是,为NaiveWaiter加入SmartSeller的增强后,this(com.firethewhole.maventest08.Seller)将匹配NaiveWaiter类。
// 引介增强,会NaiveWaiter织入Seller增强,实现类为SmartSeller
@Aspect
public class EnableSellerAspect {
    @DeclareParents(value="com.firethewhole.maventest08.NaiveWaiter", defaultImpl=SmartSeller.class)
    public Seller seller;
}

// 代理增强切点
@Aspect
public class TestAspect {
    @AfterReturning("this(com.firethewhole.maventest08.Seller)")
    public void thisTest() {
        System.out.println("this Test() executed");
    }
}

ApplicationContext ctx = new ClassPathXmlApplicationContext("com/firethewhole/maventest08/aspectj/example/beans.xml");
Waiter naiveWaiter = (Waiter) ctx.getBean("naiveWaiter");
naiveWaiter.greetTo("John");
naiveWaiter.serveTo("John");
((Seller)naiveWaiter).sell("Beer", "John");

输出:

NaiveWaiter.greet to John
this Test() executed
NaiveWaiter.serve to John
this Test() executed
SmartSeller: sell Beer to John

*****这里和书上不一样,书上最后一个sell方法也被织入增强,但是实际上是没有。

切点的复合运算
使用&& || !或者and or not组合各个切点函数
@Aspect
public class TestAspect {
    @After("within(com.firethewhole.maventest08.*) && execution(* greetTo(..))")
    public void greetToFun() {
        System.out.println("--greetToFun() executed!--");
    }
    
    @Before("!target(com.firethewhole.maventest08.NaiveWaiter) && execution(* serveTo(..))")
    public void notServeInNaiveWaiter() {
        System.out.println("--notServeInWaiveWaiter() executed!--");
    }
    
    @AfterReturning("target(com.firethewhole.maventest08.Waiter) || target(com.firethewhole.maventest08.Seller)")
    public void waiterOrSeller() {
        System.out.println("--waiterOrSeller() executed!--");
    }
}


命名切点
直接在AspectJ类中使用的切点函数是匿名切点,我们可以在一个专门的类中,将所有切点函数都命名然后使用。
// 注意这里的访问修饰符,private表示仅能在该类中使用,和正常的修饰符是一样的
public class TestNamePointcut {
    @Pointcut("within(com.firethewhole.maventest08.*)")
    private void inPackage() {
    }

    @Pointcut("execution(* greetTo(..))")
    protected void greetTo() {
    }

    @Pointcut("inPackage() and greetTo()")
    public void inPkgGreetTo() {
    }
}

@Aspect
public class TestAspect {
    @Before("com.firethewhole.maventest08.aspectj.advanced.TestNamePointcut.inPkgGreetTo()")
    public void pkgGreetTo() {
        System.out.println("--pkgGreetTo() executed!--");
    }
    
    @Before("!target(com.firethewhole.maventest08.NaiveWaiter) && com.firethewhole.maventest08.aspectj.advanced.TestNamePointcut.inPkgGreetTo()")
    public void pkgGreetToNoNaiveWaiter() {
        System.out.println("--pkgGreetToNoNaiveWaiter() executed!--");
    }
}


增强织入顺序
不同的切面类靠实现Order接口来决定,相同的切面类中的增强是按照定义的顺序织入的。

访问连接点信息
// @Around增强需要使用ProceedingJoinPoint,其他的增强类型使用JoinPoint
@Aspect
public class TestAspect {
    @Around("execution(* greetTo(..)) && target(com.firethewhole.maventest08.NaiveWaiter)")
    public void joinPointAccess(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("-----joinPointAccess-----");
        System.out.println("arg[0]:" + pjp.getArgs()[0]);
        System.out.println("signature:" + pjp.getTarget().getClass());
        pjp.proceed();
        System.out.println("-----JoinPointAccess-----");
    }
}

输出:

-----joinPointAccess-----
arg[0]:John
signature:class com.firethewhole.maventest08.NaiveWaiter
NaiveWaiter.greet to John
-----JoinPointAccess-----


绑定连接点方法入参
这个例子一定要用CGLib代理
// args()会根据参数中的name,num自动去增强逻辑bindJoinPointParams中查找实际的类型信息
@Aspect
public class TestAspect {
    @Before("target(com.firethewhole.maventest08.NaiveWaiter) && args(name,num,..)")
    public void bindJoinPointParams(int num, String name) {
        System.out.println("-----bindJoinPointParams-----");
        System.out.println("name:" + name);
        System.out.println("num:" + num);
        System.out.println("-----bindJoinPointParams-----");
    }
}

ApplicationContext ctx = new ClassPathXmlApplicationContext("com/firethewhole/maventest08/aspectj/example/beans.xml");
NaiveWaiter naiveWaiter = (NaiveWaiter) ctx.getBean("naiveWaiter");
naiveWaiter.smile("John", 2);

输出:

-----bindJoinPointParams-----
name:John
num:2
-----bindJoinPointParams-----
NaiveWaiter.smile to John 2 times


绑定代理对象
@Aspect
public class TestAspect {
    @Before("this(waiter)")
    public void bindProxyObj(Waiter waiter) {
        System.out.println("-----bindProxyObj-----");
        System.out.println(waiter.getClass().getName());
        System.out.println("-----bindProxyObj-----");
    }
}

输出:

-----bindProxyObj-----
com.firethewhole.maventest08.NaiveWaiter$$EnhancerBySpringCGLIB$$6f6d5c3b
-----bindProxyObj-----
NaiveWaiter.greet to John

可以看到实际生成的代理对象的类型。

绑定类注解对象
在使用@target或者@args时指定类注解类型,可以在增强逻辑中使用。

绑定返回值
// 在各种增强类型中,如果有返回值相关的信息可以指定
@Aspect
public class TestAspect {
    @AfterReturning(value="target(com.firethewhole.maventest08.SmartSeller)",returning="retVal")
    public void bindReturnValue(int retVal) {
        System.out.println("-----bindReturnValue-----");
        System.out.println("returnValue:" + retVal);
        System.out.println("-----bindReturnValue-----");
    }
}

输出:

SmartSeller: sell Beer to John
-----bindReturnValue-----
returnValue:100
-----bindReturnValue-----


绑定抛出异常
@Aspect
public class TestAspect {
    @AfterThrowing(value="target(com.firethewhole.maventest08.SmartSeller)", throwing="iae")
    public void bindException(IllegalArgumentException iae) {
        System.out.println("-----bindException-----");
        System.out.println("exception:" + iae.getMessage());
        System.out.println("-----bindException-----");
    }
}
分享到:
评论

相关推荐

    Spring基础:AOP编程(1)

    这篇博客“Spring基础:AOP编程(1)”可能介绍了AOP的基础知识及其在Spring中的实现。 面向切面编程(AOP)是一种编程范式,旨在减少代码的重复性,提高模块间的解耦度。AOP通过将横切关注点(如日志、事务管理、...

    Spring基础:AOP编程(2)

    在本篇关于“Spring基础:AOP编程(2)”的文章中,我们将深入探讨Spring框架中的面向切面编程(Aspect-Oriented Programming, AOP),这是一种强大的设计模式,它允许我们分离关注点,尤其是那些横切关注点,如日志、...

    Spring基础:AOP编程(5)

    在本篇博文中,我们将深入探讨Spring框架中的一个核心特性——面向切...通过以上介绍,我们对Spring中的AOP编程有了基本了解。在实际开发中,AOP可以帮助我们更好地组织代码,降低复杂度,提高代码的可维护性和可读性。

    Spring基础:AOP编程(3)

    **Spring AOP编程详解** 在Java开发中,Spring框架因其强大的功能和易用性而备受推崇,其中AOP(Aspect-Oriented Programming,面向切面编程)是其核心特性之一。AOP允许开发者将关注点从核心业务逻辑中分离出来,...

    Spring AOP面向方面编程原理:AOP概念

    Spring AOP是在Spring框架的基础上实现的一种面向方面编程机制。 1. **方面(Aspect)**:这是AOP的核心概念之一,指代一个关注点的模块化,该关注点可能会横切多个对象。例如事务管理就是一个典型的横切关注点,...

    Spring基础:Spring AOP简单使用

    Spring AOP,全称Aspect Oriented Programming(面向切面编程),是Spring框架的重要组成部分,它扩展了传统的面向对象编程(OOP),使得开发者能够更好地处理系统中的横切关注点,如日志、事务管理、权限控制等。...

    小马哥讲 Spring AOP 编程思想 - API 线索图.pdf

    在讨论Spring AOP(面向切面编程)时,首先需要理解几个核心概念。Spring AOP 是Spring框架提供的一个功能模块,它允许开发者将横切关注点(cross-cutting concerns)从业务逻辑中解耦出来,通过在方法调用前后进行...

    hualinux spring 3.15:Spring AOP.pdf

    根据提供的文件内容,可以提取出以下知识点: ...文档中提到的实践示例,例如前置通知、后置通知、返回通知、异常通知和环绕通知的具体编码实现,都是通过具体的代码示例来说明如何在Spring中应用AspectJ进行AOP编程。

    spring-boot aop

    Spring Boot AOP(面向切面编程)是一种强大的设计模式,它允许我们在不修改现有代码的情况下,插入额外的功能或监控代码。在Spring框架中,AOP主要用于日志记录、事务管理、性能统计等场景。本示例是关于如何在...

    spring AOP切面编程

    Spring AOP(Aspect Oriented Programming,面向切面编程)是Spring框架的重要组成部分,它扩展了传统的面向对象编程,使得开发者可以方便地实现横切关注点,如日志、事务管理、性能监控等。在Spring中,AOP通过代理...

    spring源代码分析:aop的实现

    其中,面向切面编程(Aspect Oriented Programming,简称AOP)是Spring的重要特性之一,它极大地简化了代码中的横切关注点,如日志、事务管理等。本文将深入Spring源码,探索AOP的实现原理。 首先,我们需要理解AOP...

    Spring中aop编程所需要的jar包

    为了在Spring中进行AOP编程,我们需要一些特定的JAR包。以下是对这些关键组件的详细说明: 1. **Spring核心包**: - `spring-core.jar`: 这是Spring框架的基础,包含了IoC(Inversion of Control,控制反转)容器...

    Spring4—AOP编程

    Spring AOP,全称Aspect-Oriented Programming(面向切面编程),是Spring框架的一个重要特性,主要用于解决程序中的横切关注点,如日志、事务管理、安全性等。AOP通过将这些关注点与核心业务逻辑分离,实现了代码的...

    AOP_使用spring框架进行面向切面编程

    面向切面编程(AOP)是一种编程范式,它旨在减少代码中的重复部分,特别是那些与核心业务逻辑无关但又必须处理...同时,通过分析和操作`springAOP`压缩包中的示例代码,可以更直观地了解AOP在Spring框架中的具体实现。

    Spring切面AOP编程的简单模拟实现

    在Spring框架中,AOP(面向切面编程)是一种强大的设计模式,它允许开发者将关注点从核心业务逻辑中分离出来,例如日志记录、事务管理等。本教程将通过模拟Spring AOP来阐述如何实现一个简单的切面编程。我们将讨论...

    spring-aop-jar

    在IT领域,Spring框架是一个广泛使用的Java应用框架,它提供了许多功能,包括依赖注入、面向切面编程(AOP)等。"spring-aop-jar"这个主题涉及到Spring框架中的核心组件之一——Spring AOP。这里我们将深入探讨...

    使用动态代理演示Spring的AOP编程原理

    为了说明Spring的AOP原理,本人使用代理模式中的动态代理完成演示AOP编程的原理的演示。相信,如果你耐心看完整个程序(几乎一行注释一行代码),那么你对Spring这个东西就不是觉得有什么神秘了! 阅读对象:凡是喜爱...

    spring aop 编程所需要的搜友JAR包

    Spring AOP(Aspect Oriented Programming,面向切面编程)是Spring框架的重要组成部分,它提供了一种在不修改源代码的情况下,对程序进行功能增强的技术。AOP的核心概念是切面(Aspect)、通知(Advice)、连接点...

Global site tag (gtag.js) - Google Analytics