1、通过反射获得类信息
Java在需要用到某个类的时候会载入该类的.class字节码文件,然后在JVM产生java.lang.Class实例对象代表该字节码文件,从该java.lang.Class实例可以获得类的方法、数据成员、注解等信息,这种从java.lang.Class获得类的相关信息的方式就称为反射。
默认加载.class文档的时候(严格来说是首次加载class文档的时候,不过一般类都是加载一次)会执行静态区块,而且如果只是声明对象的话不会加载.class文档,如下所示:
class ClassTest{
static{
System.out.println("载入ClassTest.class文档");
}
}
public class Main {
public static void main(String[] args)throws Exception
{
ClassTest test1; //这里不会加载.class文档
ClassTest test2 = new ClassTest(); //到这里会加载.class文档
}
}
类被加载之后系统就会为该类生成一个对应的Class类型对象(类的加载指的就是将类的class字节码文件读入内存,并为其创建一个java.lang.Class对象),比如Foo类的话就是Class<Foo>类型的对象,也就是说不管生成了多少Foo类对象,只存在一个Class<Foo>类的对象。有三种方式来获得类的Class对象:Class.forName()静态方法、对象的getClass()方法、类的class静态成员,推荐使用第三种即类的class成员来获得Class对象,因为其在编辑阶段就可以检查需要访问的Class是否存在,使用第一种方式的话因为在编辑或编译阶段无法知道要访问的Class是否存在,所以需要处理类不存在异常,第二种方式因为使用的是方法所以相对于第三种方法直接使用成员会慢点。还可以获得基本类型的Class对象,如int.class(通过Integer.TYPE也可以获得)。
class Foo{
void func()throws Exception{
Class t1 = Class.forName("Foo");
Class t2 = new Foo().getClass();
Class<Foo> t3 = Foo.class; //也可以不指定泛型:Class t3 = Foo.class;
}
}
在使用数据库的时候,因为JDK无法事先知道厂商操作(实现)java.sql.Driver接口的类名称,所以开发人员可以通过第一种方式即Class.forName()来动态加载类,如下所示。Class.forName()还有另一个版本可以指定加载class文档的时候不执行静态区块,而是在建立类实例的时候才执行:
Class.forName("com.mysql.cj.jdbc.Driver");
class ClassTest{
static{
System.out.println("载入ClassTest.class文档");
}
}
public class Main {
public static void main(String[] args)throws Exception
{
Class clz = Class.forName("ClassTest",
false,
ClassTest.class.getClassLoader()/*加载ClassTest.class文档的类加载器*/);
Class clz2 = Class.forName("ClassTest",
true,
Main.class.getClassLoader()/*使用当前类的类加载器来载入类*/); //相当于是Class.forName("ClassTest")
}
}
如下为使用Class对象实现的单例类:
class Singleton
{
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance()
{
if(instance == null)
{
synchronized (Singleton.class) {
if(instance == null)
{
instance = new Singleton();
}
}
}
return instance;
}
}
Class类中包含了获取类的构造器(Constructor类型)、方法(Method类型)、成员(Field类型)、注解(Annotation类型)、内部类、父类、修饰符、类名、所在包、是否为枚举或接口或注解等信息:
import java.util.*;
import java.lang.reflect.*;
import java.lang.annotation.*;
// 定义可重复注解
@Repeatable(Annos.class)
@interface Anno {}
@Retention(value=RetentionPolicy.RUNTIME)
@interface Annos {
Anno[] value();
}
// 使用4个注解修饰该类
@SuppressWarnings(value="unchecked")
@Deprecated
// 使用重复注解修饰该类
@Anno
@Anno
public class ClassTest
{
// 为该类定义一个私有的构造器
private ClassTest()
{
}
// 定义两个有参数的构造器
public ClassTest(String name)
{
System.out.println("执行有参数的构造器");
}
public Foo(int i){}
// 定义一个无参数的info方法
public void info()
{
System.out.println("执行无参数的info方法");
}
// 定义两个有参数的info方法
public void info(String str)
{
System.out.println("执行有参数的info方法" + ",其str参数值:" + str);
}
public void info(int i){}
// 定义一个测试用的内部类
class Inner
{
}
public static void main(String[] args)throws Exception
{
// 获取ClassTest对应的Class对象
Class<ClassTest> clazz = ClassTest.class;
System.out.printf("类名称: %s%n", clazz.getName());
System.out.printf("修饰权限:%s%n, Modifier.toString(clazz.getModifiers())"); //public/protected/private
System.out.printf("是否为接口: %s%n", clazz.isInterface());
System.out.printf("是否为基本类型: %s%n", clazz.isPrimitive());
System.out.printf("是否为数组对象: %s%n", clazz.isArray());
System.out.printf("父类名称: %s%n", clazz.getSuperclass().getName());
System.out.printf("所在包:%s%n, clazz.getPackage().getName()");
System.out.printf("所在模块: %s%n", clazz.getModule());
// getDeclaredConstructor()获取该Class对象所对应类的全部构造器(包括私有的构造器,对于私有构造器还应该调用setAccessible(true)才能使用)
//getConstructor()获得的是所有public构造器
Constructor[] ctors = clazz.getDeclaredConstructors();
System.out.println("ClassTest的全部构造器如下:");
for (Constructor c : ctors)
{
System.out.println(c);
}
//获取指定参数类型的构造器,通过构造器可以创建对象
Foo tt = (Foo)clazz.getDeclaredConstructor().newInstance();/通过反射来创建对象
Foo tt2 = (Foo)clazz.getDeclaredConstructor(String.class).newInstance("test");//通过反射来创建对象,使用带一个String参数的构造方法
Foo tt3 = (Foo)clazz.getDeclaredConstructor(int.class).newInstance(100);通过反射来创建对象,使用带一个int参数的构造方法
// 获取该Class对象所对应类的全部public构造器
Constructor[] publicCtors = clazz.getConstructors();//getConstructor()为获得指定参数类型的public构造器
System.out.println("ClassTest的全部public构造器如下:");
for (Constructor c : publicCtors)
{
System.out.println(c);
}
//getConstructor()为获取指定参数类型的public构造器
Foo ttt = (Foo)cls.getConstructor().newInstance();//通过反射创建对象
// getDeclaredMethods()获取该Class对象所对应类的全部方法(包括私有方法,不包括父类方法)
//getMethods()为获取所有的公共方法,包括父类的方法
Method[] publicMtds = clazz.getDeclaredMethods();
System.out.println("ClassTest的全部方法如下:");
for (Method md : publicMtds)
{
String strModifier = Modifier.toString(md.getModifiers()); //修饰权限, public/protected/private
System.out.println(md);
}
// 获取该Class对象所对应类的全部public方法
Method[] mtds = clazz.getMethods();
System.out.println("ClassTest的全部public方法如下:");
for (Method md : mtds)
{
System.out.println(md);
}
// 获取该Class对象所对应类的指定方法
System.out.println("ClassTest里带一个String参数的info()方法为:" + clazz.getMethod("info" , String.class));
System.out.println("ClassTest里带一个int参数的info()方法为:" + clazz.getMethod("info" , int.class));
// 获取该Class对象所对应类的上的全部注解
Annotation[] anns = clazz.getAnnotations();
System.out.println("ClassTest的全部Annotation如下:");
for (Annotation an : anns)
{
System.out.println(an);
}
// 获取该Class对象所对应类的上的指定注解
System.out.println("该Class元素上的@SuppressWarnings注解为:"
+ Arrays.toString(clazz.getAnnotationsByType(SuppressWarnings.class)));
System.out.println("该Class元素上的@Anno注解为:"
+ Arrays.toString(clazz.getAnnotationsByType(Anno.class)));
// 获取该Class对象所对应类的全部内部类
Class<?>[] inners = clazz.getDeclaredClasses();
System.out.println("ClassTest的全部内部类如下:");
for (Class c : inners)
{
System.out.println(c);
}
// 使用Class.forName方法加载ClassTest的Inner内部类
Class inClazz = Class.forName("ClassTest$Inner");
// 通过getDeclaringClass()获得该类所在的外部类
System.out.println("inClazz对应类的外部类为:" + inClazz.getDeclaringClass());
System.out.println("ClassTest的父类为:" + clazz.getSuperclass());
}
}
Constructor、Method从Executable抽象基类派生,该类中包含了获取方法的注解、修饰符、形参信息(修饰符、类型、名称等)、形参个数、是否包含个数可变的形参等方法。需要注意的是类编译的时候指定-parameters选项才能获得该类的形参信息。
import java.lang.reflect.*;
import java.util.*;
class Test
{
public void replace(String str, List<String> list){}
}
public class MethodParameterTest
{
public static void main(String[] args)throws Exception
{
Class<Test> clazz = Test.class;
// 获取String类的带两个参数的replace()方法
Method replace = clazz.getMethod("replace", String.class, List.class);
// 获取指定方法的参数个数
System.out.println("replace方法参数个数:" + replace.getParameterCount());
// 获取replace的所有参数信息
Parameter[] parameters = replace.getParameters();
int index = 1;
// 遍历所有参数
for (Parameter p : parameters)
{
if (p.isNamePresent())
{
System.out.println("---第" + index++ + "个参数信息---");
System.out.println("参数名:" + p.getName());
System.out.println("形参类型:" + p.getType());
System.out.println("泛型类型:" + p.getParameterizedType());
}
}
}
}
如下所示的Test类,我们无法通过反射获得该对象的泛型信息,因为反射获得的是类的信息,而泛型类型是在生成对象的时候指定的:
class Test<T>{}
public class App {
public static void main(String[] args) {
Test t = new Test<Integer>(); //指定泛型类型为Integer
}
}
我们可以通过使用继承,在派生类中指定泛型类型,这样就能通过反射获得类的泛型信息:
abstract class absTest<T> {
public Class getTemplateType()
{
Type type = getClass().getGenericSuperclass();
Type[] types = ((ParameterizedType)type).getActualTypeArguments();
Class<T> clazz = (Class<T>) types[0];
return clazz;
}
}
class Test extends absTest<Integer/*指定泛型类型为Integer*/> {
}
public class Main {
public static void main(String[] args) {
Test test = new Test();
Class clazz = test.getTemplateType();
System.out.println(clazz.getName()); //输出为 java.lang.Integer
}
}
可以直接通过反射来获得类中成员的泛型信息:
class Test{
private List<String> list;
//获取成员List中的泛型
public static void testList() throws NoSuchFieldException, SecurityException {
Type t = Test.class.getDeclaredField("list").getGenericType();
if (ParameterizedType.class.isAssignableFrom(t.getClass())) {
for (Type t1 : ((ParameterizedType) t).getActualTypeArguments()) {
System.out.print(t1 + ",");
}
}
}
}
public class App {
public static void main(String[] args)throws Exception {
Test.testList(); //输出为class java.lang.String,
}
}
2、通过反射生成对象和操作对象
通过反射生成对象可以使用Class对象的newInstance()方法(使用默认构造函数)或者Constructor对象的newInstance()方法(使用指定构造函数),如果我们事先不知道类名,只知道类文件名或所在目录,那么可以通过Class.forName()来动态加载文档,然后通过构造器来创建对象实例:
Class<?> clazz = Class.forName("ClassName");
Object obj = clazz.newInstance(); //使用Class对象的newInstance()创建对象,该方法已被废弃
Constructor ctor = clazz.getConstructor(String.class); //获得带一个String参数的构造方法
Object obj = ctor.newInstance("test"); //使用Constructor对象的newInstance()创建对象
通过Method中的invoke(Object obj, Object... args)来调用指定的方法,第一个参数为执行该方法的对象(静态方法的话可以为null),后面参数为形参列表,如果需要调用私有方法的话,先调用Method的setAccessible(true)设置取消访问权限检查(调用Method或FieldsetAccessible()时,如果模块权限不允许的话会抛出异常,比如java.base模块(String、Integer等JDK类所属的模块)中的类就没有开放权限):
Class<?> targetClass = target.getClass();
Method mtd = targetClass.getMethod("FunName" , String.class);
mtd.invoke(target , "test");
通过Class对象的getDeclaredFields()/getFields()、getDeclaredField()/getField()来获得类的成员变量Field,通过Field的get()、set()(引用类型成员)或getXxx()、setXxx()(基本数据类型成员)来获取和设置成员变量的值:
import java.lang.reflect.*;
class Person
{
private String name;
private int age;
public String toString()
{
return "Person[name:" + name +
" , age:" + age + " ]";
}
}
public class FieldTest
{
public static void main(String[] args)
throws Exception
{
Person p = new Person();
Class<Person> personClazz = Person.class;
Field nameField = personClazz.getDeclaredField("name");// 获取Person的名为name的成员变量
String strModifier = Modifier.toString(nameField.getModifiers()); //获得修饰权限,public/protected/private
nameField.setAccessible(true); // 设置通过反射访问该成员变量时取消访问权限检查
nameField.set(p , "Yeeku.H.Lee"); // 调用set()方法为p对象的name成员变量设置值
Field ageField = personClazz.getDeclaredField("age");// 获取Person类名为age的成员变量
ageField.setAccessible(true);// 设置通过反射访问该成员变量时取消访问权限检查
ageField.setInt(p , 30);// 调用setInt()方法为p对象的age成员变量设置值
System.out.println(p);
}
}
虽然我们不知道数组的构造函数,但也可以通过 Array类来动态的创建数组:
Object arr1 = Array.newInstance(String.class, 10); // 创建一个元素类型为String ,长度为10的数组
Array.set(arr1, 5, "疯狂Java讲义"); // 为arr数组中index为5的元素赋值,引用类型使用set()
Object book1 = Array.get(arr1 , 5); // 取出arr数组中index为5的元素的值,引用类型使用get()
String[] ar = (String[])arr1;
ar[0] = "AD";
String ss = ar[0];
Object arr2 = Array.newInstance(int.class, 10);
Array.setInt(arr2, 5, 10); // 基本数据类型使用setXxx()
int num = Array.getInt(arr2 , 5); // 基本数据类使用getXxx()
Object arr = Array.newInstance(String.class, 2, 3); //创建原始类型为String的二维数组,数组大小为2,数组元素为3个大小的数组
Array.set(arr, 0, new String[]{"java", "c++", "c#"}); //为二维数组的第一个元素赋值
Object aryObj = Array.get(arr, 0); //获得二维数组的第一个元素,它也是一个数组
String str = (String)Array.get(aryObj, 0); //"java"
String[][] cast1 = (String[][])arr;
String str1 = cast1[0][1]; // "c++"
String[] cast2 = cast1[0];
String str2 = cast2[2]; // "c#"
3、使用反射生成实现动态代理
JDK动态代理只能为接口创建动态代理,使用Proxy类的newProxyInstance()静态方法(或getProxyClass)来创建动态代理类和动态代理实例,如下所示,其第一个参数是类加载器,第二个参数指定该代理对象实现的所有接口的Class对象,第三个参数指定与之关联的InvocationHandler对象,执行代理对象的方法时会被替换成执行InvocationHandler对象的invoke()方法。
newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);
如下所示实现了使用Proxy类的newProxyInstance()方法生成了实现Person接口的对象后,调用该对象的方法实际上调用的是另一个对象(实现了InvocationHandler接口的对象)的方法的功能。
import java.lang.reflect.*;
interface Person
{
void walk();
void sayHello(String name);
}
class MyInvokationHandler implements InvocationHandler
{
/*
执行动态代理对象的所有方法时,都会被替换成执行如下的invoke方法
其中:
proxy:代表动态代理对象
method:代表正在执行的方法
args:代表调用目标方法时传入的实参。
*/
public Object invoke(Object proxy, Method method, Object[] args)
{
System.out.println(method + "called");
if (args != null)
{
System.out.println("parameters:");
for (Object val : args)
{
System.out.println(val);
}
}
else
{
System.out.println("no parameters");
}
return null;
}
}
public class ProxyTest
{
public static void main(String[] args)
throws Exception
{
// 创建一个InvocationHandler对象
InvocationHandler handler = new MyInvokationHandler();
// 使用指定的InvocationHandler来生成动态代理对象
Person p = (Person)Proxy.newProxyInstance(Person.class.getClassLoader()
, new Class[]{Person.class}, handler);
// 调用动态代理对象的walk()和sayHello()方法,实际上是调用MyInvokationHandler的invoke()方法。
p.walk();
p.sayHello("孙悟空");
}
}
动态代理的应用:假设现在有很多类,在执行它们的方法之前必须先执行另一个类中的方法来初始化(或者在方法之后执行清理工作), 比如下面的GunDog类的run()方法在调用之前必须先执行Util类的method()方法,如果哪一天Util类的method()方法名称或者参数有变或者直接被废弃转为使用另一个类中的方法,那么所有调用Util类method()方法的地方都要改动。可以使用动态代理使调用GunDog的run()方法的时候就先执行Util的method()方法:
import java.lang.reflect.*;
public class Util
{
static public void method()
{
System.out.println("=====通用方法=====");
}
}
public interface Dog
{
void run();
}
public class GunDog implements Dog
{
public void run() // 实现info()方法
{
System.out.println("我奔跑迅速");
}
}
public class MyInvokationHandler implements InvocationHandler
{
// 需要被代理的对象
private Object target;
public void setTarget(Object target)
{
this.target = target;
}
// 执行动态代理对象的所有方法时,都会被替换成执行如下的invoke方法
public Object invoke(Object proxy, Method method, Object[] args)
throws Exception
{
//先执行Util的method方法
Util.method();
//再执行本来要执行的method方法
Object result = method.invoke(target , args);// 以target作为主调来执行method方法
return result;
}
}
public class Main {
public static void main(String[] args)throws Exception
{
Dog target = new GunDog();
MyInvokationHandler handler = new MyInvokationHandler();
handler.setTarget(target);
// 创建一个动态代理
Dog dog = (Dog)Proxy.newProxyInstance(target.getClass().getClassLoader()
, target.getClass().getInterfaces() , handler);//Class的getInterfaces()方法返回该类实现的所有接口的Class对象
dog.run();
}
}
可以定义一个工厂类来生成对应的动态代理对象,这样就形成了一个通用的创建指定动态代理的方法,不用每个需要先执行Util的method()的地方都重复写一份代码来创建对应的动态代理:
public class MyProxyFactory
{
// 为指定target生成动态代理对象
public static Object getProxy(Object target)throws Exception
{
MyInvokationHandler handler = new MyInvokationHandler();
handler.setTarget(target);
// 创建、并返回一个动态代理
return Proxy.newProxyInstance(target.getClass().getClassLoader()
, target.getClass().getInterfaces() , handler);//Class的getInterfaces()方法返回该类实现的所有接口的Class对象
}
}
public class Main {
public static void main(String[] args)throws Exception
{
Dog target = new GunDog();
Dog dog = (Dog)MyProxyFactory.getProxy(target);// 以target来创建动态代理
dog.run();
}
}
这种动态代理在AOP()中被称为AOP代理,AOP代理可替代目标对象,AOP代理包含了目标对象的全部方法,但AOP代理里的方法可以在执行目标之前、之后插入一些通常处理,如下图所示:
相对于动态代理,还有一种静态代理的方式,如下所示的GunDogProxy为静态代理类,静态代理方式的缺点是需要为每个功能类来对应定义一个代理类:
interface Dog {
void run();
}
class GunDog implements Dog {
public void run()
{
System.out.println("我奔跑迅速");
}
}
class GunDogProxy implements Dog{
private Dog dogObj;
GunDogProxy(Dog dogObj){ this.dogObj = dogObj; }
public void run(){
method();
dogObj.run();
}
static public void method()
{
System.out.println("=====通用方法=====");
}
}
public class Main {
public static void main(String[] args){
Dog dog = new GunDogProxy(new GunDog());
dog.run();
}
}
4、反射和泛型
例如String.class的类型实际上是Class<String>,如果Class对应的类暂时未知,则使用Class<?>。下面的对象工厂类中的getInstance1()方法创建实例后返回的是Object类型,需要进行类型转换,而getInstance2()方法使用了泛型,所以调用后无需使用强制类型转换:
import java.util.*;
import javax.swing.*;
import java.lang.reflect.*;
class Foo
{
public Foo(){}
}
public class CrazyitObjectFactory
{
public static Object getInstance1(String clsName)
{
try
{
Class cls = Class.forName(clsName);
return cls.newInstance();
}
catch(Exception e)
{
e.printStackTrace();
return null;
}
}
public static <T> T getInstance2(Class<T> cls)
{
try
{
//使用Constructor.newInstance而不是Class.newInstance,避免警告
Constructor<T> c = cls.getConstructor();
return c.newInstance();
}
catch(Exception e)
{
e.printStackTrace();
return null;
}
}
public static void main(String[] args)
{
// 获取实例后需要进行类型转换
Foo f1 = (Foo)CrazyitObjectFactory.getInstance1("Foo");
// 获取实例后无须类型转换
Foo f2 = CrazyitObjectFactory.getInstance2(Foo.class);
//f2.func();
}
}
前面说过,通过类对应的Class对象可以获得该类的所有成员变量Field类型,Field的getType()可以获得该成员的类型,其返回的就是Class<?>类型:
public class Test
{
private int _num;
public static void main(String[] args) throws Exception
{
Class<Test> clazz = Test.class;
Field f = clazz.getDeclaredField("_num");
Class<?> t = f.getType();
System.out.println("_num的类型:" + t); //int
}
}
如果成员是带泛型类型的类型,如集合类型List<String>、Map<String, Integer>,则可以使用Field的getGenericType()获得该成员的类型信息,然后转换为ParameterizedType对象,通过ParameterizedType对象来获得泛型信息:
import java.util.*;
import java.lang.reflect.*;
public class GenericTest
{
private Map<String , Integer> score;
public static void main(String[] args)
throws Exception
{
Class<GenericTest> clazz = GenericTest.class;
Field f = clazz.getDeclaredField("score");
Type gType = f.getGenericType(); // 获得成员变量f的类型
if(gType instanceof ParameterizedType)
{
ParameterizedType pType = (ParameterizedType)gType;
Type rType = pType.getRawType(); //获得成员的类型
System.out.println("成员类型是:" + rType); //输出“interface java.util.Map”
Type[] tArgs = pType.getActualTypeArguments();// 获得泛型类型
System.out.println("泛型信息是:");
for (int i = 0; i < tArgs.length; i++) //依次输出:class java.lang.String、class java.lang.Integer
{
System.out.println("第" + i + "个泛型类型是:" + tArgs[i]);
}
}
else
{
System.out.println("获取泛型类型出错!");
}
}
}
5、反射与模块
现在有cc.openhome和cc.openhome.reflect两个模块,想要在cc.openhome模块中使用反射来获得cc.openhome.reflect模块中类的信息的话,可以编写cc.openhome模块的模块描述文件如下,这时的模块图如下所示(NetBeans中开启模块描述文件后,会有一个Graph按钮来查看模块图 )。对于cc.openhome.reflect的模块描述文件也要使用exports来设置模块中哪些包中的公开成员可以被其它模块存取,如果想要私有成员也能被其它模块存取的话需要使用Opens或者使用open来修饰整个模块表示模块中所有包中的类的私有成员都可以被其它模块存取:
module cc.openhome{
requires cc.openhome.reflect;
}
module cc.openhome.reflect{
exports cc.openhome.reflect;
}
module cc.openhome.reflect{
opens cc.openhome.reflect;
}
/*open module cc.openhome.reflect{
exports cc.openhome.reflect;
}*/
6、使用ServiceLoader
现在有接口PlayerProvoider,接口实现类PlayerImpl,以及需要调用接口实现类功能的Main类,它们都属于不同的模块,在Main类中使用反射来获得PlayerImpl对象的话需要在PlayerImpl所在模块的描述文件中添加exports cc.openhome.impl,在Main类所在模块的描述文件中添加requires cc.openhome.impl,这样的话Main类的模块就依赖在PlayerImpl类模块了。可以使用ServiceLoader来避免依赖问题,如下所示的ServiceLoader会寻找各模块中是否有接口PlayerProvoider的具体操作(实现类),找到第一个后就运用反射建立对应的实例:
/*PlayerProvoider接口的声明*/
package cc.openhome.api;
import java.util.ServiceLoader;
public interface PlayerProvoider{
void play();
public static PlayerProvoider getPlayer(){
return ServiceLoader.load(PlayerProvoider.class).findFirst().orElseThrow(()->new RuntimeException("未找到PlayerProvoider实现"));
}
}
/*PlayerProvoider接口所在模块cc.openhome.api的描述文件*/
module cc.openhome.api{
exports cc.openhome.api;
uses cc.openhome.api.PlayerProvoider; //必须使用uses来设定当前模块会使用哪个接口提供服务
}
/*
import cc.openhome.api.PlayerProvoider;
module cc.openhome.api{
exports cc.openhome.api;
uses PlayerProvoider; //使用uses来设定当前模块会使用哪个接口提供服务
}*/
/*PlayerImpl类的声明*/
package cc.openhome.impl;
import cc.openhome.api.PlayerProvoider;
public class PlayerImpl implements PlayerProvoider{
@Override
public void play(){}
}
/*cc.openhome.impl实现类所在模块的描述文件*/
module cc.openhome.impl{
requires cc.openhome.api;
provides cc.openhome.api.Provider with cc.openhome.impl.PlayerImpl; //必须使用provides设定此模块类是cc.openhome.api.Provider的操作类
}
/*
import cc.openhome.api.Provider;
import cc.openhome.impl.PlayerImpl;
module cc.openhome.impl{
requires cc.openhome.api;
provides Provider with PlayerImpl;
}*/
/*Main类*/
import cc.openhome.api.PlayerProvoider;
public class Main {
public static void main(String[] args){
PlayerProvoider player = PlayerProvoider.getPlayer();
player.play();
}
}
在不使用模块化的时候也可以使用ServiceLoader,来实现不显示的使用接口的实现类的功能,这样以后可以方便的替换接口的实现类而不用修改代码。方法是在JAR中META-INF/services文件夹,放入与类全名相同的文件,其中写入操作对象的类全名。
7、使用ServiceLoader
JDK使用类加载器来载入.class文件,在JDK9中有三种层次的类加载器System类加载器、Platform类加载器(System的父加载器)、Bootstrap类加载器(Platform的父加载器)。System类加载器可以加载程序模块路径上的类、类路径上的类以及放在jdk.javadoc、jdk.jartool等模块中的JDK特定工具类。Platform类加载器可以加载Java SE API、实现类。Bootstrap类加载器可以加载java.base、java.logging、java.prefs、java.deskto模块中的类。可以通过Class实例的getClassLoader()来获得加载其的类加载器是哪个:
public class Main {
public static void main(String[] args){
Class clz = Main.class;
System.out.println(clz.getClassLoader()); //AppClassLoader
System.out.println(clz.getClassLoader().getParent()); //PlatformClassLoader
System.out.println(clz.getClassLoader().getParent().getParent()); //null,默认不能获得Bootstrap加载器
}
}
System类加载器需要加载类的时候,如果符合祖父类加载器Bootstrap,那么就会使用Bootstrap类加载器,否则委托父加载器Platform试着加载类,如果找不到的话就会在模块路径和类路径上试着加载类,如果还是没找到的话就会抛出ClassNotFoundException异常。Platform类加载器需要加载类的时候会委托System类加载器来加载类,找不到的话就使用父加载器Bootstrap来试着加载类,还是找不到类的话再在Platform自身定义下的模块中搜索。即System、Platform加载器会先搜索各类加载器定义的模块,如果某个类加载器定义的模块适用的话就会使用该加载器来加载类:
package cc.openhome;
public class Main {
public static void main(String[] args)throws ClassNotFoundException{
ClassLoader platform = Main.class.getClassLoader().getParent(); //获得Platform类加载器
Class clz = platform.loadClass("cc.openhome.Some"); //加载当前模块下的Some类
System.out.println(clz.getClassLoader()); //输出为AppClassLoader,即Some类由System类加载器加载而非Platform类加载器加载
}
}
ClassLoader的静态方法getSystemClassLoader()、getPlatformClassLoader()可以获得System、Platform类加载器。
System加载器、Platform加载器、Bootstrap加载器的搜索路径是固定的,如果在程序运行中想要动态的从其他路径加载类,可以使用自定义的加载器URLClassLoader。URLClassLoader使用默认构造函数的话父加载器为SystemLoader,所以会委托System类加载器来加载类,如果找不到的话再使用URLClassLoader在指定路径搜索类。
import java.net.URL;
import java.net.URLClassLoader;
public class Main {
public static void main(String[] args) throws Exception{
URL url = new URL("file:/c:/workspace/classes");
ClassLoader loader = new URLClassLoader(new URL[]{url});
Class clz = loader.loadClass("cc.openhome.Other");
}
}
同一个类加载器载入的.class文件,只会产生一个Class实例,两个不同的类加载器载入.class文档的话,会产生两个Class实例:
import java.net.URL;
import java.net.URLClassLoader;
public class Main {
public static void main(String[] args)throws Exception{
String path = "file:/c:/workspace/classes/";
String className = "cc.openhome.Ohter";
ClassLoader loader1 = new URLClassLoader(new URL[]{new URL(path)});
Class clz1 = loader.loadClass(className);
ClassLoader loader2 = new URLClassLoader(new URL[]{new URL(path)});
Class clz2 = loader.loadClass(className);
boolean b = clz1 == clz2; //false
}
}