第四十五讲-Spring代理的特点
第四十五讲-Spring代理的特点
本讲我们来演示一下Spring中代理类的特点。
1. 依赖注入和初始化阶段影响的是原始对象
2. 代理和目标是两个对象,两者成员变量并不公用数据
3. static方法, final方法, private 方法均无法实现增强!
换句话说,依赖注入是给原始对象进行依赖注入,初始化也是给原始对象进行初始化,这两个阶段此时和代理并没有关系。
下面我们举个例子来说明一下:
@Component
public class Bean1 {
private static final Logger log = LoggerFactory.getLogger(Bean1.class);
protected Bean2 bean2;
protected boolean initialized;
@Autowired
public void setBean2(Bean2 bean2) {
log.debug("setBean2(Bean2 bean2)");
this.bean2 = bean2;
}
@PostConstruct
public void init() {
log.debug("init");
initialized = true;
}
public Bean2 getBean2() {
log.debug("getBean2()");
return bean2;
}
public boolean isInitialized() {
log.debug("isInitialized()");
return initialized;
}
public void m1() {
System.out.println("m1() 成员方法");
}
final public void m2() {
System.out.println("m2() final 方法");
}
static public void m3() {
System.out.println("m3() static 方法");
}
private void m4() {
System.out.println("m4() private 方法");
}
}
@Component
public class Bean2 { }
@Aspect
@Component
public class MyAspect {
// 故意对所有方法增强
@Before("execution(* com.cherry.a45.Bean1.*(..))")
public void before() {
System.out.println("before");
}
}
@SpringBootApplication
public class A45 {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(A45.class, args);
Bean1 proxy = context.getBean(Bean1.class);
/*
1.演示 spring 代理的设计特点
依赖注入和初始化影响的是原始对象
代理与目标是两个对象,二者成员变量并不共用数据
*/
// showProxyAndTarget(proxy);
//
// System.out.println(">>>>>>>>>>>>>>>>>>>");
// System.out.println(proxy.getBean2());
// System.out.println(proxy.isInitialized());
/*
2.演示 static 方法、final 方法、private 方法均无法增强
*/
proxy.m1();
proxy.m2();
proxy.m3();
Method m4 = Bean1.class.getDeclaredMethod("m1");
m4.setAccessible(true);
m4.invoke(proxy);
context.close();
}
public static void showProxyAndTarget(Bean1 proxy) throws Exception {
System.out.println(">>>>> 代理中的成员变量");
System.out.println("\tinitialized=" + proxy.initialized);
System.out.println("\tbean2=" + proxy.bean2);
if (proxy instanceof Advised advised) {
System.out.println(">>>>> 目标中的成员变量");
Bean1 target = (Bean1) advised.getTargetSource().getTarget();
System.out.println("\tinitialized=" + target.initialized);
System.out.println("\tbean2=" + target.bean2);
}
}
}
我们写了一个切面类,是对Bean1类中的所有方法(set,init)都Y应该实现功能增强。那我们看一下,在执行依赖注入和初始化的时候,Bean2中的方法会不会被增强。我们来测试一遍:
[DEBUG]14:59:54.097[main] com.itheima.a45.Bean setBean2(Bean2 bean2)
[DEBUG]14:59:54.098 [main]com.itheima.a45.Bean1 -init
[TRACE]14:59:54.108 [main]o.s.a.a.a.AnnotationAwareAspectJAutoProxyCreator Creating implicit.proxy for bean bean1'with 0 common interceptors
before
[DEBUG]15:01:21.040 [main]com.itheima.a45.Bean1 setBean2(Bean2bean2)
before
[DEBUG]15:01:21.040 [main]com.itheima.a45.Bean1 init
[INFO 15:01:21.072 [main]c.a.druid.pool.DruidDataSource {dataSource-1 closing ..
[INFO 15:01:21.074 [main]c.a.druid.pool.DruidDataSource {dataSource-1}closed
从日志里可以看到,依赖注入(setBean2)和初始化方法(init)都执行了,但是并没有被增强,其实是没有的,如果增强了,那么这两个方法的前置通知就会执行,但是此处并没有执行。换句话说,依赖注入和初始化方法调用的并不是代理对象增强的setBean2()和init()方法,而是原始对象的setBean2()和init()方法!还记得我们之前讲过的吗:代理对象是在bean初始化之后才创建的,从这一点也可以认为依赖注入阶段和初始化阶段是并不存在代理对象的,更不会调用代理对象的增强方法的!
当我们从容器中获取的bean对象,其实就是获取的是代理的bean对象,此时再调用该bean对象,就会增强该对象的方法!
需要注意的是,Spring中的容器是不存在原始对象的,所有的单例都是代理对象,如果想要获取目标对象,就需要将代理对象转为一个Advised
接口,因为Spring在创建代理时会为每个代理实现一个叫做Advised
的接口。转成接口之后,接口中有一个getTargetSource().getTarget()
方法来获取目标,如上面的showProxyAndTarget
方法。
>>>>>代理中的成员变量
initialized=false
bean2=null
>>>>>目标中的成员变量
initialized=true
bean2=com.cherry.a45.Bean204b765e92
为什么代理中的成员变量的初始化和依赖注入是false和null,上面已经说了,代理对象的创建是不走依赖注入和初始化方法的,而是在初始化方法之后创建的,也就是说代对象跳过了依赖注入和初始化阶段!
注意,对于要增强的方法,该方法不能是静态方法、被final关键字修饰、被private关键字修饰的方法,这些方法是无法通过代理实现功能增强的!换句话说,代理能增强的方法是那些能够重写的方法,不能被重写的方法是无法通过代理实现增强的!这里要特别注意。
如上面测试代码中的m1, m2, m3, m4方法:
public void m1() {
System.out.println("m1() 成员方法");
}
final public void m2() {
System.out.println("m2() final 方法");
}
static public void m3() {
System.out.println("m3() static 方法");
}
private void m4() {
System.out.println("m4() private 方法");
}
// 主方法中:
proxy.m1();
proxy.m2();
proxy.m3();
Method m4 = Bean1.class.getDeclaredMethod("m1");
m4.setAccessible(true);
m4.invoke(proxy);
测试如下:
before
m1()成员方法
m2()final方法
m3()static方法
m4()private方法
我们发现,只有成员方法被增强了;
由于代理类是通过继承父类重写父类方法来实现功能增强的,父类方法被final关键子修饰了,导致子类无法重写父类的该方法,因此不会实现增强,因此走的还是原始对象的逻辑;
对于静态方法是不支持重写的,解释和上面的解释类似;
对于私有方法,子类是不能重写父类的私有方法的,解释和上面的类似。
当然,讲到这里,我们以前说过,如果想突破上面的限制,我们可以使用之前最初介绍的非代理的AOP模式,例如编译时增强的AspectJ和类加载时增强agent,这两个能够突破上面的限制,能够实现静态方法、被final关键字修饰、被private关键字修饰的方法的增强。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构