Spring AOP对AspectJ的支持 spring

接上文,讲解了AspectJ的两种织入方式。我们来看看用@AspectJ标注的Aspect的写法

@Aspect
public class LoggingAnnoAspect {

    @Before("execution(* org.java.codelib..*.say*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Begin Process: " + joinPoint.getSignature().getName() + "()");
    }

    @AfterThrowing(pointcut = "execution(* org.java.codelib..*.method2*(..))", throwing = "error")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
        System.out.println("After Throwing: " + joinPoint.getSignature().getName());
        System.out.println("Exception: " + error);
        System.out.println("******");
    }

    @Around("execution(* org.java.codelib..*.method3*(..))")
    public void logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("Around method: " + joinPoint.getSignature().getName());
        System.out.println("Method arguments: " + Arrays.toString(joinPoint.getArgs()));
        joinPoint.proceed();
        System.out.println("Around after is running!");
        System.out.println("******");
    }

}

主要通过下面两点来进行解读

@AspectJ中的Pointcut

这里需要用到AspectJ的一个注解org.aspectj.lang.annotation.Pointcut,一个典型的AspectJ的声明如下

@Aspect
public class SampleAspect {

    @Pointcut("execution(* org.java.codelib..*.method1*(..))")
    public void method1Excution() {

    }

}

这里的Pointcut包括两部分

  • Pointcut Expression:

    这里即execution(* org.java.codelib..*.method1*(..))规定了该Pointcut匹配规则的地方,这里即会指定包org.java.codelib包及其子包结构下方法为method1。同样这里的表达式支持几种逻辑运算(&&,||,!)

  • Pointcut Signature

    这里的签名与通常说的方法签名类似,即method1Excution用来承载表达式。该方法除了必须返回void签名没有限制,实际上在特定的通知情况下需要特定的签名来完成


首先来看看表达式(Pointcut Expression)的具体语法

  • execution

    用来指定方法的签名,是使用最多的标志。语法如下:
    execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param) throws-pattern) 其中方法的返回类型、参数必须要指定,如execution(public void org.package.Foo.doSomething(String))这里指明了返回为void,以及签名为一个String类型,可以简写为execution( void doSomething(String))
    同样这里也可以支持通配,主要有两种*..

    *用来匹配一个单词(word),例如上面可以简写为 execution( * *(String))当然这样简写后将会匹配所有的返回类型以及方法签名不仅限于前面说的doSomething

    ..与前者不同的是,这个将匹配多个位置,主要用于以下两个地方execution(* org.java.codelib..*.method1*(..))即这里会匹配org.java.codelib包及该包下所有字包,同时也匹配方法签名0到多个,类似的还有
    execution(* *(..,String))最后一个参数必须为String
    execution(* *(*,String,..))第二个参数必须为String,剩下的类型个数不限

  • within

    within用来声明指定类型的Joinpoint,如within(org.java.codelib.aspectj.SampleAspectJ)那么这里只会匹配这个特定类下的方法。当然也可以支持上面说到的两种通配,其意义都一样

  • this和target

    this和target分别指方法的调用方和被调方,通常会一起使用表示只有在this调用target的情况下符合,如this(Object2)&&target(Object3)表示只有在Object2调用Object3时才会进行AOP拦截

  • args

    用来捕获拥有具体参数类型,数量的方法,而不管该方法在什么类型中被声明args(org.java.sample.User)这里将会匹配所有类中方法签名为这个指定类型的对象User,而不管是哪个类中声明的方法。同时对于这种void method(Object obj)如果在运行中该Object为User那么也一样匹配。

  • @within

    @within只接受注解作为类型,用来表示拥有该注解的类型的类被匹配(注意只是类上有效,方法无效)。例子见这里

  • @annotation

    这个和上面的@within就比较类似了,只接受注解作为类型,和上面的区别是在方法上有效。这也是应用很广泛的Pointcut了,比如我们的事务控制,通常在需要的方法加上@Transaction

  • 更多的表达式

    更多的表达式见PointcutPrimitive

怎样工作?

我们知道Pointcut需要完成的工作是根据条件找到目标的Joinpoint。所以在SpringAOP中有提供一个AspectJExpressionPointcut其本质也是一个SpringAOP的Pointcut,下面是相关的类图

