一、反射介绍
1.0 动静语言和动态语言
-
动静语言
- 是一类在运行是能够扭转其构造的语言:例如新的函数,对象,甚至代码能够被引进,已有的函数能够被删除或者是其余构造上的变动。艰深点就是说能够在运行时代码能够依据某些条件扭转本身构造
- 次要动静语言:Object-C、JavaScript、PHP、Python 等
-
动态语言
- 与动静语言向对应的,运行时构造不可变的语言就是动态语言。如 Java、C、C++
- Java 尽管不是动静语言,但 Java 能够称之为“准动静语言”。即 Java 有肯定的动态性,咱们能够利用反射机制取得相似动静语言的个性。
1.1 什么是反射
反射就是 Reflection,Java 的反射是指程序在运行期能够拿到一个对象的所有信息。
失常状况下,如果咱们要调用一个对象的办法,或者拜访一个对象的字段,通常会传入对象实例:
// Main.java
import com.itranswarp.learnjava.Person;
public class Main {String getFullName(Person p) {return p.getFirstName() + " " + p.getLastName();}
}
然而,如果不能取得 Person
类,只有一个 Object
实例,比方这样:
String getFullName(Object obj) {return ???}
怎么办?有童鞋会说:强制转型啊!
String getFullName(Object obj) {Person p = (Person) obj;
return p.getFirstName() + " " + p.getLastName();
}
强制转型的时候,你会发现一个问题:编译下面的代码,依然须要援用 Person
类。不然,去掉 import
语句,你看能不能编译通过?
所以,反射是为了解决在运行期,对某个实例无所不知的状况下,如何调用其办法。
1.2 对象编译在 JVM 散布状况
<img src=”https://tva1.sinaimg.cn/large/007S8ZIlgy1gges29pxjoj30tw0t2q34.jpg” alt=”image-20200205095809050″ style=”zoom:50%;float:left” />
String 类、Dog 类在办法区中的类文件代码中,存了属性、构造方法、办法等信息,其实在 Class 中也就定义了 Field、Constructor、Method 这些信息,相当于定义一个对象的接口
<img src=”https://tva1.sinaimg.cn/large/007S8ZIlgy1ggerrn9w2xj30u00coaaq.jpg” alt=”image-20200205095809050″ style=”zoom:80%;float:left” />
1.3 Class 类
除了 int
等根本类型外,Java 的其余类型全部都是class
(包含interface
)。例如:
String
Object
Runnable
Exception
- …
认真思考,咱们能够得出结论:class
(包含interface
)的实质是数据类型(Type
)。无继承关系的数据类型无奈赋值:
Number n = new Double(123.456); // OK
String s = new Double(123.456); // compile error!
而 class
是由 JVM 在执行过程中动静加载的。JVM 在第一次读取到一种 class
类型时,将其加载进内存。
每加载一种 class
,JVM 就为其创立一个Class
类型的实例,并关联起来。留神:这里的 Class
类型是一个名叫 Class
的class
。它长这样:
public final class Class {private Class() {}}
以 String
类为例,当 JVM 加载 String
类时,它首先读取 String.class
文件到内存,而后,为 String
类创立一个 Class
实例并关联起来:
Class cls = new Class(String);
这个 Class
实例是 JVM 外部创立的,如果咱们查看 JDK 源码,能够发现 Class
类的构造方法是 private
,只有 JVM 能创立Class
实例,咱们本人的 Java 程序是无奈创立 Class
实例的。
所以,JVM 持有的每个 Class
实例都指向一个数据类型(class
或interface
):
┌───────────────────────────┐
│ Class Instance │──────> String
├───────────────────────────┤
│name = "java.lang.String" │
└───────────────────────────┘
┌───────────────────────────┐
│ Class Instance │──────> Random
├───────────────────────────┤
│name = "java.util.Random" │
└───────────────────────────┘
┌───────────────────────────┐
│ Class Instance │──────> Runnable
├───────────────────────────┤
│name = "java.lang.Runnable"│
└───────────────────────────┘
一个 Class
实例蕴含了该 class
的所有残缺信息:
┌───────────────────────────┐
│ Class Instance │──────> String
├───────────────────────────┤
│name = "java.lang.String" │
├───────────────────────────┤
│package = "java.lang" │
├───────────────────────────┤
│super = "java.lang.Object" │
├───────────────────────────┤
│interface = CharSequence...│
├───────────────────────────┤
│field = value[],hash,... │
├───────────────────────────┤
│method = indexOf()... │
└───────────────────────────┘
因为 JVM 为每个加载的 class
创立了对应的 Class
实例,并在实例中保留了该 class
的所有信息,包含类名、包名、父类、实现的接口、所有办法、字段等,因而,如果获取了某个 Class
实例,咱们就能够通过这个 Class
实例获取到该实例对应的 class
的所有信息。
这种通过 Class
实例获取 class
信息的办法称为反射(Reflection)。
1.4 如何获取一个 Class 实例
如何获取一个 class
的Class
实例?有三个办法:
-
间接通过一个
class
的动态变量class
获取:Class cls = String.class;
-
通过该实例变量提供的
getClass()
办法String s = "Hello"; Class cls = s.getClass();
-
通过静态方法
Class.forName()
Class cls = Class.forName("java.lang.String");
因为 Class
实例在 JVM 中是惟一的,所以,上述办法获取的 Class
实例是同一个实例。能够用 ==
比拟两个 Class
实例:
Class cls1 = String.class;
String s = "Hello";
Class cls2 = s.getClass();
boolean sameClass = cls1 == cls2; // true
1.5 Class 实例比拟和 instanceof 区别
Integer n = new Integer(123);
boolean b1 = n instanceof Integer; // true,因为 n 是 Integer 类型
boolean b2 = n instanceof Number; // true,因为 n 是 Number 类型的子类
boolean b3 = n.getClass() == Integer.class; // true,因为 n.getClass()返回 Integer.class
boolean b4 = n.getClass() == Number.class; // false,因为 Integer.class!=Number.class
用 instanceof
岂但匹配指定类型,还匹配指定类型的子类。而用 ==
判断 class
实例能够准确地判断数据类型,但不能作子类型比拟。
通常状况下,咱们应该用 instanceof
判断数据类型,因为面向形象编程的时候,咱们不关怀具体的子类型。只有在须要准确判断一个类型是不是某个 class
的时候,咱们才应用 ==
判断 class
实例。
1.6 Class 实例中的根本信息
public class Main {public static void main(String[] args) {printClassInfo("".getClass());
printClassInfo(Runnable.class);
printClassInfo(java.time.Month.class);
printClassInfo(String[].class);
printClassInfo(int.class);
}
static void printClassInfo(Class cls) {System.out.println("Class name:" + cls.getName());
System.out.println("Simple name:" + cls.getSimpleName());
if (cls.getPackage() != null) {System.out.println("Package name:" + cls.getPackage().getName());
}
System.out.println("is interface:" + cls.isInterface());
System.out.println("is enum:" + cls.isEnum());
System.out.println("is array:" + cls.isArray());
System.out.println("is primitive:" + cls.isPrimitive());
}
}
留神到数组(例如 String[]
)也是一种Class
,而且不同于String.class
,它的类名是[Ljava.lang.String
。此外,JVM 为每一种根本类型如 int 也创立了Class
,通过int.class
拜访。
如果获取到了一个 Class
实例,咱们就能够通过该 Class
实例来创立对应类型的实例:
// 获取 String 的 Class 实例:
Class cls = String.class;
// 创立一个 String 实例:
String s = (String) cls.newInstance();
上述代码相当于 new String()
。通过Class.newInstance()
能够创立类实例,它的局限是:只能调用 public
的无参数构造方法。带参数的构造方法,或者非 public
的构造方法都无奈通过 Class.newInstance()
被调用。
1.7 动静加载
JVM 在执行 Java 程序的时候,并不是一次性把所有用到的 class 全副加载到内存,而是第一次须要用到 class 时才加载。例如:
// Main.java
public class Main {public static void main(String[] args) {if (args.length > 0) {create(args[0]);
}
}
static void create(String name) {Person p = new Person(name);
}
}
当执行 Main.java
时,因为用到了 Main
,因而,JVM 首先会把Main.class
加载到内存。然而,并不会加载 Person.class
,除非程序执行到create()
办法,JVM 发现须要加载 Person
类时,才会首次加载 Person.class
。如果没有执行create()
办法,那么 Person.class
基本就不会被加载。
这就是 JVM 动静加载 class
的个性。
动静加载 class
的个性对于 Java 程序十分重要。利用 JVM 动静加载 class
的个性,咱们能力在运行期依据条件加载不同的实现类。例如,Commons Logging 总是优先应用 Log4j,只有当 Log4j 不存在时,才应用 JDK 的 logging。利用 JVM 动静加载个性,大抵的实现代码如下:
二、反射次要性能
反射的外围是 JVM 在运行时才动静加载类或调用办法 / 拜访属性,它不须要当时(写代码的时候或编译期)晓得运行对象是谁。
Java 反射次要提供以下性能:
- 在运行时判断任意一个对象所属的类;
- 在运行时结构任意一个类的对象;
- 在运行时判断任意一个类所具备的成员变量和办法(通过反射甚至能够调用 private 办法);
- 在运行时调用任意一个对象的办法
2.1 拜访字段
咱们先看看如何通过 Class
实例获取字段信息。Class
类提供了以下几个办法来获取字段:
- Field getField(name):依据字段名获取某个 public 的 field(包含父类)
- Field getDeclaredField(name):依据字段名获取以后类的某个 field(不包含父类)
- Field[] getFields():获取所有 public 的 field(包含父类)
- Field[] getDeclaredFields():获取以后类的所有 field(不包含父类)
public class Main {public static void main(String[] args) throws Exception {
Class stdClass = Student.class;
// 获取 public 字段 "score":
System.out.println(stdClass.getField("score"));
// 获取继承的 public 字段 "name":
System.out.println(stdClass.getField("name"));
// 获取 private 字段 "grade":
System.out.println(stdClass.getDeclaredField("grade"));
}
}
class Student extends Person {
public int score;
private int grade;
}
class Person {public String name;}
上述代码首先获取 Student
的Class
实例,而后,别离获取 public
字段、继承的 public
字段以及 private
字段,打印出的 Field
相似:
public int Student.score
public java.lang.String Person.name
private int Student.grade
一个 Field
对象蕴含了一个字段的所有信息:
-
getName()
:返回字段名称,例如,"name"
; -
getType()
:返回字段类型,也是一个Class
实例,例如,String.class
; -
getModifiers()
:返回字段的修饰符,它是一个int
,不同的 bit 示意不同的含意。
以 String
类的 value
字段为例,它的定义是:
public final class String {private final byte[] value;
}
咱们用反射获取该字段的信息,代码如下:
Field f = String.class.getDeclaredField("value");
f.getName(); // "value"
f.getType(); // class [B 示意 byte[]类型
int m = f.getModifiers();
Modifier.isFinal(m); // true
Modifier.isPublic(m); // false
Modifier.isProtected(m); // false
Modifier.isPrivate(m); // true
Modifier.isStatic(m); // false
2.1.1 获取字段值
利用反射拿到字段的一个 Field
实例只是第一步,咱们还能够拿到一个实例对应的该字段的值。
例如,对于一个 Person
实例,咱们能够先拿到 name
字段对应的 Field
,再获取这个实例的name
字段的值:
public class Main {public static void main(String[] args) throws Exception {Object p = new Person("Xiao Ming");
Class c = p.getClass();
Field f = c.getDeclaredField("name");
Object value = f.get(p);
System.out.println(value); // "Xiao Ming"
}
}
class Person {
private String name;
public Person(String name) {this.name = name;}
}
上述代码先获取 Class
实例,再获取 Field
实例,而后,用 Field.get(Object)
获取指定实例的指定字段的值。
运行代码,如果不出意外,会失去一个 IllegalAccessException
,这是因为name
被定义为一个 private
字段,失常状况下,Main
类无法访问 Person
类的 private
字段。要修复谬误,能够将 private
改为 public
,或者,在调用Object value = f.get(p);
前,先写一句:
f.setAccessible(true);
调用 Field.setAccessible(true)
的意思是,别管这个字段是不是public
,一律容许拜访。
能够试着加上上述语句,再运行代码,就能够打印出 private
字段的值。
有童鞋会问:如果应用反射能够获取 private
字段的值,那么类的封装还有什么意义?
答案是失常状况下,咱们总是通过 p.name
来拜访 Person
的name
字段,编译器会依据 public
、protected
和private
决定是否容许拜访字段,这样就达到了数据封装的目标。
而反射是一种非常规的用法,应用反射,首先代码十分繁琐,其次,它更多地是给工具或者底层框架来应用,目标是在不晓得指标实例任何信息的状况下,获取特定字段的值。
此外,setAccessible(true)
可能会失败。如果 JVM 运行期存在 SecurityManager
,那么它会依据规定进行查看,有可能阻止setAccessible(true)
。例如,某个SecurityManager
可能不容许对 java
和javax
结尾的 package
的类调用setAccessible(true)
,这样能够保障 JVM 外围库的平安。
2.1.2 设置字段值
通过 Field 实例既然能够获取到指定实例的字段值,天然也能够设置字段的值。
设置字段值是通过 Field.set(Object, Object)
实现的,其中第一个 Object
参数是指定的实例,第二个 Object
参数是待批改的值。示例代码如下:
public class Main {public static void main(String[] args) throws Exception {Person p = new Person("Xiao Ming");
System.out.println(p.getName()); // "Xiao Ming"
Class c = p.getClass();
Field f = c.getDeclaredField("name");
f.setAccessible(true);
f.set(p, "Xiao Hong");
System.out.println(p.getName()); // "Xiao Hong"
}
}
class Person {
private String name;
public Person(String name) {this.name = name;}
public String getName() {return this.name;}
}
2.2 调用办法
咱们曾经能通过 Class
实例获取所有 Field
对象,同样的,能够通过 Class
实例获取所有 Method
信息。Class
类提供了以下几个办法来获取Method
:
-
Method getMethod(name, Class...)
:获取某个public
的Method
(包含父类) -
Method getDeclaredMethod(name, Class...)
:获取以后类的某个Method
(不包含父类) -
Method[] getMethods()
:获取所有public
的Method
(包含父类) -
Method[] getDeclaredMethods()
:获取以后类的所有Method
(不包含父类)
public class Main {public static void main(String[] args) throws Exception {
Class stdClass = Student.class;
// 获取 public 办法 getScore,参数为 String:
System.out.println(stdClass.getMethod("getScore", String.class));
// 获取继承的 public 办法 getName,无参数:
System.out.println(stdClass.getMethod("getName"));
// 获取 private 办法 getGrade,参数为 int:
System.out.println(stdClass.getDeclaredMethod("getGrade", int.class));
}
}
class Student extends Person {public int getScore(String type) {return 99;}
private int getGrade(int year) {return 1;}
}
class Person {public String getName() {return "Person";}
}
上述代码首先获取 Student
的Class
实例,而后,别离获取 public
办法、继承的 public
办法以及 private
办法,打印出的 Method
相似:
public int Student.getScore(java.lang.String)
public java.lang.String Person.getName()
private int Student.getGrade(int)
一个 Method
对象蕴含一个办法的所有信息:
-
getName()
:返回办法名称,例如:"getScore"
; -
getReturnType()
:返回办法返回值类型,也是一个 Class 实例,例如:String.class
; -
getParameterTypes()
:返回办法的参数类型,是一个 Class 数组,例如:{String.class, int.class}
; -
getModifiers()
:返回办法的修饰符,它是一个int
,不同的 bit 示意不同的含意。
2.2.1 调用办法
当咱们获取到一个 Method
对象时,就能够对它进行调用。咱们以上面的代码为例:
String s = "Hello world";
String r = s.substring(6); // "world"
如果用反射来调用 substring
办法,须要以下代码:
public class Main {public static void main(String[] args) throws Exception {
// String 对象:
String s = "Hello world";
// 获取 String substring(int)办法,参数为 int:
Method m = String.class.getMethod("substring", int.class);
// 在 s 对象上调用该办法并获取后果:
String r = (String) m.invoke(s, 6);
// 打印调用后果:
System.out.println(r);
}
}
留神到 substring()
有两个重载办法,咱们获取的是 String substring(int)
这个办法。思考一下如何获取 String substring(int, int)
办法。
对 Method
实例调用 invoke
就相当于调用该办法,invoke
的第一个参数是对象实例,即在哪个实例上调用该办法,前面的可变参数要与办法参数统一,否则将报错
2.2.2 调用静态方法
如果获取到的 Method 示意一个静态方法,调用静态方法时,因为无需指定实例对象,所以 invoke
办法传入的第一个参数永远为 null
。咱们以Integer.parseInt(String)
为例:
public class Main {public static void main(String[] args) throws Exception {// 获取 Integer.parseInt(String)办法,参数为 String:
Method m = Integer.class.getMethod("parseInt", String.class);
// 调用该静态方法并获取后果:
Integer n = (Integer) m.invoke(null, "12345");
// 打印调用后果:
System.out.println(n);
}
}
2.2.3 调用非 public 办法
和 Field 相似,对于非 public 办法,咱们尽管能够通过 Class.getDeclaredMethod()
获取该办法实例,但间接对其调用将失去一个 IllegalAccessException
。为了调用非 public 办法,咱们通过Method.setAccessible(true)
容许其调用:
此外,setAccessible(true)
可能会失败。如果 JVM 运行期存在 SecurityManager
,那么它会依据规定进行查看,有可能阻止setAccessible(true)
。例如,某个SecurityManager
可能不容许对 java
和javax
结尾的 package
的类调用setAccessible(true)
,这样能够保障 JVM 外围库的平安。
2.2.4 多态办法
一个 Person
类定义了 hello()
办法,并且它的子类 Student
也覆写了 hello()
办法,那么,从 Person.class
获取的 Method
,作用于Student
实例时,调用的办法到底是哪个?
public class Main {public static void main(String[] args) throws Exception {
// 获取 Person 的 hello 办法:
Method h = Person.class.getMethod("hello");
// 对 Student 实例调用 hello 办法:
h.invoke(new Student());
}
}
class Person {public void hello() {System.out.println("Person:hello");
}
}
class Student extends Person {public void hello() {System.out.println("Student:hello");
}
}
运行上述代码,发现打印出的是Student:hello
,因而,应用反射调用办法时,依然遵循多态准则:即总是调用理论类型的覆写办法(如果存在)。上述的反射代码:
Method m = Person.class.getMethod("hello");
m.invoke(new Student());
实际上相当于:
Person p = new Student();
p.hello();
2.3 调用构造方法
咱们通常应用 new
操作符创立新的实例:
Person p = new Person();
如果通过反射来创立新的实例,能够调用 Class 提供的 newInstance()办法:
Person p = Person.class.newInstance();
调用 Class.newInstance()的局限是,它只能调用该类的 public 无参数构造方法。如果构造方法带有参数,或者不是 public,就无奈间接通过 Class.newInstance()来调用。
为了调用任意的构造方法,Java 的反射 API 提供了 Constructor 对象,它蕴含一个构造方法的所有信息,能够创立一个实例。Constructor 对象和 Method 十分相似,不同之处仅在于它是一个构造方法,并且,调用后果总是返回实例:
public class Main {public static void main(String[] args) throws Exception {// 获取构造方法 Integer(int):
Constructor cons1 = Integer.class.getConstructor(int.class);
// 调用构造方法:
Integer n1 = (Integer) cons1.newInstance(123);
System.out.println(n1);
// 获取构造方法 Integer(String)
Constructor cons2 = Integer.class.getConstructor(String.class);
Integer n2 = (Integer) cons2.newInstance("456");
System.out.println(n2);
}
}
通过 Class 实例获取 Constructor 的办法如下:
-
getConstructor(Class...)
:获取某个public
的Constructor
; -
getDeclaredConstructor(Class...)
:获取某个Constructor
; -
getConstructors()
:获取所有public
的Constructor
; -
getDeclaredConstructors()
:获取所有Constructor
。
留神 Constructor
总是以后类定义的构造方法,和父类无关,因而不存在多态的问题。
调用非 public
的Constructor
时,必须首先通过 setAccessible(true)
设置容许拜访。setAccessible(true)
可能会失败。
2.4 获取继承关系
2.4.1 获取 interface
因为一个类可能实现一个或多个接口,通过 Class
咱们就能够查问到实现的接口类型。例如,查问 Integer
实现的接口:
public class Main {public static void main(String[] args) throws Exception {
Class s = Integer.class;
Class[] is = s.getInterfaces();
for (Class i : is) {System.out.println(i);
}
}
}
运行上述代码可知,Integer
实现的接口有:
- java.lang.Comparable
- java.lang.constant.Constable
- java.lang.constant.ConstantDesc
要特地留神:getInterfaces()
只返回以后类间接实现的接口类型,并不包含其父类实现的接口类型:
public class Main {public static void main(String[] args) throws Exception {Class s = Integer.class.getSuperclass();
Class[] is = s.getInterfaces();
for (Class i : is) {System.out.println(i);
}
}
}
Integer
的父类是 Number
,Number
实现的接口是java.io.Serializable
。
此外,对所有 interface
的Class
调用 getSuperclass()
返回的是null
,获取接口的父接口要用getInterfaces()
:
System.out.println(java.io.DataInputStream.class.getSuperclass()); // java.io.FilterInputStream,因为 DataInputStream 继承自 FilterInputStream
System.out.println(java.io.Closeable.class.getSuperclass()); // null,对接口调用 getSuperclass()总是返回 null,获取接口的父接口要用 getInterfaces()
2.4.2 继承关系
当咱们判断一个实例是否是某个类型时,失常状况下,应用 instanceof
操作符:
Object n = Integer.valueOf(123);
boolean isDouble = n instanceof Double; // false
boolean isInteger = n instanceof Integer; // true
boolean isNumber = n instanceof Number; // true
boolean isSerializable = n instanceof java.io.Serializable; // true
如果是两个 Class
实例,要判断一个向上转型是否成立,能够调用isAssignableFrom()
:
// Integer i = ?
Integer.class.isAssignableFrom(Integer.class); // true,因为 Integer 能够赋值给 Integer
// Number n = ?
Number.class.isAssignableFrom(Integer.class); // true,因为 Integer 能够赋值给 Number
// Object o = ?
Object.class.isAssignableFrom(Integer.class); // true,因为 Integer 能够赋值给 Object
// Integer i = ?
Integer.class.isAssignableFrom(Number.class); // false,因为 Number 不能赋值给 Integer
三、反射优缺点
3.1 长处
- 反射机制极大的进步了程序的灵活性和扩展性,升高模块的耦合性,进步本身的适应能力。
- 通过反射机制能够让程序创立和管制任何类的对象,无需提前硬编码指标类。
- 应用反射机制可能在运行时结构一个类的对象、判断一个类所具备的成员变量和办法、调用一个对象的办法。
反射机制是构建框架技术的根底所在,应用反射能够防止将代码写死在框架中。正是反射有以上的特色,所以它能动静编译和创建对象,极大的激发了编程语言的灵活性,强化了多态的个性,进一步晋升了面向对象编程的形象能力,因此受到编程界的青眼。
3.2 毛病
只管反射机制带来了极大的灵活性及方便性,但反射也有毛病。反射机制的性能十分弱小,但不能滥用。在能不应用反射实现时,尽量不要应用,起因有以下几点:
- 性能问题。
Java 反射机制中蕴含了一些动静类型,所以 Java 虚拟机不可能对这些动静代码进行优化。因而,反射操作的效率要比失常操作效率低很多。咱们应该防止在对性能要求很高的程序或常常被执行的代码中应用反射。而且,如何应用反射决定了性能的高下。如果它作为程序中较少运行的局部,性能将不会成为一个问题。
- 平安限度
应用反射通常须要程序的运行没有平安方面的限度。如果一个程序对安全性提出要求,则最好不要应用反射。
- 程序健壮性
反射容许代码执行一些通常不被容许的操作,所以应用反射有可能会导致意想不到的结果。反射代码毁坏了 Java 程序结构的抽象性,所以当程序运行的平台发生变化的时候,因为形象的逻辑构造不能被辨认,代码产生的成果与之前会产生差别。
四、反射理论利用场景
4.1 Spring IOC
IOC:即“管制反转”,不是什么技术,而是一种思维。应用 IOC 意味着将你设计好的对象交给容器管制,而不是传统的在你的对象外部间接管制。在 Spring 的配置文件中,常常看到如下配置:
<bean id="userService" class="com.tim.wang.sourcecode.reflection.springioc.UserServiceImpl"></bean>
那么通过这样配置,Spring 是怎么帮咱们实例化对象,并且放到容器中去了了,就是通过反射!!!
// 解析 <bean .../> 元素的 id 属性失去该字符串值为“courseDao”String idStr = "courseDao";
// 解析 <bean .../> 元素的 class 属性失去该字符串值为“com.qcjy.learning.Dao.impl.CourseDaoImpl”String classStr = "com.qcjy.learning.Dao.impl.CourseDaoImpl";
// 利用反射常识,通过 classStr 获取 Class 类对象
Class<?> cls = Class.forName(classStr);
// 实例化对象
Object obj = cls.newInstance();
//container 示意 Spring 容器
container.put(idStr, obj);
当一个类外面须要利用另一类的对象时,Spring 的配置如下所示:
<bean id="courseService" class="com.qcjy.learning.service.impl.CourseServiceImpl">
<!-- 管制调用 setCourseDao()办法,将容器中的 courseDao bean 作为传入参数 -->
<property name="courseDao" ref="courseDao"></property>
</bean>
持续用伪代码的模式来模仿实现一下 Spring 底层解决原理:
// 解析 <property .../> 元素的 name 属性失去该字符串值为“courseDao”String nameStr = "courseDao";
// 解析 <property .../> 元素的 ref 属性失去该字符串值为“courseDao”String refStr = "courseDao";
// 生成将要调用 setter 办法名
String setterName = "set" + nameStr.substring(0, 1).toUpperCase() + nameStr.substring(1);
// 获取 spring 容器中名为 refStr 的 Bean,该 Bean 将会作为传入参数
Object paramBean = container.get(refStr);
// 获取 setter 办法的 Method 类,此处的 cls 是方才反射代码失去的 Class 对象
Method setter = cls.getMethod(setterName, paramBean.getClass());
// 调用 invoke()办法,此处的 obj 是方才反射代码失去的 Object 对象
setter.invoke(obj, paramBean);