Spring - AspectJ基础

AOP的基本组成主要有以下几个

基本术语介绍
切面(Aspect)切面由切入点和通知组成,说白了就是要给哪些方法代理(切入点) 以及代理它需要干什么(通知)
目标对象(Target)目标对象指将要被增强的对象,即包含主业务逻辑的类对象。或者说是被一个或者多个切面所通知的对象。
连接点(JoinPoint)程序执行过程中明确的点,如方法的调用或特定的异常被抛出。连接点由两个信息确定:方法(表示程序执行点,即在哪个目标方法)和相对点(表示方位,即目标方法的什么位置,比如调用前,后等)
切入点(PointCut)切入点是对连接点进行拦截的条件定义。切入点表达式如何和连接点匹配是AOP的核心,Spring缺省使用AspectJ切入点语法。 一般认为,所有的方法都可以认为是连接点,但是我们并不希望在所有的方法上都添加通知,而切入点的作用就是提供一组规则(使用 AspectJ pointcut expression language 来描述) 来匹配连接点,给满足规则的连接点添加通知。
通知(Advice)通知是指拦截到连接点之后要执行的代码,包括了“around”、“before”和“after”等不同类型的通知。Spring AOP框架以拦截器来实现通知模型,并维护一个以连接点为中心的拦截器链。
织入(Weaving)织入是将切面和业务逻辑对象连接起来, 并创建通知代理的过程。织入可以在编译时,类加载时和运行时完成。在编译时进行织入就是静态代理,而在运行时进行织入则是动态代理。
增强器(Advisor)Advisor是切面的另外一种实现,能够将通知以更为复杂的方式织入到目标对象中,是将通知包装为更复杂切面的装配器。Advisor由切入点和Advice组成。 Advisor这个概念来自于Spring对AOP的支撑,在AspectJ中是没有等价的概念的。Advisor就像是一个小的自包含的切面,这个切面只有一个通知。切面自身通过一个Bean表示,并且必须实现一个默认接口。

对于程序员来说,需要配置的只有

  • PointCut 过滤出符合要求的方法的条件
  • Advice 对于符合条件的方法,要在什么阶段做什么

AspectJ在Spring中的基本使用

如何开启对AspectJ的支持

首先,AspectJ框架并不是Spring开发的,而是Spring的开发人员整合进来的,所以如果要使用AspectJ相关的功能,当然不能直接使用啦,而要先将他“使能”。
“使能”的方式在官方手册中看到的有两种,一种是通过注解,一种是通过xml配置。
要用Java @Configuration 启用 @AspectJ 支持,请添加 @EnableAspectJAutoProxy
注解,如下例所示。

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}

如果要通过xml的方式开启对AspectJ的支持,则请使用 aop:aspectj-autoproxy
元素,如下例所示。

<aop:aspectj-autoproxy/>

在启用了AspectJ之后,Spring就会对具有Aspect注解的Component特别对待了。
Spring手册的原话:

启用 @AspectJ 支持后,任何在你的 application context 中定义的bean,其类是 @AspectJ
切面(有 @Aspect 注解),会被Spring自动检测到,并用于配置Spring
AOP。

这里的Component是指任何在Spring容器中的Bean,不局限于通过注解产生的Bean。
比如以下两个方式产生的Bean都会被Spring发现

<bean id="myAspect" class="com.xyz.NotVeryUsefulAspect">
  <!-- configure properties of the aspect here -->
</bean>
@Aspect
@Component
public class LogAspect {
}

手册中的写法如下,并没有添加Component注解,但我在测试中不加Component注解并不能实现对方法的拦截

package com.xyz;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class NotVeryUsefulAspect {
}

在查阅后面的手册内容时发现Spring已经解答了我的疑惑

通过组件扫描自动检测切面
你可以通过 @Configuration 类中的 @Bean
方法,将aspect类注册为Spring
XML配置中的普通Bean,或者让Spring通过classpath扫描来自动检测它
们—与任何其他Spring管理的Bean一样。然而,请注意,@Aspect
注解并不足以实现classpath中的自动检测。为此,你需要添加一个单独的
@Component
注解(或者,根据Spring组件扫描器的规则,添加一个符合条件的自定义
stereotype 注解)。

什么意思呢,就是如果我们只写一个AspectJ注解,他是不会被Spring管理的,必须要再某种方式让他被Spring发现。

如何在Aspect中实现对方法的拦截

在一个Aspect类中,可以存在很多PointCut(切入点),Advice(通知)。
所以说,也可以将每个Aspect看作一个容器,但Spring的容器存放的是Bean而Aspect存放的PointCut和Advice的配置。
所以说要实现一个HelloWorld级别的AOP就很简单了,先创建一个需要被切入的类

    public void doIt(String a,int as){
        System.out.println("doIt:");
    }

