Spring在处理@AspectJ注解表达式时,需要将Spring的asm模块添加到类路径中。Spring采用AspectJ提供的@AspectJ注解类库及相应的解析类库,需要在pom.xml文件中添加aspectj.weaver和aspectj.tools类包的依赖。
首先是业务类的代码:
public class NaiveWaiter implements Waiter {
public void greetTo(String clientName) {
System.out.println("NaiveWaiter:greet to "+clientName+"...");
}
}
然后使用@AspectJ注解定义一个切面:
@AspectJ
public class PreGreetingAspect{
@Before("execution(* greetTo(..))")
public void beforeGreeting(){
System.out.println("How are you");
}
}
最后通过AspectJProxyFactory为NativeWaiter生成织入PreGreetingAspect切面的代理:
public class AspectJProxyTest {
@Test
public void proxy(){
Waiter target = new NaiveWaiter();
AspectJProxyFactory factory = new AspectJProxyFactory();
factory.setTarget(target);
factory.addAspect(PreGreetingAspect.class);
Waiter proxy = factory.getProxy();
proxy.greetTo("John");
proxy.serveTo("John");
}
}
AnnotationAwareAspectJAutoProxyCreator能够将@AspectJ注解切面类自动织入目标Bean中,PreGreetingAspect是使用@AspectJ注解描述的切面类,而NativeWaiter是匹配切点的目标类。
以下使用基于Schema的aop命名空间进行配置:
首先在配置文件中引入aop命名空间,然后通过aop命名空间的Spring支持9个@AspectJ切点表达式函数,大致分为4种类型:
(1)方法切点函数:通过描述目标类方法 的信息定义连接点
(2)方法入参切点函数:通过描述目标类方法入参的信息定义连接点
(3)目标类切点函数:通过描述目标类类型的信息定义连接点
(4)代理类切点函数:通过描述目标类的代理类的信息定义连接点
这4种类型的切点函数如下:
@AspectJ支持3种通配符
(1)*:匹配上下文中任意一个字符
(2)..:匹配上下文中任意多个字符,在表示类时,必须和*联合使用
(3)+:按类型匹配指定类的所有类(继承或扩展指定类的所有类还有类本身),必须跟在类名后面。
@AspectJ函数按其是否支持通配符及支持程度,分为3类:
(1)支持所有的通配符:execution()和within(),如within(com.smart.*)、within(com.smart.service..*.*Service+)
(2)仅支持“+”通配符:args()、this()和target()
(3)不支持通配符:@args()、@within()、@target()和@annotation()
Spring支持一下切点运算符:
&&:与操作符,相当于切点的交集运算。由于&是XML特殊字符,Spring提供了and,如within(com.smart..*)and args(String)表示在com,smart包下所有类(当前包及子孙包)拥有一个String入参的方法。
||:或操作符,相当于切点的并集运算,or是等效的操作符。
!:非操作符,相当于切点的反集运算,not是等效的操作符。
注解类的成员中value和argNames的含义是相同的:
value:用于定义切点
argNames:指定注解所标注增强方法的参数名,多个参数名用逗号分隔。
(1)@Before:有两个成员value和argNames
(2)@AfterReturning:有4个成员value和argNames,还有
pointcut:表示切点的信息,显示指定pointcut值,会覆盖value的设置值
returning:将目标对象方法的返回值绑定给增强的方法
(3)@Around:有两个成员:value和argNames
(4)@AfterThrowing:有4个成员value和argNames,还有
pointcut:表示切点的信息,显示指定pointcut值,会覆盖value的设置值
throwing:将抛出的异常绑定到增强方法中
(5)@After:Final增强,不管是抛出异常还是正常退出,该增强都会得到执行。有两个成员value和argNames
(6)@DeclareParents:引介增强,相当于IntroductionInterceptor,有两个成员value和defaultImpl,defaultImpl是默认的接口实现类
下面通过切面技术将SmartSeller加入到NativeWaiter中,让NativeWaiter实现Seller的接口:
@Aspect
public class EnableSellerAspect {
@DeclareParents(value="com.smart.NaiveWaiter",defaultImpl=SmartSeller.class)
public Seller seller;
}
在Spring配置文件中配置好切面和NativeWaiter Bean
测试代码:
public class DeclaredParentsTest{
public static void main(String[] args){
String configPath = "com/smart/aspectj/basic/beans.xml";
ApplicationContext ctx = new ClassPathApplicationContext(configPath);
Waiter waiter = (Waiter)ctx.getBean("waiter");
waiter.greetTo("John");
Seller seller = (Seller)waiter;
seller.sell("Beer","John");
}
}
@Aspect
public class TestAspect {
@Before("!target(com.smart.NaiveWaiter) && execution(* serveTo(..)))")
public void notServeInNaiveWaiter() {
System.out.println("--notServeInNaiveWaiter() executed!--");
}
@After("within(com.smart.*) && execution(* greetTo(..)))")
public void greeToFun() {
System.out.println("--greeToFun() executed!--");
}
@AfterReturning("target(com.smart.Waiter) || target(com.smart.Seller)")
public void waiterOrSeller(){
System.out.println("--waiterOrSeller() executed!--");
}
}
命名切点的使用类方法作为切点的名称,此外方法的访问修饰符还控制了切点的可引用性。如下是命名切点的结构:
命名切点定义好后,可以在定义切面时通过名称引用切点,如下:
(1)如果增强在同一个切面类中声明,则依照增强在切面类中定义的顺序进行织入
(2)如果增强位于不同的切面类中,且这些切面类都实现了org.springframework.core.Ordered接口,则由接口方法的顺序号决定(顺序号小的先织入)
(3)如果增强位于不同的切面类中,且这些切面类没有实现org.springframework.core.Ordered接口,则织入的顺序是不确定的。
使用
通过
(1)基于@AspectJ注解的方式:适用项目采用Java5.0
(2)基于
(3)基于
(4)基于Advisor类的方式:适用项目只能使用低版本的Spring
切面不同定义方式具体实现比较: