aop简单例子,spring aop中的概念及使用

开启aspectj的支持
为了在Spring配置中使用@AspectJ aspects,你必须首先启用Spring对基于@AspectJ aspects的配置支持,自动代理(autoproxying)基于通知是否来自这些切面。 自动代理是指Spring会判断一个bean是否使用了一个或多个切面通知,并据此自动生成相应的代理以拦截其方法调用,并且确认通知是否如期进行。
通过在你的Spring的配置中引入下列元素来启用Spring对@AspectJ的支持:
如果是schema方式,可以查看
http://www.kuqin.com/spring2.0_doc/xsd-config.html#xsd-config-body-schemas-aop,添加对该标签的支持,标签是
<aop:aspectj-autoproxy/>,
如果想强制使用CGLIB代理,需要将 <aop:aspectj-autoproxy> 的 proxy-target-class 属性设为true。
如果使用的是dtd,可以在applicationcontext中添加如下内容
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" />

添加aspect支持包
aspectjweaver.jar 和 aspectjrt.jar

例子
首先需要让spring对aspectj提供支持
一般你可以单纯的使用aspectj进行aop,也可以让spring和aspectj联合来开发,aspectj功能强大,但需另外的编译器和熟悉aspectj的语法

Spring与Aspectj进行aop,也有2种方式,一是单纯的使用aspectj注解,二是在配置文件中进行定义。前者较为灵活强大,后者利于管理模块化。

<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
        <!--启动注解配置bean-->
<context:annotation-config />
        <!--注解配置bean需要扫描的过滤器-->
<context:component-scan base-package="com.liyixing.spring" />
        <!--开启aspectj-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<context:annotation-config>false</context:annotation-config>
        <!--定义切面-->
<bean id="myAspect" class="com.liyixing.spring.aspect.MyAspect"></bean>
</beans>

定义接口
package com.liyixing.spring.service;

public interface IAccountService {
public void getAccount();

public void deleteAccount();
}

定义实现
package com.liyixing.spring.service.imp;



import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;



import com.liyixing.spring.dao.IAccountDao;

import com.liyixing.spring.service.IAccountService;



@Service(value = "accountService")

public class AccountService implements IAccountService {

private IAccountDao accountDao;



public IAccountDao getAccountDao() {

return accountDao;

}



@Autowired

public void setAccountDao(IAccountDao accountDao) {

System.out.println("---------------");

System.out.println("setAccountDao");

this.accountDao = accountDao;

System.out.println("---------------");

}



public void getAccount() {

accountDao.getAccount();

System.out.println("getAccount()");

System.out.println();

}



public void deleteAccount() {

System.out.println("---------------");

System.out.println("delete");

System.out.println("---------------");

}

}


切面
package com.liyixing.spring.aspect;



import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

import org.aspectj.lang.annotation.Pointcut;



@Aspect

public class MyAspect {

@SuppressWarnings("unused")

@Pointcut("this(com.liyixing.spring.service.IAccountService)")

private void anyOldTransfer() {

///这里的private是为了防止外部通过方法调用访问切入点。而可访问性(private还是public)是不会应用到aop的。
}



@Before("anyOldTransfer()")

public void doBefore() {

System.out.println("defore");

}

}


测试:
package com.liyixing.spring.test;



import org.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;



import com.liyixing.spring.service.IAccountService;



public class Test {



/**

* @param args

*/

public static void main(String[] args) {

ApplicationContext context = new ClassPathXmlApplicationContext(

"classpath:beans.xml");

System.out.println(context.getBeanDefinitionCount());

String[] beanNames = context.getBeanDefinitionNames();



for (String beanName : beanNames) {

System.out.println(beanName);

}



System.out.println();



IAccountService accountService = (IAccountService) context

.getBean("accountService");



accountService.getAccount();

accountService.deleteAccount();

}

}


执行
调试中可以看出
首先将实际的accountService对象创建出来。
相当于执行new AccountService();
然后会初始化这个对象,调用.setXXX之类的
而通过
(IAccountService) context
.getBean("accountService");
这样的方式取出来的时候,这个对象已经是一个代理对象了(不是new AccountService()的对象)。因此初始化的时候(创建对象,给对象注入依赖的时候,是不会受到aop的影响的)。

声明一个切面
在启用@AspectJ支持的情况下,在application context中定义的任意带有一个@Aspect切面(拥有@Aspect注解)的bean都将被Spring自动识别并用于配置在Spring AOP。 以下例子展示了为了完成一个不是非常有用的切面所需要的最小定义:

