泛型-註解-Servlet3.0-動態代理


泛型
1 回顧泛型類
泛型類:具有一個或多個泛型變量的類被稱之爲泛型類。
public class A<T> {
private T t;
public A(T t) {
this.t = t;
}
public T get() {
return t;
}
}
2 泛型方法
泛型方法的特點:
 方法的參數中會使用泛型變量;
 方法的返回值中會使用泛型變量。
public <T> T get(T[] ts) {
return ts[ts.lengt / 2];
}
String[] names ={“zhangSan”, “liSo”, “wangWu”};
String name = get(names);
  調用泛型方法時無需指定泛型變量,編譯器會通過實際參數的類型來識別泛型變量的類型,上例中傳遞的參數爲String[]類型,那麼相當於給泛型類型T賦值爲String。
3 繼承(實現)泛型類(接口)
繼承泛型類需要爲父類的泛型變量賦值!就好比創建泛型類的對象時需要給泛型變量賦值一樣。
// 創建泛型類對象
List<String> list = new ArrayList<String>();
// 繼承泛型類1:子類也是泛型類
public class MyList1<T> extends ArrayList<T> {
…
}
// 繼承泛型類2:子類不是泛型類
public class MyList2 extends ArrayList<String> {
…
}
4 通配符
爲了說明通配符的作用,我們先看個例子:
List<Object> list1 = new ArrayList<String>();
List<Object> list2 = new ArrayList<Integer>();
上面的調用都是編譯不通過的!
這說明想寫一個即可以打印list1,又可以打印list2的方法是不可能的!
public static void fun(List<Object> list) {…}
List<String> list1 = new ArrayList<String>();
List<Integer> list2 = new ArrayList<Integer>();
fun(list1);//編譯不通過
fun(list2);//編譯不通過
如果把fun()方法的泛型參數去除,那麼就OK了。即不使用泛型!
public static void fun(List list) {…}//會有一個警告
List<String> list1 = new ArrayList<String>();
List<Integer> list2 = new ArrayList<Integer>();
fun(list1);
fun(list2);
上面代碼是沒有錯了,但會有一個警告。警告的原因是你沒有使用泛型!Java希望大家都去使用泛型。
你可能會說,這裏TMD根本就不能使用泛型。
4.1 通配符概述
通配符就是專門處理這一問題的。
public static void fun(List<?> list) {…}
上面代碼中的“?”就是一個通配符,它只能在“<>”中使用。造成不能把它從“<>”中拿出來。
這時你可以向fun()方法傳遞List<String>、List<Integer>類型的參數了。當傳遞List<String>類型的參數時,表示給“?”賦值爲String;當傳遞List<Integer>類型的
參數給fun()方法時,表示給“?”賦值爲Integer。
4.2 通配符的缺點
  帶有通配符的參數不能使用與泛型相關的方法,例如:list.add(“hello”)編譯不通過。
上面的問題是處理了,但通配符也有它的缺點。
在上面例子中,List<?> list參數中的通配符可以被賦任何值,但同時你也不知道通配符被賦了什麼值。
當你不知道“?”是什麼時,會使你不能使用任何與泛型相關的方法。也就是說fun()方法的參數list不能再使用它的與泛型相關的方法了。例如:list.add(“hello”)是錯
誤的,因爲List類的add()方法的參數是T類型,而現在你不知道T是什麼類型,你怎麼去添加String的東西給list呢?如果使用者在調用fun()方法時傳遞的不
是List<String>,而是List<Integer>時,你添加String當然是不可以的。
當然,還可以調用list的get()方法。就算你不知道“?”是什麼類型,但它肯定是Object類型的。所以你可以:Object o = list.get(0);
4.3 通配符的限制
  通配符只能出現在引用的定義中,而不能出現在創建對象中。例如:new ArrayList<?>(),這是不可以的。ArrayList<?> list = null,這是可以的。
