学习笔记——Spring(5)AOP

定义

AOP——面向切面编程:是一种编程典范,它通过分离横切关注点来增加程序的模块化。简单说就是AOP可以在不修改现有代码的情况下,对现有代码增加一些功能。

  • 横切关注点:软件系统可以看成是由一组关注点组成的,其中,直接的业务关注点,是直切关注点。而为直切关注点提供服务的,就是横切关注点。

  • 横切关注点被模块化为特殊的类,这些类称为切面

  • 切面也需要完成工作。在 AOP 术语中,切面的工作被称为通知。通知定义了切面内容以及何时使用。除了描述切面要完成的工作,通知还解决何时执行这个工作。

Spring 切面可应用的 5 种通知类型:

  1. Before——在方法调用之前调用通知

  2. After——在方法完成之后调用通知,无论方法执行成功与否

  3. After-returning——在方法执行成功之后调用通知

  4. After-throwing——在方法抛出异常后进行通知

  5. Around——通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为 

  • 连接点:连接点是一个应用执行过程中能够插入一个切面的点。切面代码可以利用这些点插入到应用的正规流程中。连接点更多是理论上的意义,如果不考虑程序的实际功能,你可以把每行代码都认为是一个jointpoint。 

  • 切点:定义通知被应用在哪些连接点

  • 切面:切面是通知和切点的集合,通知和切点共同定义了切面的全部功能——它是什么,在何时何处完成其功能。

  • 引入:引入允许我们向现有的类中添加方法或属性

  • 织入:织入是将切面应用到目标对象来创建的代理对象过程。切面在指定的连接点被织入到目标对象中,在目标对象的生命周期中有多个点可以织入。

        编译期——切面在目标类编译时期被织入,这种方式需要特殊编译器。AspectJ的织入编译器就是以这种方式织入切面。

        类加载期——切面在类加载到JVM ,这种方式需要特殊的类加载器,他可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5 的 LTW 就支持这种织入方式
        运行期——切面在应用运行期间的某个时刻被织入。一般情况下,在织入切面时候,AOP 容器会为目标对象动态的创建代理对象。Spring AOP 就是以这种方式织入切面。

 AOP实现——AspectJ

步骤:

  1. 引入AspectJ相关jar包
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.2</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.4</version>
        </dependency>

2、创建一个类声明为切面,并且放入IOC容器中。如下:创建路径和测试类

具体代码:

AopTest:(具体execution设置方式,另做记录)

package aop;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * @Description:把这个类声明为切面,并放入IOC容器中
 * @version:
 */
@Aspect
@Component
public class AopTest {

//    声明一个前置通知:在目标方法前执行 JoinPoint可有可无,它记录了目标方法的相关信息
    @Before("execution(public int aop.TestController.doSomething(int,int))")
    public void beforeMethod(JoinPoint joinPoint){
        System.out.println("方法前执行");
        System.out.println(joinPoint);
    }

//   声明一个后置通知:在目标方法后执行(不论是否出现异常)。后置通知不能访问目标方法执行结果
    @After("execution(public int aop.TestController.doSomething(int,int))")
    public void afterMethod(){
        System.out.println("方法后执行");
    }
//    声明一个返回通知:在目标方法执行后,可以得到返回值信息 注解中通过“returning”定义返回值名字,并在方法使用同名参数接收
    @AfterReturning(value = "execution(public int aop.TestController.doSomething(int,int))", returning = "result")
    public void afterRetuningMethod(JoinPoint joinPoint, Object result){
        System.out.println("可获取方法返回值");
        System.out.println("AfterReturning返回结果:"+result);
    }

//    声明一个异常通知:在目标方法执行异常时,执行此方法
    @AfterThrowing("execution(public int aop.TestController.doSomething(int,int))")
    public void exceptionMethod(){
        System.out.println("方法异常时执行");
    }
}

 TestController:

package aop;

import org.springframework.stereotype.Component;

@Component
public class TestController {
    public int doSomething(int i, int j){
        System.out.println("执行乘法运算");
        return i*j;
    }
}

配置文件:

<?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:util="http://www.springframework.org/schema/util"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="aop"></context:component-scan>
    <!--使AspectJ注解起作用,所起作用为:自动匹配的类生成代理对象在调用方法时,
    (如果方法是切面中通知注解中的目标方法,AOP框架会自动为目标方法所在类生成代理对象,
    然后在调用方法前将通知中的代码添加进去)-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

