前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >CatalinaDaemon 中的反射机制

CatalinaDaemon 中的反射机制

作者头像
Fisherman渔夫
发布2020-02-18 11:31:17
4520
发布2020-02-18 11:31:17
举报
文章被收录于专栏:渔夫渔夫

引子:最近学了一下 Tomcat ,觉得自己有必要回顾一下 Java 反射机制,加深理解了。

一、反射知识的回顾

 这里部分引用 李兴华的java se 实战经典中对 Java 反射的讲解。

反射之中包含了一个“反”的概念,所以要想解释反射就必须先从“正”开始解释,一般而言,当用户使用一个类的时候,应该先知道这个类,而后通过这个类产生实例化对象,但是“反”指的是通过对象找到类。

代码语言:javascript
复制
package cn.mldn.demo;
class Person {}
public class TestDemo {
    public static void main(String[] args) throws Exception {
        Person per = new Person() ; // 正着操作
        System.out.println(per.getClass().getName());   // 反着来
    }
}

 以上的代码使用了一个getClass()方法,而后就可以得到对象所在的“包.类”名称,这就属于“反”了,但是在这个“反”的操作之中有一个getClass()就作为发起一切反射操作的开端。

 Person 的父类是 Object 类,而上面所使用getClass()方法就是 Object 类之中所定义的方法。

 取得 Class 对象:public final Class<?> getClass()。反射之中的所有泛型都定义为 ?,返回值都是 Object。

 而这个 getClass() 方法返回的对象是 Class 类的对象,所以这个 Class 就是所有反射操作的源头。但是在讲解其真正使用之前还有一个需要先解释的问题,既然 Class 是所有反射操作的源头,那么这个类肯定是最为重要的,而如果要想取得这个类的实例化对象,Java 中定义了至少四种方式:

方式一:通过 Object 类的getClass()方法取得,基本不用:因为这已经使用了new关键字,使用反射的必要性不大了。

代码语言:javascript
复制
package cn.mldn.demo;
class Person {}
public class TestDemo {
    public static void main(String[] args) throws Exception {
        Person per = new Person() ; // 正着操作
        Class<?> cls = per.getClass() ; // 取得Class对象
        System.out.println(cls.getName());  // 反着来
    }
}

方式二:使用类.class取得,在日后学习 Hibernate 开发的时候使用

代码语言:javascript
复制
package cn.mldn.demo;
class Person {}
public class TestDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Person.class ;   // 取得Class对象
        System.out.println(cls.getName());  // 反着来
    }
}

方式三:使用 Class 类内部定义的一个 static 方法,最多使用

 取得 Class 类对象:public static Class<?> forName(String className) throws ClassNotFoundException;

代码语言:javascript
复制
package cn.mldn.demo;
class Person {}
public class TestDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("cn.mldn.demo.Person") ;   // 取得Class对象
        System.out.println(cls.getName());  // 反着来
    }
}

方式四:使用类加载器来加载相关类,得到Class<?>对象:

代码语言:javascript
复制
package cn.mldn.demo;
class Person {}
public class TestDemo {
    public static void main(String[] args) throws Exception {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        Class<?> cls = cl.loadClass("cn.mldn.demo.Person") ;   // 取得Class对象
        System.out.println(cls.getName());  // 反着来
    }
}

 那么现在一个新的问题又来了,取得了 Class 类的对象有什么用处呢?对于对象的实例化操作之前一直依靠构造方法和关键字 new 完成,可是有了 Class 类对象之后,现在又提供了另外一种对象的实例化方法:

 通过反射实例化对象:public T newInstance() throws InstantiationException, IllegalAccessException;

范例:通过反射实例化对象

代码语言:javascript
复制
package cn.mldn.demo;
class Person {
    @Override
    public String toString() {
        return "Person Class Instance .";
    }
}
public class TestDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("cn.mldn.demo.Person") ;   // 取得Class对象
        Object obj = cls.newInstance() ;    // 实例化对象,和使用关键字new一样
        Person per = (Person) obj ; // 向下转型
        System.out.println(per);
    }
}

 那么现在可以发现,对于对象的实例化操作,除了使用关键字 new 之外又多了一个反射机制操作,而且这个操作要比之前使用的 new 复杂一些,可是有什么用?

 对于程序的开发模式之前一直强调:尽量减少耦合,而减少耦合的最好做法是使用接口,但是就算使用了接口也逃不出关键字 new,所以实际上 new 是造成耦合的关键元凶。

范例:回顾一下之前所编写的工厂设计模式

