程序小屋

记录生活中的点滴,分享、学习、创新

文章内容 1623902341

Spring AOP @AspectJ 入门基础

1、一个简单的例子

Waiter接口:

package com.yyq.annotation;
public interface Waiter {
    void greetTo(String name);
    void serveTo(String name);
}

NaiveWaiter业务类:

复制代码

package com.yyq.annotation;
public class NaiveWaiter implements Waiter {
    @Override
    public void greetTo(String name) {
        System.out.println("NaiveWaiter:greet to " + name + "...");
    }
    @Override
    public void serveTo(String name) {
        System.out.println("NaiveWaiter:serving to " + name + "...");
    }
}

复制代码

PreGreetingAspect切面实现类:

复制代码

package com.yyq.annotation;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
//通过该注解将PreGreetingAspect标识为一个切面
@Aspect
public class PreGreetingAspect {
    //定义切点和增强类型
    @Before("execution(* greetTo(..))")
    //增强的横切逻辑
    public void beforeGreeting(){
        System.out.println("How are you");
    }
}

复制代码

测试方法:

复制代码

@Test
    public void aspectJProxyTest(){
        Waiter target = new NaiveWaiter();
        AspectJProxyFactory factory = new AspectJProxyFactory();
        //设置目标对象
        factory.setTarget(target);
        //添加切面对象
        factory.addAspect(PreGreetingAspect.class);
        //生成织入切面的代理对象
        Waiter proxy = factory.getProxy();
        proxy.greetTo("Anny");
        proxy.serveTo("Mei");
    }

复制代码

输出结果:

How are you

NaiveWaiter:greet to Anny...

NaiveWaiter:serving to Mei...

 

通过Spring配置使用@AspectJ切面:复制代码

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    <!--目标Bean-->
    <bean id="waiter" class="com.yyq.annotation.NaiveWaiter"/>
    <!--使用了@AspectJ注解的切面类-->
    <bean class="com.yyq.annotation.PreGreetingAspect"/>
    <!--自动代理创建器,自动将@AspectJ注解切面类织入到目标Bean中-->
    <bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"/>
</beans>

复制代码

测试方法:

复制代码

@Test
    public void aspectJProxyTest2(){
        String configPath = "com\\yyq\\annotation\\beans.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath);
        Waiter waiter = (Waiter)ctx.getBean("waiter");
        waiter.greetTo("John");
    }

复制代码输出结果:

How are you

NaiveWaiter:greet to John...

 

使用基于Schema的AOP命名空间配置:复制代码

<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
    <aop:aspectj-autoproxy/>
    <bean id="waiter" class="com.yyq.annotation.NaiveWaiter"/>
    <bean class="com.yyq.annotation.PreGreetingAspect"/>
</beans>

复制代码

 测试方法:

复制代码

  @Test
    public void aspectJProxyTest3(){
        String configPath = "com\\yyq\\annotation\\beans2.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath);
        Waiter waiter = (Waiter)ctx.getBean("waiter");
        waiter.greetTo("Herry");
    }

复制代码输出结果:

How are you

NaiveWaiter:greet to Herry...

 

2、@AspectJ基础    1)切点表达式函数

    AspectJ 5的切点表达式由关键字和操作参数组成,如execution(* greetTo(..))的切点表达式,“execute”为关键字,而“* greetTo(..)”为操作参数。在这里,execute代表目标类执行某一方法,而“* greetTo(..)”是描述目标方法的匹配模式串,两者联合起来所表示的切点匹配目标类greetTo()方法的连接点。为了描述方便,我们将 execution()称作函数,而将匹配串“* greetTo(..)”称作函数的入参。 

    Spring支持9个@ApsectJ切点表达式函数,它们用不同的方式描述目标类的连接点,根据描述对象的不同,可以将它们大致分为4种类型:         · 方法切点函数:通过描述目标类方法信息定义连接点;         · 方法入参切点函数:通过描述目标类方法入参的信息定义连接点;         · 目标类切点函数:通过描述目标类类型信息定义连接点;         · 代理类切点函数:通过描述目标类的代理类的信息定义连接点; 

