反射的概述:
重點掌握:
1、理解Class類並獲取Class實例;
2、創建運行時類的對象;
3、調用運行時類的指定結構;
關於反射的理解:
Reflection(反射)被視爲動態語言的關鍵,反射機制允許程序在執行期藉助Reflection API獲得任何類的內部屬性,並能夠直接操作任意對象的內部屬性和方法。
框架 = 反射 + 註解 + 設計模式
體會反射的動態性:
//體會反射的動態性
@Test
public void test2(){
for(int i = 0;i < 100;i++){
int num = new Random().nextInt(3);//0,1,2
String classPath = "";
switch(num){
case 0:
classPath = "java.util.Date";
break;
case 1:
classPath = "java.lang.Object";
break;
case 2:
classPath = "com.atguigu.java.Person";
break;
}
try {
Object obj = getInstance(classPath);
System.out.println(obj);
} catch (Exception e) {
e.printStackTrace();
}
}
}
創建一個指定類的對象:
/*
創建一個指定類的對象。
classPath:指定類的全類名
*/
public Object getInstance(String classPath) throws Exception {
Class clazz = Class.forName(classPath);
return clazz.newInstance();
}
反射機制能提供的功能:
相關API:
java.lang.Class:反射的源頭
java.lang.reflect.Method
java.lang.reflect.Field
java.lang.reflect.Constructor
…
關於反射的幾個問題:
1、new+構造器和反射都能夠創建對象,但是一般情況下都是使用new的方式來解決。
2、什麼時候會使用反射的方式?
反射的特性:動態性;當編譯時無法確定對象的類型,必須要等到運行時才能夠進行確定時,就要使用反射的方式創建對象。l例如,服務器在運行時,並不知道你是要進行登錄還是進行註冊,必須等你發送請求時才能進行確定。
3、反射機制和封裝性是不是矛盾的?
封裝性的含義是通過權限修飾符來告訴使用人員,類中哪些方法可以被調出來使用,哪一些方法只是創造出來自己內部使用的,不建議調到外部使用,可能調用類內部其他的結構能更好的的完成任務。而反射可以調用私有的方法和屬性,只是表明Java中可以做到調用私有的結構,並不矛盾。
Class類的理解與獲取Class的實例:
類的加載過程----瞭解
類的加載器的作用:
類的加載器的分類:
Java類編譯、運行的執行的流程:
使用Classloader加載src目錄下的配置文件:(掌握)
主要知道兩種方式要求的文件存放的位置不同,Properties中key和value都要求是String。
@Test
public void test2() throws Exception {
Properties pros = new Properties();
//此時的文件默認在當前的module下。
//讀取配置文件的方式一:
// FileInputStream fis = new FileInputStream("jdbc.properties");
// FileInputStream fis = new FileInputStream("src\\jdbc1.properties");
// pros.load(fis);
//讀取配置文件的方式二:使用ClassLoader
//配置文件默認識別爲:當前module的src下
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("jdbc1.properties");
pros.load(is);
String user = pros.getProperty("user");
String password = pros.getProperty("password");
System.out.println("user = " + user + ",password = " + password);
}
1.Class類的理解:
1、類的加載過程:
程序經過javac.exe命令後,會生成一個或多個字節碼文件(.class結尾)。接着我們使用java.exe命令對某個字節碼文件進行解釋運行。相當於將某個字節碼文件加載到內存中。此過程稱爲類的加載。加載到內存中的類,我們稱之爲運行時類,此運行時類就作爲Class的一個實例。(換句話說Class類的實例對應着一個運行時類)
2、加載到內存中的運行時類,會緩存一段時間。在此時間內我們可以通過不同的方式獲取此運行時類。(且不同方式獲得的運行時類相同,都對應着同一個運行時類)
2.獲取Class實例的幾種方式:(前三種方式需要掌握)
//方式一:調用運行時類的屬性:.class
Class clazz1 = Person.class;
System.out.println(clazz1);
//方式二:通過運行時類的對象,調用getClass()
Person p1 = new Person();
Class clazz2 = p1.getClass();
System.out.println(clazz2);
//方式三:調用Class的靜態方法:forName(String classPath)
Class clazz3 = Class.forName("com.atguigu.java.Person");
// clazz3 = Class.forName("java.lang.String");
System.out.println(clazz3);
System.out.println(clazz1 == clazz2);
System.out.println(clazz1 == clazz3);
//方式四:使用類的加載器:ClassLoader (瞭解)
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass("com.atguigu.java.Person");
System.out.println(clazz4);
System.out.println(clazz1 == clazz4);
總結:創建類的對象的方式
方式一:new + 構造器
方式二:要創建Xxx類的對象,可以考慮:Xxx、Xxxs、XxxFactory、XxxBuilder類中查看是否有
靜態方法的存在。可以調用其靜態方法,創建Xxx對象。
方式三:通過反射
3、Class實例可以是哪些結構的說明:
補充:
對於數組而言。只要元素類型和維度一樣就是同一個Class:
int[] a = new int[10];
int[] b = new int[100];
Class c10 = a.getClass();
Class c11 = b.getClass();
// 只要數組的元素類型與維度一樣,就是同一個Class
System.out.println(c10 == c11);//true
創建運行時類的對象:
1、代碼舉例:
Class<Person.> clazz = Person.class;
Person obj = clazz.newInstance;
2、說明:
newInstance():調用此方法創建對應的運行時類的對象。內部調用了運行時類的空參構造器。
要想此方法正常的運行,要求:
1、運行時類要提供空參的構造器;
2、此空參構造器的權限要夠調用,通常設置爲public的。
補充: 在javabean中要求提供一個public的空參構造器的原因:
1、便於通過反射創建運行時類的對象;
2、便於子類默認調用此構造器時存在。
反射應用二:獲取運行時類的完整結構“
我們可以通過反射,獲取對應的運行時類中所有的屬性、方法、構造器、父類、接口、父類的泛型、包、註解、異常等。。。。”
幾個需要關注的:(其他了解就行)
獲取父類的泛型(JDBC可以用到);
獲取實現的接口(動態代理);
獲取註解(後面框架)
典型代碼:
@Test
public void test1(){
Class clazz = Person.class;
//獲取屬性結構
//getFields():獲取當前運行時類及其父類中聲明爲public訪問權限的屬性
Field[] fields = clazz.getFields();
for(Field f : fields){
System.out.println(f);
}
System.out.println();
//getDeclaredFields():獲取當前運行時類中聲明的所屬性。(不包含父類中聲明的屬性
Field[] declaredFields = clazz.getDeclaredFields();
for(Field f : declaredFields){
System.out.println(f);
}
}
@Test
public void test1(){
Class clazz = Person.class;
//getMethods():獲取當前運行時類及其所父類中聲明爲public權限的方法
Method[] methods = clazz.getMethods();
for(Method m : methods){
System.out.println(m);
}
System.out.println();
//getDeclaredMethods():獲取當前運行時類中聲明的所方法。(不包含父類中聲明的方法
Method[] declaredMethods = clazz.getDeclaredMethods();
for(Method m : declaredMethods){
System.out.println(m);
}
}
/*
獲取構造器結構
*/
@Test
public void test1(){
Class clazz = Person.class;
//getConstructors():獲取當前運行時類中聲明爲public的構造器
Constructor[] constructors = clazz.getConstructors();
for(Constructor c : constructors){
System.out.println(c);
}
System.out.println();
//getDeclaredConstructors():獲取當前運行時類中聲明的所的構造器
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for(Constructor c : declaredConstructors){
System.out.println(c);
}
}
/*
獲取運行時類的父類
*/
@Test
public void test2(){
Class clazz = Person.class;
Class superclass = clazz.getSuperclass();
System.out.println(superclass);
}
/*
獲取運行時類的帶泛型的父類
*/
@Test
public void test3(){
Class clazz = Person.class;
Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println(genericSuperclass);
}
/*
獲取運行時類的帶泛型的父類的泛型
代碼:邏輯性代碼 vs 功能性代碼
*/
@Test
public void test4(){
Class clazz = Person.class;
Type genericSuperclass = clazz.getGenericSuperclass();
ParameterizedType paramType = (ParameterizedType) genericSuperclass;
//獲取泛型類型
Type[] actualTypeArguments = paramType.getActualTypeArguments();
// System.out.println(actualTypeArguments[0].getTypeName());
System.out.println(((Class)actualTypeArguments[0]).getName());
}
/*
獲取運行時類實現的接口
*/
@Test
public void test5(){
Class clazz = Person.class;
Class[] interfaces = clazz.getInterfaces();
for(Class c : interfaces){
System.out.println(c);
}
System.out.println();
//獲取運行時類的父類實現的接口
Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
for(Class c : interfaces1){
System.out.println(c);
}
}
/*
獲取運行時類所在的包
*/
@Test
public void test6(){
Class clazz = Person.class;
Package pack = clazz.getPackage();
System.out.println(pack);
}
/*
獲取運行時類聲明的註解
*/
@Test
public void test7(){
Class clazz = Person.class;
Annotation[] annotations = clazz.getAnnotations();
for(Annotation annos : annotations){
System.out.println(annos);
}
}
反射應用三:調用運行時類的指定結構:(需要掌握)
@Test
public void testField1() throws Exception {
Class clazz = Person.class;
//創建運行時類的對象
Person p = (Person) clazz.newInstance();
//1. getDeclaredField(String fieldName):獲取運行時類中指定變量名的屬性
Field name = clazz.getDeclaredField("name");
//2.保證當前屬性是可訪問的
name.setAccessible(true);
//3.獲取、設置指定對象的此屬性值
name.set(p,"Tom");
System.out.println(name.get(p));
}
調用指定的方法:
@Test
public void testMethod() throws Exception {
Class clazz = Person.class;
//創建運行時類的對象
Person p = (Person) clazz.newInstance();
/*
1.獲取指定的某個方法
getDeclaredMethod():參數1 :指明獲取的方法的名稱 參數2:指明獲取的方法的形參列表
*/
Method show = clazz.getDeclaredMethod("show", String.class);
//2.保證當前方法是可訪問的
show.setAccessible(true);
/*
3. 調用方法的invoke():參數1:方法的調用者 參數2:給方法形參賦值的實參
invoke()的返回值即爲對應類中調用的方法的返回值。
*/
Object returnValue = show.invoke(p,"CHN"); //String nation = p.show("CHN");
System.out.println(returnValue);
System.out.println("*************如何調用靜態方法*****************");
// private static void showDesc()
Method showDesc = clazz.getDeclaredMethod("showDesc");
showDesc.setAccessible(true);
//如果調用的運行時類中的方法沒返回值,則此invoke()返回null
// Object returnVal = showDesc.invoke(null);
Object returnVal = showDesc.invoke(Person.class);
System.out.println(returnVal);//null
}
調用指定的構造器:
@Test
public void testConstructor() throws Exception {
Class clazz = Person.class;
//private Person(String name)
/*
1.獲取指定的構造器
getDeclaredConstructor():參數:指明構造器的參數列表
*/
Constructor constructor = clazz.getDeclaredConstructor(String.class);
//2.保證此構造器是可訪問的
constructor.setAccessible(true);
//3.調用此構造器創建運行時類的對象
Person per = (Person) constructor.newInstance("Tom");
System.out.println(per);
}
反射的應用四:動態代理(以及面向AOP)
1、代理模式的原理:
使用一個代理將原始對象包裝起來,用代理對象取代原始對象。任何對原始對象的調用都要通過代理對象來進行,代理對象決定是否以及何時將方法調用轉移到原始對象上。
靜態代理:
1、實現Runnable接口的方法創建多線程。
Class MyThread implements Runnable{} //相當於被代理類
Class Thread implements Runnable{} //相當於代理類
main(){
MyThread t = new MyThread();
Thread thread = new Thread(t);
thread.start();//啓動線程;調用線程的run()
}
2、自己實現靜態代理:
interface ClothFactory{
void produceCloth();
}
//代理類
class ProxyClothFactory implements ClothFactory{
private ClothFactory factory;//用被代理類對象進行實例化
public ProxyClothFactory(ClothFactory factory){
this.factory = factory;
}
@Override
public void produceCloth() {
System.out.println("代理工廠做一些準備工作");
factory.produceCloth();
System.out.println("代理工廠做一些後續的收尾工作");
}
}
//被代理類
class NikeClothFactory implements ClothFactory{
@Override
public void produceCloth() {
System.out.println("Nike工廠生產一批運動服");
}
}
public class StaticProxyTest {
public static void main(String[] args) {
//創建被代理類的對象
ClothFactory nike = new NikeClothFactory();
//創建代理類的對象
ClothFactory proxyClothFactory = new ProxyClothFactory(nike);
proxyClothFactory.produceCloth();
}
}
靜態代理的缺點:
1、代理類和目標對象都是在編譯器就確定下來了,不利於程序的擴展。
2、每個代理類只能爲一個接口服務,這樣在程序開發中會產生過多的代理類。
動態代理的特點:
動態代理是指通過代理類來調用目標對象的方法,並且是在程序運行時根據需要動態的創建目標類的代理對象。
動態代理的實現:
動態代理的實現需要解決兩個問題:
1、問題一:如何根據加載到內存中的被代理類,動態的創建一個代理類及其對象。 (通過Proxy.newProxyInstance()實現,三個參數分別是被代理類的同加載器;被代理類實現的接口;最後一個是實現InvocationHandler接口的實現類,用於定位方法)
2、問題二:當通過代理類的對象調用方法a時,如何動態的去調用被代理類中的同名方法a。(通過InvocationHandler接口的實現類及其方法invoke(),當代理類調用方法a是會轉換爲調用該實現類中的invoke()方法)。
interface Human{
String getBelief();
void eat(String food);
}
//被代理類
class SuperMan implements Human{
@Override
public String getBelief() {
return "I believe I can fly!";
}
@Override
public void eat(String food) {
System.out.println("我喜歡喫" + food);
}
}
class HumanUtil{
public void method1(){
System.out.println("====================通用方法一====================");
}
public void method2(){
System.out.println("====================通用方法二====================");
}
}
class ProxyFactory{
//調用此方法,返回一個代理類的對象。解決問題一
public static Object getProxyInstance(Object obj){//obj:被代理類的對象
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
//這裏接口的傳入是爲了和obj實現同一個接口,可以調用被代理類的方法。
}
}
class MyInvocationHandler implements InvocationHandler{
private Object obj;//需要使用被代理類的對象進行賦值
public void bind(Object obj){
this.obj = obj;
}
//當我們通過代理類的對象,調用方法a時,就會自動的調用如下的方法:invoke()
//將被代理類要執行的方法a的功能就聲明在invoke()中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
HumanUtil util = new HumanUtil();
util.method1();
//method:即爲代理類對象調用的方法,此方法也就作爲了被代理類對象要調用的方法
//obj:被代理類的對象
Object returnValue = method.invoke(obj,args);
util.method2();
//上述方法的返回值就作爲當前類中的invoke()的返回值。
return returnValue;
}
}
public class ProxyTest {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
//proxyInstance:代理類的對象
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
//當通過代理類對象調用方法時,會自動的調用被代理類中同名的方法
String belief = proxyInstance.getBelief();
System.out.println(belief);
proxyInstance.eat("四川麻辣燙");
System.out.println("*****************************");
NikeClothFactory nikeClothFactory = new NikeClothFactory();
ClothFactory proxyClothFactory = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory);
proxyClothFactory.produceCloth();
}
}