背景
想对 runnable 这些类进行增强。
@DeclareParents的作用
@DeclareParents注解是spring AOP(切面)功能提供一种组件,它可以在代理目标类上增加新的行为(新增新的方法)。
可能你看到这里会有点蒙,没关系我们看下面的栗子,应该就清楚明白了。
@DeclareParents的应用demo
首先我们先通过一张图简单的理解一下,@DeclareParents注解实现的思路(注: 该图来源于《Spring in action中文版》一书,想要资料可以联系我)。
被代理的类(现有的行为-方法)
package com.swh.test.delareParents;
/**
* 现有行为方法
*/
public interface IPay {
void pay();
}
package com.swh.test.delareParents;
import org.springframework.stereotype.Component;
@Component("wechatPay")
public class WechatPay implements IPay{
@Override
public void pay() {
System.out.println("wechat pay");
}
}
代理增强类
package com.swh.test.delareParents;
/**
* 增强的行为方法
*/
public interface IPayEnhance {
void payType();
}
package com.swh.test.delareParents;
import org.springframework.stereotype.Component;
/**
* 增强类的实现
*/
@Component
public class WechatPayEnhance implements IPayEnhance{
@Override
public void payType() {
System.out.println("this pay type is wechat!");
}
}
组装类
package com.swh.test.delareParents;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class PayAspectJ {
@DeclareParents(value = "com.swh.test.delareParents.IPay+",defaultImpl = WechatPayEnhance.class)
public IPayEnhance wechatPayEnhance;
}
bean的配置类
package com.swh.test.delareParents;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class AnnotationConfig {
}
@EnableAspectJAutoProxy 注解的作用启用AspectJ自动代理,会为使用@Aspect注解的bean创建一个代理,如果没有这个注解,则从spring获取bean时获取的原始类,本栗子中代理的是 IPay 接口。spring在创建WechatPay 类时,会创建一个代理类。
调用者
package com.swh.test;
import com.swh.test.delareParents.IPay;
import com.swh.test.delareParents.IPayEnhance;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestAA {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext("com.swh.test");
IPay wechatPay = (IPay) annotationConfigApplicationContext.getBean("wechatPay");
wechatPay.pay();
IPayEnhance wechatPayEnhance = (IPayEnhance) wechatPay;
wechatPayEnhance .payType();
}
}
执行结果分析
根据执行过程中的debug可见,从spring容器中获取的wechatPay bean是一个代理对象,并且实现了两个接口,分别是IPay 和 IPayEnhance ,因此这个代理类则拥有了IPay和IPayEnhance两种接口的行为。
@DeclareParents 的结构
@DeclareParents注解由三部分组成:
value 属性指定了哪种类型的bean要引入新的接口行为。(注:本栗子中value是IPay 并在IPay后面多了一个+,该+标识IPay下面的所有子类型,而不是IPay本身)
defaultImpl 属性指定了新提供的行为接口的实现类。本栗子我们引入的是WechatPayEnhance类
@DeclareParents 标注的类标识需要引入的新提供的行为接口
@DeclareParents 小结
@DecalereParents 可以为已经存在的Bean扩展新的行为方法,有的人会问为什么不直接在原始的接口中新加方法。
当然这种也是可以的,但是这样做有个问题,加入原始接口下面有好多实现类,则需要对所有的实现类进行修改。
这样做功能能够完成,但是从设计角度来看并不是最好的做法。
况且有的时候根本没有机会修改所有的实现类,比如假如引用的原始类是第三方提供的并且没有源码。这种情况就没有办法通过直接扩展原始类来解决问题。
当然还有其他的的应用场景,大家有什么不同的见解或者有不明白的地方,可以在评论区留言评论,我们一起交流。
chat
Q1-还有其他方法吗?通过 spring aop 能实现吗
Spring AOP 默认情况下只能代理基于接口的类,而无法直接拦截类中的方法。然而,你可以通过一些技巧来尝试在 Spring AOP 中拦截类的方法,尽管这可能会有限制和局限性。
一种方法是创建一个包装类来实现 java.util.concurrent.Executor
接口,然后将实际的 Executor
对象委托给这个包装类。你可以在包装类中实现接口的方法,并在方法内部调用实际的 Executor
方法。这样,你可以使用 Spring AOP 拦截这个包装类的方法。
以下是一个示例,演示如何通过包装类和 Spring AOP 来拦截 java.util.concurrent.Executor
的方法:
- 创建包装类: 创建一个实现了
java.util.concurrent.Executor
接口的包装类。
import java.util.concurrent.Executor;
public class ExecutorWrapper implements Executor {
private Executor actualExecutor;
public ExecutorWrapper(Executor actualExecutor) {
this.actualExecutor = actualExecutor;
}
@Override
public void execute(Runnable command) {
// 在方法调用前添加逻辑
System.out.println("Before executing Executor method.");
// 调用实际的 Executor 方法
actualExecutor.execute(command);
// 在方法调用后添加逻辑
System.out.println("After executing Executor method.");
}
}
- 创建切面类: 创建一个 Spring AOP 切面类,拦截包装类的方法。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class ExecutorAspect {
@DeclareParents(value = "java.util.concurrent.Executor+", defaultImpl = ExecutorWrapper.class)
private static Executor mixin;
}
在这个示例中,我们使用了 @DeclareParents
注解将 ExecutorWrapper
类添加到所有实现了 java.util.concurrent.Executor
接口的类中。
虽然这个方法能够让你在 Spring AOP 中拦截 java.util.concurrent.Executor
接口的方法,但也需要你进行包装和代理,可能会引入一些额外的复杂性。
如果需要更广泛和深入的方法拦截功能,你可能需要考虑使用 AspectJ 等更强大的 AOP 框架。
Q2-详细解释一下 @DeclareParents
@DeclareParents
是 AspectJ 提供的一个注解,也在 Spring AOP 中得到了支持。它用于向现有类引入新的接口和/或增加新的字段。
当你希望给一个现有的类引入新的接口,但又不想修改这个类的源代码时,就可以使用 @DeclareParents
注解。这在一些情况下非常有用,特别是当你希望在 AOP 切面中拦截这个新接口的方法。
下面是 @DeclareParents
注解的详细解释:
@DeclareParents(value = "目标类型", defaultImpl = 默认实现类)
value
:这个属性指定了目标类型,即你希望引入新接口的类或一组类。你可以使用 AspectJ 切点表达式来匹配目标类型。defaultImpl
:这个属性指定了默认的实现类,它是一个实现了新引入接口的默认行为的类。当目标类型在运行时被代理时,将会使用这个实现类。
@DeclareParents
注解的作用是在编译时为目标类型生成一个新的子类,这个子类包含了新引入的接口的实现。然后,通过 Spring AOP 或 AspectJ,可以将这个新的子类用作目标对象,从而实现对新接口方法的拦截。
以下是一个更完整的示例,演示如何使用 @DeclareParents
注解在 Spring AOP 中引入新接口:
public interface MyInterface {
void myMethod();
}
public class MyClass {
// 原始类,不实现 MyInterface
}
@Aspect
@Component
public class InterfaceIntroductionAspect {
@DeclareParents(value = "com.example.MyClass", defaultImpl = MyInterfaceImpl.class)
private MyInterface mixin;
}
public class MyInterfaceImpl implements MyInterface {
@Override
public void myMethod() {
System.out.println("Intercepted method call through introduced interface.");
}
}
// 在其他类中
@Component
public class MainService {
@Autowired
private MyClass myClass;
public void invokeMyMethod() {
myClass.myMethod(); // 这里实际上调用的是 MyInterfaceImpl 的方法
}
}
在上述示例中,通过 @DeclareParents
注解,我们为 MyClass
引入了 MyInterface
接口,实现了新接口的方法。
这样,在 MainService
中,调用 myClass.myMethod()
实际上调用的是 MyInterfaceImpl
的方法。
Q3-如何使用 @DeclareParents 的方式,对 Runnable 进行增强?
使用 @DeclareParents
来为 java.lang.Runnable
接口引入新接口并增强它的功能,可以通过以下步骤完成:
- 定义新接口: 首先,你需要定义一个新的接口,该接口将包含你希望在
Runnable
上引入的新方法。
public interface EnhancedRunnable {
void beforeRun();
void afterRun();
}
- 实现新接口: 创建一个实现了
EnhancedRunnable
接口的类,实现新接口的方法。
public class EnhancedRunnableImpl implements EnhancedRunnable {
@Override
public void beforeRun() {
System.out.println("Before run");
}
@Override
public void afterRun() {
System.out.println("After run");
}
}
- 创建切面: 使用
@DeclareParents
注解来为java.lang.Runnable
接口引入新接口。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class RunnableIntroductionAspect {
@DeclareParents(value = "java.lang.Runnable+", defaultImpl = EnhancedRunnableImpl.class)
private EnhancedRunnable mixin;
}
在这个示例中,我们使用 @DeclareParents
注解来为 java.lang.Runnable
接口引入 EnhancedRunnable
接口,同时指定了默认的实现类 EnhancedRunnableImpl
。
- 使用增强后的接口: 现在你可以使用增强后的
EnhancedRunnable
接口,并调用新增的方法。
public class Main {
public static void main(String[] args) {
EnhancedRunnable enhancedRunnable = new EnhancedRunnableImpl();
enhancedRunnable.beforeRun();
Runnable runnable = (Runnable) enhancedRunnable;
runnable.run();
enhancedRunnable.afterRun();
}
}
在上述示例中,我们创建了一个对象,既实现了 EnhancedRunnable
接口,也是 Runnable
接口的实现类。
我们可以调用 EnhancedRunnable
的新增方法,同时在 run
方法执行前后,会触发 EnhancedRunnable
接口中的方法。
小结
参考资料
chat