代理模式

47 阅读4分钟

什么是代理模式?

我们使用代理对象来代替真实对象的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能

代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作

静态代理

静态代理中,我们对目标对象的每个方法增强都是手动完成的,不太灵活而且麻烦,实际应用非常少。

从 JVM 层面来说, 静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件

实现步骤大概如下:

定义一个接口及其实现类

创建一个代理类同样实现这个接口

将目标对象注入进代理类,然后在代理类的对应方法调用目标类的对应方法,这样我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己需要做的事情

下面是具体使用代码:

// 定义一个发生邮件的接口
public interface SendEmailService {
    void sendEmail();
}
// 定义实现发送邮件接口的类
public class SendEmailServiceImpl implements SendEmailService{
   @Override
   public void sendEmail() {
      System.out.println("发送邮件");
   }
}
// 定义代理对象也实现发送邮件接口
public class SendEmailProxy implements SendEmailService {
    private final SendEmailService sendEmailService;

    public SendEmailProxy(SendEmailService sendEmailService) {
        this.sendEmailService = sendEmailService;
    }

    @Override
    public void sendEmail() {
        // 调用方法前我们可以自定义操作
        System.out.println("调用方法前我们可以自定义操作");
        sendEmailService.sendEmail();
        // 调用方法后我们可以自定义操作
        System.out.println("调用方法后我们可以自定义操作");

    }
}
public class Main {
	// 实际使用
   public static void main(String[] args) {
      SendEmailService sendEmailService = new SendEmailServiceImpl();
      SendEmailService sendEmailServiceProxy = new SendEmailProxy(sendEmailService);
      sendEmailServiceProxy.sendEmail();
   }
}

输出:

调用方法前我们可以自定义操作 发送邮件 调用方法后我们可以自定义操作 可以看到,我们实现了对SendEmailServiceImpl的sendEmail()方法增强

缺点:不灵活,一旦接口多了个方法,目标对象和代理对象都要修改

优点:简单

动态代理

相比于静态代理来说,动态代理更加灵活,我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们实现接口,我们可以直接代理实现类(CGLIB代理机制)

从JVM角度来说,动态代理是在运行时动态生成类的字节码,并加载到JVM中。

说到动态代理,Spring AOP、RPC框架,它们的实现都依赖动态代理。

就Java来说,动态代理的实现方式有很多种,比如JDK动态代理,CGLIB动态代理等

JDK动态代理机制

在Java动态代理机制中InvocationHandler接口和Proxy类是核心

Proxy类中使用频率最高的方法是:newProxyInstance(),这个方法主要用来生成一个代理对象

这个方法一共有3个参数

loader:类加载器,用于加载代理对象

interfaces:被代理类实现的一些接口

h:实现了InvocationHandler接口的对象

要实现动态代理的话,还必须要实现InvocationHandler来自定义处理逻辑。当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler接口的invoke方法来调用

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; 参数解读:

proxy:动态生成的代理类

method:与代理类对象调用的方法相对应

args:当前method方法的参数

也就是说,通过Proxy类的newProxyInstance()创建的代理对象在调用方法的时候,实际上会调用到实现InvocationHandler接口的类的invoke()方法。我们可以在invoke()方法中自定义处理逻辑,比如在方法执行前后做什么事情。

使用步骤:

定义一个接口及其实现类;

自定义InvocationHandler并重写invoke方法,在invoke方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;

通过Proxy.newProxyInstance方法创建代理对象。


定义一个发送邮件的接口:

public interface SendEmailService {
    void sendEmail(String message);
}
实现发送邮件接口

public class SendEmailServiceImpl implements SendEmailService{
   @Override
   public void sendEmail(String message) {
      System.out.println("发送邮件内容:" + message);
   }
}
定义JDK动态代理类

public class DebugInvocationHandler implements InvocationHandler {

    /**
     * 代理类中的真实对象
     */
    private final Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("JDK动态代理开始,这是执行前逻辑");
        Object result = method.invoke(target, args);
        System.out.println("JDK动态代理结束,这是执行后逻辑");
        return result;
    }
}
获取代理对象的工厂

public class JdkProxyFactory {

    public static Object getProxy(Object target) {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),  // 目标类的类加载器
                target.getClass().getInterfaces(),   // 代理类实现的接口
                new DebugInvocationHandler(target)   // 代理对象对应的自定义InvocationHandler
        );
    }
}
getProxy():主要通过Proxy.newProxyInstance()方法获取某个类的代理对象

测试使用

public static void main(String[] args) {
	SendEmailService sendEmailService = (SendEmailService) 		   JdkProxyFactory.getProxy(new SendEmailServiceImpl());
    sendEmailService.sendEmail("hello");
}

输出

JDK动态代理开始,这是执行前逻辑 发送邮件内容:hello JDK动态代理结束,这是执行后逻辑 Java