----------------------android培训、java培训、期待与您交流! ----------------------
在现实生活中有各种各样的代理,他们卖的都是生产商提供的产品生产商把产品交给代理商卖,而代理商并不生产该商品,他们只是负责把产品卖给用户使用,这样用户需要买商品就不需要直接去生产该商品的工厂去买了,直接从代理商手中就能购买到相同的产品。
在程序开发中,经常会遇到使用某个类的方法过程中,比如需要知道运行该方法所耗的时间,而该方法并没有提供记录运行时间的功能,那么就要自己手动的添加记录运行时间的代码,如下:
- class Demo{
- public void some(){
- long beginTime = System.currentTimeMillis();
- System.out.println("doSomething……");
- long endTime = System.currentTimeMillis();
- System.out.println(
- "some()运行时间为:"+(endTime-beginTime));
- }
- }
过了段时间后不想记录运行时间了,那么就应该倒回来把源代码中的记录时间的功能删除,这样是否合理呢?好嘛,用接口来解决该问题,于是这么写:
- interface SomeInterface{
- void some();
- }
- class Demo implements SomeInterface{
- public void some(){
- System.out.println("doSomething……");
- }
- }
- class DemoProxy implements SomeInterface{
- private SomeInterface Mysome;
- public DemoProxy(SomeInterface some){
- this.Mysome = some;
- }
- public void some(){
- long beginTime = System.currentTimeMillis();
- Mysome.some();
- long endTime = System.currentTimeMillis();
- System.out.println(
- "some()运行时间为:"+(endTime-beginTime));
- }
- }
要记录some()方法的运行时间,可以这么写:
DemoProxy proxy = new DemoProxy(new Demo());
proxy.some();
不想记录运行时间,则这么写:
Demo demo = new Demo();
demo.some();
上述程序中,定义了一个some()方法的接口,通过静态代理勉强实现了一个代理功能,当需要记录some()方法的运行时间,那么就可以用SomeInterface的实现类通过复写some()方法,而真正实现some()方法的是Demo类中的some()方法,这个静态代理的局限性就是必须操作特定的接口,当一个类中有多个接口时就必须定义多个代理对象,操作与维护代理对象将会有不少的负担。
JAVA的反射API中有个Proxy类,这个类可以动态的建立接口的操作对象,也就是说我们不必为特定接口操作特定的代理对象,它会自动生成我们要代理的类的接口,然后调用我们要操作的对象的方法(我们调用的方法必须要在其实现的接口上定义的方法),从而实现动态代理,使用动态代理机制,可使用一个处理者代理多个接口的操作对象。如下:
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
- class MyProxy implements InvocationHandler{
- private Object target;
- public Object proxy(Object target){
- this.target =target;
- Object obj = Proxy.newProxyInstance(
- target.getClass().getClassLoader(),
- target.getClass().getInterfaces(),
- this);
- return obj;
- }
- public Object invoke
- (Object proxy,Method method,Object[] args){
- Object retsult = null;
- long beginTime =0;
- long endTime = 0;
- try{
- beginTime = System.currentTimeMillis();
- Thread.sleep(1000);
- retsult = method.invoke(target, args);
- endTime = System.currentTimeMillis();
- }catch(Exception ex){
- ex.printStackTrace();
- }
- System.out.println(method.getName()
- +"运行时间为:"+(endTime-beginTime)+"毫秒");
- return retsult;
- }
- }
上述程序中,实现了动态代理,通过Proxy.newProxyInstance(ClassLoader Loder,Class[] interface,InvocationHandler h)生成了代理对象,newProxyInstance接收3个参数其中的 ClassLoader是类加载器,一般情况是用要代理的类的接口的类加载器,因此这里使用需要代理的对象的类加载器(target.getClass().getClassLoader());
Class[] interface表示需要代理的对象实现的接口,因为有些类会实现多个接口,所以它可以接收多个接口,这里用需要代理的对象实现的接口,target.getClass().getInterface();
最后一个是InvocationHandler类型,查阅API发现,它是一个接口,那么这里就直接实现该接口,这里调用this,表示该类本身有InvocationHandler的行为。
InvocationHandler接口只有一个invoke()方法,该方法就是实现动态代理的核心方法,该方法也接收3个参数,其中Object proxy,就是这次使用的Proxy对象;Method method就是要代理的对象中用户调用的原方法;最后一个Object[] args是用户调用的原对象中的方法接收的参数,因为有些方法不止接收一个参数,所以这里用数组表示。在方法的内部就可以添加自己希望对代理的类中的方法进行的一些操作,比如记录该方法的运行日志、进行异常处理或者记录运行时间等等操作。这里只是记录方法运行的时间,因为CPU执行的非常快,为了看到运行效果对该线程进行了阻断1000毫秒。现在定义一个测试类,对ArrayList进行代理:
- import java.util.ArrayList;
- import java.util.Collection;
- public class ProxyText {
- public static void main(String[] args) {
- ArrayList list = new ArrayList();
- MyProxy1 myProxy = new MyProxy1();
- Collection collection =(Collection) myProxy.proxy(list);
- collection.add("hello");
- collection.add("JAVA");
- System.out.println("集合大小为:"+collection.size());
- }
- }
程序的执行结果如下(不同的电脑执行的结果可能不同):
JAVA真正需要某个类时才会加载对应的.class文档,而非在程序启动就加载所有的类。是谁负责把这些.class文件加载在内存中的?答案就是:类加载器。JVM启动后会产生三个默认类加载器,首先产生Bootstrap Loader,然后Bootstrap Loader会产生Extended Loader,并将Extended Loader的父类加载器设为Extended Loader,接着Bootstrap Loader会产生System Loader,并将System Loader的父类加载器设为Extended Loader,如下图:
如下例:
- package Reflection;
- public class ClassLoaderText {
- public static void main(String[] args) {
- ClassLoader loader =
- ClassLoaderText.class.getClassLoader();
- System.out.println(loader);
- System.out.println(loader.getParent());
- System.out.println(loader.getParent().getParent());
- }
- }
上述程序片段用于得到加载ClassLoaderText类的类加载器,得到这个类的加载器后再用它得到它的父类加载器,执行结果如下:
sun.misc.Launcher$AppClassLoader@535ff48b
sun.misc.Launcher$ExtClassLoader@40affc70
null
从执行结果可以看到,加载ClassLoaderText类的类加载器为AppClassLoader,这属于System Loader,而AppClassLoader的父类加载器是ExtClassLoader,ExtClassLoader的父类原本是Bootstrap Loader,但是为什么会打印出null呢?这是因为Bootstrap Loader是用C语言编写的,所以java语言并不能解析。
System Loader会搜索ClassPath路径,默认是当前工作路径下的.class文档,可以使用System.getProperty("java.class.path")来取得java.class.path指定的路径,在使用JAVA执行程序时也可以加上-cp来覆盖原有的ClassPath路径。
Extended Loader会搜索系统参数java.ext.dirs中指定位置的类,默认是jre目录lib\ext中的.class文档,或者是lib\ext目录中的.jar文档里的类。
Bootstrap Loader会搜索系统参数sun.boot.class.path中指定位置的类,默认是jre目录中的.class文档或者是lib目录中的.jar(比如rt.jar)文档里的类。
在加载类时,每个类加载器会先将加载类的任务交给父类加载器,如果父类加载器找不到,才由自己加载,所以在加载指定类时,会以Bootstrap Loader→Extended Loader→System Loader顺序寻找类。如果所有类加载器都找不到指定的类就会抛出NoClassDefFoundError。
上面的例子中,在加载ClassLoaderText类时,System Loader会委托给父类加载器Extended Loader加载类,而Extended Loader则又会委托Bootstrap Loader进行加载,由于Bootstrap Loader在它设定的路径中找不到该类,所以由Extended Loader尝试着在它设定的路径下查找,在找不到类的情况才会由System Loader设定的路径下进行查找,而最终在ClassPath下找到该类,从而进行加载。
类加载器都继承自抽象类ClassLoader,每个.class文档加载后,都会有个Class实例来代表,可以由Class的getClassLoader()方法取得加载对应的.class文档的ClassLoader实例。因而,可以定义自己的类加载器,通过继承ClassLoader可以实现自定义类加载器,自定义类加载器的父类将会是System Loader。
由同一个类加载器载入的.class文档,只会有一个Class实例。如果同一个.class文档由两个不同的类加载器载入,则会有两份不同的Class实例。但是如果有两个自定义类加载器尝试搜索相同的类,假如在父类加载器System Loader以上的层级就可以找到指定的类,那么只会有一份Class实例,如果父类加载器找不到而是由各自定义的类加载器找到,那么就会有两份Class实例。