下面是在application context中的一个常见的bean定义,这个bean指向一个使用了 @Aspect 注解的bean类:

<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
   <!-- configure properties of aspect here as normal -->
</bean>
下面是 NotVeryUsefulAspect 类定义,使用了 org.aspectj.lang.annotation.Aspect 注解。

package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class NotVeryUsefulAspect {

}
切面(用 @Aspect 注解的类)和其他类一样有方法和字段定义。他们也可能包括切入点,通知和引入(inter-type)声明。任意带有一个@Aspect切面(拥有@Aspect注解)的bean都将被Spring自动识别并配置在Spring AOP。

声明一个切入点(pointcut)
如下的例子定义了一个切入点'transfer',这个切入点匹配了任意名为"transfer"的方法执行:
@Pointcut("execution(* transfer(..))")
private void transfer() {}
切入点表达式,也就是 @Pointcut 注解的值,是正规的AspectJ 5切入点表达式。 如果你想要更多了解AspectJ的切入点语言,可以查看AspectJ编程指南http://www.eclipse.org/aspectj/doc/released/progguide/index.html

支持的切入点指定者

Spring AOP 支持在切入点表达式中使用如下的AspectJ切入点指定者:

其他的切入点类型

完整的AspectJ切入点语言支持额外的切入点指定者,但是Spring不支持这个功能。 他们分别是call, initialization, preinitialization, staticinitialization, get, set, handler, adviceexecution, withincode, cflow, cflowbelow, if, @this 和 @withincode。 在Spring AOP中使用这些指定者将会导致抛出IllegalArgumentException异常。

Spring AOP支持的切入点指定者可能在将来的版本中得到扩展,不但支持更多的AspectJ 切入点指定者(例如"if"),还会支持某些Spring特有的切入点指定者,比如"bean"(用于匹配bean的名字)。

execution - 匹配方法执行的连接点,这是你将会用到的Spring的最主要的切入点指定者。

within - 限定匹配特定类型的连接点(在使用Spring AOP的时候,在匹配的类型中定义的方法的执行)。

this - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中bean reference(Spring AOP 代理)是指定类型的实例。

target - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中目标对象(被代理的appolication object)是指定类型的实例。

args - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中参数是指定类型的实例。

@target - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中执行的对象的类已经有指定类型的注解。

@args - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中实际传入参数的运行时类型有指定类型的注解。

@within - 限定匹配特定的连接点,其中连接点所在类型已指定注解(在使用Spring AOP的时候,所执行的方法所在类型已指定注解)。

@annotation - 限定匹配特定的连接点(使用Spring AOP的时候方法的执行),其中连接点的主题有某种给定的注解。

因为Spring AOP限制了连接点必须是方法执行级别的,pointcut designators的讨论也给出了一个定义,这个定义和AspectJ的编程指南中的定义相比显得更加狭窄。 除此之外,AspectJ它本身有基于类型的语义,在执行的连接点'this'和'target'都是指同一个对象,也就是执行方法的对象。 Spring AOP是一个基于代理的系统,并且严格区分代理对象本身(对应于'this')和背后的目标对象(对应于'target')

合并切入点表达式

切入点表达式可以使用using '&&', '||' 和 '!'来合并.还可以通过名字来指向切入点表达式。 以下的例子展示了三种切入点表达式: anyPublicOperation(在一个方法执行连接点代表了任意public方法的执行时匹配); inTrading(在一个代表了在交易模块中的任意的方法执行时匹配) 和 tradingOperation(在一个代表了在交易模块中的任意的公共方法执行时匹配)。

@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}

@Pointcut("within(com.xyz.someapp.trading..*")
private void inTrading() {}

