一、动态代理
1、运行时实现指定的接口
想实现某个接口,你需要写一个类,然后在类名字的后面给出“implements”XXX接口。
public interface MyInterface {
void fun1();
void fun2();
}
public class MyInterfaceImpl implements MyInterface {
public void fun1() {
System.out.println("fun1()");
}
public void fun2() {
System.out.println("fun2()");
}
}
上面的代码对我们来说没有什么新鲜感,我们要说的是动态代理技术可以通过一个方法调用就可以生成一个对指定接口的实现类对象。
Class[] cs = {MyInterface.class};
MyInterface mi = (MyInterface)Proxy.newProxyInstance(loader, cs, h);
上面代码中,Proxy类的静态方法newProxyInstance()方法生成了一个对象,这个对象实现了cs数组中指定的接口。没错,返回值mi是MyInterface接口的实现类。你不要问这个类是哪个类,你只需要知道mi是MyInterface接口的实现类就可以了。你现在也不用去管loader和h这两个参数是什么东东,你只需要知道,Proxy类的静态方法newProxyInstance()方法返回的方法是实现了指定接口的实现类对象,甚至你都没有看见实现类的代码。
动态代理就是在运行时生成一个类,这个类会实现你指定的一组接口,而这个类没有.java文件,是在运行时生成的,你也不用去关心它是什么类型的,你只需要知道它实现了哪些接口即可。
2、newProxyInstance()方法的参数
Proxy类的newInstance()方法有三个参数:
ClassLoader loader:它是类加载器类型,你不用去理睬它,你只需要知道怎么可以获得它就可以了:MyInterface.class.getClassLoader()就可以获取到ClassLoader对象,没错,只要你有一个Class对象就可以获取到ClassLoader对象;
Class[] interfaces:指定newProxyInstance()方法返回的对象要实现哪些接口,没错,可以指定多个接口,例如上面例子只我们只指定了一个接口:Class[] cs = {MyInterface.class};
InvocationHandler h:它是最重要的一个参数!它是一个接口!它的名字叫调用处理器!你想一想,上面例子中mi对象是MyInterface接口的实现类对象,那么它一定是可以调用fun1()和fun2()方法了,难道你不想调用一下fun1()和fun2()方法么,它会执行些什么东东呢?其实无论你调用代理对象的什么方法,它都是在调用InvocationHandler的invoke()方法!
public static void main(String[] args) {
Class[] cs = {MyInterface.class};
ClassLoader loader = MyInterface.class.getClassLoader();
InvocationHandler h = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("无论你调用代理对象的什么方法,其实都是在调用invoke()...");
return null;
}
};
MyInterface mi = (MyInterface)Proxy.newProxyInstance(loader, cs, h);
mi.fun1();
mi.fun2();
}
3、invoke()方法
InvocationHandler的invoke()方法的参数有三个:
Object proxy:代理对象,也就是Proxy.newProxyInstance()方法返回的对象,通常我们用不上它;
Method method:表示当前被调用方法的反射对象,例如mi.fun1(),那么method就是fun1()方法的反射对象;
Object[] args:表示当前被调用方法的参数,当然mi.fun1()这个调用是没有参数的,所以args是一个零长数组。
最后要说的是invoke()方法的返回值为Object类型,它表示当前被调用的方法的返回值,当然mi.fun1()方法是没有返回值的,所以invoke()返回的就必须是null了。
4、动态代理的用途
动态代理的用途与装饰模式很相似,就是为了对某个对象进行增强。所有使用装饰者模式的案例都可以使用动态代理来替换。
下面我们用一个例子来说明动态代理的用途!
我们来写一个Waiter接口,它只有一个serve()方法。MyWaiter是Waiter接口的实现类:
public interface Waiter {
public void serve();
}
public class MyWaiter implements Waiter {
public void serve() {
System.out.println("服务...");
}
}
现在我们要对MyWaiter对象进行增强,要让它在服务之前以及服务之后添加礼貌用语,即在服务之前说“您好!”,在服务之后说:“很高兴为您服务!”。
public class MainApp1 {
public static void main(String[] args) {
ClassLoader loader = MainApp1.class.getClassLoader();
Class[] cs = {Waiter.class};
Waiter target = new MyWaiter();
MyInvocationHandler h = new MyInvocationHandler(target);
Waiter waiter = (Waiter)Proxy.newProxyInstance(loader, cs, h);
waiter.serve();
}
}
class MyInvocationHandler implements InvocationHandler {
public Waiter target;
public MyInvocationHandler(Waiter target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("您好!");
Object result = method.invoke(target, args);
System.out.println("很高兴为您服务!");
return result;
}
}
二、类加载器
1.什么是类加载器
类加载器就是用来加载类的东西!类加载器也是一个类:ClassLoader
类加载器可以被加载到内存,是通过类加载器完成的!Java提供了三种类加载器,分别是:
bootstrap classloader:引导类加载器,加载rt.jar中的类;
sun.misc.Launcher$ExtClassLoader:扩展类加载器,加载lib/ext目录下的类;
sun.misc.Launcher$AppClassLoader:系统类加载器,加载CLASSPATH下的类,即我们写的类,以及第三方提供的类。通常情况下,Java中所有类都是通过这三个类加载器加载的。
类加载器之间存在上下级关系,系统类加载器的上级是扩展类,扩展类加载器的上级是引导类加载器。
2.JVM眼中的相同的类
在JVM中,不可能存在一个类被加载两次的事情!一个类如果已经被加载了,当再次试图加载这个类时,类加载器会先去查找这个类是否已经被加载过了,如果已经被加载过了,就不会再去加载了。
但是,如果一个类使用不同的类加载器去加载是可以出现多次加载的情况的!也就是说,在JVM眼中,相同的类需要有相同的class文件,以及相同的类加载器。当一个class文件,被不同的类加载器加载了,JVM会认识这是两个不同的类,这会在JVM中出现两个相同的Class对象!甚至会出现类型转换异常!
3.类加载器的代理模式
当系统类加载器去加载一个类时,它首先会让上级去加载,即让扩展类加载器去加载类,扩展类加载器也会让它的上级引导类加载器去加载类。如果上级没有加载成功,那么再由自己去加载!
例如我们自己写的Person类,一定是存放到CLASSPATH中,那么一定是由系统类加载器来加载。当系统类加载器来加载类时,它首先把加载的任务交给扩展类加载去,如果扩展类加载器加载成功了,那么系统类加载器就不会再去加载。这就是代理模式了!
相同的道理,扩展类加载器也会把加载类的任务交给它的“上级”,即引导类加载器,引导类加载器加载成功,那么扩展类加载器也就不会再去加载了。引导类加载器是用C语言写的,是JVM的一部分,它是最上层的类加载器了,所以它就没有“上级了”。它只负责去加载“内部人”,即JDK中的类,但我们知道Person类不是我们自己写的类,所以它加载失败。
当扩展类加载器发现“上级”不能加载类,它就开始加载工作了,它加载的是lib\ext目录下的jar文件,当然,它也会加载失败,所以最终还是由系统类加载器在CLASSPATH中去加载Person,最终由系统类加载器加载到了Person类。
代理模式保证了JDK中的类一定是由引导类加载加载的!这就不会出现多个版本的类,这也是代理模式的好处。
4.Tomcat的类加载器
Tomcat会为每个项目提供一个类加载器,Tomcat提供的类加载器负责加载自己项目下的类,即WEB-INF\lib和WEB-INF\classes下的类。但Tomcat提供的类加载器不会使用传统的代理模式,而是自己先去加载,如果加载不到,再使用代理模式。
Tomcat提供的类加载器有这样一个好处,就是可以使自己项目下的类优先被加载!