当然这个类的实例对象需要以Bean的形式存放在Spring的容器中,要不然Spring就不可能实现代理了,这里采用Component注解将其注入Spring中。
然后,完善Aspect的配置(我要在哪些方法的什么阶段做什么?)

package com.dronff.aspect;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

@Aspect
@Configuration
public class LogAspect {
    @Before("execution(* com.dronff.TestComponent.*(String,int))")

    public void a(){
        System.out.println("before");
    }
}


这里截取其他博主的归纳,再来看这个代码
在这里插入图片描述
所以,我们上面的示例代码切入的方法就是,com.dronff包下的TestComponent类的所有第一个参数类型为String且第二个参数类型为int的方法,并且要在运行这个方法之前执行(因为用的是Before注解)

对于这个例子,我们还可以将PointCut抽离出来

@Aspect
@Configuration
public class LogAspect {
    @Pointcut("execution(* com.dronff.TestComponent.*(String,int))")
    public void myPointcut(){

    }
    @Before("myPointcut()")
    public void a(){
        System.out.println("before");
    }
}

执行效果最终是一致的。
Spring AOP还支持一个额外的名为 bean
的PCD。这个PCD可以让你把连接点的匹配限制在一个特定的命名的Spring
Bean或命名的Spring Bean的集合(当使用通配符时)。bean PCD有以下形式。

bean(idOrNameOfBean)

idOrNameOfBean 标记可以是任何Spring Bean的名称。我们提供了使用 *
字符的有限通配符支持,因此,如果你为你的Spring Bean建立了一些命名惯例,你可以写一个
bean PCD表达式来选择它们。就像其他的pointcut 指定器一样,Bean PCD也可以和
&&(和)、||(或)、以及 ! (否定) 操作符一起使用。

Spring AOP支持以下AspectJ的切点指定器(PCD),用于切点表达式中。
• execution: 用于匹配方法执行的连接点。这是在使用Spring
AOP时要使用的主要切点指定器。
• within: 将匹配限制在某些类型内的连接点(使用Spring
AOP时,执行在匹配类型内声明的方法)。
• this: 将匹配限制在连接点(使用Spring AOP时方法的执行),其中bean引用(Spring
AOP代理)是给定类型的实例。
• target: 将匹配限制在连接点(使用Spring
AOP时方法的执行),其中目标对象(被代理的应用程序对象)是给定类型的实例。
• args: 将匹配限制在连接点(使用Spring
AOP时方法的执行),其中参数是给定类型的实例。
• @target: 限制匹配到连接点(使用Spring
AOP时方法的执行),其中执行对象的类有一个给定类型的注解。
• @args: 将匹配限制在连接点(使用Spring
AOP时方法的执行),其中实际传递的参数的运行时类型有给定类型的注解。
• @within: 将匹配限制在具有给定注解的类型中的连接点(使用Spring
AOP时,执行在具有给定注解的类型中声明的方法)。
• @annotation: 将匹配限制在连接点的主体(Spring
AOP中正在运行的方法)具有给定注解的连接点上。

你可以通过引用 @Aspect 类的全称名称和 @Pointcut
方法的名称,在任何你需要切面表达式的地方引用在这样的切面定义。例如,为了使服务层成为
事务性的,你可以写下面的内容,com.dronff.aspect.LogAspect.myPointcut()
命名的pointcut。

还可以通过xml来配置你的Aspect

<aop:config>
  <aop:advisor
  pointcut="com.xyz.CommonPointcuts.businessService()"
  advice-ref="tx-advice"/>
</aop:config>
<tx:advice id="tx-advice">
  <tx:attributes>
  <tx:method name="*" propagation="REQUIRED"/>
  </tx:attributes>
</tx:advice>

但这种方式并不常用,所以不做赘述

接下来尝试实现一个MyTransactional注解作为事务注解

package com.dronff;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTransactional {
}

@Aspect
@Configuration
public class LogAspect {
    @Pointcut("@annotation(com.dronff.MyTransactional)")
    public boolean myPointcut(){
        return true;
    }
    @Before("com.dronff.aspect.LogAspect.myPointcut()")
    public void a(){
        System.out.println("before");
    }
}

这里采用了@annotation指示器, 表示任何连接点(仅在Spring AOP中的method execution),其中执行的方法有一个@MyTransactional 注解。

同时还可以在Advice中传递参数,捕获异常等操作,可以在Spring手册350页的位置找到相关描述,Spring中文手册百度网盘链接:

链接:https://pan.baidu.com/s/1ItDsUuD-HW4gUvyVGSrA7w
提取码:85kx

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值