@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}
就上所示的,从更小的命名组件来构建更加复杂的切入点表达式是一种最佳实践。 当用名字来指定切入点时使用的是常见的Java成员可视性访问规则。 (比如说,你可以在同一类型中访问私有的切入点,在继承关系中访问受保护的切入点,可以在任意地方访问公共切入点。 成员可视性访问规则不影响到切入点的 匹配。

这里的private是为了防止外部通过方法调用访问切入点。而可访问性(private还是public)是不会应用到aop的。

在AspectJ 1.5.1中有一个bug (#140357)有时候可能会导致Spring所使用的AspectJ切入点解析失败, 即使用一个已命名的切入点来引用到另一个同类型的切入点的时候。 在AspectJ的开发中已经解决这个bug,可以在AspectJ的下载页面得到。在1.5.2发布时将会包含这一fix。 如果你遇到了这个问题,你可以去下载AspectJ的开发构建包,并且更新你的 aspectjweaver.jar,这是在AspectJ 1.5.2发布前的临时解决方案。

示例

Spring AOP 用户可能会经常使用 execution pointcut designator。执行表达式的格式如下:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
除了返回类型模式,名字模式和参数模式以外,所有的部分都是可选的。 返回类型模式决定了方法的返回类型必须依次匹配一个连接点。 你会使用的最频繁的返回类型模式是 *,它代表了匹配任意的返回类型。 一个全称限定的类型名将只会匹配返回给定类型的方法。名字模式匹配的是方法名。 你可以使用 * 通配符作为所有或者部分命名模式。 参数模式稍微有点复杂:() 匹配了一个不接受任何参数的方法, 而 (..) 匹配了一个接受任意数量参数的方法(零或者更多)。 模式 (*) 匹配了一个接受一个任何类型的参数的方法。 模式 (*,String) 匹配了一个接受两个参数的方法,第一个可以是任意类型,第二个则必须是String类型。 请参见AspectJ编程指南的 Language Semantics 部分http://www.eclipse.org/aspectj/doc/released/progguide/semantics-pointcuts.html。

下面给出一些常见切入点表达式的例子。

任意公共方法的执行:

execution(public * *(..))
任何一个以“set”开始的方法的执行:

execution(* set*(..))
AccountService 接口的任意方法的执行:

execution(* com.xyz.service.AccountService.*(..))
定义在service包里的任意方法的执行:

execution(* com.xyz.service.*.*(..))
定义在service包或者子包里的任意方法的执行:

execution(* com.xyz.service..*.*(..))
在service包里的任意连接点(在Spring AOP中只是方法执行) :

within(com.xyz.service.*)
在service包或者子包里的任意连接点(在Spring AOP中只是方法执行) :

within(com.xyz.service..*)
实现了 AccountService 接口的代理对象的任意连接点(在Spring AOP中只是方法执行) :

this(com.xyz.service.AccountService)
'this'在binding form中用的更多:- 请常见以下讨论通知的章节中关于如何使得代理对象可以在通知体内访问到的部分。
实现了 AccountService 接口的目标对象的任意连接点(在Spring AOP中只是方法执行) :

target(com.xyz.service.AccountService)
'target'在binding form中用的更多:- 请常见以下讨论通知的章节中关于如何使得目标对象可以在通知体内访问到的部分。
任何一个只接受一个参数,且在运行时传入的参数实现了 Serializable 接口的连接点 (在Spring AOP中只是方法执行)

args(java.io.Serializable)
'args'在binding form中用的更多:- 请常见以下讨论通知的章节中关于如何使得方法参数可以在通知体内访问到的部分。
请注意在例子中给出的切入点不同于 execution(* *(java.io.Serializable)): args只有在动态运行时候传入参数是可序列化的(Serializable)才匹配,而execution 在传入参数的签名声明的类型实现了 Serializable 接口时候匹配。

有一个 @Transactional 注解的目标对象中的任意连接点(在Spring AOP中只是方法执行)

@target(org.springframework.transaction.annotation.Transactional)
'@target' 也可以在binding form中使用:请常见以下讨论通知的章节中关于如何使得annotation对象可以在通知体内访问到的部分。
任何一个目标对象声明的类型有一个 @Transactional 注解的连接点(在Spring AOP中只是方法执行)

@within(org.springframework.transaction.annotation.Transactional)
'@within'也可以在binding form中使用:- 请常见以下讨论通知的章节中关于如何使得annotation对象可以在通知体内访问到的部分。
任何一个执行的方法有一个 @Transactional annotation的连接点(在Spring AOP中只是方法执行)

@annotation(org.springframework.transaction.annotation.Transactional)
'@annotation' 也可以在binding form中使用:- 请常见以下讨论通知的章节中关于如何使得annotation对象可以在通知体内访问到的部分。
任何一个接受一个参数,并且传入的参数在运行时的类型实现了 @Classified annotation的连接点(在Spring AOP中只是方法执行)

@args(com.xyz.security.Classified)
'@args'也可以在binding form中使用:- 请常见以下讨论通知的章节中关于如何使得annotation对象可以在通知体内访问到的部分。
1.前置通知
@Before在目标方法执行前执行
如:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

  @Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
  public void doAccessCheck() {
// ...
  }

}
如果使用一个in-place 的切入点表达式,我们可以把上面的例子换个写法:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

  @Before("execution(* com.xyz.myapp.dao.*.*(..))")
  public void doAccessCheck() {
// ...
  }

}

