一、通过一个最基础的例子记录基于Schema方式的spring AOP实现。
以下模拟一个业务处理类:
public class HelloWorldServiceImpl implements HelloWorldService { @Override public String sayHello(String param,People people) { System.out.println("People's name is " + people.getName()); return "success"; } }
以下模拟切面通知实现类,其他诸如环绕通知、后置异常通知就不一一列出了:
public class HelloWorldAspect { public void beforeAdvice(String pa,People arg) { System.out.println("=================前置通知!" + pa); } public void afterFinallyAdvice(String pa, People arg) { System.out.println("=================后置最终通知!" + arg.getName()); } public void afterReturn(String pa,People arg,String value) { System.out.println("=================value = " + value); } }
以下是applicationContext.xml文件配置:
<bean id="helloWorldService" class="cn.com.enorth.service.impl.HelloWorldServiceImpl"/> <bean id="aspect" class="cn.com.enorth.aop.HelloWorldAspect"/> <aop:config> <aop:pointcut expression="execution(* cn.com.enorth..*.*(..)) and args(pa,arg)" id="pointcut"/> <aop:aspect ref="aspect"> <aop:before pointcut-ref="pointcut" method="beforeAdvice"/> <aop:after pointcut-ref="pointcut" method="afterFinallyAdvice"/> <aop:after-returning pointcut-ref="pointcut" method="afterReturn" returning="value"/> </aop:aspect> </aop:config>
以下是测试方法:
public class Test { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); HelloWorldService service = ctx.getBean("helloWorldService",HelloWorldService.class); People people = new People(); people.setName("Super Man"); service.sayHello("Hello", people); } }
按照Spring的正常调用方式调用业务类的相应方法,如果该方法匹配AOP的切入点模式,就会自动调用切面实现类中的相应方法。
如果通知方法中需要使用被匹配方法的参数,则要在匹配模式中加入"and args(参数列表)",参数名要与通知方法中的参数名一一对应。如果需要使用被匹配方法的返回值,则要在后置返回通知中定义return属性,其值与通知方法中的参数名一致。如上述配置中return=”value“与HelloWorldAspect类中afterReturn方法的第三个参数名”value“相一致。
关于环绕通知:
环绕通知十分强大,可以决定目标方法(被匹配到的方法)是否执行,什么时候执行,执行的时候是否替换参数,执行完毕是否替换返回值。环绕通知通过<aop:around>标签及其属性进行配置。
环绕通知的第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型,在通知方法内部使用PorceedingJoinPoint的proceed()方法是目标方法执行。proceed()方法可选择是否传入Object[]数组替换目标方法执行时的参数。目标方法的返回值,将由通知方法的返回值取代。如果目标方法返回值类型为void,则在通知方法内执行proceed()方法返回null。而如果目标方法有返回值,通知方法为void类型,调用目标方法的程序也将获得null。
二、基于@AspectJ的AOP
1、Spring默认并不支持@AspectJ风格的切面声明,为了启用支持需要做如下配置:
<aop:aspectj-autoproxy />
2、声明切面
使用@Aspect注解进行声明
@Aspect public class NewAspect {}
然后将该切面在配置文件中声明为bean,Spring就能自动识别并进行AOP方面的配置:
<bean id="aspect" class="....NewAspect"/>
切入点以及各类通知都可以在该类中定义,见下文。
3、一个@AspectJ风格的AOP实现如下:
以下模拟业务处理类:
public class NewServiceImpl implements NewService { @Override public void newSay(People people) { System.out.println(people.getName()); } }
以下模拟一个切面处理类:
public class NewAspect { @Pointcut(value="execution(* cn.com.enorth..*.new*(..)) && args(people)") public void pointCut(People people){} /* * value取值可以是命名切入点“pointCut(people)” * 也可以是新的切入点表达式“execution(* cn.com.enorth..*.*(..)) && args(people)” */ @Before(value="pointCut(people)") public void befort(People people){ System.out.println("===============" + people.getName()); } @AfterReturning(value="pointCut(people)",returning="value") public void afertReturning(People people,Object value){ System.out.println("===============" + value); } }
以下是applicationContext.xml中需要注册的bean:
<bean id="newService" class="cn.com.enorth.service.impl.NewServiceImpl"/> <bean id="newAspect" class="cn.com.enorth.aop.NewAspect"/>
以下是测试代码:
public class Test { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); NewService service = ctx.getBean("newService",NewService.class); People people = new People(); people.setName("Super Man"); service.newSay(people); } }
对照Schema风格的AOP实现,@AspectJ风格也就很好理解了。差异比较大的一点是,@AspectJ风格中可引用的切入点是通过单独定义的一个方法实现的,在上下文中通过方法名以及参数列表引用,即:
@Pointcut(value="execution(* cn.com.enorth..*.new*(..)) && args(people)") public void pointCut(People people){}
而在Schema风格中,这是通过<aop:pointcut>标签实现的,在上下文中通过切入点的id引用,即:
<aop:pointcut expression="execution(* cn.com.enorth..*.*(..)) and args(pa,arg)" id="pointcut"/>
另外,@AspectJ风格中的&&、||、!在Schema风格中分别用and、or、not表示。
三、参数传递
上面的介绍中已经通过在定义切入点的时候,使用“args(参数列表)”匹配模式的方式传递被匹配方法的参数到通知方法中。下面介绍在通知方法中使用JoinPoint接口类型获取更多信息。
任何通知方法的第一个参数都可以是JoinPoint类型,例外的是环绕方法的第一个参数必须是ProceedingJoinPoint,它是JoinPoint的子类。第一个参数也可以是JoinPoint.StaticPart,它只包含连接点的静态信息。
1、JoinPoint接口方法说明
String toString();//连接点所在位置的相关信息
String toShortString();//简短相关信息
String toLongString();//详细相关信息
Object getThis();//返回AOP代理对象
Object getTarget();//返回目标对象
Object[] getArgs();//返回被通知方法的参数列表
Signature getSignature();//返回当前连接点签名
SourceLocation getSourceLocation();//返回连接点方法所在类文件中的位置
String getKind();//返回连接点类型
StaticPart getStaticPart();//返回连接点静态部分
2、ProceedingJoinPoint接口定义:用于环绕通知,使用proceed方法执行目标方法。
public interface ProceedingJoinPoint extends JoinPoint { public Object proceed() throws Throwable; public Object proceed(Object[] args) throws Throwable; }
3、JoinPoint.StaticPart:提供连接点的静态信息部分
public interface StaticPart{ Signature getSignature(); // 返回当前连接点签名 String getKind(); //返回连接点类型 int getId(); //返回连接点的唯一标识 String toString(); //同上 String toShortString(); //同上 String toLongString(); //同上 }
四、通知执行顺序
1、同一个切面中通知的执行顺序
①前置通知 / 环绕通知proceed方法执行之前的部分//这两者的先后顺序不确定
②被通知方法
③后置通知 / 环绕通知proceed方法执行之后的部分//这两者的执行顺序不确定
2、不同切面中通知的执行顺序
当定义在不同切面的同类型的通知(比如都是前置通知),需要在用一个连接点执行时,如果没有指定切面的执行顺序,那么这两个通知的执行顺序是未知的。
如果有必要确定执行顺序,需要执行切面执行的优先级。可以通过实现org.springframework.core.Ordered接口或者使用注解@Order指定切面优先级。Order.getValue()返回值越小优先级越高。不推荐使用接口方法指定优先级,两种风格下配置优先级的方式如下:
@Aspect @Order(2) public class OrderAspect2{}
<aop:aspect ref="..." order="1">...</aop:aspect>