Java动态代理技术
一.代理模式
代理模式的实现
想要了解动态代理,我们就得从23种设计模式的代理模式开始。
我们来看看以下场景,有一个Chinese类和Englisher类,它们都实现了Speakable接口。(中国人讲中文,英国人讲英文)
public class Chinese implements Speakable{
@Override
public void speak() {
System.out.println("你好 伙伴");
}
}
public class Englisher implements Speakable{
@Override
public void speak() {
System.out.println("hello guy!");
}
}
public interface Speakable {
void speak();
}
假设它们都分别有一个代理对象ChineseProxy 和 EnglishProxy
这里先提一点:代理模式中,代理类和被代理类需要实现同一个接口,代理对象只能够代理被代理对象实现的接口方法
ChineseProxy :
public class ChineseProxy implements Speakable{
private final Chinese chinese = new Chinese();
@Override
public void speak() {
// 代理准备工作
System.out.println("代理开始做准备工作");
// 通知被代理对象处理逻辑
chinese.speak();
// 代理收尾工作
System.out.println("代理开始做收尾工作");
}
}
EnglishProxy:
public class EnglisherProxy implements Speakable{
private final Englisher englisher = new Englisher();
@Override
public void speak() {
// 代理准备工作
System.out.println("英国人代理开始做准备工作");
// 通知被代理对象处理逻辑
System.out.print("英国人收到代理通知,开始工作:");
englisher.speak();
// 代理收尾工作
System.out.println("英国人代理开始做收尾工作");
}
}
我们在需要使用到Chinese和Englisher的时候不直接使用它们,而是使用它们的代理
public static void main(String[] args){
ChineseProxy chineseProxy = new ChineseProxy();
chineseProxy.speak();
System.out.println();
EnglisherProxy englisherProxy = new EnglisherProxy();
englisherProxy.speak();
}
运行结果:
小结:
代理模式的模板代码就是这样。
我们在开发过程中,出于安全等原因,有一些类是不直接开放给用户使用的。用户只能通过这些类的代理对象来间接访问这些类的方法
动态代理:
静态代理的缺点及动态代理的引入:
上述模板代码属于代理模式中的静态代理,而对于静态代理:对于每一个被代理类我们都需要手动编写一份代理类的Java类文件,比如Chinese类的代理类ChineseProxy对应一个Java类文件,Englisher类对应EnglisherProxy类对应一个Java类文件。如果被代理类很庞大的情况下,那代理类的数量自然也很庞大,那我们就需要生成很多的Java类文件,这将会影响编译速度(编译简单来说就是从 .java文件到 .class文件的过程) ,并且也不能保证每个代理类都会被使用。这种场景下,我们可以不在编译阶段就生成如此多的class文件,而是在RunTime阶段动态生成代理类的字节码并加载到内存,这就是动态代理
动态代理的代码实现:
public static void main(String[] args){
Speakable englishProxy = (Speakable) Proxy.newProxyInstance(
Englisher.class.getClassLoader(),
new Class[]{Speakable.class},
new MyInvocationHandler(new Englisher())
);
Speakable chineseProxy = (Speakable) Proxy.newProxyInstance(
Englisher.class.getClassLoader(),
new Class[]{Speakable.class},
new MyInvocationHandler(new Englisher())
);
englishProxy.speak();
System.out.println("\n\n");
chineseProxy.speak();
}
// InvocationHandler
static class MyInvocationHandler implements InvocationHandler {
// 被代理对象
private Speakable people;
public MyInvocationHandler(Speakable people){
this.people = people;
}
// 这里会拦截接口对象Speakable的方法,注意接口对象Speakable继承自Object类,所以调用Object类的方法比如equal()也会被拦截
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理开始准备工作");
System.out.print("代理对象接到通知,开始工作:");
Object object = method.invoke(people,args);
System.out.println("代理开始收尾工作");
return object;
}
}
运行结果:
小结:
JDK中提供了Proxy这个类来实现动态代理
Speakable chineseProxy = (Speakable) Proxy.newProxyInstance(
Englisher.class.getClassLoader(),
new Class[]{Speakable.class},
new MyInvocationHandler(new Englisher())
);
可以看到,动态代理的生成时通过Proxy.newInstance方法,该方法主要有三个参数
1.类加载器
2.代理对象和被代理对象同时实现的接口类(可能有多个)
3.自定义的方法拦截器,InvocationHandler
而对于InvocationHandler,它是一个接口类,需要我们去实现
// InvocationHandler
static class MyInvocationHandler implements InvocationHandler {
// 被代理对象
private Speakable people;
public MyInvocationHandler(Speakable people){
this.people = people;
}
// 这里会拦截接口对象Speakable的方法,注意动态代理类会继承自Object类,所以调用Object类的方法比如equals()之类的,也会被拦截
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理开始准备工作");
System.out.print("代理对象接到通知,开始工作:");
Object object = method.invoke(people,args);
System.out.println("代理开始收尾工作");
return object;
}
}
这里主要讲一下invoke回调,该回调会对动态代理对象的方法进行拦截,如果动态代理对象的方法被调用,就会触发这里的invoke回调实现对方法的拦截。
容易陷入的误区:这里的chineseProxy.speak() 方法被调用了以后就会触发invoke回调,需要注意的是不只有chineseProxy的接口实现方法被调用才会触发该回调,需要知道的是Object是所有类的父类,所以chineseProxy中父类Object方法比如equals被调用的时候,也会被拦截从而触发invoke回调。