4.4 帶有下邊界的通配符
List<? extends Number> list;
其中<? extends Number>表示通配符的下邊界,即“?”只能被賦值爲Number或其子類型。
public static void fun(List<? extends Number> list) {…}
fun(new ArrayList<Integer>());//ok
fun(new ArrayList<Double>());//ok
fun(new ArrayList<String>());//不okfun()方法的參數爲List<? extends Number>後,說明你只能賦值給“?”Number或Number的子類型。
雖然這多了一個限制,但也有好處,因爲你可以list的get()方法了。就算你不知道“?”是什麼類型,但你知道它一定是Number或Number的子類型。所
以:Number num = list.get(0)是可以的。
但是,還是不能調用list.add()方法!
4.5 帶有下邊界的通配符
List<? super Integer> list;
其中<? super Integer>表示通配符的下邊界,即“?”只能被賦值爲Integer或其父類型。
public static void fun(List<? super Integer> list) {…}
fun(new ArrayList<Integer>());//ok
fun(new ArrayList<Number>());//ok
fun(new ArrayList<Object>());//ok
fun(new ArrayList<String>());//不ok
這時再去調用list.get()方法還是隻能使用Object類型來接收:Object o = list.get(0)。因爲你不知道“?”到底是Integer的哪個父類。
但是你可以調用list.add()方法了,例如:list.add(new Integer(100))是正確的。因爲無論“?”是Integer、Number、Object,list.add(new Integer(100))都是正確的。
4.6 通配符小結
1. 方法參數帶有通配符會更加通用;
2. 帶有通配符類型的對象,被限制了與泛型相關方法的使用;
3. 下邊界通配符:可以使用返回值爲泛型變量的方法;
4. 上邊界通配符:可以使用參數爲泛型變量的方法。
5 泛型父類獲取子類傳遞的類型參數
看一個例子:
public class A<T> {
}
pubilc class B extends A<String> {
}
public class C extends A<Integer> {
}
如果你需要在A類中得到子類給T賦值的類型,那麼可以使用下面的方法:
public class A<T> {
public A() {
ParameterizedType pType = (ParameterizedType)this.getClass().getGenericSuperclass();
Class clazz = (Class)pType.getActualTypeArguments()[0];
System.out.println(clazz.getName());
}
}
註解
1 註解的概述
註釋你還記得麼?開個玩笑而已!
註釋是給人看的,而註解是給程序看的!
註釋是用來替代配置文件的!你回憶一下,我們以前總是要寫一些配置文件,例如web.xml你還記得麼?裏面要寫<servlet>和<servlet-mapping>!誰來讀配置文件呢?
當然是Tomcat!誰來寫配置文件呢?當然是我們來寫了!
在Servlet3.0中就可以使用使用註解來代替配置文件,開發者就不用再寫配置文件了,而是寫註解,然後Tomcat來讀取註解。
註解也是類,需要定義了才能使用!
分別在Servlet3.0中有一個註解類爲@WebServlet,然後我們就可以在Servlet中使用@WebServlet中使用這個註解了。這個註解就是用來替代<servlet>了。然後Tomcat會
通過反射來讀取註解中的信息!
2 Java中的註解
Java中的覺註解:
 @Overrid:作用在方法上的註解。當方法不是重寫父類的方法時會報錯;
 @Deprecated:作用在方法上。標記該方法爲作廢方法(已過時);
 @SuppressWarnings:作用在方法上,壓制警告。
3 定義註解類
定義註解類不能使用classenum,也不能使用interface,而是使用@interface
public @interface MyAnn{}
4 使用註解目標
  註解可以作用在:類(接口或枚舉)、屬性、方法、構造器、包、參數、局部變量
