代理模式的作用:增强原始对象的方法。
静态代理模式的弊端:需要的类繁多,额外功能修改不方便。
解决方法:动态代理模式(JDK原生支持)。
我的理解:
代理三要素:原始对象(本来就有),额外功能(在InvocationHandler.invoke实现),封装(Proxy.newInstance(classloader,interfaces,invocationahandler))。
可知,JDK的原生,所需要的额外功能类需要实现InvocationHandler接口,通过java.reflect.Proxy类来获得代理对象。但是我们也可以发现,获取该代理对象的步骤,代码大致相同。若要修改额外功能,则需要重复书写一些代码,存在着冗余,所以,我们能不能将重复的代码进行封装一下呢?让我们真正的只写额外功能呢?
所以我们需要对一些代码进行封装。那么怎么封装呢?我想到了Servlet,只要继承HttpServlet就可以实现Servlet的功能了,其中的一些相同的方法也已经实现了。所以,我们将其进行类似于Servlet的封装。
分析:
将额外功能添加到代理类上,进行原有功能的加强。通过观察InvocationHandler接口可知,其中真实方法,真实参数都具有(因为实现了相同的接口),但缺少真实对象。所以我们需要把真实对象给设置进去。
原有功能:
public interface ISell {
public void sell();
public void sayOut(String words);
}
public class Seller implements ISell {
@Override
public void sell() {
System.out.println("real sell...");
}
@Override
public void sayOut(String words) {
System.out.println(words);
}
}
额外功能:
为了减少耦合,额外功能在一个独立的类中,例如
public class Ext1 implements InvocationHandler {
private Object realObject;
public void setRealObject(Object realObject) {
this.realObject = realObject;
}
public Object getProxyObject(){
return Proxy.newProxyInstance(Ext1.class.getClassLoader(),realObject.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 额外公
System.out.println("---log---");
// 原来方法的执行
Object ret= method.invoke(realObject,args);
return ret;
}
}
test代码:
@Test
public void test() throws Exception {
Ext1 ex1 = new Ext1();
ex1.setRealObject(new Seller());
ISell seller = (ISell) ex1.getProxyObject();
seller.sell();
}
运行结果:
如果额外功能变更的话,我们需要重新写一遍和Ext1一样的代码,而只是invoke方法之中有变化而已,所以产生了冗余,所以我们进行它的封装。
额外功能应该只有额外功能而已,但是得到代理对象的代码也在其中,不符合面向对象的思想。
封装改进:
应用设计模式:工厂设计模式,适配器设计模式。
通过观察InvocationHandler的实现类,我们可以得到重复的代码:
所以,每一个代理类都必须有画红线的那3句代码。
我们先设计代理类的顶层接口。
为什么要设置Class对象呢,你猜???
public interface IExt extends InvocationHandler {
public void setRealClass(Class realClass )throws Exception;
// 运行在原始方法之前,之后的方法,额外功能无非在原始方法运行之前和之后。
public void before();
public void after();
}
但是那个realObject还没有呢?那么你想到了怎么来实现吗?对就是抽象类,我们将接口实现在抽象类之中,对于重复的代码我们直接实现,对于不确定的代码让程序员自己实现,写在before和after中不就可以了。到这里是不是就有Servlet那味了呢?
public abstract class GenericExt implements IExt{
private Object realObject;
@Override
public void setRealClass(Class realClass) throws Exception {
this.realObject = realClass.getDeclaredConstructor().newInstance();
}
//Invocationhandle是调用的此接口,强制类型转换,只会有这一个方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
this.before();
Object ret = method.invoke(realObject,args);
this.after();
return ret;
}
@Override
public void before() {
}
@Override
public void after() {
}
这就设计出我们额外功能类的模板了,我们以后所写的额外功能类只要继承这个GenericExt就可以了。要想在额外功能实现之前,之后的方法,随便自己定义。
那么怎么获得代理类(原始对象加强类)呢?我们仔细思考。如何获得Proxy.newInstance呢?其实就两点,实现InvocationHandler接口的类,原始功能的接口数组。
有人就不明白了,还有个类加载器呢?去哪里了。其实,这个类加载器ClassLoader是谁的都可以,比如说你传一个Integer.class.getClassLoader也可以。为啥了?
JVM通过类加载器将.Java文件(源码)加载到虚拟机中转化成.class文件,一个.Java对应一个类加载器,一个Class文件。但是动态代理类是没有.Java文件的,所以就没有类加载。但是JVM要根据类加载器给对象分配内存,实例化该类;动态代理设计模式是直接在JVM中自动生成的,是直接就在JVM虚拟机中的啊,所以不需要.Java文件,那么按道理JVM是不会给其实例化的,但是我们又要用,所以,我们可以借一个类加载器,借谁的呢?无所谓,只要是一个有.Java源文件的就可以。动态代理是不是很鸡贼啊!
所以,工厂产生代理类的代码如下:为了避免强制类型转换,我使用了泛型。
public class ProxyGet {
private ProxyGet() {
}
/**
* @param ext 表示额外功能的类
* @param realClass 真实对象的class文件
* @param <T> 泛型,
* @return 返回代理对象
* @throws Exception 异常一次性抛出。
*/
public static <T> T getProxyObject(IExt ext,Class<T> realClass) throws Exception {
ext.setRealClass(realClass);
return (T) Proxy.newProxyInstance(realClass.getClassLoader(), realClass.getInterfaces(),(InvocationHandler) ext);
}
}
定义额外功能:
public class ExtImpl extends GenericExt{
@Override
public void before() {
System.out.println("111...");
}
@Override
public void after() {
System.out.println("222...");
}
}
Test代码如下:
@Test
public void test() throws Exception {
ExtImpl ex = new ExtImpl();
ISell sell = ProxyGet.getProxyObject(ex,Seller.class);
sell.sayOut("hello world!");
}
运行结果如下:
但是,有有人可能会问了,你这个before和after是没有参数共享的,如果我要计算代码运行时间并输出呢?这就需要在before写一下代码,在after写一段代码。两段代码之间有联系。确实,还并不完善。我也想了很久,算是想到了一个解决方案吧。就是在代理类的invoke方法作用域中内置一个Map集合(Map是最为合适的)。然后before和after方法把这个Map当参数(因为Java是引用传递的)。那么就可以实现两个代理方法间的数据共享。
最后,填坑了。
为什么要用Class对象呢。
其实就是个人喜好,传对象也可以获得Class文件,就需要先getClass,有点长,看着不舒服。再者,传个对象感觉怪怪的(毕竟我没有真实对象,拿什么传呢?狗头保命)。
还能再修改的地方:比如说一个服务,UserService,应该是一个单例就可以的。而我却是在代理类中创建一个新的。怎么解决我还没想好。
解决:在我的工厂模式的介绍中,书写了通过工厂来获得该单例的方法(正好是通过class文件哦!),大家可以去看看哦。
spring框架中也可以解决。它的aop做的非常好。