Spring-AOP

Spring-AOP

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。

作用:在不惊动原始代码设计的情况下对功能做增强(无侵入式)

AOP常用的术语

  • 切面(Aspect):那些重复的公共的功能称为切面(用来封装通知方法的类,日志,权限)切面由切点和增强组成,他既包含横切的定义,也包括了连接点的定义,SpringAOP就是负责实施切面的框架,他将切面定义为横切逻辑织入到切面所指定的连接点
  • 连接点(join Point):程序执行的某个特定位置(抽取横切关注点的位置),比如(类开始初始化前,初始化后,方法前,方法后,异常后) 一个类或者一段程序代码拥有一些具有边界的特定点,这些特定点称为连接点
  • 切入点(pointcut):每个程序都拥有多个连接点,比如一个类有2个方法,这2个方法就是连接点,连接点就是程序中具体的事物,AOP 通过切点来定位特定的连接点,连接点相当于数据库中的记录,而切点相当于是查询记录的条件。在程序中,切入点是连接点位置的集合
  • 代理(Proxy):Spring AOP 中有 JDK 动态代理和 CGLib 代理,目标对象实现了接口时采用 JDK 动态代理,反之采用 CGLib 代理。
  • 目标对象(Target):代理的目标对象(想要增强的对象),指一个或多个切面所通知的对象。
  • 织入(weaving):就是把增强添加到目标类具体的连接点上的过程
  • 通知(Adivice):
    • 指切面对于某个连接点所产生的动作,也就是目标方法执行前后要进行的方法,包括前置通知、后置通知、返回后通知、异常通知和环绕通知。

AOP理解

抽取

AOP的目标对象已经确定,因为使用动态代理的方式,代理对象自动创建

从目标对象中,把非核心业务代码进行抽取出来,此处“非核心业务代码”就是横切关注点

抽出来之后,要把“非核心业务代码”放在另外一个类中,该就叫做切面Aspect

在切面类中,如何封装横切关注点?每一个横切关注点都是一个方法,这个方法就叫做通知Adivice,因此切面是用来封装通知

织入

从哪里抽取出来的,就要在哪个位置进行织入,这个织入的地方叫做连接点

如何定位连接点?要用到切入点来进行定位

Spring-AOP代码实现

基于注解的AOP

导入依赖
<!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.1</version>
</dependency>
<!-- junit测试 -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

<!-- spring-aspects会帮我们传递过来aspectjweaver -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.1</version>
</dependency>
目标接口
public interface Calculator {
    int add(int i, int j);
    int sub(int i, int j);
    int mul(int i, int j);
    int div(int i, int j);
}
目标对象
@Component
public class CalculatorPureImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        int result = i + j;
        System.out.println("方法内部 result = " + result);
        return result;
    }

    @Override
    public int sub(int i, int j) {
        int result = i - j;
        System.out.println("方法内部 result = " + result);
        return result;
    }

    @Override
    public int mul(int i, int j) {
        int result = i * j;
        System.out.println("方法内部 result = " + result);
        return result;
    }

    @Override
    public int div(int i, int j) {
        int result = i / j;
        System.out.println("方法内部 result = " + result);
        return result;
    }
}
创建切面类并配置

将切面类和目标对象都交给Spring容器进行管理,配置注解扫描

    <context:component-scan base-package="com.zengqh.aop">
    </context:component-scan>
<!--    开启基于基于注解的AOP-->
    <aop:aspectj-autoproxy />

切面类通过注解@Aspect标识,表示这是一个切面类

@Component
@Aspect
public class LoggerAspect {
}
前置通知
@Component
@Aspect
public class LoggerAspect {

    /**
     *
     * @Before()前置通知, 在目标方法(切入点)执行之前执行。
     * value 属性绑定通知的切入点表达式,可以关联切入点声明,也可以直接设置切入点表达式
     * 注意:如果在此回调方法中抛出异常,则目标方法不会再执行,会继续执行后置通知 -> 异常通知。
     */
    @Before("execution(public int com.zengqh.aop.CalculatorPureImpl.add(int,int))")
    public void beforeAdviceMethod(){  
        System.out.println("LoggerAspect-->前置通知");
    }
}

测试

@Test
public void test001(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-config.xml");
    Calculator bean = ioc.getBean(Calculator.class);
    int add = bean.add(1, 1);
}

在这里插入图片描述

切入点表达式语法
②语法细节
用*号代替“权限修饰符”和“返回值”部分表示“权限修饰符”和“返回值”不限
在包名的部分,一个“*”号只能代表包的层次结构中的一层,表示这一层是任意的。
	例如:*.Hello匹配com.Hello,不匹配com.atguigu.Hello