package cn.itcast.annocation;
@MyAnn
public class MyClass {
@MyAnn
private int a;
@MyAnn
public MyClass() {}
@MyAnn
public void fun1() {}
@MyAnn
public void fun2(@MyAnn String s) {
@MyAnn
int n = 10;
}
}
5 註解的屬性
定義註解時也可以給出屬性
public @interface MyAnn {
String value();
int value1();
}
其中value就是屬性!你可能會說,它是一個方法!沒錯,它是一個方法,但我們非要稱之爲屬性,因爲把它當做屬性更加好理解。
當爲註解指定屬性後,那麼在使用註解時就必須要給屬性賦值了:
@MyAnn(value1=100,value="hello")
public class MyClass {
}
註解的屬性還可以有默認值,在使用註解時就可以不給帶有默認值的屬性賦值了。但沒有給出默認值的屬性還是要賦值的。
public @interface MyAnn {
String value() default "hello world";
int value1();
}
@MyAnn(value1=100)
public class MyClass {
}
在使用註解時,如果只給名爲value的屬性賦值,那麼可以不給出屬性的名稱直接給出值。
public @interface MyAnn {
String value() default "hello world";
int value1() default 100;
}
@MyAnn()
public class MyClass {
}
@MyAnn(value="hello")
public class MyClass {
}
@MyAnn(value1=200)
public class MyClass {
}
@MyAnn(value="hello",value1=200)
public class MyClass {
}
@MyAnn("hello annocation")
public class MyClass {
}
@MyAnn(300)
public class MyClass {
}
@MyAnn("hello",value1=200)
public class MyClass {
}
 註解的屬性後面要有一對圓括號,而且圓括號內不能給出東西。就像是無參的方法一樣;
 註解的屬性類型只能是:基本類型、String、Enum、Class、註解、以上類型的一維數組類型;
 註解的屬性可以有默認值,例如:int a() default 100;
 數組的屬性默認值:int[] arr() default {1,2,3},這裏不能使用new int[]{1,2,3}
 使用註解時,在給數組屬性賦值時的格式:@MyAnn(arr={1,2,3});
 特殊名稱的屬性(value):如果註解只需要給一個屬性賦值,並且這個屬性名爲value,那麼可以省略屬性名稱與等號
6 註解的作用目標
在定義註解時可以限制註解的作用目錄!例如讓註解只能作用在類和方法上。
這需要使用元註解:@Target。該註解有一個屬性value,類型爲ElementType[],它是枚舉類型。
public @interface Target {
ElementType[] value();
}
public enum ElementType {
TYPE,FIELD,METHOD,PARAMETED,CONSTRUCTOR,LOCAL_VARIABLE,ANNOCATION_TYPE,PACKAGE
}
在定義註解時,可以使用@Target註解來限制註解的作用目標:
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface MyAnn {
}
這樣MyAnn就只能作用在類和方法上的!其中ElementType.TYPE表示類和接口。
@MyAnn()
public class MyClass {
@MyAnn()
private int a;
@MyAnn()
public void fun() {}
}
7 註解的保留策略
註解的保留策略是指,註解是隻保留在源代碼上,還是保留到class文件上,再或者是類在運行時,可以被類加載器加載到內存中。
如果希望註解被反射,那麼註解就要保留到運行時,而不是源代碼或類文件上。
指定註解的保留策略需要使用元註解@Retention,它有一個value屬性,類型爲RetentionPolicy類型,RetentionPolicy是枚舉類型:
public @interface Retention {
RetentionPolicy value();
}
public enum RetentionPolicy {
SOURCE, CLASS, RUNTIME
}
下面代碼是指定註解保留到運行時
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface MyAnn {
String value() default "hello";
int value1() default 100;
}
8 通過反射讀取註解
讀取註解需要使用反射來完成
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface MyAnn {
String value() default "hello";
int value1() default 100;
}
@MyAnn(value="hello world", value1=200)
public class MyClass {
private int a;
@MyAnn("myMethod")
public void fun() {}
}
public class Demo1 {
public static void main(String[] args) throws Exception {
Class clazz = MyClass.class;
MyAnn myAnn = (MyAnn) clazz.getAnnotation(MyAnn.class);
System.out.println(myAnn.value());
System.out.println(myAnn.value1());
Method method = clazz.getMethod("fun");
MyAnn myAnn1 = method.getAnnotation(MyAnn.class);
System.out.println(myAnn1.value());
System.out.println(myAnn1.value1());
}
}
Servlet3.0新特性
1 Servlet3.0新特性概述
  Servlete3.0的主要新特性如下三部分:
 使用@WebServlet@WebFilter@WebListener三個註解來替代web.xml文件中的Servlet、Filter、Listener的配置;
 Servlet異步處理:當Servlet處理比較費時的問題時,這會讓客戶感覺到很卡。當使用異常處理時可以把已經處理好的內容先一步響應給客戶端瀏覽器,然後使用另一個
線程來完成費時的操作,也就是把內容一部分一部分的顯示出來;
 上傳組件:不用再使用fileupload等第三方的上傳組件,使用Servlet3.0的上傳組件會更方便。
