引言
在反射出現之前,當我們有一個java類之後,我們在類的外部是不能使用類中的私有結構的,比如說不能調用private的構造器,private的方法等等。但是出現了反射之後,我們就可以調用這個類的任何結構,包括私有的。
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectionTest {
@Test
public void test1(){
Person p = new Person("tom",23);
p.show();
}
@Test
public void test2() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//1.獲取類對象
Class<Person> personClass = Person.class;
//2.獲取類對象的構造器
Constructor constructor = personClass.getConstructor(String.class, int.class);
Object tom = constructor.newInstance("Jerry", 23);
//3.獲取類方法
Method show = personClass.getDeclaredMethod("show");
show.invoke(tom);
//反射的特殊功能,可以調用私有的類結構
Constructor<Person> cons1 = personClass.getDeclaredConstructor(String.class);
cons1.setAccessible(true);
Person wanglei = cons1.newInstance("wanglei");
wanglei.show();
}
}
反射和單例模式
單例模式私有化了構造器,希望這個類的實例只需要創建一個就可以了。到那時反射是解決能不能用的問題,單例模式是建議你的這張寫法,他們是不衝突的。
關於java.lang.Class的理解
Class作爲反射的源頭
(1)類的加載過程:程序經過javac.exe編譯之後會生成一個或多個字節碼文件.class結尾,每一個java類對應一個字節碼文件,通過java.exe解釋運行某個包含有mian方法的字節碼文件。相當於把這個字節碼文件加載到內存中,此過程稱爲類的加載。記載到內存中的類稱之爲運行時類,這個運行時類作爲Class的一個實例。換句話說Class的實例就對應着一個運行時類。
獲取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);
三步:
加載:將類的class文件加入到內存,並創建Class對象,此過程由類加載器完成
鏈接:確保Class文件的字節流中包含的信息符合當前虛擬機的要求,並且不會危害虛擬機自身的安全。
初始化:在編譯生成class文件時,編譯器會產生兩個方法加於class文件中,一個是類的初始化方法clinit, 另一個是實例的初始化方法init。clinit指的是類構造器,主要作用是在類加載過程中的初始化階段進行執行,執行內容包括靜態變量初始化和靜態塊的執行。
引言
代理模式是23種設計模式中的一種,是比較重要的知識,在Spring框架中比較重要的AOP(Aspect Oriented Programing)也是基於動態代理實現的。
代理的理解
關於代理的理解,我舉個例子說明:20年前,我們想要購買一臺聯想電腦的方式是:我們去聯想的工廠,工廠賣給我們電腦,並且提供售後的服務。
在這幾十年的發展中,出現了一些經銷商,他們從聯想工廠進貨,然後賣給我們,電腦壞了我們也先去經銷商的店裏找他,然後他再去找工廠,於是我們和工廠的聯繫其實就中斷了。
2020年,我們想要買一臺筆記本電腦,會直接去天貓、蘇寧、京東等大型的經銷商那裏去買一臺電腦,出現問題就7天無理由退貨。
這裏我們就是客戶端Client,那些經銷商也叫做代理商,聯想工廠就叫做被代理對象。這樣的過程就是代理。
動態代理
動態代理的特點:字節碼隨用隨創建,隨用隨加載
動態代理的作用:在不修改源碼的基礎上增強方法
動態代理的分類:2類,一個是基於接口的動態代理,一個是基於子類的動態代理
基於接口的動態代理
由JDK提供的Proxy類實現,要求被代理類至少實現一個接口。實現過程如下
(1)首先創建Lenovo類並且實現一個接口
package com.alibaba200408.動態代理;
public class Lenovo implements ILenovo {
@Override
public void sale(Double money) {
System.out.println("拿到"+money+"元,電腦發貨");
}
@Override
public void afterService(Double money) {
System.out.println("拿到"+money+"元,售後服務開始");
}
}
代碼很簡單,只涉及了2個方法,一個是銷售電腦,一個是維修電腦。
接口的代碼如下
package com.alibaba200408.動態代理;
public interface ILenovo {
void sale(Double money);
void afterService(Double money);
}
(2)實例化一個動態代理的對象,通過代理對象調用方法
package com.alibaba200408.動態代理;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
//聯想廠家
ILenovo lenovo = new Lenovo();
//創建代理對象
ILenovo proxyInstance = (ILenovo) Proxy.newProxyInstance(lenovo.getClass().getClassLoader(),
lenovo.getClass().getInterfaces(),
new InvocationHandler() {
/**
* invoke方法的作用是調用任何被代理對象的方法都會被這個invoke方法攔截
* proxy:表示當前代理對象的引用
* method:表示當前正在執行的方法
* args:表示當前執行的方法的參數
*
* 返回值就是當前方法的返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//提供增強的代碼
Double money = (Double) args[0];
if ("sale".equals(method.getName())){
method.invoke(lenovo, money * 0.8);
}else {
method.invoke(lenovo,args);
}
return null;
}
});
//使用代理對象
proxyInstance.afterService(10000.0);
}
}
基於子類的動態代理
這種實現方式要求被代理類不能是final的,因爲final修飾的類不能有子類,其次這種方式需要第三方的cglib。
基於子類的動態代理
涉及到的類Enhancer
使用Enhancer中的create()方法
要求:被代理類不能是最終類
參數一:被代理對象的字節碼
參數二:callback用於寫提供增強的代碼,我們一般使用它的實現類MethodInterceptor
//生成代理對象
Product proxy = Enhancer.create(product.class,new MethodInterceptor(){
@Override
public Object intercept(Object proxy, Method method, Object[] args,MethodProxy methodProxy) throws Throwable {
//提供增強的代碼
Double money = (Double) args[0];
if ("sale".equals(method.getName())){
method.invoke(lenovo, money * 0.8);
}else {
method.invoke(lenovo,args);
}
return null;
}
});