【问题标题】:what kind of method can be advised by spring aop or what's a spring aop joinpoint's limitation?spring aop 可以建议什么样的方法或 spring aop 连接点的限制是什么?
【发布时间】:2020-04-23 08:31:42
【问题描述】:

我正在使用 spring aop,我发现有 3 种情况,但我不太清楚: 情况 1:单个类不实现或扩展任何类或接口 在这种情况下,任何非私有方法都将成为连接点

情况2:类实现接口,实现方法 在这种情况下,只有在接口中声明的方法才会是连接点

情况3:一个类扩展了一个超类并覆盖了超类的方法 在这种情况下,所有子类的方法都不会是连接点。

spring aop是这样设计的吗?

这是我使用的代码:

JdkProxyInterface.java

package com.example.proxytestdemo;

public interface JdkProxyInterface {

    void function(int i);
}

JdkProxy.java

package com.example.proxytestdemo;
import org.springframework.stereotype.Component;

@Component
public class JdkProxy implements JdkProxyInterface {

    @Override
    @TimeIt
    public void function(int i) {
        System.out.println("JdkProxy function");
    }

    @TimeIt
    public void function1(int i) {
        System.out.println("JdkProxy function");
    }

    @TimeIt
    public void function2(int i) {
        System.out.println("JdkProxy function");
    }

}

SubJdkProxy.java

package com.example.proxytestdemo;

import org.springframework.stereotype.Component;

@Component
public class SubJdkProxy extends JdkProxy {

    @TimeIt
    public void functionSubJdkProxy(int i) {
        System.out.println("functionSubJdkProxy");
    }
}

TimeIt.java

package com.example.proxytestdemo;
import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
@Inherited
public @interface TimeIt {
    boolean enabled() default true;
}

TimePointCut.java

package com.example.proxytestdemo;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;


@Aspect
@Component
public class TimePointCut {

    @Pointcut("execution(* com.example.proxytestdemo..*(..))")
    public void calcTime1() {
    }

    @Around(value = "calcTime1()")
    public Object aspectProcess(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("proxy begin....... ");
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        TimeIt annotation = method.getAnnotation(com.example.proxytestdemo.TimeIt.class);
        if (annotation == null) {
            annotation = pjp.getTarget().getClass().getAnnotation(TimeIt.class);
            if (annotation == null) {
                for (Class<?> cls : pjp.getClass().getInterfaces()) {
                    annotation = cls.getAnnotation(TimeIt.class);
                    if (annotation != null) {
                        break;
                    }
                }
            }
        }
        if (annotation != null) {
            System.out.println(annotation.enabled());
        }
        Object o = null;
        long t1 = 0, t2 = 0;
        try {
            t1 = System.currentTimeMillis();
            o = pjp.proceed();
            t2 = System.currentTimeMillis();
        } catch (Exception e) {
            throw e;
        } finally {
            System.out.println("proxy end....... ");
            System.out.println("time cost: "+ (t2-t1)/1000 + "s");
        }

        return o;
    }
}

我发现 JdkProxy.function1() 和 JdkProxy.function2() 和 SubJdkProxy.functionSubJdkProxy() 不能建议。

对不起,由于IDEA的提示,我犯了一个错误。 IDEA's hint

【问题讨论】:

  • AOP 将作用于您在 PointCut 正则表达式中指定的位置!
  • 是的,但有一些限制,例如,我不能建议从另一个类扩展的类方法。
  • 如何调用方法 function1()function2()functionSubJdkProxy() ?如果可以调用这些方法,则可以拦截所有 3 个方法。您能否分享调用这些方法的代码以便拦截调用?
  • 对我来说,您的代码也可以正常工作。你必须犯一个配置错误。所以请展示你如何启动和配置你的应用程序。

标签: java spring spring-aop


【解决方案1】:

您的应用程序应该可以工作。看,我尝试了各种组合,它们都有效:

驱动程序应用:

