代理模式

使用代理对象代替对真实对象的访问,这样就可以在不修改原目标对象的前提下,提供额外的操作,拓展目标对象的功能。代理模式的主要作用是拓展目标对象的功能,比如在目标对象的某个方法执行前后可以增加一些自定义的操作。

静态代理

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

静态代理实现步骤:

  • 定义一个接口和其实现类
  • 创建一个代理类同样实现这个接口
  • 将目标对象注入代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。

代码演示:

  • 定义发送短信的接口

    1
    2
    3
    public interface SmsService{
    String send(String message);
    }
  • 定义实现发送短信的接口

    1
    2
    3
    4
    5
    6
    public class SmsServiceImpl implements SmsService{
    public String send(String message){
    sout("send message:"+message);
    return message;
    }
    }
  • 创建代理类并同样实现发送短信的接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class smsProxy implements SmsService{
    private final SmsService smsService;

    public SmsProxy(SmsService smsService){
    this.smsService = smsService;
    }
    @override
    public String send(String message){
    //调用方法之前,先添加自己的操作
    sout("before method");
    smsService.send(message);
    //调用方法之后
    sout("after method");
    return null;
    }
    }
  • 实际使用

    1
    2
    3
    4
    5
    6
    7
    public class Mian{
    psvm{
    SmsService smsService = new SmsServiceImpl();
    SmsProxy smsProxy = new SmsProxy(smsService);
    smsProxy.send("java");
    }
    }

动态代理

不需要针对每一个目标类都单独创建一个代理类,并且也不需要必须实现接口,可以直接代理实现类。从JVM角度来说,动态代理是在运行时动态生成字节码文件,并加载到JVM中的。

java实现动态代理的方式有很多种吗,比如JDK动态代理,CGLIB动态代理等等。

JDK动态代理机制

InvocationHandlerProxy类是核心。

Proxy类中使用最频繁的方法是newProxyInstance(),用于生成一个代理对象。

一共三个参数:

  • loder:类加载器,用于加载代理对象
  • interfaces:被代理类实现的一些接口
  • h:实现了InvocationHandler接口的对象。

要实现动态代理,还需要实现InvocationHandler来自定义处理逻辑。当动态代理对象调用一个方法时候,方法的调用会被转发到InvocationHandler接口类的invoke方法来调用。

JDK动态代理使用步骤:

  • 定义一个借口及其实现类
  • 自定义 InvocationHandler 并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;
  • 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象;
1
2
3
4
5
6
7
/**
* description:
* 定义发送短信的接口
**/
public interface SmsService {
String send(String message);
}
1
2
3
4
5
6
7
8
9
10
11
/**
* description:
* 实现发送短信的接口
**/
public class SmsServiceImpl implements SmsService{
@Override
public String send(String message) {
System.out.println("send message:"+message);
return message;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* description:
* 定义一个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("before method"+method.getName());
Object result = method.invoke(target,args);
System.out.println("after method"+method.getName());
return result;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* description:
* 获取代理对象的工厂类
**/
public class JdkProxyFactory {
public static Object getProxy(Object target){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new DebugInvocationHandler(target)
);
}
}
1
2
3
4
5
6
public class Main {
public static void main(String[] args) {
SmsService smsService = (SmsService)JdkProxyFactory.getProxy(new SmsServiceImpl());
smsService.send("java");
}
}

CGLIB动态代理机制

JDK只能代理实现了接口的类。

使用步骤:

  • 定义一个类
  • 自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;
  • 通过 Enhancer 类的 create()创建代理类;

使用的话,需要添加依赖:

1
2
3
4
5
6
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>

比较

JDK动态代理和CGLIB动态代理

  1. JDK 动态代理只能只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
  2. 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。

静态代理与动态代理

  1. 灵活性 :动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!
  2. JVM 层面 :静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

请我喝杯咖啡吧~

支付宝
微信