目 录CONTENT

文章目录

Java核心卷(一)切面编程

Jinty
2024-06-25 / 0 评论 / 0 点赞 / 20 阅读 / 10930 字

注解

@interface修饰符

表明该类为注解类型

Annotation接口

所有注解类都是隐式继承java.lang.annotation.Annotation接口

元注解

@Inherited

指示自动继承批注类型。如果注释类型声明中存在继承的元注释,并且用户在类声明上查询注释类型,并且类声明没有针对此类型的注释,则将自动查询类的超类以获取注释类型。将重复此过程,直到找到此类型的批注,或者达到类层次结构 (Object) 的顶部。如果没有超类具有此类型的注释,则查询将指示相关类没有此类注释。

请注意,如果批注类型用于批注类以外的任何内容,则此元批注类型不起作用。另请注意,此元注释仅导致注释从超类继承;对已实现接口的注释不起作用。

可继承的

@Retention

指示使用批注类型的批注将保留多长时间。如果批注类型声明中不存在 Retention 批注,则保留策略默认为 RetentionPolicy. CLASS。

仅当元注释类型直接用于注释时,保留元注释才有效。如果元注释类型用作其他注释类型中的成员类型,则该类型不起作用。

生命周期

@Target

指示批注类型适用的上下文。注释类型可能适用的声明上下文和类型上下文在 JLS 9.6.4.1 中指定,并在源代码中用枚举常量表示 java. lang. annotation. ElementType。

@Target如果注释类型T上不存在元注释,则可以将类型的T注释写为除类型参数声明以外的任何声明的修饰符。

@Target如果存在元注释,编译器将强制执行枚ElementType举常量指示的使用限制,这与 JLS 9.7.4 一致。

标记目标

@Documented

指示缺省情况下,javadoc 和类似工具将记录具有类型的注释。此类型应用于批注类型的声明,这些类型的批注会影响其客户端对批注元素的使用。如果类型声明使用 Documented 进行批注,则其批注将成为带批注元素的公共 API 的一部分。

文档

@Repeatable

注释类型 java. lang. annotation. Repeatable 用于指示其 (meta-) 注释的声明的注释类型是 可重复的。的 @Repeatable 值指示可重复批注类型的 包含批注类型 。

可重复的

例子

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@Inherited
public @interface ApiLog {
    /**
     * 分组
     */
    @AliasFor(value = "group")
    String value() default "";

    /**
     * 分组
     */
    @AliasFor(value = "value")
    String group() default "";

    /**
     * 策略:ACCESS-所有访问,ERROR-仅错误
     */
    ApiLogPolicy policy() default ApiLogPolicy.ACCESS;

    /**
     * 记录请求参数
     */
    boolean isSaveRequestParams() default false;

    /**
     * 是否记录响应内容
     */
    boolean isSaveResponseBody() default false;

    /**
     * 记录请求参数、响应内容支持的最大长度
     */
    int maxSaveLength() default -1;
}

其中:@AliasFor为Spring框架提供注解

切面编程

原理

使用反射技术对目标对象进行代理,产生代理对象,通过在代理对象中插入横切逻辑代码,实现对目标对象的增强功能。

具体来说,切面编程的实现通常是通过动态代理或者静态代理来实现的,动态代理主要是通过 JDK 提供的 Proxy 类或者 CGLIB 库来实现,静态代理则是自己手动编写代理类。通过代理对象来实现切面编程,可以在不修改原有代码的情况下,对原有的功能进行增强或者横切逻辑的插入,提高代码的复用性和可维护性。

术语:切面(Aspect)、织入(Weaving)、切点(Pointcut)、通知(Advice)、连接点(Joinpoint)

把切面(aspect)连接到其它的应用程序类型或者对象上,并创建一个被通知(advised)的对象,这样的行为叫做织入。指将切面代码与目标代码合并的过程。

Signature接口

表示联接点处的签名。此接口与 java. lang. reflect. Member并行。

此接口通常用于跟踪或记录应用程序以获取有关连接点的反射信息,即使用 j2se 1.4 java. util. logging API

JoinPoint接口与ProceedingJoinPoint接口

提供对联接点上可用的状态和有关该状态的静态信息的反射性访问。此信息可从建议正文中使用特殊表格 thisJoinPoint获得。此反射信息的主要用途是跟踪和记录应用程序。

ProceedingJoinPoint 公开了 proceed(..) 方法,以便在支持环绕通知around advice

  • 如何获取方法切点的方法名称、方法参数名、方法参数值?

  • 如何获取类切点的相关信息?

  • 字段属性切点?

Spring 切面

@Aspect

@Aspect注解用于定义切面(Aspect)类,它告诉Spring容器这个类是一个切面,并应该横切应用程序的其他组件。通过在切面类上添加@Aspect注解,Spring能够将切面类中定义的通知(advice)和切点(pointcut)与目标类(被切面织入的类)进行关联。

@Pointcut

切点

切点定义方式

在面向切面编程(AOP)中,切点定义了在哪些连接点上应用横切逻辑。切点可以通过不同的方式进行定义:

  1. 基于表达式的方式:切点表达式是最常用的定义切点的方式。使用切点表达式,可以指定匹配规则来确定应用横切逻辑的连接点。例如,在Spring AOP中,可以使用execution()、within()等指示符来定义切点表达式。

  2. 基于注解的方式:可以使用注解来标记需要应用横切逻辑的方法或类,然后通过切面中的@Pointcut注解定义切点,指定要匹配的注解类型。

  3. 基于配置文件的方式:在一些AOP框架中,如AspectJ,可以通过XML或其他配置文件来明确指定切点,使用语法来精确描述匹配的连接点。

  4. 编程方式:有时候也可以通过编程方式来定义切点,即在编写切面的代码中根据条件手动筛选出要应用横切逻辑的连接点。