2.返回后通知
@AfterReturning返回后通知通常在一个匹配的方法返回的时候执行。
如:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

  @AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
  public void doAccessCheck() {
// ...
  }

}
有时候你需要在通知体内得到返回的值。你可以使用以 @AfterReturning 接口的形式来绑定返回值:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

  @AfterReturning(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
returning="retVal")
  public void doAccessCheck(Object retVal) {
// ...
  }

}
在 returning 属性中使用的名字必须对应于通知方法内的一个参数名。 当一个方法执行返回后,返回值作为相应的参数值传入通知方法。 一个 returning 子句也限制了只能匹配到返回指定类型值的方法。 (在本例子中,返回值是 Object 类,也就是说返回任意类型都会匹配)

3.异常通知

@AfterThrowing,异常通知在一个方法抛出异常后执行。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

  @AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
  public void doRecoveryActions() {
// ...
  }

你通常会想要限制通知只在某种特殊的异常被抛出的时候匹配,你还希望可以在通知体内得到被抛出的异常。 使用 throwing 属性不光可以限制匹配的异常类型(在通知中的参数类型决定,如果你不想限制,请使用 Throwable 作为异常类型),还可以将抛出的异常绑定到通知的一个参数上。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

  @AfterThrowing(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
throwing="ex")
  public void doRecoveryActions(DataAccessException ex) {
// ...
  }

}

}

4.@After
不论一个方法是如何结束的,最终通知都会运行。
如:

@After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
  public void doReleaseLock() {
    // ...
  }

6.2.4.5. 环绕通知

最后一种通知是环绕通知。环绕通知在一个方法执行之前和之后执行。它使得通知有机会 在一个方法执行之前和执行之后运行。而且它可以决定这个方法在什么时候执行,如何执行,甚至是否执行。 环绕通知经常在某线程安全的环境下,你需要在一个方法执行之前和之后共享某种状态的时候使用。 请尽量使用最简单的满足你需求的通知。(比如如果简单的前置通知也可以适用的情况下不要使用环绕通知)。

环绕通知使用@Around注解来声明。通知的第一个参数必须是 ProceedingJoinPoint类型。在通知体内,调用 ProceedingJoinPoint的proceed()方法会导致 后台的连接点方法执行。proceed 方法也可能会被调用并且传入一个 Object[]对象-该数组中的值将被作为方法执行时的参数。
当传入一个Object[]对象的时候,处理的方法与通过AspectJ编译器处理环绕通知略有不同。 对于使用传统AspectJ语言写的环绕通知来说,传入参数的数量必须和传递给环绕通知的参数数量匹配 (不是后台的连接点接受的参数数量),并且特定顺序的传入参数代替了将要绑定给连接点的原始值 (如果你看不懂不用担心)。
package com.liyixing.spring.aspect;



import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.After;

import org.aspectj.lang.annotation.AfterReturning;

import org.aspectj.lang.annotation.AfterThrowing;

import org.aspectj.lang.annotation.Around;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

import org.aspectj.lang.annotation.Pointcut;



@Aspect

public class MyAspect {

@SuppressWarnings("unused")

@Pointcut("execution( public * *(..) )")

private void anyOldTransfer() {

}



@Before("com.liyixing.spring.aspect.MyAspect.anyOldTransfer()")

public void doBefore() {

System.out.println("defore");

}



@AfterReturning(pointcut = "com.liyixing.spring.aspect.MyAspect.anyOldTransfer()", returning = "o")

public void doReturn(Object o) {

System.out.println("return");

System.out.println("return is " + o);

System.out.println("");

}



@AfterThrowing(pointcut = "com.liyixing.spring.aspect.MyAspect.anyOldTransfer()", throwing = "ex")

public void exception(Exception ex) {

System.out.println("exception");

System.out.println("ex is " + ex.getMessage());

System.out.println();

}



@After("com.liyixing.spring.aspect.MyAspect.anyOldTransfer()")

public void after() {

System.out.println("method after");

System.out.println();

}



@Around("com.liyixing.spring.aspect.MyAspect.anyOldTransfer()")

public Object arount(ProceedingJoinPoint jp) throws Throwable {

Object ret = jp.proceed();



return ret;

}

}


以上代码中可以看到结果是在调用jp.proceed();
之后,after等其他同志会被调用。

6.通知参数
Spring 2.0 提供了完整的通知类型 - 这意味着你可以在通知签名中声明所需的参数,(就像我们在前面看到的后置和异常通知一样)而不总是使用Object[]。

1.访问当前的连接点
任何通知方法可以将第一个参数定义为org.aspectj.lang.JoinPoint类型(环绕通知需要定义第一个参数为ProceedingJoinPoint类型, 它是 JoinPoint 的一个子类)。JoinPoint 接口提供了一系列有用的方法,比如 getArgs()(返回方法参数)、 getThis()(返回代理对象)、getTarget()(返回目标)、 getSignature()(返回正在被通知的方法相关信息)和 toString() (打印出正在被通知的方法的有用信息)。可以查看javadoc。

2.传递参数给通知
为了可以在通知体内访问参数, 你可以使用args来绑定。如果在一个args表达式中应该使用类型名字的地方使用一个参数名字,那么当通知执行的时候对应的参数值将会被传递进来。可能给出一个例子会更好理解。假使你想要通知(advise)接受某个Account对象作为第一个参数的DAO操作的执行,你想要在通知体内也能访问到account对象,你可以写如下的代码:

@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() &&" +
"args(account,..)")
public void validateAccount(Account account) {
  // ...
}
切入点表达式的 args(account,..) 部分有两个目的:首先它保证了 只会匹配那些接受至少一个参数的方法的执行,而且传入的参数必须是Account类型的实例, 其次它使得在通知体内可以通过account 参数访问实际的Account对象。

另一种方法:
定义一个切入点,这个切入点在匹配某个连接点的时候“提供”了 Account对象的值,然后直接从通知中访问那个命名切入点。看起来和下面的示例一样:
@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() &&" + "args(account,..)")
private void accountDataAccessOperation(Account account) {}
@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
//}

当然我们还可以这么作
package com.liyixing.spring.aspect;



import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

import org.aspectj.lang.annotation.Pointcut;



import com.liyixing.spring.model.Account;



@Aspect

