Java 静态代理、JDK动态代理、CGLIB代理
代理(Proxy):是一种设计模式,提供了对目标对象另外的访问方式,即通过代理对象访问目标对象。
好处:可以在目标对象实现的基础上,增强额外的功能操作,即目标对象的扩展功能。
举个例子:假如产品经理提了一个新的需求,需要在某个方法调用之前以及调用之后打印日志。那么我们就可以通过代理的方式,不修改方法内部的代码而实现。
1. Java静态代理
静态代理: 代理对象和目标对象实现相同的接口或者继承相同的父类,然后通过调用代理对象的方法来调用目标对象的方法实现代理。
// 委托接口
public interface HelloService {
void sayHello();
}
// 接口实现类
public class HelloServiceImpl implements HelloService {
@Override
public void sayHello() {
System.out.println("hello 我是接口实现类");
}
}
// 代理类
public class HelloServiceProxy implements HelloService {
private HelloService helloService;
public HelloServiceProxy(HelloService helloService) {
this.helloService = helloService;
}
@Override
public void sayHello() {
System.out.println("目标对象方法调用前打印日志 ...");
this.helloService.sayHello();
System.out.println("目标对象方法调用后打印日志 ...");
}
}
// 测试静态代理类
public class Main {
public static void main(String[] args) {
HelloService target = new HelloServiceImpl();
HelloService proxy = new HelloServiceProxy(target);
proxy.sayHello();
}
}
运行结果
我们将接口实现类注入到代理类中,然后通过代理类调用真正的实现类实现的方法。那么我们就可以在调用实现的方法的前后做一些处理。
优点:
可以做到不修改目标对象功能的前提下,对目标对象进行扩展。
缺点:
因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,同时接口一旦增加新的方法,代理对象也需要实现相应的方法。
2. JDK动态代理
为解决静态代理为不同接口实现不同的代理类而带来的缺点,我们可以利用动态代理的方式,代理类无需实现目标类的接口,就可以代理目标类的方法,但是目标类必须要实现接口。
// 方法调用前后打印日志的代理类
public class LogProxy implements InvocationHandler {
Object target;
public LogProxy(Object target) {
this.target = target;
}
/**
* 动态生成代理类对象,Proxy.newProxyInstance
* @return 返回代理类的实例
*/
public Object newProxyInstance() {
return Proxy.newProxyInstance(
//指定代理对象的类加载器
target.getClass().getClassLoader(),
//代理对象需要实现的接口,可以同时指定多个接口
target.getClass().getInterfaces(),
//方法调用的实际处理者,代理对象的方法调用都会转发到这里
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(target + " 的 " + method.getName() + " 方法即将调用");
Object ret = method.invoke(this.target, args);
System.out.println(target + " 的 " + method.getName() + " 方法调用完毕");
return ret;
}
}
// 测试代理类
public class Main {
public static void main(String[] args) {
HelloService target = new HelloServiceImpl();
LogProxy logProxy = new LogProxy(target);
HelloService proxyInstance = (HelloService) logProxy.newProxyInstance();
proxyInstance.sayHello();
/* 同一个代理类,可以代理实现同接口的目标类 */
CharSequence target2 = new String("hello");
LogProxy logProxy2 = new LogProxy(target2);
CharSequence proxyInstance2 = (CharSequence) logProxy2.newProxyInstance();
System.out.println(proxyInstance2.length());
}
}
运行结果:
代理类通过实现 InvocationHandler
接口的 invoke()
方法,并通过 Proxy
类的静态方法newProxyInstance()
方法动态生成代理类实例, 即可对注入的目标类进行代理,而且同一个代理类可以代理实现不同接口的目标类,相比较静态代理大大提高了开发效率。
那么动态代理是如何实现的呢?
在了解动态代理之前,我们先来回顾一下,在JVM中是如何将类加载到内存中的。
加载
- 通过全类名获取定义此类的二进制字节流
- 将字节流所代表的静态存储结构转换为方法区的运行时数据结构
- 在内存中生成一个代表该类的
Class
对象,作为方法区这些数据的访问入口
在步骤1中,二进制字节流可以是从文件、ZIP包、网络流、运行时计算生成等方式获取。其中的运行时计算生成就是动态代理技术的实现方式。
那么我们就可以根据不同的目标类动态的生成不同的代理类实例的字节码,即可在内存中得到不同的代理类实例。
分析 Proxy
源码,我们可以看到其内部就是通过动态生成字节码的方式来得的到代理类实例。
动态生成的代理类又是如何实现代理的呢?
在下面这段程序中,proxyInstance
调用的 sayHello()
方法,实际上调用的是 logProxy
的 invoke()
方法,在invoke()
内部我们又调用了目标类真正的方法,从而实现代理的功能。
HelloService target = new HelloServiceImpl();
LogProxy logProxy = new LogProxy(target);
HelloService proxyInstance = (HelloService) logProxy.newProxyInstance();
proxyInstance.sayHello();
也就是说,在 proxyInstance
的 sayHello()
方法,实际上是调用 proxyInstace.invoke()
,即
// proxyInstance 代理类实例的方法
public void sayHello() {
// ...
proxyInstance.invoke(this, method, null); // method 为目标类的真正方法
// ...
}
在 invoke()
方法中,method 为目标类的真正方法,那么我们如何获取到关于 method
的相关信息呢?
答案就是:通过反射
@Override
public Object invoke(Object proxy, Method method, Object