在日常开发中,我们常常会遇到这样的需求:要在原有的核心业务逻辑(比如数学计算)中,加入一些通用的辅助功能(比如打印日志、权限校验)。

如果直接修改核心代码,在里面写满 System.out.println(),不仅会让代码变得臃肿不堪(耦合度极高),后续如果日志格式需要变动,还会带来极大的维护灾难。

为了优雅地解决这个问题,Java 引入了经典的设计模式——静态代理模式(Static Proxy)。本文将带你从零开始,通过具体的代码实例拆解,彻底搞懂静态代理的运行流向与核心思想。


一、 核心架构梳理

要实现一个标准的静态代理,我们的程序通常需要由四个核心组件构成。理清它们的关系,是理解代理模式的第一步:

  1. 接口(Interface):定义一套规范(例如 MathCalculator)。无论是真正的业务类还是代理类,都必须遵守这套规范。

  2. 目标类(Target):负责实现真正的核心业务逻辑(例如 MathCalculatorlmpl)。它极其纯粹,只包含业务代码

  3. 代理类(Proxy):同样实现该接口。它的核心特点是内部包装了一个目标类,并在调用目标类前后,插入额外的辅助代码(例如 CalculatorStaticProxy)。

  4. 客户端/测试类:程序的入口,负责将目标类和代理类组装起来运行(例如 MathTest)。


二、 代码逐级拆解与执行分析

下面我们按照开发顺序,一步步实现带有日志增强的计算器程序。

1. 制定接口规范

接口负责声明程序应该具备哪些功能,这里我们定义了加减乘除四个方法。

// MathCalculator.java
package com.example.springaop.calculator; 

public interface MathCalculator {
    int add(int a, int b); // 加法
    int sub(int a, int b); // 减法
    int mul(int a, int b); // 乘法
    int div(int a, int b); // 除法
}

2. 编写纯粹的核心业务(目标类)

实现上述接口,专注于数学计算,绝对不掺杂任何日志代码

// MathCalculatorlmpl.java
package com.example.springaop.calculator.impl;

import com.example.springaop.calculator.MathCalculator;
import org.springframework.stereotype.Component;

@Component 
public class MathCalculatorlmpl implements MathCalculator {
    
    @Override
    public int add(int a, int b) {
        int result = a + b; // 纯粹的核心逻辑:计算加法
        System.out.println("结果:"+result); 
        return result; 
    }
    
    // ... sub、mul、div 方法同理省略
}

3. 核心魔法:编写代理类

为了在不修改原目标类的情况下加上日志,我们需要一个“包装器”。

// CalculatorStaticProxy.java
package com.example.springaop.proxy.statics;

import com.example.springaop.calculator.MathCalculator;

public class CalculatorStaticProxy implements MathCalculator { 

    // 关键点1:内部必须持有一个真正的目标对象
    private MathCalculator target; 

    // 关键点2:通过构造方法,把真正的目标对象注入进来
    public CalculatorStaticProxy(MathCalculator mc) {
        this.target = mc;
    }

    @Override
    public int add(int a, int b) {
        // 【前置增强】:核心计算前,打印日志
        System.out.println("[日志]执行了add方法"+a+","+b);
        
        // 【核心调用】:通过内部引用 target,调用真正的计算公式
        int add = target.add(a, b); 
        
        // 【后置增强】:核心计算后,打印日志
        System.out.println("[日志]add返回"+add);
        
        return add;
    }
}

三、 测试运行与控制台输出流向

代码写好了,它们是如何相互配合的呢?我们来看看测试代码及其输出结果。

// MathTest.java
package com.example.springaop;

import org.junit.jupiter.api.Test; 

public class MathTest {

    @Test 
    void test01() {
        // --- 对比测试 A:无代理模式 ---
        MathCalculator target = new MathCalculatorlmpl();
        target.add(1, 2); 
        System.out.println("测试完成...");

        /* * [情况 A 输出结果] 没有任何日志,只有死板的计算。
         * 结果:3
         * 测试完成...
         */


        // --- 对比测试 B:静态代理模式 ---
        // 步骤1:创建代理对象,把 target 包装进代理对象中
        CalculatorStaticProxy proxy = new CalculatorStaticProxy(target);
        
        // 步骤2:客户端不再直接调用 target,而是调用代理对象 proxy
        int add = proxy.add(1, 2); 
        System.out.println(add); // 最后打印返回值
        
        /* * [情况 B 输出结果] 完美实现了日志增强!代码流向如下:
         * [日志]执行了add方法1,2     <-- 代理类拦截,执行【前置增强】
         * 结果:3                    <-- 代理类放行,目标类执行【真实计算】
         * [日志]add返回3             <-- 代理类拦截,执行【后置增强】
         * 3                          <-- 测试类最后打印最终结果
         */
    }
}

四、 总结与进阶思考

通过引入静态代理模式,我们完美遵守了软件开发中的单一职责原则

  • 业务类:这辈子只管计算公式。

  • 代理类:这辈子只负责打印日志。

解耦带来的好处是巨大的:如果我们今天决定把日志从控制台打印改成写入到本地文件中,只需要修改 CalculatorStaticProxy 即可,核心的数学公式代码一行都不需要动,极大降低了引发新 Bug 的风险。

🤔 引申思考:静态代理的“死穴”

静态代理虽然优雅,但有一个致命缺点——必须手动编写代理类。 试想一下,如果你开发的系统中不仅有计算器,还有订单系统、用户系统、支付系统,共计上百个接口都需要加日志,难道我们要手动写上百个像 CalculatorStaticProxy 这样的代理类吗?这显然是不现实的。

为了解决这个“类爆炸”的问题,Java 演进出了 动态代理(Dynamic Proxy) 技术,而大名鼎鼎的 Spring AOP(面向切面编程) 正是基于此原理,能够在程序运行时自动帮我们生成代理对象。彻底解放开发者的双手!

 

更多推荐