Spring对AOP的支持
Spring在新版本中对AOP功能进行了重要的增强:
- 新增了基于Schema的配置支持,为AOP专门提供了aop命名空间。
- 新增了对AspectJ切点表达式语言的支持。@AspectJ是AspectJ1.5新增的功能,它通过Java5.0的注解技术,允许开发者在POJO中定义切面。Spring使用和@AspectJ相同风格非注解,并通过AspectJ提供的注解库和解析库处理切点。由于Spring只支持方法级的切点,所以仅对@AspectJ提供了有限的支持。
- 可以无缝地集成AspectJ.AspectJ提供了语言级切面的实现,Spring对开源世界里一切优秀的东西向来采取兼容并蓄的态度。
SpringAOP包括基于XML配置的AOP和AspectJ注解的AOP.
虽然AspectJ提供对AOP更为细致的实现,但像实例化切面,属性访问切面、条件切面等功能,在实际应用中并不常见。
Java5.0注解知识
@param @return 等Javadoc标签就是注解标签,为第三方工具提供了描述程序代码的注释信息。Java5.0注解可以看做javadoc和Xdoclet标签的延伸与发展。在Java5.0中可以自定义这些标签,并通过java语言的反射机制来获取类中标注的注解,完成特定的功能。
注解是代码的副属信息,遵循一个原则:注解语言不能直接干涉程序代码的运行,无论删除或增加注解,代码都能正常运行。第三方工具可以利用代码中的注解间接控制程序代码的运行,他们通过java反射机制读取注解的信息,并根据这些信息更改目标程序的逻辑,SpringAOP对@AspectJ提供支持,正是采取这种方法。
使用@AspectJ
定义切面类
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
//通过该注解定义一个切面
@Aspect
public class PreGreetingAspect {
@Before("execution(* greetTo(..))")//定义切点和增强类型
public void beforeGreeting(){//增强的横切逻辑
System.out.println("How are you");
}
}
测试类
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;
public class AspectJProxyTest {
public static void main(String[] args) {
Waiter target = new NaiveWaiter();
//定义一个Aspect类型的代理工厂
AspectJProxyFactory factory = new AspectJProxyFactory();
//设置代理目标类
factory.setTarget(target);
//添加切面类
factory.addAspect(PreGreetingAspect.class);
//生成织入切面的代理对象
Waiter proxy = factory.getProxy();
proxy.greetTo("欢欢");
proxy.serveTo("欢欢");
}
}
此时会发现,在PreGreetingAspect 切面类中定义的前置方法和通过BeforeAdvice接口定义的前置方法增强不一样,一个明显的区别是PreGreetingAspect 类的beforeGreeting()方法没有任何入参,而BeforeAdvice的接口方法before(Method method, Object[] args, Object target)的入参提供了一条访问目标对象方法和入参的途径。@Aspect定义的切面也提供了访问目标对象连接点信息的方法。
通过配置使用@AspectJ
xml配置
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--目标bean-->
<bean id="waiter" class="com.ijianghu.advice.impl.NaiveWaiter"/>
<!--使用定义了@AspectJ的切面-->
<bean class="com.ijianghu.aspect.PreGreetingAspect"/>
<!--自动代理创建器,自动将@AspectJ注解切面类织入目标bean中-->
<!--AnnotationAwareAspectJAutoProxyCreator能够将AspectJ注解切面类自动织入到目标Bean中。-->
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"
/>
</beans>
schema-aop配置
<?xml version="1.0" encoding="UTF-8"?>
<!--首先引入aop命名空间-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop">
<!--aspectj-autoproxy自动为Spring容器中那些匹配@aspectj切面的Bean创建代理,完成切面织入-->
<!--Spring在内部依旧使用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,
具体实现细节被aop:aspectj-autoproxy 隐藏起来-->
<aop:aspectj-autoproxy/>
<bean id="waiter" class="com.ijianghu.advice.impl.NaiveWaiter"/>
<bean class="com.ijianghu.aspect.PreGreetingAspect"/>
</beans>
@AspectJ语法基础
切点表达式函数
切点表达式由关键字和操作参数组成,如execution(* greetTo(..))
Spring支持9个@AspectJ切点表达式函数,用不同的方式描述目标类的连接点,根据描述对象的不同,大致分为4类:
- 方法切点函数:通过描述目标类方法的信息定义连接点
- 方法入参切点函数:通过描述目标类方法入参的信息定义连接点
- 目标类切点函数: 通过描述目标类类型的信息定义连接点
- 代理类切点函数:通过描述目标类的代理类的信息定义连接点
详情请参考博客
在函数入参中使用通配符
@Aspect支持3种通配符
:匹配任意字符,但它只能匹配上下文中的一个元素
..:匹配任意字符,可以匹配上下文中的多个元素,但在表示类时,必须和联合使用,而在入参时则单独使用。
+:表示按类型匹配指定类的所有类,必须跟在类名后面,如com.smart.Car+。继承或扩展指定类的所有类,同时还包括指定类本身。
[详情参考博客](https://blog.csdn.net/gexiaoyizhimei/article/details/99988144)
逻辑运算符
切点表达式由切点函数组成,切点函数之间还可以进行逻辑运算,组成复合切点。Spring支持以下切点运算符
- &&:与操作符,相当于切点的交集运算
- ||:或操作符:相当于切点的并集运算
- !:非操作符:相当于切点的反集元素那,not是等效的操作符
@Aspect中并不提供and or not,它们是Spring为了在xml配置文件中方便定义切点表达式而特意添加的等价运算符,和一般的语法表达式不一样,在Spring中使用and or not 时,允许不在前后添加空格。
不同增强类型
Spring本身使用接口描述各种增强类型,@AspectJ也为各种增强类型提供了不同的注解类,它们位于org.aspectj.lang.annotation.*包中。这些注解拥有若干个成员,可以通过成员完成定义切点信息,绑定连接参数等操作。注解期限都是RetentionPolicy.RUNTIME,标注目标都是ElementType.METHOD。
- @Before
前置增强,相当于BeforeAdvice.Before注解类有两个成员。
- value:该成员用于定义切点
- argNames:由于无法通过Java反射机制获取方法入参名,所以如果在java编译时为启用调式信息,或者需要在运行期解析切点,就必须通过这个成员指定注解所标注增强方法的参数名(注意二者名字必须完全相同),多个参数名用逗号隔开。
- @AfterReturning
后置增强, ==AfterReturningAdvice
- value
- pointcut
- returning
- agrNames
- @Around
环绕增强 == MethodInterceptor
- value
- argNames
- @AfterThrowing
抛出增强,相当于ThrowsAdvice
- value
- pointcut
- throwing
- argNames
- @After
Final增强,不管是抛出异常还是正常退出,该增强都会得到执行。该增强没有对应的增强接口,可以看成ThrowsAdvice和AfterReturningAdvice的混合物,一般用于释放资源,相当于try{}finally{}的控制流。
- value
- argNames
- DeclareParents
引介增强, == IntroductionInterceptor
- value
- defaultImpl:默认的接口实现类
引介增强用法
- 定义增强切面
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
@Aspect
public class EnableSellerAspect {
@DeclareParents(value = "com.ijianghu.advice.impl.NaiveWaiter",//指定要增强的目标类
defaultImpl = com.ijianghu.aspect.impl.SmartSeller.class) //指定目标增强实现类
public Seller seller; //要实现的目标接口
}
- 配置Spring
<?xml version="1.0" encoding="UTF-8"?>
<!--首先引入aop命名空间-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop">
<!--aspectj-autoproxy自动为Spring容器中那些匹配@aspectj切面的Bean创建代理,完成切面织入-->
<!--Spring在内部依旧使用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,
具体实现细节被aop:aspectj-autoproxy 隐藏起来-->
<aop:aspectj-autoproxy/>
<bean id="waiter" class="com.ijianghu.advice.impl.NaiveWaiter"/>
<bean class="com.ijianghu.aspect.declare.EnableSellerAspect"/>
</beans>
- 测试
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class DeclareAspectJProxyTest {
public static void main(String[] args) {
String configPath = "com\\ijianghu\\aspect\\config\\applicationContext-schema-declare.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath);
Waiter waiter = (Waiter) ctx.getBean("waiter");
waiter.greetTo("欢欢");
Seller seller = (Seller)waiter;
seller.sell("beer");
}
}
切点函数详解
切点函数是AspectJ表达式语言的核心,也是使用@AspectJ进行切面定义的难点。