public class MyAspect {

@SuppressWarnings("unused")

//定义切入点的同时,定义参数。args(o,..),这里的o只确定切入点的参数名。
//后面的连接点的参数名由连接点的字符串内容决定(该特性在上面的那种方式中依然有效)
@Pointcut("execution( public * *(..) ) && args(o,..)")

private void anyOldTransfer(Account o) {

}



//这里的连接点决定了连接点接受参数的名字是v
这里当然还可以写成public void doBefore(Object v) {。这里的参数类型和上面的切入点定义的参数类型不一样。这里的类型Object是切入点的类型的父类。而子类或其他类型是无效的。因为他们无法指向Account类型(切入点定义的类型)。这里的类型当然是表示,参数我将以Object类型收取(Object当然是可以指向Account的咯)。而Account的子类GuestAccout或者Integer类型当然是无法指向Account的了。

@Before("com.liyixing.spring.aspect.MyAspect.anyOldTransfer(v)")

public void doBefore(Account v) {

System.out.println("defore");

System.out.println("defore args o is " + v);

System.out.println();

}

}


两个参数
package com.liyixing.spring.aspect;



import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

import org.aspectj.lang.annotation.Pointcut;



@Aspect

public class MyAspect {

@SuppressWarnings("unused")

//这里则表示接受第一个参数,第二个参数。
//第一个参数以Oject接受,第二个参数也是以Object接受。
spring的参数(包括execution的参数列表)是不运行
..,参数名[,..]这种方式的,只能是
参数明[,..]或者
..
不允许在写了参数限制的情况下,在参数限制之前写..通配符。

@Pointcut("execution( public * *(..) ) && args(o,i,..)")

private void anyOldTransfer(Object o, Object i) {

}



@Before("com.liyixing.spring.aspect.MyAspect.anyOldTransfer(v, i)")

public void doBefore(Object v, Object i) {

System.out.println("defore");

System.out.println("defore args o is " + v);

System.out.println("defore args i is " + i);

System.out.println();

}
}

详细资料查看aspectj编程指南。

决定参数名

绑定在通知上的参数依赖切入点表达式的匹配名,并借此在(通知(advice)和切入点(pointcut))的方法签名中声明参数名。 参数名 无法 通过Java反射来获取,所以Spring AOP使用如下的策略来决定参数名字:

如果参数名字已经被用户明确指定,则使用指定的参数名: 通知(advice)和切入点(pointcut)注解有一个额外的"argNames"属性,该属性用来指定所注解的方法的参数名 - 这些参数名在运行时是 可以 访问的。例子如下:

@Before(
   value="com.xyz.lib.Pointcuts.anyPublicMethod() && " +
"@annotation(auditable)",
   argNames="auditable")
public void audit(Auditable auditable) {
  AuditCode code = auditable.value();
  // ...
}
如果一个@AspectJ切面已经被AspectJ编译器(ajc)编译过了,那么就不需要再添加 argNames 参数了,因为编译器会自动完成这一工作。
使用 'argNames' 属性有点不那么优雅,所以如果没有指定'argNames' 属性, Spring AOP 会寻找类的debug信息,并且尝试从本地变量表(local variable table)中来决定参数名字。 只要编译的时候使用了debug信息(至少要使用 '-g:vars' ),就可获得这些信息。 使用这个flag编译的结果是: (1)你的代码将能够更加容易的读懂(反向工程), (2)生成的class文件会稍许大一些(不重要的), (3)移除不被使用的本地变量的优化功能将会失效。 换句话说,你在使用这个flag的时候不会遇到任何困难。

如果不加上debug信息来编译的话,Spring AOP将会尝试推断参数的绑定。 (例如,要是只有一个变量被绑定到切入点表达式(pointcut expression)、通知方法(advice method)将会接受这个参数, 这是显而易见的)。 如果变量的绑定不明确,将会抛出一个 AmbiguousBindingException 异常。

如果以上所有策略都失败了,将会抛出一个 IllegalArgumentException 异常。

处理参数

我们之前提过我们将会讨论如何编写一个 带参数的 的proceed()调用,使得不论在Spring AOP中还是在AspectJ都能正常工作。 解决方法是保证通知签名依次绑定方法参数。比如说:

@Around("execution(List<Account> find*(..)) &&" +
"com.xyz.myapp.SystemArchitecture.inDataAccessLayer() && " +
"args(accountHolderNamePattern)")
public Object preProcessQueryPattern(ProceedingJoinPoint pjp, String accountHolderNamePattern)
throws Throwable {
  String newPattern = preProcess(accountHolderNamePattern);
  return pjp.proceed(new Object[] {newPattern});
}
大多数情况下你都会这样绑定(就像上面的例子那样)。

7.通知顺序
如果有多个通知想要在同一连接点运行会发生什么?通知(Advice)顺序

如果有多个通知想要在同一连接点运行会发生什么?Spring AOP 的执行通知的顺序跟AspectJ的一样。 在“进入”连接点的情况下,最高优先级的通知会先执行(所以上面给出的两个前置通知(before advice)中,优先级高的那个会先执行)。 在“退出”连接点的情况下,最高优先级的通知会最后执行。(所以上面给出的两个前置通知(before advice)中,优先级高的那个会第二个执行)。 对于定义在相同切面的通知,根据声明的顺序来确定执行顺序。比如下面这个切面:

@Aspect
public class AspectWithMultipleAdviceDeclarations {

  @Pointcut("execution(* foo(..))")
  public void fooExecution() {}

  @Before("fooExecution()")
  public void doBeforeOne() {
// ..
  }

  @Before("fooExecution()")
  public void doBeforeTwo() {
// ..
  }

  @AfterReturning("fooExecution()")
  public void doAfterOne() {
// ..
  }

