Java反射机制作用
1.编译期(Compile Time)之外的运行期(Runtime)检查类,接口,变量以及方法的信息。
2.运行期实例化对象,调用方法,通过调用 get/set 方法获取变量的值。(常用)
Java6的反射机制例子
//得到MyObject所有方法
Method[] methods = MyObject.class.getMethods();
for(Method method : methods){
System.out.println("method = " + method.getName());
}
Java反射可以获取类的如下内容
① 编译期获得对象(已知类名):MyObject.class //获取MyObject对象
运行期获得对象(编译期未知类名,运行期获得类名字符串)
Class.forName("packageName.className");
② cls.getName() 返回类的全限定类名(包含包名)
cls.getSimpleName() //仅返回类的名称(不包含包名)
③ cls.getModifiers() 获得修饰符(public…)
④ cls.getPackage() 获取包名
⑤ cls.getSuperclass() 获取父类
⑥ cls.getInterfaces() 获取实现接口集合
⑦ cls.getConstructors() 获取所有构造方法
⑧ cls.getMethods() 获取一个类的所有方法
⑨ cls.getFields() 获取一个类的所有成员变量
⑩ cls..getAnnotations() 获取一个类的所有注解
Java构造器
①Constructor constructor = cls.getConstructor(new Class[]{String.class}); //获取指定构造器
②Class[] constructor.getParameterTypes() //获取指定构造方法的方法参数
③ 利用 Constructor 对象实例化一个类
constructor.newInstance()方法的方法参数是一个可变参数列表,但是当你调用构造方法的时候你必须提供精确的参数,即形参与实参必须一一对应。
Constructor constructor
=MyObject.class.getConstructor(String.class);
MyObject myObject = (MyObject)
constructor.newInstance("args1");
Java变量
① Field[] cls.getFields(); //获取所有成员变量
② Field cls.getField(“someField”) //获取指定成员变量
③ field.getName() //得到变量名称
④ field.getType() //获取变量类型
⑤ Class aClass = MyObject.class
Field field = aClass.getField("someField");
MyObject objectInstance = new MyObject();
Object value = field.get(objectInstance);
field.set(objetInstance, value);
传入 Field.get()/Field.set()方法的参数 objetInstance 应该是拥有指定变量的类的实例。在上述的例子中传入的参数是 MyObjec t类的实例,是因为 someField 是 MyObject 类的实例。 如果变量是静态变量的话(public static)那么在调用 Field.get()/Field.set()方法的时候传入 null 做为参数而不用传递拥有该变量的类的实例。
Java方法
① cls.getMethods(); //获取所有方法集合
② cls.getMethod(“doFunction”, new Class[]{String.class}); //获取制定方法,并且参数是String类型,若没有参数设置为null
③ Class[] method.getParameterTypes(); //获取指定方法的参数
④ Class method.getReturnType(); //获取指定方法的返回类型
⑤ 通过Method调用方法
//获取一个方法名为doSomesthing,参数类型为String的方法
Method method = MyObject.class.getMethod("doSomething", String.class);
Object returnValue = method.invoke(null, "parameter-value1");
Java访问器
Getter
Getter 方法的名字以 get 开头,没有方法参数,返回一个值。
Setter
Setter 方法的名字以 set 开头,有一个方法参数。
setters 方法有可能会有返回值也有可能没有,一些 Setter 方法返回 void,一些用来设置值,有一些对象的 setter 方法在方法链中被调用(译者注:这类的 setter 方法必须要有返回值),因此你不应该妄自假设 setter 方法的返回值,一切应该视情况而定。
//判断getter/setter方法
public static boolean isGetter(Method method)
{
if(!method.getName().startsWith("get")) return false;
if(method.getParameterTypes().length != 0) return false;
if(void.class.equals(method.getReturnType()) return false;
return true;
}
public static boolean isSetter(Method method)
{
if(!method.getName().startsWith("set")) return false;
if(method.getParameterTypes().length != 1) return false;
return true;
}
Java泛型
两个典型的使用泛型的场景:
1、声明一个需要被参数化(parameterizable)的类/接口。
2、使用一个参数化类。
① 泛型方法返回类型
Method method = MyClass.class.getMethod("getStringList", null);
Type returnType = method.getGenericReturnType();
if(returnType instanceof ParameterizedType){
ParameterizedType type = (ParameterizedType) returnType;
Type[] typeArguments = type.getActualTypeArguments();
//打印出 “typeArgClass = java.lang.String”,Type[]数组typeArguments 只有一个 结果 – 一个代表 java.lang.String 的 Class 类的实例。Class 类实现了 Type 接口。
for(Type typeArgument : typeArguments){
Class typeArgClass = (Class) typeArgument;
System.out.println("typeArgClass = " + typeArgClass);
}
}
②泛型方法参数类型
method = Myclass.class.getMethod("setStringList", List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for(Type genericParameterType : genericParameterTypes){
if(genericParameterType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType) genericParameterType;
Type[] parameterArgTypes = aType.getActualTypeArguments();
for(Type parameterArgType : parameterArgTypes){
Class parameterArgClass = (Class) parameterArgType;
System.out.println("parameterArgClass = " + parameterArgClass);
}
}
}
③ 泛型变量类型
Field field = MyClass.class.getField("stringList");
Type genericFieldType = field.getGenericType();
if(genericFieldType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType) genericFieldType;
Type[] fieldArgTypes = aType.getActualTypeArguments();
for(Type fieldArgType : fieldArgTypes){
Class fieldArgClass = (Class) fieldArgType;
System.out.println("fieldArgClass = " + fieldArgClass);
}
}
Java 动态代理
常见用例
数据库连接以及事物管理
单元测试中的动态 Mock 对象
自定义工厂与依赖注入(DI)容器之间的适配器
类似 AOP 的方法拦截器
利用Java反射机制你可以在运行期动态的创建接口的实现。 java.lang.reflect.Proxy 类就可以实现这一功能。
①创建代理
Proxy.newProxyInstance()方法创建动态代理
newProxyInstance()方法有三个参数:
InvocationHandler handler = new MyInvocationHandler();
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
MyInterface.class.getClassLoader(), //1、类加载器(ClassLoader)用来加载动态代理类。new Class[] { MyInterface.class }, //2、一个要实现的接口的数组。handler); //3、一个 InvocationHandler 把所有方法的调用都转到代理上。
InvocationHandler 接口
public interface InvocationHandler{
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
实现类
public class MyInvocationHandler implements InvocationHandler{
Object invoke(Object proxy, Method method, Object[] args) throws Throwable { }
}
传入 invoke()方法中的 proxy 参数是实现要代理接口的动态代理对象。通常你是不需要他的。
invoke()方法中的 Method 对象参数代表了被动态代理的接口中要调用的方法,从这个 method 对象中你可以获取到这个方法名字,方法的参数,参数类型等等信息。关于这部分内容可以查阅之前有关 Method 的文章。
Object 数组参数包含了被动态代理的方法需要的方法参数。注意:原生数据类型(如int,long等等)方法参数传入等价的包装对象(如Integer, Long等等)。
Java 动态类加载与重载
①类加载器
所有 Java 应用中的类都是被 java.lang.ClassLoader 类的一系列子类加载的。因此要想动态加载类的话也必须使用 java.lang.ClassLoader 的子类。
一个类一旦被加载时,这个类引用的所有类也同时会被加载。类加载过程是一个递归的模式,所有相关的类都会被加载。但并不一定是一个应用里面所有类都会被加载,与这个被加载类的引用链无关的类是不会被加载的,直到有引用关系的时候它们才会被加载。
②类加载体系
在 Java 中类加载是一个有序的体系。当你新创建一个标准的 Java 类加载器时你必须提供它的父加载器。当一个类加载器被调用来加载一个类的时候,首先会调用这个加载器的父加载器来加载。如果父加载器无法找到这个类,这时候这个加载器才会尝试去加载这个类。
③类加载
类加载器加载类的顺序如下: 1、检查这个类是否已经被加载。 2、如果没有被加载,则首先调用父加载器加载。 3、如果父加载器不能加载这个类,则尝试加载这个类。
动态类加载
ClassLoader classLoader = MainClass.class.getClassLoader();
try {
Class aClass = classLoader.loadClass("com.jenkov.MyClass");
System.out.println("aClass.getName() = " + aClass.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
④动态类重载
动态类重载有一点复杂。Java 内置的类加载器在加载一个类之前会检查它是否已经被加载。因此重载一个类是无法使用 Java 内置的类加载器的,如果想要重载一个类你需要手动继承 ClassLoader。
在你定制 ClassLoader 的子类之后,你还有一些事需要做。所有被加载的类都需要被链接。这个过程是通过 ClassLoader.resolve()方法来完成的。由于这是一个 final 方法,因此这个方法在 ClassLoader 的子类中是无法被重写的。resolve()方法是不会允许给定的 ClassLoader 实例链接一个类两次。所以每当你想要重载一个类的时候你都需要使用一个新的 ClassLoader 的子类。你在设计类重载功能的时候这是必要的条件。
⑤自定义类重载
在前面已经说过你不能使用已经加载过类的类加载器来重载一个类。因此你需要其他的 ClassLoader 实例来重载这个类。