我們平時(shí)調(diào)用方法或者創(chuàng)建一個(gè)類的實(shí)例都是在代碼之中寫死的,有沒有什么辦法例如就是傳一個(gè)方法名進(jìn)一個(gè)來,然后就能動(dòng)態(tài)調(diào)用這個(gè)方法呢,諸如此類的在程序運(yùn)行時(shí)能夠獲取某個(gè)類自身的所有信息在java當(dāng)中被稱之為反射,反射是java的核心技術(shù),各種框架當(dāng)中無一不用到反射,可以說程序當(dāng)中自動(dòng)化功能的實(shí)現(xiàn)都需要反射去實(shí)現(xiàn).
Java.lang.reflect(反射包)
包中包括以下類:
Class :代表一個(gè)類,可以獲取一個(gè)類中的所有信息
Field :代表類的成員變量
Method :代表類的方法
Constructor: 代表類的構(gòu)造方法
Array : 提供了動(dòng)態(tài)創(chuàng)建數(shù)組,以及訪問數(shù)組的元素的靜態(tài)方法
Proxy : 提供用于創(chuàng)建動(dòng)態(tài)代理類和實(shí)例的靜態(tài)方法,它還是由這些方法創(chuàng)建的所有動(dòng)態(tài)代理類的超類
1.Class
每個(gè)java類運(yùn)行時(shí)都在JVM里表現(xiàn)為一個(gè)Class對(duì)象,可通過類名.class,類對(duì)象.getClass(),Class.forName("全類名")獲取class對(duì)象
方法名中帶有Declared的方法表示本類的信息,無論公私有屬性或函數(shù)
- String getName():獲取全類名(包名和類名)
- String getSimpleName() :獲取類名
- Class<?> forName(String className) :根據(jù)全類名獲取Class對(duì)象
- T newInstance():根據(jù)Class對(duì)象新建一個(gè)對(duì)象(類中必須要有一個(gè)無參的構(gòu)造函數(shù))
- ClassLoader getClassLoader():獲得類的類加載器姨伤。
- Class getSuperclass():獲取類的父類刹泄,繼承了父類則返回父類觉既,否則返回java.lang.Object.
- boolean isEnum() :判斷是否為枚舉類型
- boolean isArray() :判斷是否為數(shù)組類型
- boolean isPrimitive() :判斷是否為基本類型
- boolean isAnnotation() :判斷是否為注解類型
- Package getPackage() :反射中獲得package
- int getModifiers() : 反射中獲得修飾符對(duì)應(yīng)的數(shù)字(若要轉(zhuǎn)換為public,可用Modifier.toString(Domain.class.getModifiers()))
- Field getField(String name):反射中獲得域成員
14 .Field[] getFields() :獲得域數(shù)組成員
15 . Method[] getMethods() :獲得所有共有方法
16 . Method getDeclaredMethod(String name, Class<?>... parameterTypes):加個(gè)Declared代表本類羊异,繼承穆律,父類均不包括床佳。而且包括所有公私有方法.
17 .Constructor<?>[] getConstructors() :獲得所有的構(gòu)造函數(shù)
18 .Class<?> getComponentType :如果是數(shù)組Class對(duì)象,則可通過此方法得到數(shù)組類型,如果不是數(shù)組用此方法返回為null
19 .Annotation getAnnotation(Class<A> annotationClass)
如果存在這樣的注解桥胞,則返回該元素的指定類型的注解恳守,否則返回null。
20 .Annotation[] getAnnotation()
21 .InputStream getResourceAsStream(String path) : path 不以’/'開頭時(shí)默認(rèn)是從此類所在的包下取資源贩虾,以’/'開頭則是從ClassPath根下獲取催烘。其只是通過path構(gòu)造一個(gè)絕對(duì)路徑,最終還是由ClassLoader獲取資源
22 .Class<?> getSuperclass() :返回父類的Class對(duì)象
23 .boolean isassignablefrom(Class<?> cls)
用來校驗(yàn)一個(gè)類是否參數(shù)中的Class實(shí)現(xiàn)指定的父類
24 .boolean isInstance(Object obj)該方法和instanceof運(yùn)算符作用等價(jià),但是instanceof是對(duì)象instanceof 類,檢查左邊的被測(cè)試對(duì)象 是不是 右邊類或接口的 實(shí)例化缎罢,而isInstance方法是 類.class.isInstance(對(duì)象),obj是被測(cè)試的對(duì)象伊群,如果obj是調(diào)用這個(gè)方法的class或接口 的實(shí)例考杉,則返回true
25 .boolean isMemberClass() :判斷當(dāng)前類是否成員類
2. Field
可以通過class的getDeclaredField(String name),getDeclaredFields(),getField(String name),getFields()獲取,通過Field的方法可以獲取舰始、設(shè)置屬性的值崇棠,并能獲取屬性的注解、字段的聲明類型蔽午。
- Class<?> getType(): 獲取屬性聲明時(shí)類型對(duì)象
- Type getGenericType() : 獲取屬性聲明時(shí)類型的Type對(duì)象
- String getName() : 獲取屬性聲明時(shí)名字
- getAnnotations() : 獲得這個(gè)屬性上所有的注釋
- int getModifiers() : 獲取屬性的修飾符對(duì)應(yīng)的值
- boolean isSynthetic() : 判斷這個(gè)屬性是否是 復(fù)合類
- get(Object obj) : 取得obj對(duì)象這個(gè)Field上的值(不通過get方法獲取,需要使用setAccessible(true)禁用訪問控制權(quán)限)
- set(Object obj, Object value) : 向obj對(duì)象的這個(gè)Field設(shè)置新值value(不通過set方法設(shè)置,需要使用setAccessible(true)禁用訪問控制權(quán)限)
- setAccessible(boolean flag) 禁用/開啟訪問控制權(quán)限
3. Method
描述類的成員方法易茬,Method 提供關(guān)于類或接口上單獨(dú)某個(gè)方法(以及如何訪問該方法)的信息。所反映的方法可能是類方法或?qū)嵗椒ǎòǔ橄蠓椒ǎ┘袄稀ethod 允許在匹配要調(diào)用的實(shí)參與底層方法的形參時(shí)進(jìn)行擴(kuò)展轉(zhuǎn)換可以通過class的getDeclaredMethod(String name, Class<?>... parameterTypes) 抽莱,getDeclaredMethods() ,getMethod(String name,Class<?>... parameterTypes)骄恶,getMethods() 獲取食铐,通過Method的invoke方法去執(zhí)行,獲取返回值僧鲁,也可以獲取方法注解虐呻、 返回值類型等号俐。
方法中存在Generic的,就表示返回值返回Type類型
- getAnnotation(Class<T> annotationClass) :如果存在該元素的指定類型的注釋更振,則返回這些注釋,否則返回 null丹莲。
- Annotation[] getDeclaredAnnotations() :返回直接存在于此元素上的所有注釋
- Class<?> getDeclaringClass() :返回當(dāng)前方法的類Class對(duì)象
- Class<?>[] getExceptionTypes() :返回 Class 對(duì)象的數(shù)組春寿,這些對(duì)象描述了聲明將此 Method 對(duì)象表示的底層方法拋出的異常類型
- Type[] getGenericExceptionTypes() :返回 Type 對(duì)象數(shù)組朗涩,這些對(duì)象描述了聲明由此 Method 對(duì)象拋出的異常
- Type[] getGenericParameterTypes() :按照聲明順序返回 Type 對(duì)象的數(shù)組,這些對(duì)象描述了此 Method 對(duì)象所表示的方法的形參類型的
- Class<?>[] getParameterTypes() :按照聲明順序返回 Class 對(duì)象的數(shù)組绑改,這些對(duì)象描述了此 Method 對(duì)象所表示的方法的形參類型
- Type getGenericReturnType() :返回表示由此 Method 對(duì)象所表示方法的正式返回類型的 Type 對(duì)象谢床。
Class<?> getReturnType() :返回一個(gè) Class 對(duì)象,該對(duì)象描述了此 Method 對(duì)象所表示的方法的正式返回類型 - int getModifiers() 以整數(shù)形式返回此 Method 對(duì)象所表示方法的 Java 語言修飾符
- String getName() :以 String 形式返回此 Method 對(duì)象表示的方法名稱厘线。
- Object invoke(Object obj, Object... args) :對(duì)帶有指定參數(shù)的指定對(duì)象調(diào)用由此 Method 對(duì)象表示的底層方法
- boolean isVarArgs(): 如果將此方法聲明為帶有可變數(shù)量的參數(shù)识腿,則返回 true;否則造壮,返回 false
4. Constructor
Constructor是對(duì)構(gòu)造方法的聲明描述渡讼,Constructor 提供關(guān)于類的單個(gè)構(gòu)造方法的信息以及對(duì)它的訪問權(quán)限。Constructor
允許在將實(shí)參與帶有底層構(gòu)造方法的形參的 newInstance() 匹配時(shí)進(jìn)行擴(kuò)展轉(zhuǎn)換耳璧∠跞可以通過class的getConstructor(Class<?>... parameterTypes) ,getConstructors()楞抡,getDeclaredConstructor(Class<?>... parameterTypes) 伟众,getDeclaredConstructors() 獲取。通過Constructor可以獲取注解召廷,參數(shù)類型等凳厢,并通過newInstance(Object... initargs) 創(chuàng)建類實(shí)例账胧。
具體的方法跟上面的大同小異,通過名字都可以猜出來什么意思了
5. Array
Array 類提供了動(dòng)態(tài)創(chuàng)建和訪問 Java 數(shù)組的方法。允許在執(zhí)行 get 或 set 操作期間進(jìn)行擴(kuò)展轉(zhuǎn)換先紫≈文啵可以通過class的isArray方法判定此 Class 對(duì)象是否表示一個(gè)數(shù)組類,getComponentType返回表示數(shù)組類型的 Class遮精。通過newInstance初始化數(shù)組居夹。
- static Object newInstance(Class cls,int array_length) :創(chuàng)建一個(gè)數(shù)組
訪問動(dòng)態(tài)數(shù)組元素的方法和通常有所不同,它的格式如下所示本冲,注意該方法返回的是一個(gè)Object對(duì)象
Array.get(arrayObject, index)為動(dòng)態(tài)數(shù)組元素賦值的方法也和通常的不同准脂,它的格式如下所示, 注意最后的一個(gè)參數(shù)必須是Object類型
Array.set(arrayObject, index, object)int getLength(Object obj) 返回?cái)?shù)組的長度
6. Proxy(代理類)
Proxy 提供用于創(chuàng)建動(dòng)態(tài)代理類和實(shí)例的靜態(tài)方法檬洞,它還是由這些方法創(chuàng)建的所有動(dòng)態(tài)代理類的父類
- static Object newProxyInstance(ClassLoader classLoader,Class<?>[] interfaceArr,InvocationHandler h) :方法上有三個(gè)參數(shù),第一個(gè)指定一個(gè)類加載器,第二個(gè)參數(shù)為,被代理類實(shí)現(xiàn)的接口數(shù)組,第三個(gè)是代理類
創(chuàng)建一個(gè)動(dòng)態(tài)代理的步驟(例如要代理的類名為Car,代理類為MyProxy):
1.為被代理類創(chuàng)建一個(gè)接口,名字為BaseCar
public interface BaseCar {
void run();
}
**2.被代理類實(shí)現(xiàn)BaseCar接口,并重寫B(tài)aseCar中的方法 **
public class Car implements BaseCar {
@Override
public void run() {
System.out.println("汽車啟動(dòng)");
}
}
3.MyProxy代理類實(shí)現(xiàn)InvocationHandler接口,并重寫invoke方法,記得要以BaseCar作為成員變量,還有給BseCar初始化,因?yàn)閙ethod.invoke要用到BaseCar的實(shí)例
public class MyProxy implements InvocationHandler {
private BaseCar baseCar;
public MyProxy(BaseCar baseCar) {
super();
this.baseCar = baseCar;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
System.out.println("汽車啟動(dòng)前");
Object invoke = method.invoke(baseCar, args);
System.out.println("汽車啟動(dòng)后");
return invoke;
}
}
可以看到invoke方法有三個(gè)參數(shù):
- 動(dòng)態(tài)代理類的引用狸膏,通常情況下不需要它。但可以使用getClass()方法添怔,得到proxy的Class類從而取得實(shí)例的類信息湾戳,如方法列表,annotation等广料。
- 方法對(duì)象的引用砾脑,代表被動(dòng)態(tài)代理類調(diào)用的方法。從中可得到方法名艾杏,參數(shù)類型韧衣,返回類型等等
- args對(duì)象數(shù)組,代表被調(diào)用方法的參數(shù)糜颠。注意基本類型(int,long)會(huì)被裝箱成對(duì)象類型(Interger, Long)
4. 執(zhí)行
MyProxy myProxy = new MyProxy(new Car());
BaseCar base = (BaseCar) Proxy.newProxyInstance
(Object.class.getClassLoader(), new Class[]{BaseCar.class}, myProxy);
base.run();
打印
汽車啟動(dòng)前
汽車啟動(dòng)
汽車啟動(dòng)后
動(dòng)態(tài)代理是Spring框架AOP的執(zhí)行原理,就是在需要執(zhí)行的方法執(zhí)行前后加入一些自定義的方法.
MyBatis的簡(jiǎn)單實(shí)現(xiàn)
先說說思路把,我們都知道MyBatis有一種使用方式,就是接口和xml配合使用,我最喜歡用這種方式因?yàn)閟ql語句和java代碼可以完全解耦,另外dao層的實(shí)現(xiàn)類都不用自己寫了,只需要在接口上面定義好方法,然后在對(duì)應(yīng)的xml文件中寫好與sql相關(guān)的配置就可以用了,MyBatis是我最喜歡用的一個(gè)框架(哎呀,跑題了),現(xiàn)在我們也要實(shí)現(xiàn)這樣的功能,不過相比MyBatis來說會(huì)簡(jiǎn)陋非常多,只為演示,所以各位不要吐槽.
1 . 首先我們定義好一個(gè)Dao接口,叫做TestDao :
public interface TestDao {
Test selectTest();
然后再寫好對(duì)應(yīng)的xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cppteam.dao.TestDao">
<select id="selectTest" resultType="com.cppteam.domain.Test">
select * from test.test
</select>
</mapper>
2 . 有一個(gè)名字為test的數(shù)據(jù)庫,名字為test的表
mysql> select * from test;
+----+------+
| id | name |
+----+------+
| 1 | 1 |
+----+------+
1 row in set (0.00 sec)
創(chuàng)建一個(gè)對(duì)應(yīng)的Test類
public class Test {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Test [id=" + id + ", name=" + name + "]";
}
}
3 .創(chuàng)建一個(gè)代理類,代理TestDao類,對(duì)應(yīng)TestDao的每一個(gè)方法的執(zhí)行,都只獲取方法名,方法返回值等等,再利用獲取到的方法名去xml文件里面找,然后獲取對(duì)應(yīng)的sql語句,執(zhí)行sql語句之后,再通過反射獲取到返回值類型,再注入那個(gè)類對(duì)應(yīng)的實(shí)例,所以整個(gè)過程是不需要實(shí)現(xiàn)類的,我們現(xiàn)在開始.
sqlSession主要是配置連接數(shù)據(jù)庫,有一個(gè)select方法返回代理類
public class SqlSession {
private static Connection connection = null;
static {
try {
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.
getConnection("jdbc:mysql://localhost:3306/test?characterEncoding=utf-8", "root","123456");
} catch (Exception e) {
// TODO: handle exception
}
}
public static Connection getConnection() {
return connection;
}
public static <T> T select(Class<T> cls) {
return (T) Proxy.newProxyInstance(cls.getClassLoader(), new Class[] { cls },
new MyProxy());
}
}
4 .這里看看MyProxy代理類(重點(diǎn)部分)
public class MyProxy implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
//先讀取xml文件
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document xml = builder.parse(Object.class.getResourceAsStream("/com/cppteam/mapper/TestMapper.xml"));
//找到第一個(gè)select結(jié)點(diǎn)
Node node = xml.getElementsByTagName("select").item(0);
NamedNodeMap attributes = node.getAttributes();
//查找屬性id的值,對(duì)應(yīng)的應(yīng)該是接口中selectTest方法的名字
Node id = attributes.getNamedItem("id");
String methodName = method.getName();
if(!id.getTextContent().trim().equals(methodName)){
throw new Exception("找不到方法");
}
//找到對(duì)應(yīng)的sql語句
String sql = node.getTextContent().trim();
//開始執(zhí)行sql語句
Connection connection = SqlSession.getConnection();
connection.setAutoCommit(true);
PreparedStatement prepareStatement = connection.prepareStatement(sql);
ResultSet resultSet = prepareStatement.executeQuery();
resultSet.next();
//得到方法的返回值類型
Class<?> returnType = method.getReturnType();
System.out.println("方法名為:"+methodName+",方法返回值類型為:"+method.getReturnType().getName());
System.out.println("執(zhí)行的sql為"+sql);
Object newInstance = returnType.newInstance();
Field[] declaredFields = newInstance.getClass().getDeclaredFields();
//對(duì)返回值類型的實(shí)例進(jìn)行賦值
for (Field field : declaredFields) {
field.setAccessible(true);
field.set(newInstance, resultSet.getObject(field.getName()));
}
return newInstance;
}
}
5 .執(zhí)行
TestDao testDao = SqlSession.select(TestDao.class);
Test test = testDao.selectTest();
System.out.println(test);
打印
方法名為:selectTest,方法返回值類型為:com.cppteam.domain.Test
執(zhí)行的sql為select * from test.test
Test [id=1, name=1]
我們應(yīng)該更加注重框架的原理,而不是框架的使用,從這個(gè)例子當(dāng)中,我們可以看到另一種應(yīng)用,就是不用實(shí)現(xiàn)類,直接通過動(dòng)態(tài)代理執(zhí)行接口方法,利用這個(gè)思路我們可以在別的方面做出更好的設(shè)計(jì).
另外說一下反射調(diào)用函數(shù),,假如現(xiàn)在有一個(gè)函數(shù):
public class Test {
public void show(int num){
System.out.println("函數(shù)參數(shù)為基本類型int");
}
}
我們現(xiàn)在用反射對(duì)它進(jìn)行調(diào)用汹族,
public class Test1 {
public static void show(Object object){
Test.class.getMethod("show", object.getClass());
}
public static void main(String[] args) {
int i = 1;
show(1);
}
}
運(yùn)行后萧求,報(bào)錯(cuò)了
java.lang.NoSuchMethodException: com.cppteam.util.Test.show(java.lang.Integer)
Test1 的main函數(shù)中對(duì)本類中的show函數(shù)調(diào)用中其兴,參數(shù)為1,而show函數(shù)的參數(shù)用的是Object對(duì)象接收夸政,所以int類型的數(shù)值會(huì)被包裝為Integer類元旬,但是實(shí)際上存在的函數(shù)參數(shù)是int類型的,所以我們不得不加以判斷守问,然后轉(zhuǎn)型.
public class Test1 {
public static void show(Object object) {
Class<?> class1 = object.getClass();
if (Integer.class.isInstance(object)) {
class1 = int.class;
}
Method method = Test.class.getMethod("show", class1);
method.invoke(new Test(), object);
}
public static void main(String[] args){
int i = 1;
show(i);
}
}
運(yùn)行結(jié)果是成功的,看打釉裙椤:
函數(shù)參數(shù)為基本類型int