首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 软件管理 > 软件架构设计 >

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

2012-09-10 
aop简单例子,spring aop中的概念及使用开启aspectj的支持为了在Spring配置中使用@AspectJ aspects,你必须

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 />

添加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" + 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”完全一样,只不过是为每个匹配于连接点的独立目标对象创建一个切面实例。

热点排行