第四十五讲-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关键字修饰的方法的增强。

posted @   LilyFlower  阅读(11)  评论(0编辑  收藏  举报
编辑推荐:
· .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语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示