Spring AOP:基于AspectJ的切面声明方式

文章目录

        • 1.什么是AspectJ?
        • 2.基于@AspectJ配置切面:
        • 3.AspectJ常用注解:
          • 3.1切点表达式函数
        • 总结:
        • 代码链接

1.什么是AspectJ?

 在之前的文章我们提到AOP面向切面编程是一种编程思想,是对OOP面向对象编程的一种补充。对于AOP这种编程思想,很多框架都进行了实现比如Spring.

 AspectJ是eclipse基金会的一个项目,是最早、功能比较强大的 AOP 实现之一。对整套 AOP 机制都有较好的实现,很多其他语言的 AOP 实现,也借鉴或采纳了 AspectJ 中很多设计。在 Java 领域,AspectJ 中的很多语法结构基本上已成为 AOP 领域的标准。而且还支持注解式开发。

 所以,Spring又将AspectJ的对于AOP的实现也引入到了自己的框架中。所以本文讨论的就是如何在Spring项目中使用AspectJ来实现切面编程。本质上是使用了AspectJ的注解类库,和解析类库,并通过JDK动态代理或者CGLib代理生成代理类,最终实现切面编程。

2.基于@AspectJ配置切面:

需要说明的是,上篇文章我们通过Pointcut和Advise接口描述切点和增强,并用Advisor整合两者描述切面,@AspectJ则采用注解描述,本质是一样的。

  • 第一步:引入相关依赖。这里主要是aspectj相关的依赖
 
        <dependency>
            <groupId>org.aspectjgroupId>
            <artifactId>aspectjrtartifactId>
            <version>${aspectjweaver.version}version>
        dependency>
        <dependency>
            <groupId>cglibgroupId>
            <artifactId>cglibartifactId>
            <version>2.2version>
        dependency>
        <dependency>
            <groupId>org.aspectjgroupId>
            <artifactId>aspectjtoolsartifactId>
            <version>1.6.9version>
        dependency>
  • 第二步:编写切面
/**
 * 通过AspectJ注解,声明切面,描述切点。
 */
@Aspect
public class PregreetingAspect {

    @Before("execution(* greetTo(..))")
    public void beforeGreeting(){
        System.out.println("How are you !");
    }
}

通过@Aspect注解就可以声明一个切面,通过@Before关键字声明前置增强逻辑。

execution是相应切点的匹配逻辑。我们可以发现,这个切面没有实现任何接口,是一个普通的POJO。仅仅通过简单的注解就可以将增强逻辑、切点和增强类型整合到一个类中。而在上篇文章中,我们需要做很多工作才能达到相同的目的。

  • 第三步:使用切面(通过编程方式完成切面织入工作)
public class AspectJBeforeTest {

    AspectJProxyFactory aspectJProxyFactory =null;
    @BeforeClass
    public void init() {
        Waiter target = new NaiveWaiter();
        aspectJProxyFactory =new AspectJProxyFactory();
        aspectJProxyFactory.setTarget(target);
        aspectJProxyFactory.addAspect(PregreetingAspect.class);
    }

    @Test
    public void aspectTest(){
        Waiter proxy = aspectJProxyFactory.getProxy();
        proxy.greetTo("John");
        proxy.serveTo("John");
    }

}

打印结果:增强已经生效

How are you !
NaiveWaiter:greet to John...
NaiveWaiter:serving John...

使用配置方式实现

上面是通过编程的方式生成代理类,如果使用配置完成,需要编写一个配置文件:


<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.1.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">
    <aop:aspectj-autoproxy/>
    <bean id="waiter" class="com.smart.NaiveWaiter"/>
    <bean class="com.smart.aspectj.example.PregreetingAspect"/>
beans>

这里引入了aop的命名空间,只需要配置aspectj-autoproxy标签就可以完成自动代理生成。当然,Spring在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被隐藏起来了。关于自动代理,可以参考上一篇文章《SpringAOP:自动创建代理》。

最后使用:

public class AspectJConfiguerBeforeTest {

    String configurePath= "";
    ApplicationContext ctx = null;
    @BeforeClass
    public void init() {
        configurePath = "com/smart/aspectj/beans.xml";
        ctx = new ClassPathXmlApplicationContext(configurePath);
    }

    @Test
    public void aspectTest(){
        Waiter proxy = (Waiter) ctx.getBean("waiter");
        proxy.greetTo("John");
        proxy.serveTo("John");
    }

}

效果和上面一致。