AspectJExpressionPointcut中完成了对上述表达式的注册、构建和方法的match。由于其本质是SpringAOP,那么是需要实现ClassFilterMethodMatcher中的几个matches方法。

我们以匹配方法来看,在AspectJExpressionPointcut有几个重载的matches通过代码可以发现是通过AspectJ中的PointcutExpression来匹配执行的方法

private ShadowMatch getShadowMatch(Method targetMethod, Method originalMethod) {
	synchronized (this.shadowMapCache) {
		ShadowMatch shadowMatch = (ShadowMatch) this.shadowMapCache.get(targetMethod);
		if (shadowMatch == null) {
			try {
				shadowMatch = this.pointcutExpression.matchesMethodExecution(targetMethod);
			}
			catch (ReflectionWorld.ReflectionWorldException ex) {
				// Failed to introspect target method, probably because it has been loaded
				// in a special ClassLoader. Let's try the original method instead...
				if (targetMethod == originalMethod) {
					throw ex;
				}
				shadowMatch = this.pointcutExpression.matchesMethodExecution(originalMethod);
			}
			this.shadowMapCache.put(targetMethod, shadowMatch);
		}
		return shadowMatch;
	}
}

@AspectJ中的Advice

@AspectJ形式的Advice,主要是用@AspectJ中的标注来注解Aspect中的普通方法,针对不同类型的Advice有相应的注解 >@Before
@After
@AfterReturning
@AfterThrowing
@Around

见名知意。就不再描述各个具体含义,来看一个例子

@Aspect
public class SampleAdviceAspect {

    @Pointcut("execution(* *(..,String))")
    //匹配任何包下的方法,其中方法的最后一个参数必须为String
    private void method() {
    }

    @Before("method()")
    public void method2() {
        System.out.println("Before ...");
    }

    @Before("execution(* *(..,String))")
    public void method3() {
        System.out.println("Before do something ...");
    }

}

上面用两种方式来使用了@Before,需要注意的是method()方法的访问权限不限。例子见这里,其他类型的通知处理方式相同。接下来看看通知中方法的参数

1、首先,在@AspectJ的Advice方法中,第一个参数默认为org.aspectj.lang.JoinPoint,需要注意两个例外@Around为ProceedingJoinPoint以及``

@Before("execution(* *(..,String))")
public void method4(JoinPoint joinPoint) {
    joinPoint.getArgs();
}

这里通过joinPoint.getArgs()获取了目标方法的参数

2、通过args、this、target、@annotation等入参

@Before("execution(* *(..,String))&&@annotation(anyJoinpointAnnotation)")
public void method5(JoinPoint joinPoint, AnyJoinpointAnnotation anyJoinpointAnnotation) {
    joinPoint.getArgs();
    //do something with AnyJoinpointAnnotation
}

注意这里@annotation(anyJoinpointAnnotation)中的签名必须和方法中的签名一致,或者多个

@Before(value = "execution(* *(..))&&args(name)&&@annotation(anyJoinpointAnnotation)", argNames = "name,anyJoinpointAnnotation")
public void method6(JoinPoint joinPoint, String name, AnyJoinpointAnnotation anyJoinpointAnnotation) {
    System.out.println("Before: " + name);
}

不过貌似argNames没多大用

再来看看几种特殊方式的入参方式吧

  • @AfterThrowing

    @AfterThrowing有一个属性throwing用来指定目标类抛出异常后改Aspect方法签名

      @AfterThrowing(pointcut = "execution(* *(..))", throwing = "re")
      public void logAfterThrowing(JoinPoint joinPoint, RuntimeException re) {
          System.out.println("After Throwing: " + joinPoint.getSignature().getName());
          System.out.println("Exception: " + re);
          System.out.println("******");
      }
    
  • @AfterReturning

    @AfterReturning有一个returning来获取返回的对象

      @AfterReturning(pointcut = "execution(* *(..))", returning = "result")
      public void logAfterReturning(JoinPoint joinPoint, Object result) {
          System.out.println("After Process: " + joinPoint.getSignature().getName());
          System.out.println("Method returned value is: " + ((User)result).toString());
          System.out.println("******");
      }
    
  • @Around

    前面说到@Around的第一个参数默认为ProceedingJoinPoint

      @Around("execution(* *(..))")
      public void logAround(ProceedingJoinPoint joinPoint) throws Throwable {
          System.out.println("Around method: " + joinPoint.getSignature().getName());
          System.out.println("Method arguments: " + Arrays.toString(joinPoint.getArgs()));
          joinPoint.proceed();
          System.out.println("Around after is running!");
      }
    

