5

AOP中的一些重要术语简介 - 一只烤鸭朝北走

 1 year ago
source link: https://www.cnblogs.com/wha6239/p/17172501.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

AOP中的一些重要术语简介

  AOP的定义:AOP(Aspect Oriented Progamming)利用称为"横切"的技术,剖解开封装的对象内部,把多个类的公共行为封装到一个可重用模块中,便于减少重复代码,降低模块之间的耦合度,符合“开闭原则”。

  上面这段关于AOP的定义是从网上抄的,是不是很拗口,我们就结合实际开发来先简单了解下AOP中的一些专业术语,然后再去学习使用它吧。

  在通常的业务开发中,我们往往只需要让业务逻辑去关注业务本身,但是一些复杂的业务我们为了保证数据的完整、后序发生问题之后能够快速定位问题,通常伴随着安全、日志、事务等代码,业务写多了我们发现这些代码通常都是类似的,那么我们不就可以将这些类似的代码封装起来,再需要使用的地方进行复用吗?AOP就为提供了这么一种思路,但是AOP中的一些专业术语特别拗口,我们在这简单总结下,做下记录方便后面的学习。

  1、通知(Advice):就是我们上面提到的除了业务代码本身之外,一些“可重用的一些代码”,先定义好,在需要的地方进行重用;

  2、连接点(JoinPoint):允许你使用“通知”的地方,就是允许你重复使用代码的地方,方法执行的前后或者方法抛出异常时,Spring只支持方法级别的连接点;

  3、切入点(Pointcut): 假如有若干方法,但是我们并不是想在所有的方法中“重用这段可复用的代码”,我们只想在某些特定的方法上使用通知,那么我们就可以使用切入点来进行这些连接点的“筛选了”;

  4、切面(Aspect):切面简单来讲就是切入点(JoinPoint)和通知(Advice)的结合体。通知(Advice)决定了要干什么(通知的方法体)?在什么时候干?(定义通知的注解类型)。而切入点决定了要在哪儿干(即执行通知定义的方法体)。

  5、引入(Introduction):允许我们向目标对象添加新的方法属性(即通过执行通知来控制对目标方法的访问);

  6、目标对象(Target):引入中所提到的目标对象,也就是要被通知的对象,也就是执行真正的业务逻辑,可以在毫不知情的情况下,织入我们的切面;

  7、代理(Proxy):Spring中的AOP都是通过动态代理来实现的,关于代理有不明白的可以参考设计模式之(8)——代理模式

    8、织入(Weaving):把切面应用到目标对象,创建代理对象的过程;

    9、AOP方法:通知+目标对象的方法。

  上面就是AOP编程中一些常用的属于,然后我们再看看AOP中最重要的通知(Advice)的分类,通知分为五类:

  为了方便下面理解,我们可以认为“连接点就是一个目标方法”。

  1、前置通知(Before Advice):在目标方法之前执行,前置通知不会影响目标方法的执行,除非前置通知抛出异常;

  2、正常返回通知(After Advice):在目标方法正常执行完成之后执行,如果目标方法抛出异常,则不会执行;

  3、异常返回通知(AfterThrowing Advice):在目标方法抛出异常之后会执行;

  4、返回通知(AfterReturning Advice):在目标方法执行完之后执行,不管目标方法是正常执行完,还是因为抛出异常退出,都会执行返回通知内的内容;

  5、环绕通知(Around Advice):环绕通知围绕在目标方法执行的前后,是一个功能最为强大的通知类型,可以在方法执行前后自定义一些操作,环绕通知还需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行,为了避免后面的空指针,环绕通知还需要返回方法的执行结果。

  以下是我写的一个简单示例,通过一个自定义注解,来向需要的地方织入通知;

  自定义注解:

package com.pep.process.annotation;

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

/**
 * @ClassName: Audit
 * @Description: 描述这个类的作用
 * @author: wwh
 * @date: 2023年3月2日 上午9:47:52
 */

@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Audit {
    
    String action() default "";

    String name() default "";
}

  定义一个切面,切面中包含切入点和通知:

package com.pep.process.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * @ClassName: AuditAop
 * @Description: 自定义切面
 * @author: wwh
 * @date: 2023年3月2日 上午9:54:48
 */
@Component
@Aspect
public class AuditAspect {
    
    /**
     * @Title: audit
     * @Description: 定义一个切入点
     * void 返回类型
     */
    @Pointcut("@annotation(com.pep.process.annotation.Audit)")
    private void audit() {
        
    }
    
    /**
     * @Title: before
     * @Description: 前置通知
     * void 返回类型
     */
    @Before("audit()")
    public void before() {
        System.err.println("我是前置通知...");
    }
    
    /**
     * @Title: around
     * @Description: 环绕通知
     * @param joinPoint
     * @return 
     * Object 返回类型
     */
    @Around("audit()")
    public Object around(ProceedingJoinPoint joinPoint) {
        try {
            System.err.println("环绕通知执行前...");
            Object proceed = joinPoint.proceed();
            System.err.println("环绕通知执行后...");
            return proceed;
        } catch (Throwable e) {
            e.printStackTrace();
            System.err.println("目标方法执行过程中发生了异常...");
            return null;
        }
    }
    
    /**
     * @Title: after
     * @Description: 描述这个方法的作用 
     * void 返回类型
     */
    @After(value="audit()")
    public void after() {
        System.err.println("我是后置通知...");
    }
    
    /**
     * @Title: afterThrow
     * @Description: 返回异常通知
     * @param ex 
     * void 返回类型
     */
    @AfterThrowing(value="audit()",throwing="ex")
    public void afterThrow(Exception ex) {
        System.err.println("异常通知...");
    }
    
    /**
     * @Title: afterReturning
     * @Description: 返回通知
     * @param obj
     * @return 
     * Object 返回类型
     */
    @AfterReturning(value="audit()",returning="obj")
    public Object afterReturning(Object obj) {
        System.err.println("我是返回通知...");
        return obj;
    }    
}

  需要注意的是异常通知的注解中的throwing参数,通过这个参数,可以目标方法执行过程中抛出的异常,绑定到异常通知的方法参数中,并且这个参数值要和方法参数名称一致。

  同理返回通知注解中的returning参数,也是将目标方法的执行结果和返回通知中方法的参数进行绑定,名称也必须一致。

  通过使用自定义的方式来向需要的地方织入通知,实现方法功能“增强”或者控制方法访问。

package com.pep.process.controller;

import net.sf.json.JSONObject;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.pep.jxw.common.util.UUIDGenerator;
import com.pep.process.annotation.Audit;

@Controller
public class LogController {
    
    @RequestMapping("/test.do")
    @Audit
    @ResponseBody
    public JSONObject log() {
        JSONObject json = new JSONObject();
        json.put("id", UUIDGenerator.getUUID());
        System.err.println("执行了目标方法...");
        return json;
    }
}

  输出结果如下:

  1139198-20230302191841317-990544797.png

   相信通过上面这个返回结果的分析,我们对各种通知的执行顺序有一个简单了解,后续的一些问题我们有空再议。

  参考文章:

  1、https://blog.csdn.net/u011402896/article/details/80369220

  2、https://zhuanlan.zhihu.com/p/610434341


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK