基于@AspectJ和Schema的AOP

Spring对AOP的支持

Spring在新版本中对AOP功能进行了重要的增强:

  1. 新增了基于Schema的配置支持,为AOP专门提供了aop命名空间。
  2. 新增了对AspectJ切点表达式语言的支持。@AspectJ是AspectJ1.5新增的功能,它通过Java5.0的注解技术,允许开发者在POJO中定义切面。Spring使用和@AspectJ相同风格非注解,并通过AspectJ提供的注解库和解析库处理切点。由于Spring只支持方法级的切点,所以仅对@AspectJ提供了有限的支持。
  3. 可以无缝地集成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类:

  1. 方法切点函数:通过描述目标类方法的信息定义连接点
  2. 方法入参切点函数:通过描述目标类方法入参的信息定义连接点
  3. 目标类切点函数: 通过描述目标类类型的信息定义连接点
  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。

  1. @Before
    前置增强,相当于BeforeAdvice.Before注解类有两个成员。
  • value:该成员用于定义切点
  • argNames:由于无法通过Java反射机制获取方法入参名,所以如果在java编译时为启用调式信息,或者需要在运行期解析切点,就必须通过这个成员指定注解所标注增强方法的参数名(注意二者名字必须完全相同),多个参数名用逗号隔开。
  1. @AfterReturning
    后置增强, ==AfterReturningAdvice
  • value
  • pointcut
  • returning
  • agrNames
  1. @Around
    环绕增强 == MethodInterceptor
  • value
  • argNames
  1. @AfterThrowing
    抛出增强,相当于ThrowsAdvice
  • value
  • pointcut
  • throwing
  • argNames
  1. @After
    Final增强,不管是抛出异常还是正常退出,该增强都会得到执行。该增强没有对应的增强接口,可以看成ThrowsAdvice和AfterReturningAdvice的混合物,一般用于释放资源,相当于try{}finally{}的控制流。
  • value
  • argNames
  1. 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进行切面定义的难点。

基于@AspectJ和Schema的AOP

上一篇:Java中的StringBuffer


下一篇:历届试题 大臣的旅费-(树的直径+dfs)