  @AfterReturning("fooExecution()")
  public void doAfterTwo() {
//..
  }

}
这样,假使对于任何一个名字为foo的方法的执行, doBeforeOne、doBeforeTwo、doAfterOne 和 doAfterTwo 通知方法都需要运行。 执行顺序将按照声明的顺序来确定。在这个例子中,执行的结果会是:

doBeforeOne
doBeforeTwo
foo
doAfterOne
doAfterTwo
换言之,因为doBeforeOne先定义,它会先于doBeforeTwo执行,而doAfterTwo后于doAfterOne定义,所以它会在doAfterOne之后执行。 只需要记住通知是按照定义的顺序来执行的就可以了。 - 如果想要知道更加详细的内容,请参阅AspectJ编程指南。

当定义在 不同的 切面里的两个通知都需要在一个相同的连接点中运行,那么除非你指定,否则执行的顺序是未知的。 你可以通过指定优先级来控制执行顺序。在Spring中可以在切面类中实现 org.springframework.core.Ordered 接口做到这一点。 在两个切面中,Ordered.getValue() 方法返回值较低的那个有更高的优先级。

引入(Introductions)

引入(Introductions)(在AspectJ中被称为inter-type声明)使得一个切面可以定义被通知对象实现一个给定的接口,并且可以代表那些对象提供具体实现。

使用 @DeclareParents注解来定义引入。这个注解被用来定义匹配的类型拥有一个新的父亲。 比如,给定一个接口 UsageTracked,然后接口的具体实现 DefaultUsageTracked 类, 接下来的切面声明了所有的service接口的实现都实现了 UsageTracked 接口。(比如为了通过JMX输出统计信息)。

@Aspect
public class UsageTracking {

  @DeclareParents(value="com.xzy.myapp.service.*+",
  defaultImpl=DefaultUsageTracked.class)
  public static UsageTracked mixin;

  @Before("com.xyz.myapp.SystemArchitecture.businessService() &&" +
  "this(usageTracked)")
  public void recordUsage(UsageTracked usageTracked) {
usageTracked.incrementUseCount();
  }

}
实现的接口通过被注解的字段类型来决定。@DeclareParents 注解的 value 属性是一个AspectJ的类型模式:- 任何匹配类型的bean都会实现 UsageTracked 接口。 请注意,在上面的前置通知(before advice)的例子中,service beans 可以直接用作 UsageTracked 接口的实现。 如果需要编程式的来访问一个bean,你可以这样写:

UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
引入可以查看http://go12345.iteye.com/blog/352745的文章。
package com.liyixing.spring.aspect;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.DeclareParents;
import org.aspectj.lang.annotation.Pointcut;

import com.liyixing.spring.interfaces.TestI;
import com.liyixing.spring.interfaces.TestImp;

@Aspect
public class MyAspect {
@DeclareParents(value = "com.liyixing.spring.service.*+", defaultImpl = TestImp.class)
public static TestI test;

@SuppressWarnings("unused")
@Pointcut("execution( public * *(..) ) && args(o,i,..)")
private void anyOldTransfer(Object o, Object i) {
}

@SuppressWarnings("unused")
@Pointcut("execution( public * *(..) )")
private void anyOldTransfer1() {
}

@Before("com.liyixing.spring.aspect.MyAspect.anyOldTransfer1()  &&"
+ "this(test)")
public void recordUsage(TestI test) {
test.test();
}

@Before("com.liyixing.spring.aspect.MyAspect.anyOldTransfer(v, i)")
public void doBefore(Object v, Object i) {
System.out.println("defore");
System.out.println("defore args o is " + v);
System.out.println("defore args i is " + i);
System.out.println();
}
}



切面实例化模型

这是一个高级主题...
默认情况下,在application context中每一个切面都会有一个实例。 AspectJ 把这个叫做单个实例化模型(singleton instantiation model)。 也可以用其他的生命周期来定义切面:- Spring支持AspectJ的 perthis 和 pertarget 实例化模型 (现在还不支持percflow、percflowbelow 和 pertypewithin )。

一个"perthis" 切面的定义:在 @Aspect 注解中指定perthis 子句。 让我们先来看一个例子,然后解释它是如何运作的:

@Aspect("perthis(com.xyz.myapp.SystemArchitecture.businessService())")
public class MyAspect {

  private int someState;

  @Before(com.xyz.myapp.SystemArchitecture.businessService())
  public void recordServiceUsage() {
// ...
  }

}
这个perthis子句的效果是每个独立的service对象执行时都会创建一个切面实例(切入点表达式所匹配的连接点上的每一个独立的对象都会绑定到'this'上)。 service对象的每个方法在第一次执行的时候创建切面实例。切面在service对象失效的同时失效。 在切面实例被创建前,所有的通知都不会被执行,一旦切面对象创建完成,定义的通知将会在匹配的连接点上执行,但是只有当service对象是和切面关联的才可以。 如果想要知道更多关于per-clauses的信息,请参阅 AspectJ 编程指南。

'pertarget'实例模型的跟“perthis”完全一样,只不过是为每个匹配于连接点的独立目标对象创建一个切面实例。

猜你喜欢

转载自liyixing1.iteye.com/blog/1042744