2 @WebServlet@WebFilter@WebListener
@WebServlet(
urlPatterns={"/AServlet"},
initParams={@WebInitParam(name="paramName",value="paramValue")},
loadOnStartup=1
)
public class AServlet extends HttpServlet {
public void init(ServletConfig config) throws ServletException {
System.out.println(config.getInitParameter("paramName"));
}
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
response.getWriter().print("Hello World!");
}
}
@WebFilter(urlPatterns={"/*"},
dispatcherTypes={DispatcherType.REQUEST, DispatcherType.FORWARD})
public class AFilter implements Filter {
public void destroy() {}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
System.out.println("start filter");
chain.doFilter(request, response);
System.out.println("end filter");
}
public void init(FilterConfig fConfig) throws ServletException {}
}
@WebListener()
public class AListener implements ServletContextListener {
public void contextDestroyed(ServletContextEvent arg0) {
System.out.println("服務器關閉了");
}
public void contextInitialized(ServletContextEvent arg0) {
System.out.println("服務器啓動了");
}
}
3 Servlet異步處理
Servlet異步處理就是讓Servlet在處理費時的請求時不要阻塞,而是一部分一部分的顯示。
也就是說,在使用Servlet異步處理之後,頁面可以一部分一部分的顯示數據,而不是一直卡,等到請求響應結束後一起顯示。
在使用異步處理之前,一定要在@WebServlet註解中給出asyncSupported=true,不然默認Servlet是不支持異步處理的。如果存在過濾器,也要設置@WebFilter
的asyncSupportedt=true@WebServlet(urlPatterns = {"/MyServlet"}, asyncSupported=true)
public class MyServlet extends HttpServlet {…}
使用異步處理大致可以分爲兩步:
 Servlet正常響應數據;
 Servlet異常響應數據。
在Servlet正常響應數據時,沒什麼可說的,可通知response.getWriter().print()來向客戶端輸出,但輸出後要使用response.getWriter().flush()刷新,不然數據只是
在緩衝區中,不能向客戶端發送數據的。
異步響應數據需要使用request.startAsync()方法獲取AsyncContext對象。然後調用AsyncContext對象的start()方法啓動異步響應,start()方法需要一個Runnable類型
的參數。在Runnable的run()方法中給出異步響應的代碼。
AsyncContext ac = request.startAsyncContext(request, response);
ac.start(new Runnable() {…});
注意在異步處理線程中使用response做響應後,要使用response.getWriter().flush()來刷新流,不然數據是不能響應到客戶端瀏覽器的。
asyncContext.start(new Runnable() {
public void run() {
for(char i = 'a'; i <= 'z'; i++) {
try {
Thread.sleep(100);
asyncContext.getResponse().getWriter().print(i + " ");
asyncContext.getResponse().getWriter().flush();
} catch (Exception e) {
e.printStackTrace();
}
}
asyncContext.complete();
}
});
Tomcat需要知道異步響應是否結束,如果響應不結束,雖然客戶端瀏覽器會看到響應的數據,但是鼠標上只是有個圈圈的不行的轉啊轉的,表示還沒有結束響應。Tomcat會等
待到超時爲止,這個超時的時間可以通過AsyncContext類的getTimeout()方法獲取,Tomcat默認爲20000毫秒。當然也可以通過setTimeOut()方法設置,以毫秒爲單位。
ac.setTimeout(1000*10)。
如果異步線程已經結束了響應,那麼可以在異步線程中調用AsyncContext.complete()方法,這樣Tomcat就知道異步線程已經完成了工作了。
@WebServlet(urlPatterns = {"/AServlet"}, asyncSupported=true)
public class AServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
out.println("Servlet begin <br>");
out.println("Servlet begin <br>");
out.println("Servlet begin <br>");
out.println("Servlet begin <br>");
out.println("Servlet begin <br>");
out.println("Servlet begin <br>");
out.println("Servlet begin <br>");
out.println("Servlet begin <br>");
out.println("Servlet begin <br>");
out.println("Servlet begin <br>");
out.println("Servlet begin <br>");
out.println("Servlet begin <br>");
out.println("Servlet begin <br>");
out.println("Servlet begin <br>");
out.println("Servlet begin <br>");
out.flush();
final AsyncContext asyncContext = request.startAsync(request, response);
asyncContext.setTimeout(1000 * 20);
asyncContext.start(new Runnable() {
public void run() {
try {
Thread.sleep(1000);
asyncContext.getResponse().getWriter().print("馬上開始" + "<br/>");
asyncContext.getResponse().getWriter().flush();
Thread.sleep(2000);
} catch (Exception e1) {
}
for(char i = 'a'; i <= 'z'; i++) {
try {
Thread.sleep(100);
asyncContext.getResponse().getWriter().print(i + " ");
asyncContext.getResponse().getWriter().flush();
} catch (Exception e) {
e.printStackTrace();
}
}
asyncContext.complete();
}
});
// asyncContext.start(businessHandleThread);
// 也可以用這種方法啓動異步線程
out.println("Servlet end <br>");
}
}
4 文件上傳
Servlet3.0提供了文件上傳的處理方案。只需要在Servlet上添加@MultipartConfig註解即可。
@WebServlet(urlPatterns={"/UploadServlet"})
@MultipartConfig(maxFileSize=1024)
public class UploadServlet extends HttpServlet { … }
當然也可以爲@MultipartConfig註解指定屬性值,它有四個屬性:
 int filesizeThreshold:指定緩存的大小,當超出這個大小後,文件會保存到磁盤上;
 String location:指定臨時文件的目錄;
 long maxFilesize:指定上傳單個文件的大小限制,如果上傳的誰的超出了這個大小,那麼就會拋出異常;
 long maxRequestSize:指定整個表單的大小限制。
