精通Spring+4.x++企业开发与实践之基于@AspectJ和Schema的AOP

#  精通Spring+4.x++企业开发与实践之基于@AspectJ和Schema的AOP

使用@AspectJ的条件

1.保证是java5以上的版本(需要使用注解,而java5及以上才使用注解)

2.需要将Spring的asm(轻量级的字节码处理框架)的模块添加到类路径中,因为java的反射无法获取入参的名字,所以Spring就是要asm处理@AspectJ中描述的方法入参名。

3.Spring使用AspectJ提供的@AspectJ注解类库及相应的解析类库,需要在pom.xml文件添加aspectjweaver和aspectj的工具类aspectjrt

例子:

PreGreetingAspect.java

@Aspect//使用该注解定义一个切面
public class PreGreetingAspect {
	/**
	 * 这段代码包含了横切的逻辑
	 * @Before 增强的类型
	 * "execution(* greetTo(..))"目标切点的表达式
	 */
	@Before("execution(* greetTo(..))")
	public void beforeGreeting(){
		System.out.println("How are you");
	}
}

测试代码:

		//创建被代理的对象
		Waiter waiter = new PoliteWaiter();
		//AspectJ代理工厂
		AspectJProxyFactory factory = new AspectJProxyFactory();
		//提娜佳目标类
		factory.setTarget(waiter);
		//添加切面类
		factory.addAspect(PreGreetingAspect.class);
		//生成切入的代理对象
		Waiter proxy = factory.getProxy();
		proxy.greetTo("张三");

执行结果: How are you

greet to 张三....

使用schema的配置方式进行配置

<?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:p="http://www.springframework.org/schema/p"
	   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-4.0.xsd
		 http://www.springframework.org/schema/context
		 http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

	<!--基于asjectj的切面驱动器-->
	<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
	<bean id="waiter" class="com.flexible.aspectj.PoliteWaiter"></bean>
	<bean class="com.flexible.aspectj.PreGreetingAspect"></bean>
	</beans>

测试代码:

	ApplicationContext context = new ClassPathXmlApplicationContext("classpath:beans.xml");
	Waiter waiter = (Waiter) context.getBean("waiter");
	waiter.greetTo("zhangsan");

执行结果:

@AspectJ语法基础

@AspectJ使用Java5.0注解和正规的AspectJ的切点表达式语言描述切面,由于Spring只支持方法的链接,所以Spring仅支持部分AspectJ的切点语言。

切点表达式函数

组成:1.关键字和操作参数execution(* greetTo(..)),execution是关键字,"* greetTo(..)"位操作参数。execution代表目标类执行某一方法,"* greetTo(..)"描述目标方法的匹配模式串,二者联合起来表示目标类的greetTo()方法的连接点。execution()称为函数,"* greet(..)"称为函数入参。

Spring支持9个@AdpectJ切点表达式函数,它用不同的方式藐视目标类的连接点。根据描述的不同可以分为4种: 1.方法切点函数:通过描述目标类方法的信息定义连接点。 2.方法入参切点函数:通过描述目标类的方法入参的信息定义连接点。 3.目标类切点函数:通过描述目标类类型的信心定义连接点。 4.代理切点函数:通过描述目标类的代理类的信息定义连接点。

入参函数的通配符

  1. *:匹配任意字符,但它只能匹配上下文中的一个元素。

  2. ..:匹配任意字符,可以匹配上下文中的多个元素,但在表示类时,必须和*联合使用,而在表示入参时则单独使用。

  3. +:表示按类型匹配指定的类的所有类,必须跟在类名后面,如com.flexible.Car+表示继承或者拓展了制定类的所有类,同时还包括指定类本身。

@AaspectJ函数按其是否支持通配符及支持的程度,可以分为三类:

1.支持通配符:execution()和within(),如 within(com.flexible.),within(com.flexible.service...*.Service).

