Spring AOP切点表达式函数

摘要: Spring中的AspectJ切点表达式函数 切点表达式函数就像我们的GPS导航软件。通过切点表达式函数,再配合通配符和逻辑运算符的灵活运用,我们能很好定位到我们需要织入增强的连接点上。经过上面的铺垫,下面来看看Springz中支持的切点表

Spring中的AspectJ切点表达式函数

切点表达式函数就像我们的GPS导航软件。通过切点表达式函数,再配合通配符和逻辑运算符的灵活运用,我们能很好定位到我们需要织入增强的连接点上。经过上面的铺垫,下面来看看Springz中支持的切点表达式函数。

1. 方法切点函数

函数 入参 说明 示例
execution() 方法匹配字符串 满足某一匹配模式的的所有目标类方法连接点 execution(* com.yc.service.*.*(..))在配置service层的事务管理时常用,定位于任意返回类型(第一个”*”) 在com.yc.service包下的所有类(第二个”*”)下的所有方法(第三个”*”),且这个方法的入参为任意类型、数量(体现在 “(..)“)
@annotation() 方法注解类名 标注了特定注解的目标方法连接点上 @anntation(com.yc.controller.needRecord),定位于controller层中任何添加@needRecord的方法,这可以方便地对控制层中某些方法被调用(如某人某时间登陆、进入后台管理界面)添加日志记录。

1. execution详解

execution的语法表达式如下:execution(<修饰符> <返回类型> <类路径> <方法名>(<参数列表>) <异常模式> ) 
其中,修饰符和异常是可选的,如果不加类路径,则默认对所有的类生效。它常用实例如下:

1. 通过方法签名、返回值定义切点:

- `execution(public * *Service(..))`:定位于所有类下返回值任意、方法入参类型、数量任意,public类型的方法
- `execution(public String *Service(..))`:定位于所有类下返回值为String、方法入参类型、数量任意,public类型的方法

2. 通过类包定义切点:

- `execution(* com.yc.controller.BaseController+.*(..))`:匹配任意返回类型,对应包下BaseController类及其子类等任意方法。
- `execution(* com.*.(..))`:匹配任意返回类型,com包下所有类的所有方法
- `execution(* com..*.(..))`:匹配任意返回类型,com包、子包下所有类的所有方法

注意.表示该包下所有类,..则涵括其子包。

3. 通过方法入参定义切点

- 这里“\*”表示任意类型的一个参数,“..”表示任意类型任意数量的参数
- `execution(* speak(Integer,*))`:匹配任意返回类型,所有类中只有两个入参,第一个入参为Integer,第二个入参任意的方法
- `execution(* speak(..,Integer,..))`:匹配任意返回类型,所有类中至少有一个Integer入参,但位置任意的方法。

2. annotation详解

此注解用于定位标注了某个注解的目标切点。下面我们来看一个模拟用户登录成功后日志记录用户名、时间和调用方法的示例,

1. 自定义注解

@Retention(RetentionPolicy.CLASS)//生命注释保留时长,这里无需反射使用,使用CLASS级别
@Target(ElementType.METHOD)//生命可以使用此注解的元素级别类型(如类、方法变量等)
public @interface NeedRecord {
}

关于自定义注解的更多属性与说明,可查看我的另一篇文章http://blog.csdn.net/qwe6112071/article/details/50949663

2. 定义切面(配置增强和定位切点)

@Aspect//将当前类标注成一个切面。
public class Annotation_aspect {
    @AfterReturning("@annotation(test.aop2.NeedRecord)")//这里指向注解类
    public void Record(JoinPoint joinPoint){//切点入参。
        System.out.println("日志记录:用户" +joinPoint.getArgs()[0] + "在" + new SimpleDateFormat("yyyy-MM-dd hh:mm;ss").format(new Date()) + "调用了"+ joinPoint.getSignature()+"方法" );
    }
}

3. 定义目标对象

@NeedRecord
public void login(String name){
    System.out.println("I'm "+name+" ,I'm logining");
}

4. 配置IOC容器

<aop:aspectj-autoproxy />   <!-- 使@AspectJ注解生效 -->
<bean class="test.aop2.Annotation_aspect" /><!-- 注册切面,使AOP自动识别并进行AOP方面的配置 -->
<bean id="userController" class="test.aop2.UserController" /><!-- 注册目标对象 -->

这里需注意: 
1. 和 标注在目标对象Annotation_aspect的注解@Aspect缺一不可,否则调用login方法时,Record方法不会被调用 
2. 必须在IOC容器中注册切面和目标对象,以便在下面测试中通过

5. 测试

public static void main(String args[]){
        ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:test/aop2/aop.xml");
        UserController userController = (UserController) ac.getBean("userController");
        userController.login("zenghao");
    }

调用测试方法后,会打印信息: 
I’m zenghao ,I’m logining 
DEBUG: org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean ‘test.aop2.Annotation_aspect#0’//log4j的日志记录打印 
日志记录:用户zenghao在2016-03-21 08:27;48调用了void test.aop2.UserController.login(String)方法

2. 方法入参切点函数