主函数类:

package controller;

import aop.TestController;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
    public static void main(String[] args){
//        读取配置文件
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring-bean.xml");
        TestController testController = (TestController)ctx.getBean("testController");
        int x = testController.doSomething(5,8);
        System.out.println("结果为:"+x);
    }
}

运行结果:

 可以发现,当同时存在前置通知、后置通知、异常通知、返回通知时,需要分别设置execution,为了减少重复代码,我们可以将它抽取出来。使用@Pointcut注解指定切点,各通知通过引用切点即可。具体实现如下:

package aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @Description:把这个类声明为切面,并放入IOC容器中
 * @version:
 */
@Aspect
@Component
public class AopTest {

//    @Pointcut定义切点,方法体为空
    @Pointcut("execution(public int aop.TestController.doSomething(int,int))")
    public void pointCutMethod(){

    }

//    通过方法名指定切点
    @Before("pointCutMethod()")
    public void beforeMethod(JoinPoint joinPoint){
        System.out.println("方法前执行");
        System.out.println(joinPoint);
    }

    @After("pointCutMethod()")
    public void afterMethod(){
        System.out.println("方法后执行");
    }

    @AfterReturning(value = "pointCutMethod()", returning = "result")
    public void afterRetuningMethod(JoinPoint joinPoint, Object result){
        System.out.println("可获取方法返回值");
        System.out.println("AfterReturning返回结果:"+result);
    }

    @AfterThrowing("pointCutMethod()")
    public void exceptionMethod(){
        System.out.println("方法异常时执行");
    }
}

 

除以上几种通知以外,还有一种通知——环绕通知。 环绕通知类似于动态代理的全过程,且环绕通知一定要有返回值,返回值即为目标方法的返回值。环绕通知在目标方法的前后、异常时都会做相应操作。具体实现如下:

package aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @Description:把这个类声明为切面,并放入IOC容器中
 * @version:
 */
@Aspect
@Component
public class AopTest {

//    @Pointcut定义切点,方法体为空
    @Pointcut("execution(public int aop.TestController.doSomething(int,int))")
    public void pointCutMethod(){

    }

//    环绕通知 必须携带ProceedingJoinPoint类型参数
    @Around("pointCutMethod()")
    public Object round(ProceedingJoinPoint proceedingJoinPoint){
        Object result = null;
        String methodName = proceedingJoinPoint.getSignature().getName();
        System.out.println(methodName);
        try {
//            前置通知
            System.out.println("begin...");

            result = proceedingJoinPoint.proceed();

//            返回通知
            System.out.println("return..."+result);
        } catch (Throwable e) {
//            异常通知
            System.out.println("error...");

        }
//        后置通知
        System.out.println("after...");

        return result;
    }
}

运行结果:

 

当多个切面指向同一目标方法时,哪个切面先执行?哪个切面后执行?
可以通过使用@Order注解来标识切面的优先级
实际上该注解主要用来控制配置类的加载顺序,也可以使用它来控制切面的优先级,注解有一个int类型的参数,可以不传,默认是最低优先级,参数的值越小,优先级越高

或者通过集成Ordered接口,实现其中的getOrder()方法,getOrder()方法的返回值类型是int,实现时返回值越小优先级越高。

 



@AfterRetuning是在目标函数执行完毕并返回结果时调用,因此可以从@AfterReturning中获取到方法返回值。示例:

@AfterReturning(value = "execution(* com.example.aop.sbt_aop.test.TestMain.index(..))", returning = "str")
    public void afterReturn(String str){
        System.out.println("AfterReturning方法执行,返回参数:"+str);
    }

上述是afterReturning获取目标方法的返回结果,如果想要传递参数给通知,我们只需要在切点处加对应正则表达式就可以了,示例:

@Before("execution(* TestMain.index(..)) && args(info)")
    public void before(String info){
        System.out.println(info);
        System.out.println("Before方法执行");
    }

对于非环绕通知还可以使用还可以使用一个连接点(JoinPoint)类型的参数,通过它也可以获取参数。示例:

@Before("execution(* TestMain.index(..))")
    public void before(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();//获取目标方法调用时传入的所有参数
        System.out.println(args);
        System.out.println("Before方法执行");
    }

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值