2.仅支持"+"通配符:args(),this()和target(),如args(com.flexible.Waiter+),target(java.util.List+),但是其实这几个函数是不是有这个通配符都时一样的。

3.不支持通配符的:@args,@within(),@target()和@annotation().

不同类型的增强

1.@Before 前置增强,相当于BeforeAdvice。Before注解类用于两个成员

  • value:该成员用于定义切点。
  • argsNames:由于无法通过Java反射机制获取方法入参名,所以如果在Java编译时未启用调式信息,或者需要在运行期间解析切点,就必须通过这个成员指定直接所标注增强的方法的参数名(注意二者名字必须完全相同),多个参数名用逗号分开。

2.@AfterReturning 后置增强,相当于AfterReturningAdvice。AfterReturning注解类拥有4个成员。

  • value:该成员用于定义切点
  • pointcut:表示切点的信息。如果显式地指定pointcut值,那么它将覆盖value的设置值,可以将pointcut成员看作value同义词。
  • returning:将目标对象方法的返回值绑定给增强的方法。
  • argNames:如上所述

3.@Around 环绕增强,相当于MethodInterceptor。Around注释类用于两个成员。

  • value:该成员用于定义切点
  • argNames:如上所述

4.@AfterThrowing 抛出增强,相当于ThrowsAdvice。AfterThrowing注解有4个成员。

  • value:指定切点

  • pointcut:表示切点的信息,如果显示指定pointcut值,那么它将覆盖value值得设置值,可以将pointcut成员看作value得同义词。

  • throwing:将抛出得异常绑定到增强方法中。

  • argNames:如上所述。

5.@After Final增强,不管时抛出异常还是正常退出,该增强都会执行,该增强没有对应得增强接口,可以把它看成ThrowsAdvice和AfterReturningAdvice得混合物,议案用于释放资源,相当于try{}finally{}的控制流程。它有两个成员;

  • value:该成员用于定义切点。

  • argNames:如上所述。

6.@DeclareParents 引介增强,相当于IntroductionInterceptor.DeclareParents注解类拥有两个成员

  • value:定义切点,表示在那个木堡垒上添加引介增强。

  • defaultImpl:默认接口实现类。

引介增强的使用

通过引介增强将waiter也具有seller的功能

EnableSellerAspect.java

@Aspect
public class EnableSellerAspect {
//1.为PoliteWaiter太你家接口实现 2.默认接口实现类 3.要是西安的目标接口。
@DeclareParents(value = "com.flexible.inroductionofdeclareparent.PoliteWaiter",defaultImpl = SmartSeller.class)
public Seller seller;
}

beans.xml

	<!--基于asjectj的切面驱动器-->
	<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
	<bean id="waiter_2" class="com.flexible.inroductionofdeclareparent.PoliteWaiter"></bean>
	<bean class="com.flexible.inroductionofdeclareparent.EnableSellerAspect"></bean>

测试代码:

		ApplicationContext context = new ClassPathXmlApplicationContext("classpath:beans.xml");
		Waiter waiter = (Waiter) context.getBean("waiter_2");
		waiter.greetTo("zhangsan");
		Seller seller = (Seller) waiter;
		seller.sell("apple");

执行结果:

切点函数详解

命名切点

直接将切点声明在增强的方式是匿名增强,而如果希望在其他地方重用一个切点,可以通过@Pointcut注解及切面类方法对切点进行命名。

例子:

import org.aspectj.lang.annotation.Pointcut;

public class NamedPointcut {
	//通过注解方法inpackage()对该切点进行命名,方法可视域修饰符为private
	//表明该命名切点只能在本切面类中使用
	@Pointcut("within(com.flexible.*)")
	private void inpackage(){}

	//通过注解方法greetTo()对该切点进行命名,方法可以视域修饰符为protected
	//表明该命名切点可以在当前包中的切面类,自切脉你类只要。
	@Pointcut("execution(* greetTo(..))")
	protected void greetTo(){}

