AspectJ详解
AspectJ是一个面向切面的框架
,它扩展了Java语言。AspectJ定义了AOP语法
,所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。
Spring2.0之后增加了对AspectJ切点表达式的支持。@AspectJ
是AspectJ1.5新增的功能,通过JDK的注解技术,允许开发者在Bean上直接通过注解定义切面。Spring使用和@AspectJ相同风格的注解,并通过AspectJ提供的注解库和解析库来处理切点。
1、AspectJ切点表达式
@AspectJ支持三种通配符
*匹配任意字符,只匹配一个元素
..匹配任意字符,可以匹配多个元素 ,在表示类时,必须和*联合使用
+表示按照类型匹配指定类的所有类,必须跟在类名后面,如com.cad.Car+,表示继承该类的所有子类包括本身
逻辑运算符
切点表达式由切点函数组成,切点函数之间还可以进行逻辑运算,组成复合切点。
&& 与操作符:相当于切点的交集运算。xml配置文件中使用切点表达式,&是特殊字符,所以需要转义字符&;来表示。
|| 或操作符:相当于切点的并集运算。
!非操作符:相当于切点的反集运算。
表达式函数
Spring支持9个@AspectJ切点表达式函数,它们用不同的方式描述目标类的连接点。我们来了解几个常用的:
- execution()
execution()是最常用的切点函数,用来匹配方法,语法如下
execution(<修饰符><返回类型><包.类.方法(参数)><异常>)
修饰符和异常可以省略。
execution(public * *(..)):匹配目标类的所有public方法,第一个*代表返回类型,第二个*代表方法名,..代表方法的参数。
execution(**User(..)):匹配目标类所有以User为后缀的方法。第一个*代表返回类型,*User代表以User为后缀的方法
execution(* com.cad.demo.User.*(..)):匹配User类里的所有方法
execution(* com.cad.demo.User+.*(..)):匹配该类的子类包括该类的所有方法
execution(* com.cad.*.*(..)):匹配com.cad包下的所有类的所有方法
execution(* com.cad..*.*(..)):匹配com.cad包下、子孙包下所有类的所有方法
execution(* addUser(Spring,int)):匹配addUser方法,且第一个参数类型是String,第二个是int
- args()
该函数接受一个类名,表示目标类方法参数是指定类时(包含子类),则匹配切点。
args(com.cad.User):匹配addUser(User user)方法等
- within()
匹配类,语法:within(<类>)
within(com.cad.User):匹配User类下的所有方法
- target()
target()函数通过判断目标类是否按类型匹配指定类决定连接点是否匹配。
target(com.cad.User):如果目标类类型是User没那么目标类所有方法都匹配切点。
- this()
this()函数判断代理对象的类是否按类型匹配指定类。
2、AspectJ增强类型
Before:前置增强。相当于BeforeAdvice的功能,方法执行前执行。
AfterReturning:后置增强。相当于AfterReturningAdvice,方法执行后执行。
Around:环绕增强。
AfterThrowing:异常抛出增强。
After:不管是抛出异常还是正常退出,该增强都会执行,类似于finally块。
DeclareParents:引介增强。
3、AspectJ基于XML配置切面
使用AspectJ我们需要的jar包有aopalliance-1.0.jar
,aspectjweaver-1.8.10.jar
,spring-aop-4.3.8.RELEASE.jar
,spring-aspects-4.3.8.RELEASE.jar
。
我们先来一个简单的配置,看看怎么使用:
//我们先创建Dog类
public class Dog {
public void eat(){
System.out.println("狗在吃饭");
}
public void run(){
System.out.println("狗在跑步");
}
}
//我们创建一个增强类
public class AdviceMethod {
public void before(){
System.out.println("主人发出命令");
}
}
<!--配置文件-->
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" //声明aop命名空间
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop //引入aop xsd文件http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-实例Dog类和增强类-->
<bean id="dog" class="com.cad.aspectj.Dog"></bean>
<bean id="advices" class="com.cad.aspectj.AdviceMethod"></bean>
<!-配置aop,proxy-target-class属性设定为true时,使用CGLib,为false时,使用JDK动态代理-->
<aop:config proxy-target-class="true">
<!--使用<aop:aspect>标签定义切面,ref引入增强-->
<aop:aspect ref="advices">
<!--通过<aop:before>声明一个前置增强,pointcut属性使用切点表达式,method指定使用增强类中方法-->
<aop:before pointcut="execution(* *(..))" method="before" />
</aop:aspect>
</aop:config>
</beans>
//我们测试一下
public class TestDemo {
@Test
public void test(){
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
Dog dog=(Dog)ac.getBean("dog");
dog.eat();
dog.run();
}
}
运行结果:
配置切点
我们上面的代码中,直接在< aop: before>前置增强标签
里使用了表达式来声明切点。
我们还可以在外面配置一个切点,使用的时候直接引用即可。
<aop:config proxy-target-class="true">
<aop:aspect ref="advices">
//定义一个切点
<aop:pointcut expression="execution(* *(..))" id="mypointcut"/>
//直接通过id引用即可
<aop:before pointcut-ref="mypointcut" method="before" />
</aop:aspect>
</aop:config>
<aop:pointcut>元素如果位于 <aop:aspect>元素之中,则只能被当前<aop:aspect>中的元素访问到。为了能被整个 <aop:config>元素中定义的所有切面访问到,必须在<aop:config>下定义。
后置增强
//我们在我们的增强类里添加一个后置增强方法after
public class AdviceMethod {
public void before(){
System.out.println("主人发出命令");
}
public void after(){
System.out.println("主任给予奖励");
}
}
<bean id="dog" class="com.cad.aspectj.Dog"></bean>
<bean id="advices" class="com.cad.aspectj.AdviceMethod"></bean>
<aop:config proxy-target-class="true">
<aop:aspect ref="advices">
<aop:pointcut expression="execution(* *(..))" id="mypointcut"/>
<aop:before pointcut-ref="mypointcut" method="before" />
//配置后置增强
<aop:after-returning pointcut-ref="mypointcut" method="after"/>
</aop:aspect>
</aop:config>
< aop:after-returning >后置增强有一个returning属性
,该属性对应后置方法里的参数值,并且必须与方法里的参数值名称相同,该参数用来接收目标方法执行后的返回值。
//我们run()方法返回一个String字符串
public class Dog {
public void eat(){
System.out.println("狗在吃饭");
}
public String run(){
System.out.println("狗在跑步");
return "跑完了";
}
}
//增强类的后置方法接收目标方法返回的参数
public class AdviceMethod {
public void before(){
System.out.println("主人发出命令");
}
public void after(String arg){
System.out.println("主任给予奖励");
System.out.println(arg);
}
}
//配置文件中配置,别的和前面的没区别
<aop:after-returning pointcut-ref="mypointcut" method="after" returning="arg"/>
运行结果:
环绕增强
//定义环绕增强方法,参数为ProceedingJoinPoint,返回值为Object,这是连接点信息,后面会详解
public class AdviceMethod {
public Object around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("环绕增强前");
Object obj=pjp.proceed();
System.out.println("环绕增强后");
return obj;
}
}
<!--配置环绕增强-->
<aop:config proxy-target-class="true">
<aop:aspect ref="advices">
<aop:pointcut expression="execution(* *(..))" id="mypointcut"/>
<aop:around method="around" pointcut-ref="mypointcut"/>
</aop:aspect>
</aop:config>
//我们测试一下
public class TestDemo {
@Test
public void test(){
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
Dog dog=(Dog)ac.getBean("dog");
dog.eat();
}
}
运行结果:
其他三个配置起来都大同小异,这里就不再一一演示。
4、增强方法访问连接点信息
AspectJ使用JoinPoint接口表示目标类的连接点对象,如果是环绕增强访问的话,则必须使用ProceedingJoinPoint
表示连接点对象,该类是JoinPoint子接口
。
JoinPoint接口主要方法
Object [] getArgs():获取连接点方法的参数
Signature getSignatrue():获取连接点的方法签名对象,方法签名由方法名称和形参列表组成。
Object getTarget():获取连接点所在的目标对象
Object getThis():获取代理对象本身
ProceedingJoinPoint主要方法
Object proceed()throws Throwable:通过反射执行目标对象的连接点方法
Object proceed(Object[] args)throws Throwable:通过反射执行目标对象的连接点方法,使用我们提供的参数。
这样,我们就可以在我们的增强方法中使用JoinPoint或者ProceedingJoinPoint参数,来获得连接点方法的一些信息。
5、AspectJ基于注解配置切面
//先使用注解来实例我们的Bean
@Component("dog")
public class Dog {
public void eat(){
System.out.println("狗在吃饭");
}
public void run(){
System.out.println("狗在跑步");
}
}
//实例增强类
@Component("advices")
//使用Aspect
@Aspect
public class AdviceMethod {
//使用环绕增强,里面参数是切点表达式
@Around("execution(* com.cad.anno.*.*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("环绕增强前");
Object obj=pjp.proceed();
System.out.println("环绕增强后");
return obj;
}
}
<?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: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.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
//使用组件扫描
<context:component-scan base-package="com.cad.anno"></context:component-scan>
//使用aspectj自动代理,自动为匹配@AspectJ切面的Bean创建代理
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
//我们测试一下
public class TestDemo {
@Test
public void test(){
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
Dog dog=(Dog)ac.getBean("dog");
dog.eat();
}
}
运行结果:
增强类中的方法可以使用不同的增强类型来定义。
- @Before:前置增强
- @AfterReturning:后置增强
- @Around:环绕增强
- @AfterThrowing:抛出异常增强
- @After:Final增强