函数 入参 说明 示例
args() 类名 定位于入参为特定类型的的方法 如args(com.yc.model.User,com.yc.model.Article),我们要定位于所有以User,Article为入参的方法,需要注意的是,类型的个数、顺序必须都一一对应)
@args() 类型注解类名 定位于被特定注解的类作为方法入参的连接点 @args(com.yc.annotation.MyAnnotation)。MyAnnotation为自定义注解,标注在目标对象方法入参上,被标注的目标都会被匹配。,如方法public myMethod(@MyAnnotation String args);

1. args()详解:

args函数接受一个类名或变量名(对应与目标对象方法的入参),并将该类名绑定到增强方法入参中。对上一个实例,我们将切面改造成:

@After("args(name)")
public void Record(JoinPoint joinPoint,String name) throws Throwable{
    System.out.println("日志记录:用户" +name+ "在" + new SimpleDateFormat("yyyy-MM-dd hh:mm;ss").format(new Date()) + "调用了"+ joinPoint.getSignature().getDeclaringTypeName()+"方法" );
    /*控制台打印
    I'm zenghao ,I'm logining
    日志记录:用户zenghao在2016-03-21 09:32;31调用了test.aop2.UserController方法
    */
}

在这里有几点是值得注意的: 
1. 在本例中,我们不能使用args(String)来匹配,因为我们在方法入参中加入了变量name,必须通过args()绑定连接点入参的机制:通过在方法声明中定义入参String name,然后args会搜寻方法定义中命名参数来获取对应的参数类型(这里是String)。 
2. 如果我们使用args(name),但在方法定义体中没声明String name,则会报错 java.lang.IllegalArgumentException: warning no match for this type name: name [Xlint:invalidAbsoluteTypeName] 
3. 在这里如果要使用args(String)匹配我们的UserController中的方法,则必须去掉方法定义中的String name参数,或将注解改成@After(value = "args(name)",argNames = "name")方可。否则会抛出异常java.lang.IllegalArgumentException: error at ::0 formal unbound in pointcut 
4. 如果我们需要配置多变量,我们可以使用args(name,age)来配置,对应对象方法Annotation_aspect.Record(String name,Integer age)和UserController.login(String name,Integer age)。 
5. 除了args(),this(),target(),@args(),@within(),@target()和@annotation等函数都可以指定参数名,来将目标连接点上的方法入参绑定到增强的方法中。

3. 目标类切点函数

函数 入参 说明 示例
within() 类名匹配串 定位于特定作用于下的所有连接点 within(com.yc.service.*ServiceImpl),可以通过此注解为特定包下的所有以ServiceImpl名字结尾的类里面的所有方法添加事务控制。
target() 类名 定位于指定类及其子类 target(com.yc.service.IUserService),则可定位到IUserService接口和它的实现类如UserServiceImpl
@within() 类型注解类名 定位与标注了特定注解的类及其实现类 @within(com.yc.controller.needRecord),比如我们可以在BaseController中标注@needRecord,则所有继承了BaseController的UserController、ArticleController等等都会被定位
@target() 类型注解类名 定位于标注了特定注解的目标类里所有方法 @target(com.yc.controller.needRecord),则可以在controller层中,为我们需要日志记录的类标注@needRecord。
  1. within()定位连接点的最细粒度是到类,相对于execution()可定位连接点大到包,小到方法入参的适用范围更窄。
  2. 相对于@within,显然@target的耦合性更低,针对性更强。比如UserController,ArticleControoler,HomeController都需要继承BaseController,如果我们在BaseController中标注@needRecord,则三个子Controller都会被@within定位到织入增强,但实际上我们不想让HomeController织入增强,显然分别在UserController和ArticleController中标注@needRecord,然后利用@target来织入增强才满足要求。
  3. @within()如果标注在一个接口上,则不会匹配实现了该接口的子类。

4. 代理类切点函数

主要为this(),大多数情况使用方法与target()相同,区别在通过引介增强引入新接口方法时,新的接口方法同样会被this()定位,但target()则不会。


切点独立命名

在前面的讲解里,我们都是直接把切点配置到切点表达式函数里的,而这种切点叫做匿名切点。但假如我们有新的需求,要为相同的切点配置多个增强,这就需要我们在多个增强中配置相同的切点。为了提高重用性和降低维护成本,我们可以通过@Pointcut注解来单独命名切点。 
和前面相同的例子,我们将注解切面Annotation_aspect改造成如下:

@Aspect
public class Annotation_aspect {

    @Pointcut("execution(public * test.aop2.*.*(..))")
    private void ClassInTest_aop2(){}//修饰为private表示此切点只能在本类中使用,这里的返回值和方法题没有实际用途

    @Pointcut("args(String)")
    protected void MethodWithArgNames(){}//修饰为protected表示此切点只能在本类及其子类中使用

    @After("ClassInTest_aop2() and MethodWithArgNames() ")
    public void Record(JoinPoint joinPoint) throws Throwable{
        System.out.println("日志记录:用户在" + new SimpleDateFormat("yyyy-MM-dd hh:mm;ss").format(new Date()) + "调用了"+ joinPoint.getSignature().getDeclaringTypeName()+"方法" );
    }

}

运行测试函数,控制台打印: 
I’m zenghao ,I’m logining 
日志记录:用户在2016-03-21 11:33;59调用了test.aop2.UserController方法

本文转自:https://yq.aliyun.com/articles/29136

猜你喜欢

转载自blog.csdn.net/tiger0709/article/details/78706726