package com.example.proxytestdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class Application {
  public static void main(String[] args) {
    try (ConfigurableApplicationContext context = SpringApplication.run(Application.class, args)) {
      doStuff(context);
    }
  }

  private static void doStuff(ConfigurableApplicationContext context) {
    JdkProxy jdkProxy = (JdkProxy) context.getBean("jdkProxy");
    jdkProxy.function(11);
    jdkProxy.function1(22);
    jdkProxy.function2(33);
    System.out.println("----------");

    JdkProxyInterface jdkProxyInterface = jdkProxy ;
    jdkProxyInterface.function(11);
    System.out.println("==========");

    SubJdkProxy subJdkProxy = (SubJdkProxy) context.getBean("subJdkProxy");
    subJdkProxy.function(11);
    subJdkProxy.function1(22);
    subJdkProxy.function2(33);
    subJdkProxy.functionSubJdkProxy(44);
    System.out.println("----------");

    jdkProxyInterface = subJdkProxy;
    jdkProxyInterface.function(11);
  }
}

控制台日志:

execution(void com.example.proxytestdemo.JdkProxy.function(int))
JdkProxy function
execution(void com.example.proxytestdemo.JdkProxy.function1(int))
JdkProxy function
execution(void com.example.proxytestdemo.JdkProxy.function2(int))
JdkProxy function
----------
execution(void com.example.proxytestdemo.JdkProxy.function(int))
JdkProxy function
==========
execution(void com.example.proxytestdemo.JdkProxy.function(int))
JdkProxy function
execution(void com.example.proxytestdemo.JdkProxy.function1(int))
JdkProxy function
execution(void com.example.proxytestdemo.JdkProxy.function2(int))
JdkProxy function
execution(void com.example.proxytestdemo.SubJdkProxy.functionSubJdkProxy(int))
functionSubJdkProxy
----------
execution(void com.example.proxytestdemo.JdkProxy.function(int))
JdkProxy function

顺便说一句,为了专注于基础知识,我将您方面的建议方法简化为:

      @Around(value = "calcTime1()")
      public Object aspectProcess(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println(pjp);
        return pjp.proceed();
      }

更新:

Spring Boot 默认为 CGLIB 代理模式,目前无法轻松重新配置为使用 JDK 代理,因为相应的注释被忽略或取代,请参阅 issue #12194。但我找到了一种方法,见my comment on Spring Boot #27375。你需要把它放到你的application.properties

spring.aop.proxy-target-class=false

然而,plain vanilla Spring 默认使用 JDK 代理模式。你必须在你的类路径上建立一个没有任何 Boot 依赖的经典 Spring 项目。但是当然只有接口定义的方法被代理,并且您不能使用在接口之外定义的方法。您也可以将 Spring 切换到 CGLIB 模式,但不能将 Boot 切换到 JDK 模式。

因为这是一个常见问题,而且无论如何我都喜欢有一个游乐场项目来回答相关问题,所以为了您的方便,我发布了this GitHub project。随意检查、克隆并使用它。


2022-02-26 更新:Here 您可以了解如何使用 Spring 自己的 AopUtils 帮助类来确定 Spring AOP 代理类型(JDK 与 CGLIB 代理)。

【讨论】:

  • 谢谢你,kriegaex。我昨天可能犯了一个错误,我使用了Idea,当我改变我的切入点表达式时它给了我一些提示,比如Idea hint,我认为这是正确的,所以我不是真的运行我的代码。您是对的,代码有效,可以建议 3 种方法。对不起我的错误o(╥﹏╥)o
  • 我还有一个小问题:如何让spring aop使用jdk动态代理?在这种情况下它总是使用cglib来代理我的类。非常感谢。
  • 更新:我在更新的答案中回答了你的后续问题,并为你发布了一个示例项目的链接。
  • 2022-02-26 更新: Here 您可以学习如何使用 Spring 自己的 AopUtils 帮助程序类来确定 Spring AOP 代理类型(JDK 与 CGLIB 代理) .
猜你喜欢
  • 2015-08-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多