Skip to content

代理模式是什么?

代理模式也称为委托者模式,属于结构型设计模式,代理模式通常是提供一个中间人,用于解决两类问题:

  • 控制对目标对象的访问
  • 在访问目标对象时增加额外功能

‼️本质是通常是提供一个中间人,以控制目标对象的访问。

什么是静态代理?

java
1、定义基础接口
public interface HttpApi {
    String get(String url);
}

2、网络请求的真正实现
public class RealModule implements HttpApi {
     @Override
     public String get(String url) {
         return "result";
     }
}

3、代理类
public class Proxy implements HttpApi {
    private HttpApi target;

    Proxy(HttpApi target) {
        this.target = target;
    }

    @Override
    public String get(String url) {
        // 扩展的功能
        Log.i("http-statistic", url);
        // 访问基础对象
        return target.get(url);
    }
}

可以看出,当需要频繁使用代理模式时,我们需要创建很多代理类,需要创建很多重复的代码。

java 动态代理

java 的动态代理是 jdk 1.3 中引入的特性,“动态”是相对于“静态”代理而言,代理类不是在编译期生成的,而是在运行时生成。

java 动态代理的核心 API 是 Proxy 类和<font style="color:rgb(62, 62, 62);">InvocationHandler</font>

  • **<font style="color:rgb(62, 62, 62);">InvocationHandler</font>**<font style="color:rgb(62, 62, 62);">代理处理器</font><font style="color:rgb(62, 62, 62);">代理处理器</font><font style="color:rgb(62, 62, 62);">InvocationHandler</font><font style="color:rgb(62, 62, 62);">invoke</font><font style="color:rgb(62, 62, 62);">invoke</font><font style="color:rgb(62, 62, 62);">Object invoke(Object proxy, Method method, Object[] args)</font>
  • **<font style="color:rgb(62, 62, 62);">Proxy</font>**<font style="color:rgb(62, 62, 62);">newProxyInstance</font>
    • <font style="color:rgb(62, 62, 62);">ClassLoader loader</font>
    • <font style="color:rgb(62, 62, 62);">Class<?>[] interfaces</font>
    • <font style="color:rgb(62, 62, 62);">InvocationHandler h</font>
java
import org.junit.jupiter.api.Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 接口
interface hello {
    void hi();
}

//  原对象
class helloOrigin implements hello {
    @Override
    public void hi() {
        System.out.println("hi!");
    }
}

// 调用处理器
class proxyHandler implements InvocationHandler {
    hello target;

    public proxyHandler(hello target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("proxy !");
        return method.invoke(target, args);
    }
}


public class DynimicProxyTest {

    @Test
    public void test() {
        // 创建原对象类
        hello originBean = new helloOrigin();
        originBean.hi();

        // 创建代理类
        hello proxyBean = (hello) Proxy.newProxyInstance(hello.class.getClassLoader(),
                                                         new Class[]{hello.class},
                                                         new proxyHandler(originBean));
        proxyBean.hi();
    }
}
****
java
public class Main {
    public static void main(String[] args) {
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(method);
                if (method.getName().equals("morning")) {
                    System.out.println("Good morning, " + args[0]);
                }
                return null;
            }
        };
        Hello hello = (Hello) Proxy.newProxyInstance(
            Hello.class.getClassLoader(), // 传入ClassLoader
            new Class[] { Hello.class }, // 传入要实现的接口
            handler); // 传入处理调用方法的InvocationHandler
        hello.morning("Bob");
    }
}

interface Hello {
    void morning(String name);
}
java
public class HelloDynamicProxy implements Hello {
    InvocationHandler handler;
    public HelloDynamicProxy(InvocationHandler handler) {
        this.handler = handler;
    }
    public void morning(String name) {
        handler.invoke(
           this,
           Hello.class.getMethod("morning", String.class),
           new Object[] { name });
    }
}

java 动态代理要求代理类必须实现某个接口,无法代理没有实现接口的类,存在一定的局限。

CGLIB是一个基于字节码的代码生成库,可以用来生成动态代理对象,与Java标准库中的动态代理相比,CGLIB动态代理所生成的代理对象是基于继承的方式,而不是基于接口的,所以也叫子类代理。很多流行的框架如<font style="color:rgb(32, 35, 39);">Hiberate</font><font style="color:rgb(32, 35, 39);">Spring</font> 等很多框架都使用<font style="color:rgb(32, 35, 39);">CGLIB</font>库。

maven 中引入CGLIB:

xml
<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.2.4</version>
</dependency>

首先先创建一个被代理的目标类:(注意目标代理类和目标代理方法不能是被 final 修饰的,因为 CGLIB动态代理是通过生成目标类的子类来实现代理,而 final 修饰的方法无法被重写)。

java
public class PersonService {
    public String sayHello(String name) {
        return "Hello " + name;
    }

    public Integer lengthOfName(String name) {
        return name.length();
    }
}

接着我们使用cglibEnhancer类创建一个代理:

java

Enhancer enhancer = new Enhancer();
// 设置目标类,可以是类或接口
enhancer.setSuperclass(PersonService.class);
// 设置拦截方法
enhancer.setCallback((FixedValue) () -> "Hello Tom!");
// 创建代理类
PersonService personService = (PersonService) enhancer.create();
System.out.println(personService.sayHello("lm"));

// 打印
// "Hello Tom!"

使用setSuperclass方法指定被代理类;

使用setCallback指定回调方法,当调用代理类的目标方法时会交由回调方法执行,上边代码例子使用FixedValue进行强制类型转换,表示回调方法直接返回一个值。

我们还可以使用setCallback指定更复杂的回调方法,比如说根据不同的被拦截的方法,采取不同的处理策略:

java
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
    if (method.getName().equals("sayHello")) {
        return "Hello Tom!";
    } else {
        return proxy.invokeSuper(obj, args);
    }
});

上边例子中,我们拦截了目标类PersonServicesayHello方法, 当调用sayHello方法时直接返回"Hello Tom!"。注意MethodInterceptor是用于进行类型转换的。