类别函数入参说明
方法切点函数execution()方法匹配模式串表示满足某一匹配模式的所有目标类方法连接点。如execution(* greetTo(..))表示所有目标类中的greetTo()方法。
@annotation()方法注解类名表示标注了特定注解的目标方法连接点。如@annotation(com.baobaotao.anno.NeedTest)表示任何标注了@NeedTest注解的目标类方法。
方法入参切点函数args()类名通过判别目标类方法运行时入参对象的类型定义指定连接点。如args(com.baobaotao.Waiter)表示所有有且仅有一个按类型匹配于Waiter的入参的方法。
@args()类型注解类名通过判别目标方法的运行时入参对象的类是否标注特定注解来指定连接点。如@args(com.baobaotao.Monitorable)表示任何这样的一个目标方法:它有一个入参且入参对象的类标注@Monitorable注解。
目标类切点函数within()类名匹配串表示特定域下的所有连接点。如within(com.baobaotao.service.*)表示 com.baobaotao.service包中的所有连接点,也即包中所有类的所有方法,而 within(com.baobaotao.service.*Service)表示在com.baobaotao.service包中,所有以 Service结尾的类的所有连接点。
target()类名假如目标类按类型匹配于指定类,则目标类的所有连接点匹配这个切点。如通过target(com.baobaotao.Waiter)定义的切点,Waiter、以及Waiter实现类NaiveWaiter中所有连接点都匹配该切点。
@within()类型注解类名假如目标类按类型匹配于某个类A,且类A标注了特定注解,则目标类的所有连接点匹配这个切点。如@within(com.baobaotao.Monitorable)定义的切点,假如Waiter类标注了@Monitorable注解,则Waiter以及Waiter实现类NaiveWaiter类的所有连接点都匹配。
@target()类型注解类名目标类标注了特定注解,则目标类所有连接点匹配该切点。如@target(com.baobaotao.Monitorable),假如NaiveWaiter标注了@Monitorable,则NaiveWaiter所有连接点匹配切点。
代理类切点函数this()类名代理类按类型匹配于指定类,则被代理的目标类所有连接点匹配切点。

 

    2)在函数入参中使用通配符

    @AspectJ支持3种通配符:


  •  * 匹配任意字符,但它只能匹配上下文中的一个元素;   
  •  .. 匹配任意字符,可以匹配上下文中的多个元素,但在表示时,必须和 * 联合使用,而在表示入参时则单独使用;
  •  + 表示按类型匹配指定类的所有类,必须跟在类名后面,如com.yyq.Car+。继承或扩展指定类的所有类,同时还包括指定类本身。

    @AspectJ函数按其是否支持通配符及支持的程度,可以分为以下3类:

  • 支持所有通配符:execution()、within(),如within(com.yyq.*)、within(com.yyq.service..*.*Service+)等;
  • 仅支持 + 通配符:args()、this()、target(),如args(com.yyq.Waiter+)、target(java.util.List+)等。虽然这3个函数可以支持+通配符,但其意义不大,因为对于这些函数来说使用和不使用+都是一样的,如target(com.yyq.Waiter+)和target(com.yyq.Waiter)是等价的。
  • 不支持通配符:@args()、@within()、@target()和@annotation(),如@args(com.yyq.anno.NeedTest)和@within(com.yyq.anno.NeedTest)。

    此外,args()、this()、target()、@args()、@within()、@target()和@annotation()这7个函数除了可以指定类名外,也可以指定变量名,并将目标对象中的变量绑定到增强的方法中。

 

    3)逻辑运算符

    切点表达式由切点函数组成,切点函数之间还可以进行逻辑运算,组成复合切点,Spring支持以下的切点运算符:


  • && 与操作符,相当于切点的交集运算,and是等效的操作符。如within(com.yyq..*) and args(String) 表示在com.yyq包下所有类(当前包以及子孙包)拥有一个String入参的方法;
  • ||  或操作符,相当于切点的并集运算,or是等效的操作符。如within(com.yyq..*) || args(String) 表示在com.yyq包下的所有类的方法,或者所有拥有一个String入参的方法;
  • ! 非操作符,相当于切点的反集运算,not是等效的操作符。如!within(com.yyq.*) 表示所有不在com.yyq包下的方法。

 

    4)不同增强类型

    @AspectJ为各种的增强类型提供了不同的注解类,它们位于org.aspectj.lang.annotation.*包中,这些注解类拥有若干个成员,可以通过这些成员完成定义切点信息、绑定连接点参数等操作;此外,这些注解的存留期限都是RetentionPolicy.RUNTIME,标注目标都是ElementType.METHOD。

  • @Before

        前置增强,相当于BeforeAdvice的功能,Before注解类拥有两个成员:

  • value:该成员用于定义切点;argNames:由于无法通过Java反射机制获取方法入参名,所有如果在Java编译时未启动调试信息或者需要在运行期解析切点,就必须通过这个成员指定注解所标注增强方法的参数名(注意两者名字必须完全相同),多个参数名用逗号分隔。
  • AfterReturning

        后置增强,相当于AfterReturningAdvice,AfterReturning注解类拥有4个成员:


  • value:该成员用于定义切点;pointcut:表示切点的信息,如果显式指定pointcut值,它将覆盖value的设置值,可以将pointcut成员看成是value的同义词;returning:将目标对象方法的返回值绑定给增强的方法;argNames:如前所述。
  • Around

        环绕增强,相当于MethodInterceptor

*