动态代理入门
?
目錄
?
一、動態代理
1、運行時實現指定的接口
2、newProxyInstance()方法的參數
3、invoke()方法
4、動態代理的用途
5.簡單的自定義連接池
二、類加載器
1.什么是類加載器
2.JVM眼中的相同的類
3.類加載器的代理模式
4.Tomcat的類加載器
一、動態代理
1、運行時實現指定的接口
????????想實現某個接口,你需要寫一個類,然后在類名字的后面給出“implements”XXX接口。
public interface MyInterface {void fun1();void fun2(); } public class MyInterfaceImpl implements MyInterface {public void fun1() {System.out.println("fun1()");}public void fun2() {System.out.println("fun2()");} }????????上面的代碼對我們來說沒有什么新鮮感,我們要說的是動態代理技術可以通過一個方法調用就可以生成一個對指定接口的實現類對象。? ??
????????Class[] cs = {MyInterface.class};MyInterface mi = (MyInterface)Proxy.newProxyInstance(loader, cs, h);????????上面代碼中,Proxy類的靜態方法newProxyInstance()方法生成了一個對象,這個對象實現了cs數組中指定的接口。沒錯,返回值mi是MyInterface接口的實現類。你不要問這個類是哪個類,你只需要知道mi是MyInterface接口的實現類就可以了。你現在也不用去管loader和h這兩個參數是什么東東,你只需要知道,Proxy類的靜態方法newProxyInstance()方法返回的方法是實現了指定接口的實現類對象,甚至你都沒有看見實現類的代碼。
????????動態代理就是在運行時生成一個類,這個類會實現你指定的一組接口,而這個類沒有.java文件,是在運行時生成的,你也不用去關心它是什么類型的,你只需要知道它實現了哪些接口即可。
2、newProxyInstance()方法的參數
????????Proxy類的newInstance()方法有三個參數:
??????????ClassLoader loader:它是類加載器類型,你不用去理睬它,你只需要知道怎么可以獲得它就可以了:MyInterface.class.getClassLoader()就可以獲取到ClassLoader對象,沒錯,只要你有一個Class對象就可以獲取到ClassLoader對象;
????????????????Class[] interfaces:指定newProxyInstance()方法返回的對象要實現哪些接口,沒錯,可以指定多個接口,例如上面例子只我們只指定了一個接口:Class[] cs = {MyInterface.class};
????????????????InvocationHandler h:它是最重要的一個參數!它是一個接口!它的名字叫調用處理器!你想一想,上面例子中mi對象是MyInterface接口的實現類對象,那么它一定是可以調用fun1()和fun2()方法了,難道你不想調用一下fun1()和fun2()方法么,它會執行些什么東東呢?其實無論你調用代理對象的什么方法,它都是在調用InvocationHandler的invoke()方法!
public static void main(String[] args) {Class[] cs = {MyInterface.class};ClassLoader loader = MyInterface.class.getClassLoader();InvocationHandler h = new InvocationHandler() {public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {System.out.println("無論你調用代理對象的什么方法,其實都是在調用invoke()...");return null;}};MyInterface mi = (MyInterface)Proxy.newProxyInstance(loader, cs, h);mi.fun1();mi.fun2();}3、invoke()方法
????????InvocationHandler的invoke()方法的參數有三個:
????????Object proxy:代理對象,也就是Proxy.newProxyInstance()方法返回的對象,通常我們用不上它;
????????Method method:表示當前被調用方法的反射對象,例如mi.fun1(),那么method就是fun1()方法的反射對象;
????????Object[] args:表示當前被調用方法的參數,當然mi.fun1()這個調用是沒有參數的,所以args是一個零長數組。
最后要說的是invoke()方法的返回值為Object類型,它表示當前被調用的方法的返回值,當然mi.fun1()方法是沒有返回值的,所以invoke()返回的就必須是null了。
4、動態代理的用途
????????動態代理的用途與裝飾模式很相似,就是為了對某個對象進行增強。所有使用裝飾者模式的案例都可以使用動態代理來替換。
????????下面我們用一個例子來說明動態代理的用途!
????????我們來寫一個Waiter接口,它只有一個serve()方法。MyWaiter是Waiter接口的實現類:
public interface Waiter {public void serve(); } public class MyWaiter implements Waiter {public void serve() {System.out.println("服務...");} }????????現在我們要對MyWaiter對象進行增強,要讓它在服務之前以及服務之后添加禮貌用語,即在服務之前說“您好!”,在服務之后說:“很高興為您服務!”。
public class MainApp1 {public static void main(String[] args) {ClassLoader loader = MainApp1.class.getClassLoader();Class[] cs = {Waiter.class};Waiter target = new MyWaiter();MyInvocationHandler h = new MyInvocationHandler(target);Waiter waiter = (Waiter)Proxy.newProxyInstance(loader, cs, h);waiter.serve();} }class MyInvocationHandler implements InvocationHandler {public Waiter target;public MyInvocationHandler(Waiter target) {this.target = target;}public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {System.out.println("您好!");Object result = method.invoke(target, args);System.out.println("很高興為您服務!");return result;} }5.簡單的自定義連接池
public class MyDataSource {private ArrayList<Connection> pool = new ArrayList<>();public MyDataSource() {for (int i = 0; i < 3; i++) {final Connection conn = JdbcUtils.getConnection();Connection connProxy = (Connection) Proxy.newProxyInstance(conn.getClass().getClassLoader(),conn.getClass().getInterfaces(), new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if (method.getName().equals("close")) {pool.add((Connection) proxy);System.out.println("連接歸還");return null;}return method.invoke(conn, args);}});pool.add(connProxy);}}public Connection getConnection() {return pool.remove(0);} } MyDataSource myDataSource = new MyDataSource();for (int i = 0; i < 4; i++) {Connection connection = myDataSource.getConnection();System.out.println(connection);connection.close();}二、類加載器
1.什么是類加載器
????????類加載器就是用來加載類的東西!類加載器也是一個類:ClassLoader
????????類加載器可以被加載到內存,是通過類加載器完成的!Java提供了三種類加載器,分別是:
????????????????bootstrap classloader:引導類加載器,加載rt.jar中的類;
????????????????sun.misc.Launcher$ExtClassLoader:擴展類加載器,加載lib/ext目錄下的類;
????????????????sun.misc.Launcher$AppClassLoader:系統類加載器,加載CLASSPATH下的類,即我們寫的類,以及第三方提供的類。
?
????????通常情況下,Java中所有類都是通過這三個類加載器加載的。
????????類加載器之間存在上下級關系,系統類加載器的上級是擴展類,擴展類加載器的上級是引導類加載器。
2.JVM眼中的相同的類
????????在JVM中,不可能存在一個類被加載兩次的事情!一個類如果已經被加載了,當再次試圖加載這個類時,類加載器會先去查找這個類是否已經被加載過了,如果已經被加載過了,就不會再去加載了。
????????但是,如果一個類使用不同的類加載器去加載是可以出現多次加載的情況的!也就是說,在JVM眼中,相同的類需要有相同的class文件,以及相同的類加載器。當一個class文件,被不同的類加載器加載了,JVM會認識這是兩個不同的類,這會在JVM中出現兩個相同的Class對象!甚至會出現類型轉換異常!
3.類加載器的代理模式
????????當系統類加載器去加載一個類時,它首先會讓上級去加載,即讓擴展類加載器去加載類,擴展類加載器也會讓它的上級引導類加載器去加載類。如果上級沒有加載成功,那么再由自己去加載!
????????例如我們自己寫的Person類,一定是存放到CLASSPATH中,那么一定是由系統類加載器來加載。當系統類加載器來加載類時,它首先把加載的任務交給擴展類加載去,如果擴展類加載器加載成功了,那么系統類加載器就不會再去加載。這就是代理模式了!
????????相同的道理,擴展類加載器也會把加載類的任務交給它的“上級”,即引導類加載器,引導類加載器加載成功,那么擴展類加載器也就不會再去加載了。引導類加載器是用C語言寫的,是JVM的一部分,它是最上層的類加載器了,所以它就沒有“上級了”。它只負責去加載“內部人”,即JDK中的類,但我們知道Person類不是我們自己寫的類,所以它加載失敗。
????????當擴展類加載器發現“上級”不能加載類,它就開始加載工作了,它加載的是lib\ext目錄下的jar文件,當然,它也會加載失敗,所以最終還是由系統類加載器在CLASSPATH中去加載Person,最終由系統類加載器加載到了Person類。
????????代理模式保證了JDK中的類一定是由引導類加載加載的!這就不會出現多個版本的類,這也是代理模式的好處。
4.Tomcat的類加載器
????????Tomcat會為每個項目提供一個類加載器,Tomcat提供的類加載器負責加載自己項目下的類,即WEB-INF\lib和WEB-INF\classes下的類。但Tomcat提供的類加載器不會使用傳統的代理模式,而是自己先去加載,如果加載不到,再使用代理模式。
????????Tomcat提供的類加載器有這樣一個好處,就是可以使自己項目下的類優先被加載!
總結
- 上一篇: Java-Web 基础加强之泛型、
- 下一篇: JDK新特性之枚举