當在Servlet上使用了@MultipartConfig註解後,那麼就可以使用request.getPart(“fieldName”)來獲取<input:file>的內容,其中Part表示一個文件表單項。
<form action="/a1/UploadServlet" method="post" enctype="multipart/form-data">
用戶名:<input type="text" name="username"/><br/>
照 片:<input type="file" name="file1" /><br/>
<input type="submit" value="提交"/>
</form>
@WebServlet(urlPatterns={"/UploadServlet"})
@MultipartConfig(maxFileSize=1024 * 1024)
public class UploadServlet extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
String username = request.getParameter("username");
response.getWriter().print("size: " + username + "<br/>");
Part part = request.getPart("file1");
response.getWriter().print("size: " + part.getSize() + "<br/>");
response.getWriter().print("type: " + part.getContentType() + "<br/>");
response.getWriter().print("name: " + part.getName() + "<br/>");
String name = part.getHeader("content-disposition");
String fileNameTmp = name.substring(name.indexOf("filename=")+10);
String fileName = fileNameTmp.substring(0,fileNameTmp.indexOf("\""));
System.out.println("fileName: " + fileName);
String savepath = this.getServletContext().getRealPath("/uploads");
part.write(savepath + "/" + fileName);
}
}
動態代理(AOP)
1 學習動態代理的目的
動態代理技術都是在框架中使用,例如:Struts1、Struts2、Spring和Hibernate中都使用了動態代理技術。如果你不想自己寫個框架,那麼你基本上是用上不動態代理
技術的。
我們學習動態代理技術的目的是爲了更好的理解框架內部的原理,也就是說是爲了將來我們學習框架打基礎!
動態代理技術有點小難度!而且明白了動態代理技術可能一時也想不到他適合在什麼情況下使用它。這些東西都會在學習框架時漸漸明白。
2 運行時實現指定的接口
想實現某個接口,你需要寫一個類,然後在類名字的後面給出“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文件,是在運行時生成的,你也不用去關心它是什麼類型的,你只需要知道它實
現了哪些接口即可。
3 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();
}
  InvocationHandler接口只有一個方法,即invoke()方法!它是對代理對象所有方法的唯一實現。也就是說,無論你調用代理對象上的哪個方法,其實都是在調
用InvocationHandler的invoke()方法。
想象中的類:
class X implements MyInterface {
private InvocationHandler h;
public X(InvocationHandler h) {
this.h = h;
}
public void fun1() {
h.invoke();
}
public void fun2() {
h.invoke();
}
}
  注意,X類是我們用來理解代理對象與InvocationHandler之間的關係的,但它是不存在的類。是我們想象出來的!也就是說,它是用來說明,無論你調用代理對象的哪