3.AspectJ常用注解:

 上面我们通过一个例子演示了如何通过@Aspect注解实现一个切面。那这些注解到底代表什么意思?有哪些需要掌握的常用注解?

@Aspect 注解 作用是告诉Spring容器,标注该注解的类是一个切面。

@Before(“execution(* greetTo(…))”) 表示这个方法是一个前置增强,其中execution用于声明切点表达式,* greetTo(…) 表示匹配目标了类的greetTo()方法,greetTo()方法可以带任意的入参和任意的返回值。

由于Spring只支持方法的连接点,所以Spring仅支持部分AspectJ的语言。上面提到execution关键字用来定义切点表达式。Spring中支持哪些表达式?

3.1切点表达式函数

Spring中支持9个@ApectJ切点表达式函数。

Spring AOP:基于AspectJ的切面声明方式_第1张图片2019.8.19补充:Spring中新增了一种切点表达式
10)bean:通过受管Bean的名字来限定连接点所在的Bean。该关键词是Spring2.5新增的。
@Pointcut(“bean(person)”)
public void before(){}
如果使用@Bean注解,在没有指定name的情况下,那么这个bean的name是方法的名称

下面分别介绍各个表达式的用法:

execution 常用示例:

举几个例子:

execution(public * *(..))           指定切入点为:任意公共方法

execution(* *.service.*.*(..))    指定只有一级包下的service子包下所有类(接口)中所有方法为切入点

execution(* *..service.*.*(..))   指定所有包下的service子包下所有类(接口)中所有方法为切入点

execution(* *.SomeService.*(..))    指定只有一级包下的SomeService类(接口)中所有方法为切入点

execution(* *..SomeService.*(..))    指定所有包下的SomeService类(接口)中所有方法为切入点

@annotation示例:

@Aspect
public class AnnotationAspect {

    @Before("@annotation(com.smart.anno.NeedTest)")
    public void beforeGreeting(){
        System.out.println("How are you !");
    }
}

一般可以自定义一个注解,然后通过该注解标识哪些方法需要织入增强。注意注解要加载实现类上。

自定义注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface NeedTest {
	boolean value() default false;
}

用注解标注目标类:

public class NaiveSeller implements Seller {
    @Override
    public int sell(String goods, String clientName) {
        return 12;
    }

    @Override
    public void greetTo(String clientName) {
        System.out.println("seller greetTo"+clientName);
    }

    @NeedTest
    @Override
    public void annotationTo() {
        System.out.println("seller annotation greetTo!");
    }
}

详细信息可以下载我的示例代码Github

args和@args()

args: 例如 ,如下注解表明,目标类参数为String类型的方法,在调用时,会织入增强。

    @Before("args(String)")
    public void beforeGreeting(){
        System.out.println("How are you String!");
    }

@args: 当切面的方法注解为下面这样时,表明目标对象的方法如果参数所对应的对象被Monitorable注解标注,则增强会生效。

切面的关键配置:

    @Before("@args(com.smart.anno.Monitorable)")
    public void afterGreeting(){
        System.out.println("参数的类有注解Monitorable!");
    }

自定义注解:Monitorable

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Monitorable {

}

目标对象关键方法:

@Override
    public void args(Waiter waiter) {
        System.out.println("args hello");
}

参数对象:

@Monitorable
public interface Waiter {
    public void greetTo(String clientName);

    public void serveTo(String clientName);

    public void smile(String clientName, int times);

}

within() 和@within: 详细示例见文末码云。

@Aspect
public class WithInAspect {

    @Before("within(com.smart.aspectj.within.*)")
    public void beforeGreeting(){
        System.out.println("WithIn say how are you!");
    }

    @After("@within(com.smart.anno.Monitorable)")
    public void afterGreeting(){
        System.out.println("参数的类有注解Monitorable!");
    }
}

target 和 @target 这两个和上面的within和@within原理类似,不同之处在于,within描述的是某个包路径下,而target描述的是具体的类。

总结:

 关于@AspectJ在Spring中的使用,非常简单,只需要用@Aspect标注一个POJO就可以声明一个切面。重点是关于切点的描述,这里涉及到切点表达式函数,在Spring中支持9种,不同的表达式有不同的匹配策略,大体可以分为四类:

方法切点函数(execution,@annotation)

方法入参切点函数(args,@args)

目标类切点函数(within,@within, target,@target)

代理类切点函数:(this)

 不同的切点函数,提供了丰富的匹配规则,具体要用到哪种,需要根据业务来确定。

关于各种切点函数的示例,详见文末的码云链接。

代码链接

码云

你可能感兴趣的:(springboot,spring)