不同的AOP框架支持不同的切点定义方式,需要根据具体的框架和应用场景选择最适合的方式来定义切点。切点的定义方式对AOP功能的实现和性能有一定的影响,因此在设计和实现时需要慎重选择合适的方式。

常见的切点定义方式包括以下几种:

  1. 静态切点:静态切点是在程序编译时就确定的切点,通常是在代码中明确指定的特定方法或类。静态切点的优点是性能较高,因为切点确定在编译时,无需在运行时进行额外的计算。缺点是灵活性较差,无法动态地根据运行时的条件来确定切点。

  2. 动态切点:动态切点是在程序运行时根据特定条件或规则来确定的切点。动态切点的优点是灵活性较高,可以根据运行时的情况动态地选择切点。缺点是性能较低,因为需要在运行时进行额外的计算和判断。

  3. 注解切点:注解切点是通过注解来定义切点的方式,可以在方法或类上添加注解来指定切点。注解切点的优点是简洁明了,易于理解和维护。缺点是性能可能会受到影响,因为框架需要解析注解并确定切点。

因此,开发者在定义切点时应该尽量简单明了,避免过于复杂或者范围过广的切点,以减少AOP框架在运行时匹配切点的开销,提高系统性能。

@Beofore

前置通知

在目标方法执行前执行通知,即在目标方法调用之前,执行额外的逻辑操作。通常用于在方法执行前进行一些前置准备工作,如参数验证、权限检查等。

@After

后置通知

在方法执行之后执行通知。与@AfterReturning不同的是,@After无法访问方法返回值。通常用于执行一些必须在目标方法执行后执行的逻辑,如资源释放、清理等操作。

@Around

环绕通知

在方法执行前后执行通知,可以自定义通知的执行顺序,包裹目标方法的执行。通常用于在目标方法执行前后做一些额外的逻辑,如性能监控、事务管理等。

@AfterReturning

方法成功返回通知

在方法成功返回后执行通知。通常用于处理方法正常返回时的逻辑,如记录日志,更新缓存等操作。

@AfterThrowing

异常抛出通知

在方法抛出异常之后执行通知。通常用于处理方法执行过程中出现的异常情况,进行异常处理或记录日志等操作。

例子(注解切面)

@Aspect
@RequiredArgsConstructor
@Component
public class ApiLogAspect {

    private final ApiAccessLogService apiAccessLogService;

    private final ObjectMapper jsonMapper = new ObjectMapper();

    @Pointcut("@annotation(apiLog)")
    public void apiLogPointcut(ApiLog apiLog) {
    }

    @Around("apiLogPointcut(apiLog)")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint, ApiLog apiLog) {
        long start = System.currentTimeMillis();
        ApiAccessLog apiAccessLog = new ApiAccessLog();
        try {
            initRequestInfo(apiAccessLog, apiLog);
            Object result = joinPoint.proceed();
            initResponseInfo(result, apiAccessLog, apiLog);
            return result;
        } catch (Throwable e) {
            initResponseInfo(e, apiAccessLog, apiLog);
            throw e;
        } finally {
            long end = System.currentTimeMillis();
            BigDecimal processInterval = BigDecimal.valueOf(end - start).divide(BigDecimal.valueOf(1000L), 2, RoundingMode.HALF_UP);
            apiAccessLog.setProcessInterval(processInterval);
            apiAccessLogService.saveLog(apiAccessLog);
        }
    }

    private void initRequestInfo(ApiAccessLog apiAccessLog, ApiLog apiLog) {
        HttpServletRequest request = RequestUtils.getRequest();
        apiAccessLog.setAccessTime(new DateTime());
        apiAccessLog.setApiEndpoint(request.getRequestURI());
        apiAccessLog.setRequestMethod(request.getMethod());
        if (apiLog.isSaveRequestParams() && apiLog.maxSaveLength() != 0) {
            apiAccessLog.setRequestParams("TODO");
        }
        apiAccessLog.setClientIp(RequestUtils.getRequestIP(request));
        if (StpUtil.isLogin()) {
            apiAccessLog.setAccountId((Long) StpUtil.getLoginId());
        }
    }

    @SneakyThrows
    private void initResponseInfo(Object result, ApiAccessLog apiAccessLog, ApiLog apiLog) {
        if (apiLog.isSaveResponseBody() && apiLog.maxSaveLength() != 0) {
            String s = jsonMapper.writeValueAsString(result);
            if (apiLog.maxSaveLength() > 0 && s.length() > apiLog.maxSaveLength()) {
                s = s.substring(0, apiLog.maxSaveLength());
            }
            apiAccessLog.setResponseBody(s);
        }
        if (result instanceof ApiRet) {
            ApiRet<?> apiRet = (ApiRet<?>) result;
            int code = apiRet.getCode();
            apiAccessLog.setResponseStatusCode(String.valueOf(code));
            if (code != ApiCode.OK.code()) {
                apiAccessLog.setErrMsg(apiRet.getMsg());
            }
        } else {
            apiAccessLog.setResponseStatusCode("0");
        }
    }

    private void initResponseInfo(Throwable e, ApiAccessLog apiAccessLog, ApiLog apiLog) {
        apiAccessLog.setErrMsg(e.getMessage());
        if (e instanceof ISysError) {
            ISysError sysError = (ISysError) e;
            int code = sysError.getCode();
            apiAccessLog.setResponseStatusCode(String.valueOf(code));
            String message = sysError.getMessage();
            if (apiLog.maxSaveLength() > 0 && message.length() > apiLog.maxSaveLength()) {
                message = message.substring(0, apiLog.maxSaveLength());
            }
            apiAccessLog.setErrMsg(message);
            throw sysError;
        }
    }

0

评论区