理解SpingAOP

Posted 你礼貌吗

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了理解SpingAOP相关的知识,希望对你有一定的参考价值。

什么是AOP?


​ AOP,即我们平时经常提到的面向切面编程。首先我们要理解一个叫横切关注点(cross-cutting concern)的概念,它其实是描述我们应用中的功能,假如有一个功能,它在应用程序中很多个地方都用了,那么我们把这样的功能称之为横切关注点

​ 日常开发中,我们都会将不同的业务场景抽象出对应的模块进行开发,而不同的模块,除了那些针对特定领域的核心功能外,还有一些相同的辅助功能,比如日志管理、安全管理、事务管理等等。横切关注点这个概念其实就点明了:类似这样的功能就是我们面向切面编程需要关注的地方。这也是面向切面编程的意义所在:它帮助我们实现横切关注点和他们所影响的对象之间的解耦。

​ 面向切面编程的实质,就是讲横切关注点模块化成称为切面的特殊的类。

AOP术语


通知(Advice)

切面的工作被称为通知。也就是定义了切面的要做什么,以及何时做的问题。下面是Spring切面有5种类型的通知,以及相对应的在SpringBoot中的五个注解

  • 前置通知(Before):方法调用之前,对应@Before
  • 后置通知(After):方法调用之后(不关心方法输出是什么)对应@After
  • 返回通知(After-returning):目标方法成功执行之后,对应@AfterReturning
  • 异常通知(After-throwing):目标方法抛出异常之后,对应@AfterThrowing
  • 环绕通知(Around):方法调用之前和之后,对应@Around
连接点(Join point)

程序中那些我们想要应用通知的地方,就是连接点。这个点可以是我们调用方法时、抛出异常时或甚至是修改某一个字段的时候。切面代码(通知)可以通过这些点插入到应用的正常流程中,是原本的功能增添新的行为。

切点(Pointcut)

我们的应用程序可能会有很多个连接点需要我们应用通知,所以我们有必要把连接点的分类汇总,抽象出相同的特点,好让正确的切面切入到正确的地方去,各司其职,而不是切入所有的连接点。切点定义 了一个切面需要在哪里进行切入。是一堆具有特定切面切入需求的连接点的共性抽象。

我们通常通过明确类和方法名、或者匹配正则表达式的方式来指定切点

连接点和切点的区别
切面(Aspect)

切面是通知和切点的结合。通知和切点共同定了切面的全部内容——它想要干什么,在何时何地完成功能。

引入(Introduction)

引入能够让我们在不修改原有类的代码的基础上,添加生的方法或属性。

织入(Weaving)

织入是把切面应用到目标对象并创建新的代理对象的过程。

SpringAOP


SpringAOP的特点

SpringAOP是通过动态代理实现的。不同于AspectJ等其他aop框架,SpringAOP只支持方法级别的切点,不支持类构造器或字段级别的切点。因此只能拦截方法进行通知,而在对象创建或对象变量的修改时,都无法应用通知

SpringBoot集成SpringAOP

SpringBoot引入AOP依赖后,spring.aop.auto属性默认是开启的,也就是说只要引入了AOP依赖后,默认已经增加了@EnableAspectJAutoProxy

现在我们来设想一个场景:我有若干个业务场景,每个场景对应一个服务,有些服务需要我在调服务的时候,打印开始和结束信息,并输入服务处理的时长。

- 依赖引入

首先在项目中引入SpringAOP的依赖:

<!--引入AOP依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 创建注解

创建一个自定义注解@ApiLog,用boolean类型的字段来决定是否开启日志输入功能,代码如下:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiLog {
    boolean isOn() default true;
}
- 定义切面

定义一个切面类MyAspect.java,代码如下

@Aspect
@Component
public class MyAspect {

    @Pointcut("execution(* com.acelin.hello.study.springTest.aop.AspectController.*(..))")
    private void onePointcut(){}

    @Around("onePointcut()")
    public Object around(ProceedingJoinPoint point)throws Throwable{

        Signature signature = point.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

        /* 获取方法上的注解 */
        ApiLog apiLog = method.getAnnotation(ApiLog.class);
        if (apiLog != null && apiLog.isOn()){
            System.out.println("ApiLog is on!");
        }

        Object[] objects = point.getArgs();
        String bussinessName = (String) objects[0];
        System.out.println("-- " + bussinessName + " start --");
        long begin = System.currentTimeMillis();
        Object object = point.proceed();
        long end = System.currentTimeMillis();
        System.out.println("-- " + bussinessName + " end  耗时" + (end - begin) + "ms --");
        return object;
    }
}
- 设置切点
@Pointcut("execution(* com.acelin.hello.study.springTest.aop.AspectController.*(..))")
private void onePointcut(){}

@Pointcut注解表示声明一个切点,然后我们要配置execution指示器,它用来匹配所有符合条件的连接点。已上面的指示器配置* com.acelin.hello.study.springTest.aop.AspectController.*(..)为例,分析一下指示器的写法

  • 第一个*号:表示的不关心方法的返回值类型
  • 中间的com.acelin.hello.study.springTest.aop.AspectController.*指定方法的特点
  • 第二个*号:表示AspectController类下的任意方法
  • (..):表示不关心参数个数和类型
- 业务接口编写

我们写一个简单的接口,然后注释我们自定义的注解,开启相关日志的输出。

@RestController
@RequestMapping("/aop")
public class AspectController implements IAspect{

    @RequestMapping("/test/1/{businessName}")
    @ApiLog(isOn = true)
    public void testAop(@PathVariable("businessName") String businessName){
        System.out.println("This is a controller about aop test!");
    }
}
- 测试

测试结果如下:

ApiLog is on!
-- 业务1 start --
This is a controller about aop test!
-- 业务1 end  耗时5ms --
通知时机

以上我们通过一个简单的例子,初步体验了aop的魅力。接下来我们来讨论以下各类通知的特点以及应用时机问题。我们对的上面的切面类进行了修改,加上所有类型的通知。

@Aspect
@Component
public class MyAspect {

    @Pointcut("execution(* com.acelin.hello.study.springTest.aop.AspectController.*(..))")
    private void onePointcut(){}

    @Before("onePointcut()")
    public void before(){
        System.out.println("-- before");
    }

    @After("onePointcut()")
    public void after(JoinPoint joinPoint) throws NoSuchMethodException{
        System.out.println("-- after");
    }

    @AfterThrowing("onePointcut()")
    public void afterThrowing(){
        System.out.println("-- afterThrowing");
    }

    @AfterReturning("onePointcut()")
    public void afterReturning(){
        System.out.println("-- afterReturning");
    }

    @Around("onePointcut()")
    public Object around(ProceedingJoinPoint point)throws Throwable{

        Signature signature = point.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

        /* 获取方法上的注解 */
        ApiLog apiLog = method.getAnnotation(ApiLog.class);
        if (apiLog != null && apiLog.isOn()){
            System.out.println("ApiLog is on!");
        }

        Object[] objects = point.getArgs();
        String bussinessName = (String) objects[0];
        System.out.println("-- " + bussinessName + " start");
        long begin = System.currentTimeMillis();
        Object object = point.proceed();
        long end = System.currentTimeMillis();
        System.out.println("-- " + bussinessName + " end  耗时" + (end - begin) + "ms");
        return object;
    }
}

然后修改接口,怎加业务名称的一个长度判断,如果名称太长,抛出异常,来模拟方法执行过程中法僧异常

@RequestMapping("/test/1/{businessName}")
@ApiLog
public void testAop(@PathVariable("businessName") String businessName){
    System.out.println("This is a controller about aop test!");
    if (businessName.length() > 4){
        throw new RuntimeException("业务名称太长!");
    }
}
- 正常情况

来看一下当方法正常执行的情况下,前置通知、后置通知、返回通知和环绕通知的应用时机。

再次调用测试接口,可看到如下结果:

ApiLog is on!
-- 业务 start
-- before
This is a controller about aop test!
-- afterReturning
-- after
-- 业务 end  耗时6ms
- 异常情况

然后我们修改业务名称为,使其超过规定长度导致触发异常抛出,结果如下:

ApiLog is on!
-- 超级复杂的业务 start
-- before
This is a controller about aop test!
-- afterThrowing
-- after

java.lang.RuntimeException: 业务名称太长!

    at com.acelin.hello.study.springTest.aop.AspectController.testAop(AspectController.java:16)

总结


以上就是对AOP相关知识的入门,相关术语的解释,以及SpringBoot集成SpringAOP的简单应用,欢迎留言讨论。

以上是关于理解SpingAOP的主要内容,如果未能解决你的问题,请参考以下文章

金蝶handler中 collection 代码片段理解

调用模板化成员函数:帮助我理解另一个 *** 帖子中的代码片段

如何理解这段代码片段中的两对括号?

精心收集的 48 个 JavaScript 代码片段,仅需 30 秒就可理解

精心收集的 48 个 JavaScript 代码片段,仅需 30 秒就可理解!(转载)

以下代码片段 C++ 的说明