在包名的部分,使用“*..”表示包名任意、包的层次深度任意
在类名的部分,类名部分整体用*号代替,表示类名任意
在类名的部分,可以使用*号代替类名的一部分
	例如:*Service匹配所有名称以Service结尾的类或接口
在方法名部分,可以使用*号表示方法名任意
在方法名部分,可以使用*号代替方法名的一部分
	例如:*Operation匹配所有方法名以Operation结尾的方法
在方法参数列表部分,使用(..)表示参数列表任意
在方法参数列表部分,使用(int,..)表示参数列表以一个int类型的参数开头
在方法参数列表部分,基本数据类型和对应的包装类型是不一样的
切入点表达式中使用 int 和实际方法中 Integer 是不匹配的
在方法返回值部分,如果想要明确指定一个返回值类型,那么必须同时写明权限修饰符
	例如:execution(public int ..Service.*(.., int)) 正确
	例如:execution(* int ..Service.*(.., int)) 错误

由于@Before("execution(public int com.zengqh.aop.CalculatorPureImpl.add(int,int))")这个切入点表达式只对指定的这个方法进行切入,如果想要对同一个包下,同一个类下的所有方法都做切入可以使用*号进行省略

    //CalculatorPureImpl类的所有方法都可以切入
	@Before("execution(* com.zengqh.aop.CalculatorPureImpl.*(..))")
    public void beforeAdviceMethod(){
        System.out.println("LoggerAspect-->前置通知");
    }
	//aop包下的类里面的所有方法都可以切入
    @Before("execution(* com.zengqh.aop.*.*(..))")
    public void beforeAdviceMethod() {
        System.out.println("LoggerAspect-->前置通知");
    }
获取连接点的信息
@Before("execution(* com.zengqh.aop.*.*(..))")
public void beforeAdviceMethod(JoinPoint joinPoint) {
    //获取连接点的方法名
    Signature signature = joinPoint.getSignature();
    //获取连接点的方法的参数
    Object[] args = joinPoint.getArgs();
    System.out.println("LoggerAspect-->前置通知的方法名为:" + "[" + signature.getName() + "]" + "参数列表为" + Arrays.asList(args));
}

测试:

@Test
public void test001(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-config.xml");
    Calculator bean = ioc.getBean(Calculator.class);
    int add = bean.add(1, 1);
    int div = bean.div(1, 1);
}

切入点表达式的复用

@Pointcut用来声明一个公共的切入点表达式

@Pointcut("execution(* com.zengqh.aop.*.*(..))")
public void pointCut() {
}

@After("pointCut()")
public void afterAdviceMethod(JoinPoint joinPoint){
}

AOP通知

前置通知:使用@Before注解标识,在被代理的目标方法前执行
返回通知:使用@AfterReturning注解标识,在被代理的目标方法成功结束后执行(寿终正寝)
异常通知:使用@AfterThrowing注解标识,在被代理的目标方法异常结束后执行(死于非命)
后置通知:使用@After注解标识,在被代理的目标方法最终结束后执行(盖棺定论)
环绕通知:使用@Around注解标识,使用try...catch...finally结构围绕整个被代理的目标方法,包
括上面四种通知对应的所有位置

切面优先级

相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。
优先级高的切面:外面
优先级低的切面:里面
使用@Order注解可以控制切面的优先级:
@Order(较小的数):优先级高
@Order(较大的数):优先级低

基于XML的AOP(看看就行)

<context:component-scan base-package="com.atguigu.aop.xml"></context:componentscan>
<aop:config>
<!--配置切面类-->
<aop:aspect ref="loggerAspect">
<aop:pointcut id="pointCut" expression="execution(*
com.atguigu.aop.xml.CalculatorImpl.*(..))"/>
<aop:before method="beforeMethod" pointcut-ref="pointCut"></aop:before>
<aop:after method="afterMethod" pointcut-ref="pointCut"></aop:after>
<aop:after-returning method="afterReturningMethod" returning="result"
pointcut-ref="pointCut"></aop:after-returning>
<aop:after-throwing method="afterThrowingMethod" throwing="ex" pointcutref="pointCut"></aop:after-throwing>
<aop:around method="aroundMethod" pointcut-ref="pointCut"></aop:around>
</aop:aspect>
<aop:aspect ref="validateAspect" order="1">
<aop:before method="validateBeforeMethod" pointcut-ref="pointCut">
</aop:before>
</aop:aspect>
</aop:config>

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值