spring aop 可以建议哪种方法,或者 spring aop 连接点的限制是什么?
what kind of method can be advised by spring aop or what's a spring aop joinpoint's limitation?
我正在使用 spring aop,我发现有 3 种情况,但我不太清楚:
情况 1:单个 class 不实现或扩展任何 class 或接口
在这种情况下,任何 none-private 方法都将成为连接点
情况2:一个class实现了一个接口,实现了方法
在这种情况下,只有在接口中声明的方法才是连接点
情况 3:class 扩展超级 class 并覆盖超级 class 的方法
在这种情况下,所有 sub class 的方法都不是连接点。
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");
}
}
亚JdkProxy.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
您的应用程序应该可以运行。看,我尝试了各种组合,它们都有效:
驱动申请:
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 引导默认为 CGLIB 代理模式,目前无法轻松地重新配置为使用 JDK 代理,因为相应的注释被忽略或取代,请参阅issue #12194. But I found a way, see my comment on Spring Boot #27375。您需要将其放入 application.properties
:
spring.aop.proxy-target-class=false
但是,普通 Spring 默认 JDK 代理模式 。您必须设置一个 classical Spring 项目,并且在您的 class 路径上没有任何引导依赖项。但是当然只有接口定义的方法被代理,你不能使用在接口之外定义的方法。您也可以将 Spring 切换到 CGLIB 模式,但不能切换到 JDK 模式。
因为这是一个常见问题,而且我喜欢有一个 playground 项目来回答相关问题,所以为了方便大家,我发布了 this GitHub project。随意检查它,克隆它并试用它。
2022-02-26 更新: Here 您可以学习如何确定 Spring AOP 代理类型(JDK vs. CGLIB代理)使用 Spring 自己的 AopUtils
助手 class.
我正在使用 spring aop,我发现有 3 种情况,但我不太清楚: 情况 1:单个 class 不实现或扩展任何 class 或接口 在这种情况下,任何 none-private 方法都将成为连接点
情况2:一个class实现了一个接口,实现了方法 在这种情况下,只有在接口中声明的方法才是连接点
情况 3:class 扩展超级 class 并覆盖超级 class 的方法 在这种情况下,所有 sub class 的方法都不是连接点。
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");
}
}
亚JdkProxy.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
您的应用程序应该可以运行。看,我尝试了各种组合,它们都有效:
驱动申请:
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 引导默认为 CGLIB 代理模式,目前无法轻松地重新配置为使用 JDK 代理,因为相应的注释被忽略或取代,请参阅issue #12194. But I found a way, see my comment on Spring Boot #27375。您需要将其放入 application.properties
:
spring.aop.proxy-target-class=false
但是,普通 Spring 默认 JDK 代理模式 。您必须设置一个 classical Spring 项目,并且在您的 class 路径上没有任何引导依赖项。但是当然只有接口定义的方法被代理,你不能使用在接口之外定义的方法。您也可以将 Spring 切换到 CGLIB 模式,但不能切换到 JDK 模式。
因为这是一个常见问题,而且我喜欢有一个 playground 项目来回答相关问题,所以为了方便大家,我发布了 this GitHub project。随意检查它,克隆它并试用它。
2022-02-26 更新: Here 您可以学习如何确定 Spring AOP 代理类型(JDK vs. CGLIB代理)使用 Spring 自己的 AopUtils
助手 class.