从原理到实践:代理模式如何在项目中落地详解(设置代理控制访问原对象)

87 阅读5分钟

设置一个中间代理来控制访问原目标对象,达到增强原对象的功能和简化访问方式的目的
场景:
场景一:不改变原方法,对原方法增加耗时的计算
场景二:rpc远程调用,client端进行动态代理类似耗时计算一样,用户不用关心client的具体实现
分类

  • 静态代理模式
  • 动态代理模式

3.1、概念

Java代理模式的核心思想是通过一个代理对象来控制对另一个对象的访问。代理对象与被代理对象具有相同的接口,因此可以在任何时候替换原始对象,并且可以通过代理对象来实现对原始对象的访问控制和管理。

代理模式主要包括两个角色:抽象角色和代理角色。其中,抽象角色定义了代理对象和被代理对象的公共接口,代理角色实现了抽象角色,并持有一个指向被代理对象的引用。通过代理角色来调用被代理对象的方法,实际上是调用了被代理对象的方法,并可以在执行前后添加一些额外的操作,如日志记录、性能监控等。

Java代理模式通常有两种实现方式:

  1. 静态代理:代理类和被代理类都必须实现同一个接口,在代码中显式地定义代理类。
  2. 动态代理:动态生成代理类,代理类不需要实现接口,可以在运行时动态地生成代理类。

Java代理模式常用于以下场景:

  1. 远程代理:客户端通过代理对象访问远程服务器上的对象。
  2. 虚拟代理:用于延迟加载资源,例如在Web页面中显示大量图片时,可以使用虚拟代理来只在需要时才加载图片。
  3. 安全代理:用于控制对敏感对象的访问,例如只有管理员才能访问某些特定的对象。

总之,Java代理模式通过代理对象实现了对被代理对象的控制和管理,提高了程序的可维护性和安全性,在软件开发中具有广泛的应用。

3.2、示例代码

  • 静态代理模式

以下是一个简单的Java静态代理模式示例代码,实现了对一个Calculator接口的加法操作的代理控制:

public interface Calculator {
    int add(int a, int b);
    int subtract(int a, int b);
}

public class SimpleCalculator implements Calculator {

    @Override
    public int add(int a, int b) {
        return a + b;
    }

    @Override
    public int subtract(int a, int b) {
        return a - b;
    }
}

public class CalculatorProxy implements Calculator {
    private final Calculator calculator;

    public CalculatorProxy(Calculator calculator) {
        this.calculator = calculator;
    }

    @Override
    public int add(int a, int b) {
        System.out.println("before add operation");
        int result = calculator.add(a, b);
        System.out.println("after add operation");
        return result;
    }

    @Override
    public int subtract(int a, int b) {
        System.out.println("before subtract operation");
        int result = calculator.subtract(a, b);
        System.out.println("after subtract operation");
        return result;
    }
}

public class Main {
    public static void main(String[] args) {
        Calculator calculator = new SimpleCalculator();
        Calculator proxy = new CalculatorProxy(calculator);

        System.out.println(proxy.add(1, 2));
        System.out.println(proxy.subtract(4, 2));

    }
}

在上面的代码中,定义了一个Calculator接口和它的实现类SimpleCalculator。然后,定义了一个代理类CalculatorProxy,它实现了Calculator接口,并包含一个Calculator类型的成员变量calculator。代理类通过调用被代理对象的方法来实现相同的功能,并在执行前后输出相应的日志信息。

在Main类中,创建了一个SimpleCalculator对象,并将其传递给CalculatorProxy构造函数来创建代理对象。最后,通过代理对象调用Calculator接口中的方法来实现对加减乘除操作的代理控制。

  • 动态代理模式

Java实现动态代理的方案有以下两种:

1)基于Java原生的java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现动态代理。

代码示例:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public interface Calculator {
    int add(int a, int b);
    int subtract(int a, int b);
}

public class SimpleCalculator implements Calculator {

    @Override
    public int add(int a, int b) {
        return a + b;
    }

    @Override
    public int subtract(int a, int b) {
        return a - b;
    }

}

