文章目录
一、简介
java反射机制是指, 在运行状态时, 对于任意一个类, 都能够知道它的所有属性和方法; 对于任意一个对象, 都能够调用它的方法和访问它的属性。这种动态获取信息以及调用对象方法的功能被称为反射机制。
通常情况下, 反射机制的基本条件之一就是先获得Class对象。通过Class对象可以获得Method、Constructor、Field等对象, 这些对象分别代表着一个类包括的方法、构造器、成员变量等。
二、使用反射生成并操纵对象
1、创建对象
使用反射来生成对象有2种方式:
1.通过Class对象调用newInstance()方法生成Class对象对应类的实例。这种方式要求这个类有默认的构造器, newInstance()方法实际上是通过默认构造器完成实例的创建。
2.通过Class对象调用getConstructor方法获取Constructor的实例, 通过Constructor的实例调用newInstance()方法创建Class对象对应类的实例。这种方式的好处就是可以指定使用哪一个构造器创建对象。
方式1——
class Birl{
public Birl(){
System.out.println("调用Birl构造器");
}
}
public class CreateObject {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
// 获取Class对象
Class<?> clazz = Class.forName("test.Lang_learn.reflect.Birl");
clazz.newInstance();
}
}
// 输出:
调用Birl构造器
方式2——
class Birl{
public Birl(){
System.out.println("调用Birl构造器");
}
public Birl(int i){
System.out.println("调用Birl有参数构造器");
System.out.println("i=" + i);
}
}
public class CreateObject {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class<?> clazz = Class.forName("test.Lang_learn.reflect.Birl");
Constructor constructor = clazz.getConstructor(Integer.TYPE);
constructor.newInstance(9999);
}
}
// 输出:
调用Birl有参数构造器
i=9999
看上去使用new的方式更简单, 但是请思考下面的情况:
许多javaEE框架都需要根据配置文件来创建Java对象, 从配置文件获取的只是某个类的字符串形式, 程序需要根据字符串来创建对象, 在这种情况下就需要反射。
Spring就使用这种方式, 通过配置文件来配置对象, 然后程序根据配置文件的信息来创建对象。
Spring配置项——
根据配置文件获取实例——
配置文件名:className.Properties ——
// 文件内容
myobj=test.Lang_learn.reflect.MyObject
测试类——
class MyObject{
public MyObject() {
System.out.println("create object!!");
}
@Override
public String toString() {
return "MyObject!!";
}
}
实现代码——
// 代码参考: 《Java疯狂讲义》
public class CreateObjectBySetting {
// 对象池
private Map<String, Object> objectPool = new HashMap<>();
private Object createObj(String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
// 根据类的全限定名创建Class对象
Class clazz = Class.forName(className);
// 使用默认构造器创建对应类实例
return clazz.newInstance();
}
public Map<String, Object> initObjectPool(String filename) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
try (FileInputStream fin = new FileInputStream(filename)) {
Properties props = new Properties();
props.load(fin);
// properties文件形式通常为key=value
// 只要根据key就能获取value
// 根据value创建的实例添加到对象池中
for (String name : props.stringPropertyNames()) {
objectPool.put(name, createObj(props.getProperty(name)));
}
} catch (IOException e) {
System.out.println("IOException:" + e);
}
return objectPool;
}
public Object getObject(String name) {
// 根据名称从对象池获取对象
return objectPool.get(name);
}
public static void main(String[] args) throws IllegalAccessException, ClassNotFoundException, InstantiationException {
CreateObjectBySetting cob = new CreateObjectBySetting();
String setting = "G:\\github-project\\learning_ssm_helloWorld\\src" +
"\\main\\java\\test\\Lang_learn\\reflect\\className.Properties";
cob.initObjectPool(setting);
Object obj = cob.getObject("myobj");
System.out.println(obj.toString());
}
}
// 输出:
create object!!
MyObject!!
2、调用方法
一般需要以下步骤:
1.获取Class对象
2.根据Class对象获取Method对象, 通过getMethod()或getMethods()方法
3.method对象调用invoke方法。
class Dog{
public void fun(String arg){
System.out.println("invoke method, arg is:" + arg);
}
}
public class InvokeMethod {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
// 获取Class对象并创建实例
Class<?> clazz = Class.forName("test.Lang_learn.reflect.Dog");
Object dog = clazz.newInstance();
// 根据方法名和参数类型获取方法
Method method = clazz.getMethod("fun", String.class);
// 调用方法
method.invoke(dog, "abcde");
}
}
// 输出:
invoke method, arg is:abcde
invoke方法声明如下:
invoke(Object obj, Object… args): obj是调用方法的对象, args表示传入的参数
调用对象的set方法, 根据配置文件设置成员变量的值——
配置文件名:className.Properties ——
// 文件内容
myobj=test.Lang_learn.reflect.MyObject
myobj%value=9999
测试类——
class MyObject{
private String value;
public void setValue(String value){
this.value = value;
}
public MyObject() {
System.out.println("create object!!");
}
@Override
public String toString() {
return "MyObject!!" + value;
}
}
实现代码——
// 代码参考: 《Java疯狂讲义》
public class ExtendCreateObjBySetting {
private Map<String, Object> objectPool = new HashMap<>();
private Properties config = new Properties();
private Object createObj(String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
// 根据类的全限定名创建Class对象
Class clazz = Class.forName(className);
// 使用默认构造器创建对应类实例
return clazz.newInstance();
}
// 加载配置文件
public void init(String filename) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
try (FileInputStream fin = new FileInputStream(filename)) {
config.load(fin);
} catch (IOException e) {
e.printStackTrace();
}
}
// 初始化对象池
public void initObjectPool() throws IllegalAccessException, InstantiationException, ClassNotFoundException {
for (String name : config.stringPropertyNames()) {
if (!name.contains("%")) {
objectPool.put(name, createObj(config.getProperty(name)));
}
}
}
public void initObjProperty() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
for (String name : config.stringPropertyNames()) {
// 字符串的形式为: 类名%属性 = 设置的属性值
if (name.contains("%")) {
String[] objAndPro = name.split("%");
// 根据类名获取对象
Object obj = getObject(objAndPro[0]);
// 拼接方法名: setXxxx
String methodName = "set" + objAndPro[1].substring(0, 1).toUpperCase() + objAndPro[1].substring(1);
// 获取Class对象
Class<?> targetClass = obj.getClass();
// 获取set方法
Method method = targetClass.getMethod(methodName, String.class);
// 调用方法
method.invoke(obj, "9999");
}
}
}
public Object getObject(String name) {
// 根据名称从对象池获取对象
return objectPool.get(name);
}
public static void main(String[] args) throws IllegalAccessException, ClassNotFoundException, InstantiationException, NoSuchMethodException, InvocationTargetException {
ExtendCreateObjBySetting ecob = new ExtendCreateObjBySetting();
String setting = "G:\\github-project\\learning_ssm_helloWorld\\src" +
"\\main\\java\\test\\Lang_learn\\reflect\\className.Properties";
ecob.init(setting);
ecob.initObjectPool();
ecob.initObjProperty();
Object obj = ecob.getObject("myobj");
System.out.println(obj.toString());
}
}
// 输出:
create object!!
MyObject!!9999
3、访问成员变量
3.1、获取成员变量
通过Class对象的getField和getFields()方法可以获取public修饰的指定成员变量或所有punlic修饰的成员变量。
若要获取private修饰的成员变量, 需要通过getDeclaredField方法, getDeclaredField方法可以获取各种访问控制符修饰的变量。
3.2、设置成员变量的值
通过Field对象的set方法可设置成员变量:
1.对于基本类型来说, Field提供了对应的方法。比如: setInt、setByte等等。
2.对于引用类型来说, 直接调用set方法。
3.3 取消访问权限检查
对于private修饰的成员, 在赋值或调用之前需要设置取消访问权限检查。
调用 setAccessible(boolean flag) 方法进行设置, true表示取消访问权限检查; false表示启用访问权限检查。
class Cat {
public int weight;
private String sex;
private int age;
@Override
public String toString() {
return "Cat{" +
"weight=" + weight +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
}
public class AcessVariable {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Cat cat = new Cat();
Class<Cat> catClass = Cat.class;
// 通过getDeclaredField方法可以获取各种访问控制符修饰的变量
Field[] declaredFields = catClass.getDeclaredFields();
System.out.println("可获取的变量:" + Arrays.toString(declaredFields));
// 获取指定的成员变量
Field sexFileld = catClass.getDeclaredField("sex");
// 设置通过反射访问成员变量时取消访问权限检查
sexFileld.setAccessible(true);
// 通过set方法设置属性值
sexFileld.set(cat, "boy");
Field ageField = catClass.getDeclaredField("age");
ageField.setAccessible(true);
ageField.setInt(cat, 3);
System.out.println(cat.toString());
}
}
// 输出:
可获取的变量:[public int test.Lang_learn.reflect.Cat.weight, private java.lang.String test.Lang_learn.reflect.Cat.sex, private int test.Lang_learn.reflect.Cat.age]
Cat{weight=0, sex='boy', age=3}
4、创建数组
在java.lang.reflect包下还提供了Array类, 这个类可以表示所有的数组。通常创建一个数组实例需要以下步骤:
(1). 调用 Array.newInstance(Class<?> componentType, int length) 方法创建实例
(2). 调用 Array.set(Object array, int index, Object value) 方法设置数组的值
(3). 调用 Array.get(Object array, int index) 方法获取数组的值
这些方法都有重载的方法。set和get方法针对元素为基础类型时还提供了专门方法:
setInt(Object array, int index, int i)
setLong(Object array, int index, long l)
...
getInt(Object array, int index)
getLong(Object array, int index)
...
public class CreateArray {
public static void main(String[] args) {
// 创建长度为3的一维数组
Object arr = Array.newInstance(String.class, 3);
// 设置索引为1处的值
Array.set(arr, 1, "a");
// 设置索引为2处的值
Array.set(arr, 2, "b");
// 根据索引获取元素
Object ele = Array.get(arr, 0);
Object ele1 = Array.get(arr, 1);
Object ele2 = Array.get(arr, 2);
System.out.println(ele);
System.out.println(ele1.toString());
System.out.println(ele2.toString());
System.out.println("====分割====");
// 创建二维数组,长度为3
Object arr2 = Array.newInstance(int.class, 3, 2);
Array.set(arr2, 0, new int[]{1, 2});
Array.set(arr2, 1, new int[]{3, 4});
Array.set(arr2, 2, new int[]{4, 5});
System.out.println(Arrays.deepToString((int[][])arr2));
}
}
//输出:
null
a
b
====分割====
[[1, 2], [3, 4], [4, 5]]
三、使用反射生成jdk动态代理
要使用反射生成动态代理, 大致要做2件事:
1.实现 InvocationHandler 接口
2.创建动态代理实例
interface Person {
void walk();
void say(String info);
}
class MyInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("传入的方法参数为:" + Arrays.toString(args));
System.out.println("正在执行的方法" + method);
return null;
}
}
public class ProxyTest {
public static void main(String[] args) {
InvocationHandler invocationHandler = new MyInvocationHandler();
Person person = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, invocationHandler);
person.walk();
person.say("hiiiiiiiiiiii");
}
}
// 输出:
传入的方法参数为:null
正在执行的方法public abstract void test.Lang_learn.reflect.Person.walk()
传入的方法参数为:[hiiiiiiiiiiii]
正在执行的方法public abstract void test.Lang_learn.reflect.Person.say(java.lang.String)
当使用jdk动态代理时, 代理对象会为代理的接口实现所有的方法。 但其实并非真正实现了, 通过代理对象调用接口方法时, 都会转为对InvocationHandler中invoke方法的调用。
在普通编程时, 并没有使用动态代理的机会, 但在编写框架和基础代码时 , 动态代理的作用就非常大。
四、动态代理和AOP
通常情况下我们写代码的改进过程是这样的:
复制、粘贴 ——> 相同代码写成一个方法, 然后调用
原始的方式——
使用方法组织相同的代码段——
使用方法组织代码的形式降低代码维护的成本, 也使代码更简洁了。 但是该方法又与代码段1,2,3 耦合了。理想的效果是: 既可以执行这个方法, 但又无需以硬编码的方式调用, 这时可以使用动态代理来实现。
// 代理的接口
interface Plane {
void fly();
}
interface Bus {
void start();
}
// 接口实现类
class PlaneOne implements Plane {
public void fly() {
System.out.println("The plane is about to take off");
}
}
class BusOne implements Bus {
public void start() {
System.out.println("The bus will start");
}
}
这个类的方法就相当于上图所提到的深色代码段所在的方法——
class CheckUtil {
public static void check() {
System.out.println("check before starting");
}
}
InvocationHandler实现类——
class MyInvocationHandler2 implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
CheckUtil.check();
return method.invoke(target, args);
}
}
class MyProxyFacroty {
// 根据target参数生成代理对象
public static Object getProxy(Object target) {
MyInvocationHandler2 mih = new MyInvocationHandler2();
mih.setTarget(target);
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
mih);
}
}
测试类——
public class ProxyTest2 {
public static void main(String[] args) {
Plane target = new PlaneOne();
Plane plane = (Plane) MyProxyFacroty.getProxy(target);
plane.fly();
Bus target2 = new BusOne();
Bus bus = (Bus)MyProxyFacroty.getProxy(target2);
bus.start();
}
}
// 输出:
check before starting
The plane is about to take off
check before starting
The bus will start
当我们通过代理对象调用代理类的方法时, 实际上是转为了对invoke方法的调用。
而invoke方法先调用了CheckUtil.check()
方法, 然后通过method.invoke(target, args)
语句, 以target为调用者对方法进行真正的调用。
这种代理在AOP(面向切面编程)中称为AOP代理, 通过AOP代理我们可以在执行目标方法之前, 插入一些通用处理。
五、泛型与Class类
从jdk1.5之后, Class类增加了泛型功能, 通过泛型类限制Class类, 使用Class<T>可避免强制转换。
假设创建一个根据类名获取对象的工厂
class CreateObjFactory{
public static Object createObj(String name){
try {
Class<?> cl = Class.forName(name);
return cl.newInstance();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
我们通过该工厂创建一个对象,语句如下:
Date date = (Date) CreateObjFactory.createObj("java.util.Date");
可以看到返回的对象还需要进行强制转换, 因为返回的是Object类型的对象。这意味着在编译阶段, 强制转换的语句不会发生问题, 即使这个类型并非正确, 直到运行阶段才会抛出异常。例如:
String string = (String) CreateObjFactory.createObj("java.util.Date");
为了在编译阶段就能发现问题, 可使用泛型
class CreateObjFactory2{
public static <T> T getInstance(Class<T> clazz) {
try {
return clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
这样调用Date date = CreateObjFactory2.getInstance(Date.class);
就可以得到Date对象。
六、通过反射获取泛型信息
通常情况下, 通过Class对象根据变量名称获取对应的Field后, 通过getType()
就能获取变量的类型。
若参数类型是有泛型限制, 想要获取泛型的类型需要:
1.通过getGenericType方法获取 “声明类型+泛型类型” ——Type gtype = field.getGenericType();
2.将gtype强转为ParameterizedType对象——ParameterizedType代表被参数化的类型, 也就是增加了泛型限制的类型。ParameterizedTypetigon提供如下方法:
(1).getRawType——获取不包括泛型信息的原始类型
(2).getActualTypeArguments()——获取泛型参数的类型
private Map<String, Integer> map = new HashMap<>();
public static void main(String[] args) throws NoSuchFieldException {
ProxyTest2 pt = new ProxyTest2();
Class<?> clazz = pt.getClass();
Field field = clazz.getDeclaredField("map");
Class<?> f = field.getType();
System.out.println("map的类型为:" + f);
Type gtype = field.getGenericType();
System.out.println("声明类型+泛型类型:" + gtype);
if (gtype instanceof ParameterizedType) {
// 转为ParameterizedType对象
ParameterizedType pType = (ParameterizedType) gtype;
Type rawType = pType.getRawType();
System.out.println("原始类型信息: " + rawType);
Type[] types = pType.getActualTypeArguments();
System.out.println("泛型类型为: " + Arrays.toString(types));
}
}
// 输出:
map的类型为:interface java.util.Map
声明类型+泛型类型:java.util.Map<java.lang.String, java.lang.Integer>
原始类型信息: interface java.util.Map
泛型类型为: [class java.lang.String, class java.lang.Integer]