個方法,最終調用的都是調用處理器的invoke()方法。
4 InvocationHandler的invoke()方法
InvocationHandler的invoke()方法的參數有三個:
 Object proxy:代理對象,也就是Proxy.newProxyInstance()方法返回的對象,通常我們用不上它;
 Method method:表示當前被調用方法的反射對象,例如mi.fun1(),那麼method就是fun1()方法的反射對象;
 Object[] args:表示當前被調用方法的參數,當然mi.fun1()這個調用是沒有參數的,所以args是一個零長數組。
  最後要說的是invoke()方法的返回值爲Object類型,它表示當前被調用的方法的返回值,當然mi.fun1()方法是沒有返回值的,所以invoke()返回的就必須是null了。
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("當前調用的方法是:" + method.getName());
return null;
}
};
MyInterface mi = (MyInterface)Proxy.newProxyInstance(loader, cs, h);
mi.fun1();
mi.fun2();
}
當前調用的方法是:fun1
當前調用的方法是:fun2
5 動態代理的用途
動態代理的用途與裝飾模式很相似,就是爲了對某個對象進行增強。所有使用裝飾者模式的案例都可以使用動態代理來替換。
下面我們用一個例子來說明動態代理的用途!
我們來寫一個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;
}
}
類加載器
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中的類一定是由引導類加載加載的!這就不會出現多個版本的類,這也是代理模式的好處。
3 自定義類加載器
我們也可以通過繼承ClassLoader類來完成自定義類加載器,自類加載器的目的一般是爲了加載網絡上的類,因爲這會讓class在網絡中傳輸,爲了安全,那麼class一定是需
要加密的,所以需要自定義的類加載器來加載(自定義的類加載器需要做解密工作)。
ClassLoader加載類都是通過loadClass()方法來完成的,loadClass()方法的工作流程如下:
 調用findLoadedClass()方法查看該類是否已經被加載過了,如果該沒有加載過,那麼這個方法返回null;
 判斷findLoadedClass()方法返回的是否爲null,如果不是null那麼直接返回,這可以避免同一個類被加載兩次;
 如果findLoadedClass()返回的是null,那麼就啓動代理模式,即調用上級的loadClass()方法,獲取上級的方法是getParent(),當然上級可能還有上級,這個動作就一
直向上走;
 如果getParent().loadClass()返回的不是null,這說明上級加載成功了,那麼就加載結果;
 如果上級返回的是null,這說明需要自己出手了,這時loadClass()方法會調用本類的findClass()方法來加載類;
 這說明我們只需要重寫ClassLoader的findClass()方法,這就可以了!如果重寫了loadClass()方法覆蓋了代理模式!
OK,通過上面的分析,我們知道要自定義一個類加載器,只需要繼承ClassLoader類,然後重寫它的findClass()方法即可。那麼在findClass()方法中我們要完成哪些工作呢?
 找到class文件,把它加載到一個byte[]中;
 調用defineClass()方法,把byte[]傳遞給這個方法即可。
FileSystemClassLoader
public class FileSystemClassLoader extends ClassLoader {
private String classpath;
public FileSystemClassLoader() {}
public FileSystemClassLoader(String classpath) {
this.classpath = classpath;
}
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] datas = getClassData(name);
if(datas == null) {
throw new ClassNotFoundException("類沒有找到:" + name);
}
return this.defineClass(name, datas, 0, datas.length);
} catch (IOException e) {
e.printStackTrace();
throw new ClassNotFoundException("類找不到:" + name);
}
}
private byte[] getClassData(String name) throws IOException {
name = name.replace(".", "\\") + ".class";
File classFile = new File(classpath, name);
return FileUtils.readFileToByteArray(classFile);
}
}
ClassLoader loader = new FileSystemClassLoader("F:\\classpath");
Class clazz = loader.loadClass("cn.itcast.utils.CommonUtils");
Method method = clazz.getMethod("md5", String.class);
String result = (String) method.invoke(null, "qdmmy6");
System.out.println(result);
4 Tomcat的類加載器
Tomcat會爲每個項目提供一個類加載器,Tomcat提供的類加載器負責加載自己項目下的類,即WEB-INF\lib和WEB-INF\classes下的類。但Tomcat提供的類加載器不會使用傳
統的代理模式(委託機制),而是自己先去加載,如果加載不到,再使用代理模式。
Tomcat提供的類加載器有這樣一個好處,就是可以使自己項目下的類優先被加載!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章