以上才基于注解的@AspectJ,其申明相当的简单,只需要在配置文件中

<aop:aspectj-autoproxy proxy-target-class="true"/>

当然这里truefalse自己斟酌,本段例子

基于Schema的AOP

首先需要在配置文件中引入相应的XSD声明:

xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd

接下来看看的主要元素

<aop:config>
	<aop:pointcut />
	<aop:advisor />
	<aop:aspect />
</aop:config>

其中

<aop:config proxy-target-class="false">`指定代理的模板为class,默认为false即代理接口,采用JDK动态代理,否则采用CGLIB代理class

<aop:advisor advice-ref="" pointcut-ref="" order=""/>这里列举了几个属性,当然也可以用相应的schema来配置,其他类似

<aop:pointcut expression="" id=""/><aop:aspect id="" ref="" order=""/>

考虑两种方式

1、BeanFactory类的转换

主要是将采用bean注册各种BeanCreator修改为<aop:config>

<bean id="simpleBean" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
	<property name="beanNames">
			//
	</property>
	<property name="interceptorNames">
		<list>
			//
		</list>
	</property>
</bean>

来看看一个简单的配置

<aop:config>
	<aop:advisor advice-ref="simpleAdvice" pointcut-ref="nameMatchMethodPointcut" />
</aop:config>

<bean id="simpleAdvice" class="org.java.codelib.spring.aop.pointcut.SimpleAdvice" />

<bean id="nameMatchMethodPointcut" class="org.springframework.aop.support.NameMatchMethodPointcut">
	<property name="mappedNames">
		<list>
			<value>sayHi</value>
			<value>greet</value>
		</list>
	</property>
</bean>

<bean id="simpleBean" class="org.java.codelib.spring.aop.pointcut.SimpleBean" />

如果不想定义Pointcut且被外部访问,也可以用pointcut属性

<aop:config>
	<aop:advisor advice-ref="simpleAdvice" pointcut="execution(* *(..))" />
</aop:config> 

2、@AspectJ到Schema

采用@AspectJ的Schema配置主要考虑两点:一是Aspect是普通的方法二是通过<aop:aspect...>来配置相应的通知、Pointcut,下面是对二部分采用这样方式的简单实现

<aop:config>
	<aop:aspect id="sampleAspect" ref="simpleAspectId">
		<aop:pointcut expression="execution(* *(..))" id="beforePointCut" />
		<aop:before method="before" pointcut-ref="beforePointCut" />
		<aop:after-returning method="logAfterReturning"
			pointcut-ref="beforePointCut" returning="user" />
		<aop:after-throwing method="logAfterThrowing"
			pointcut-ref="beforePointCut" throwing="re" />
	</aop:aspect>
</aop:config>

<bean id="simpleAspectId" class="org.java.codelib.spring.aop.schema.AspectJSampleAspect" />

<bean id="simpleBean" class="org.java.codelib.spring.aop.pointcut.SimpleBean" />

本段例子

最后来看看AOP很广泛的应用事务

<aop:config>
	<aop:advisor pointcut="execution(* com.jd.slm.service..*Service.*(..))"
		advice-ref="txAdvice" />
</aop:config>

<tx:advice id="txAdvice" transaction-manager="transactionManager">
	<tx:attributes>
		<tx:method name="save*" propagation="REQUIRED"
			rollback-for="Throwable" isolation="READ_COMMITTED" />
		<tx:method name="update*" propagation="REQUIRED"
			rollback-for="Throwable" isolation="READ_COMMITTED" />
		<tx:method name="delete*" propagation="REQUIRED"
			rollback-for="Throwable" isolation="READ_COMMITTED" />
	</tx:attributes>
</tx:advice>

从上面的配置可以看出,tx标签本质也是一个Advice,再看看XSD的描述可以发现这个TransactionInterceptor而他实现了MethodInterceptor



blog comments powered by Disqus