代码语言:javascript
复制
package cn.mldn.demo;
interface Fruit {
    public void eat() ;
}
class Apple implements Fruit {
    public void eat() {
        System.out.println("吃苹果。");
    };
}
代码语言:javascript
复制
class Factory {
    public static Fruit getInstance(String className) {
        if ("apple".equals(className)){
            return new Apple() ;
        }
        return null ;
    }
}
public class FactoryDemo {
    public static void main(String[] args) {
        Fruit f = Factory.getInstance("apple") ;
        f.eat() ;
    }
}

 以上为之前所编写最简单的工厂设计模式,但是在这个工厂设计模式之中有一个最大的问题:如果现在接口的子类增加了,那么工厂类肯定需要修改,这是它所面临的最大问题,而这个最大问题造成的关键性的病因是 new,那么如果说现在不使用关键字new了,变为了反射机制呢?

 反射机制实例化对象的时候实际上只需要“包.类”就可以,于是根据此操作,修改工厂设计模式。

代码语言:javascript
复制
package cn.mldn.demo;
interface Fruit {
    public void eat() ;
}
class Apple implements Fruit {
    public void eat() {
        System.out.println("吃苹果。");
    };
}
class Orange implements Fruit {
    public void eat() {
        System.out.println("吃橘子。");
    };
}
class Factory {
    public static Fruit getInstance(String className) {
        Fruit f = null ;
        try {
            f = (Fruit) Class.forName(className).newInstance() ;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return f ;
    }
}
public class FactoryDemo {
        public static void main(String[] args) {
        Fruit f = Factory.getInstance("cn.mldn.demo.Orange") ;
        f.eat() ;
    }
}

 发现,这个时候即使增加了接口的子类,工厂类照样可以完成对象的实例化操作,这个才是真正的工厂类,可以应对于所有的变化。如果单独从开发角度而言,与开发者关系不大,但是对于日后学习的一些框架技术这个就是它实现的命脉,在日后的程序开发上,如果发现操作的过程之中需要传递了一个完整的“包.类”名称的时候几乎都是反射机制作用。


 上述工程模式提供的反射机制实际上也不算彻底,因为我们可以将所有对象以 Object 类型引用,通过反射的方法来调用对象的方法。下面就来看看 Tomcat 中的 Servlet 容器 CatalinaDaemon 中使用的反射机制实现原理。

二、CatalinaDaemon 实例对象构造过程中使用的反射

代码语言:javascript
复制
    ClassLoader catalinaLoader = null;//先得到一个实例对象引用变量
    catalinaLoader = createClassLoader("server", commonLoader);//得到卡特琳娜类加载器实例
	/**
	* 以下两个方法用于设置在特定情况下默认使用的类加载器
	*/
    Thread.currentThread().setContextClassLoader(catalinaLoader);

    SecurityClassLoad.securityClassLoad(catalinaLoader);

	
    Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");//通过类加载器来加载一个类,返回 Class 对象
    
    Object startupInstance = startupClass.getConstructor().newInstance();//通过 Class 对象得到构造器,然后在调用newInstance()方法,相当于调用了无参构造方法,返回一个实例对象
    
    /**
    * 以下方法用于设置父类加载器
    */
	String methodName = "setParentClassLoader"; 
	Class<?> paramTypes[] = new Class[1];
    paramTypes[0] = Class.forName("java.lang.ClassLoader");
	
	Method method =
            startupInstance.getClass().getMethod(methodName, paramTypes);
     
	method.invoke(startupInstance, paramValues);
    /**
    * 以下方法用于得到一个 Catalina 实例对象,使用 Object 类变量引用它
    */	
    catalinaDaemon = startupInstance;

 使用反射来进行方法调用的原因:

 这里我们使用反证法来说明原因,假设使用一般的方法:instance.method0()通过实例对象来调用方法的方式(假设我们都不知道可以使用反射来解决)。

 上面已经提到了,我们得到一个 Catalina 实例对象,使用 Object 类变量引用它。那么,如果不进行向下转型,编译器中就只能调用父类 Object 的方法,这个显然不能满足要求。为了避免这个情况,我们可以使用强制类型转换,即向下转型。但是,这又引发了一个问题,我们之前引入反射机制一步一步得到 Class 对象、Object 引用的实例对象,其目的是为了不直接在代码中使用指定类型,这样有助于代码解耦,容易后续修改。如果使用强制类型转换就破坏了上面做的所有努力。

 所以实际上,catalinaDaemon 实例对象的所有方法调用都是通过反射实现的。例如上个 code block 中提到的部分代码:

代码语言:javascript
复制
	Method method =
            startupInstance.getClass().getMethod(methodName, paramTypes);
     
	method.invoke(startupInstance, paramValues);
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、反射知识的回顾
  • 二、CatalinaDaemon 实例对象构造过程中使用的反射
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档