public class CalculatorInvocationHandler implements InvocationHandler {
    private final Object target;

    public CalculatorInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before " + method.getName() + " operation");
        Object result = method.invoke(target, args);
        System.out.println("after " + method.getName() + " operation");
        return result;
    }
}

public class Main {
    public static void main(String[] args) {
        Calculator calculator = new SimpleCalculator();
        InvocationHandler invocationHandler = new CalculatorInvocationHandler(calculator);

        Calculator proxy = (Calculator) Proxy.newProxyInstance(
            calculator.getClass().getClassLoader(),
            calculator.getClass().getInterfaces(),
            invocationHandler);

        System.out.println(proxy.add(1, 2));
        System.out.println(proxy.subtract(4, 2));
    }
}

2)基于第三方库,如CGLIB和Byte Buddy等实现动态代理。

代码示例:

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.matcher.ElementMatchers;

public interface Calculator {
    int add(int a, int b);
    int subtract(int a, int b);
    int multiply(int a, int b);
    int divide(int a, int b);
}

public class SimpleCalculator implements Calculator {

    @Override
    public int add(int a, int b) {
        return a + b;
    }

    @Override
    public int subtract(int a, int b) {
        return a - b;
    }

}

public class Main {
    public static void main(String[] args) throws Exception {
        Class<? extends Calculator> proxyClass = new ByteBuddy()
            .subclass(Calculator.class)
            .method(ElementMatchers.any())
            .intercept(MethodDelegation.to(CalculatorInterceptor.class))
            .make()
            .load(Main.class.getClassLoader())
            .getLoaded();

        Calculator calculator = proxyClass.newInstance();
        System.out.println(calculator.add(1, 2));
        System.out.println(calculator.subtract(4, 2));
    }

    public static class CalculatorInterceptor {
        @RuntimeType
        public static Object intercept(@Origin Method method, @AllArguments Object[] args) throws Exception {
            System.out.println("before " + method.getName() + " operation");
            Object result = method.invoke(new SimpleCalculator(), args);
            System.out.println("after " + method.getName() + " operation");
            return result;
        }
    }
}

其中,第一种方案是通过Java原生API实现动态代理,而第二种方案则是基于Byte Buddy库来实现动态代理。

3.3、实现落地

以下是一个使用Java代理模式实现安全代理的示例代码:

public interface UserService {
    void addUser(String name, String password);
}

public class UserServiceImpl implements UserService {

    @Override
    public void addUser(String name, String password) {
        System.out.println("Adding user...");
    }
}

public class UserServiceProxy implements UserService {
    private final UserService userService;

    public UserServiceProxy(UserService userService) {
        this.userService = userService;
    }

    @Override
    public void addUser(String name, String password) {
        if (!isAdmin()) {
            throw new SecurityException("Only administrator can add new user");
        }

        userService.addUser(name, password);
    }

    private boolean isAdmin() {
        // 模拟管理员权限验证
        return false;
    }
}

public class Main {
    public static void main(String[] args){
        UserService realUserService = new UserServiceImpl();
        UserService userServiceProxy = new UserServiceProxy(realUserService);

        try {
            userServiceProxy.addUser("user1", "password1");
        } catch (SecurityException e) {
            System.out.println("Error: " + e.getMessage());
        }
    }
}

在上面的代码中,定义了一个UserService接口和它的实现类UserServiceImpl。然后,通过创建一个代理对象UserServiceProxy来控制对UserService对象方法的访问。

在UserServiceImpl中,addUser方法可以添加新用户。但在UserServiceProxy中,在调用实际的addUser方法之前,会先进行管理员权限验证。如果当前用户不具有管理员权限,则抛出一个SecurityException异常。这样就可以确保只有经过身份验证的管理员才能访问添加用户操作。

在Main类中,创建了一个UserServiceImpl对象,并将其传递给UserServiceProxy构造函数来创建代理对象。然后,通过代理对象调用UserService接口中的方法addUser来实现对添加新用户操作的代理控制。

======================================================

如果文章对你有帮助,不要忘记加个关注、点个赞!!!