	//引用命名切点定义的切点,本切点也是命名切点,它对应的可视域为public
	@Pointcut("inpackage() && greetTo()")
	public void inPkgGreetTo(){}

}

命名切点的使用类方法作为切点的名称,此方法的访问修饰符还控制了切点的可引用性。命名切点定义好之后我们就可以在定义切面的时通过名称引用切点。

例子:

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

@Aspect
public class NamedAspect {
	@Before("NamedPointcut.inPkgGreetTo()")
	public void pkgGreetTo(){
		System.out.println("----------pkgGreetTo() executed!---");
	}
	@Before("!target(com.flexible.pointcutbreakdown.annotation.PoliteWaiter) && NamedPointcut.inPkgGreetTo()")
	public void pkgGreetToNotNaivewaiter(){
		System.out.println("----------pkgGreetToNotNaivewaiter() executed!---");
	}
}

增强植入的顺序

一个连接点可u哦同时匹配多个其欸但,切点对应的增强在连接点上的植入顺序有三种情况需要讨论

1.如果增强在同一个切面类中声明,则依照增强在切面类中定义的顺序织入

2.如果增强位于不同的切面类中,且这些切面类都实现了org.springframework.core.Ordered接口,则由接口方法的顺序好决定(顺序号小的先织入)

3.如果增强位于不同的切面类中,且这些切面类没有实现org.springframework.core.Ordered接口,则织入顺序是不确定的。

例子: 如果有切面A和切面B,而且这两个切面都实现了org.springframework.core.Ordered接口,A的顺序是1,而得顺序是2,而且A定义了三个切点,B定义两个切点。那么访问顺序如下图所示:

访问连接点信息

AspectJ使用org.aspectj.lang.JoinPoint接口表示目标类连接点对象。如果是环绕增强,则使用org.aspectj.lang.ProceedingJoinPoint表示连系欸但对象,该类是JoinPoint得子接口。任何增强方法都可以通过将第一个入参声明为JoinPoint访问连系欸但上下文信息。 1.JoinPoint

  • java.lang.Object[] getArgs():获取连系欸但方法运行时得入参列表。
  • Signature getSignature():获取连接点得方法签名对象。
  • java.lang.Object getTarget():获取连接点所在目标对象
  • java.lang.Object getThis():获取代理对象本身。

2.ProceedingJoinPoint

ProceedingJoinPoint继承于JoinPoint子接口,它新增了两个用于执行连接点得方法。

  • java.lang.Object.proceed() throws java.lang.Throwable:通过反射执行目标对象得连接点处得方法。
  • java.lang.Object proceed(java.lang.Object[] args)throws java.lang.Throwable:通过反射执行目标对象连接点处得方法,不过使用新得入仓替换原来得入参。

例子:

TestAspect.java

@Aspect
public class TestAspect {

	@Around("execution(* com.flexible.obtainproceedingpointinfo..*(..))")
	public void joinPointAccess(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
		System.out.println("------joinPointAccess----");
		System.out.println("args[0]:" + proceedingJoinPoint.getArgs()[0]);
		System.out.println("signature:" + proceedingJoinPoint.getTarget().getClass());
		proceedingJoinPoint.proceed();
		System.out.println("------joinPointAccess----");
	}
}

beans.xml

	<!--基于asjectj的切面驱动器-->
	<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
	<!--获取连接点的信息-->
	<bean id="waiter_4" class="com.flexible.obtainproceedingpointinfo.PoliteWaiter"></bean>
	<bean class="com.flexible.obtainproceedingpointinfo.TestAspect"></bean>

测试代码:

		ApplicationContext context = new ClassPathXmlApplicationContext("classpath:beans.xml");
		Waiter waiter = (Waiter) context.getBean("waiter_4");
		waiter.greetTo("zhangsan");

执行结果:

绑定连接点方法入参

猜你喜欢

转载自my.oschina.net/u/3474937/blog/2962194