主要內(nèi)容:
- 泛型
- 注解
- 動(dòng)態(tài)代理
- 類加載器
一、泛型
1.1 泛型(Generic)的作用
jdk5以前,對(duì)象保存到集合中就會(huì)時(shí)區(qū)其特性,取出時(shí)通常要程序員手工進(jìn)行類型的強(qiáng)制轉(zhuǎn)換,這樣不可避免就會(huì)引發(fā)程序的一些安全性問題。
jdk5中的泛型允許程序員在編寫結(jié)合代碼時(shí),就限制集合的處理類型,從而把原來(lái)程序運(yùn)行時(shí)可能發(fā)生的問題,轉(zhuǎn)變?yōu)榫幾g時(shí)的問題,以此提高程序的可讀性和穩(wěn)定性。
注意:泛型是提供給javac編譯器使用的,它用于限定集合的輸出類型,讓編譯器在源代碼級(jí)別上擋住向集合中插入非法數(shù)據(jù)。但編譯器編譯完帶有泛型的java程序后,生成的class文件中將不再帶有泛型信息,以此使程序運(yùn)行效率不受到影響,這個(gè)過程稱之為“擦除”。
- 泛型的基本術(shù)語(yǔ),以
ArrayList<E>
為例,<>
念typeof
?ArrayList<E>
中的E稱為類型參數(shù)變量
?ArrayList<Integer>
中的Integer稱為實(shí)際類型參數(shù)
?整個(gè)稱為ArrayList<E>
泛型類型
?整個(gè)ArrayList<Integer>
稱為參數(shù)化的類型ParameterizedType
1.2 泛型的典型應(yīng)用
- 使用迭代器迭代泛型集合中的元素
- 使用增強(qiáng)for循環(huán)迭代泛型集合中的元素
- 存取HashMap中的元素
- 使用泛型時(shí)的幾個(gè)常見問題:
1.使用泛型時(shí),泛型類型必須為引用類型,不能是基本數(shù)據(jù)類型
2.ArrayList<String> list = new ArrayList<Object>();
3.ArrayList<Object> list = new ArrayList<String>();
4.ArrayList<String> list = new ArrayList ();
5.rayList list = new ArrayList<String>();
1.3 自定義泛型-泛型方法
1.3.1 回顧(工程day22
)
Demo1.java
package cn.itcast.generic;
public class Demo1 {
public <T> void aa(T t){
}
public <T> void bb(T t){
}
}
說(shuō)明:
- 1.一般我們定義泛型方法如上,如果我們先想使用泛型參數(shù),那么我們需要先定義再使用,即在方法中使用
<T>
來(lái)定義泛型。 - 2.當(dāng)多個(gè)方法都需要使用相同的泛型時(shí),我們可以將泛型定義在類上,這樣方法中就不需要再次定義了:
package cn.itcast.generic;
public class Demo1<T> {
public void aa(T t){
}
public void bb(T t){
}
}
但是注意:在類上定義泛型只對(duì)類中的普通方法(非靜態(tài)成員)有效,對(duì)靜態(tài)方法是無(wú)效的,也就是說(shuō)如果我們想在靜態(tài)方法中使用泛型,那么我們需要單獨(dú)定義之后再使用。如:
public static <T> void cc(T t){}
- 3.注意:只有對(duì)象類型才能作為泛型方法的實(shí)際參數(shù)類型,基本數(shù)據(jù)類型則不能。
1.3.2 一個(gè)小題目:編寫一個(gè)泛型方法,接收一個(gè)任意數(shù)組,并顛倒數(shù)組中的所有元素
Demo2.java
package cn.itcast.generic;
import java.util.Arrays;
public class Demo2 {
public static void main(String[] args) {
Integer arr[] = {1,2,3,4};
reverse(arr);
System.out.println(Arrays.asList(arr));
}
private static <T> void reverse(T[] arr) {
int start = 0;
int end = arr.length - 1;
T tmp = arr[0];
while(start < end){
tmp = arr[start];
arr[start] = arr[end];
arr[end] = tmp;
start++;
end--;
}
}
}
1.4 自定義泛型-泛型類和反射泛型
- 泛型的典型應(yīng)用:BaseDao和反射泛型
BaseDao.java
package cn.itcast.generic;
import org.hibernate.Session;
public abstract class BaseDao<T> {
private Session session;
private Class clazz;
public BaseDao(Class clazz){
this.clazz = clazz;
}
public void add(T t){
session.save(t);
}
public T find(String id){
return (T) session.get(clazz, id);
}
public void update(T t){
session.update(t);
}
public void delete(String id){
T t = (T) session.get(clazz, id);//先得到再進(jìn)行刪除
session.delete(t);
}
}
CategoryDao.java
package cn.itcast.generic;
public class CategoryDao extends BaseDao<Category> {
public CategoryDao(Class clazz) {
super(Category.class);
}
}
說(shuō)明:
1.以后我們的dao層實(shí)現(xiàn)中對(duì)于增刪改查有很多類似的代碼,這里我們希望只需要定義一個(gè)dao層實(shí)現(xiàn),然后其他需要相關(guān)方法的dao層實(shí)現(xiàn)只需要繼承此公共dao層即可,如
CategoryDao.java
。2.當(dāng)然這里我們要用到hibernate,而find方法中我們需要相關(guān)類的類型,我們可以使用構(gòu)造函數(shù)傳遞進(jìn)來(lái),但是這種方式在繼承的時(shí)候還需要顯式調(diào)用構(gòu)造方法,比較麻煩。還可以使用反射的方法進(jìn)行傳遞:
public BaseDao(){
//拿到的是子類,因?yàn)槟膫€(gè)類調(diào)用這個(gè)方法,那么this就表示哪個(gè)類,
//而我們new一個(gè)CategoryDao時(shí)就會(huì)i調(diào)用其默認(rèn)的構(gòu)造方法,而這個(gè)
//構(gòu)造方法會(huì)調(diào)用父類的構(gòu)造方法,所以這個(gè)方法相當(dāng)于是CategoryDao調(diào)用的,于是this就表示CategoryDao。
Class clazz = this.getClass();
//得到父類,clazz表示CategoryDao,其父類就是BaseDao <CategoryDao>
ParameterizedType pt = (ParameterizedType)clazz.getGenericSuperclass();
//一個(gè)參數(shù)化泛型中可能有多個(gè)參數(shù),我們這里取第一個(gè)參數(shù),即得到具體的實(shí)體類型。
clazz = (Class) pt.getActualTypeArguments()[0];
System.out.println(clazz);
}
此時(shí)我們只需要繼承即可,無(wú)需再顯式調(diào)用構(gòu)造方法。此方法中第一步是拿到具體dao層的類型,然后通過此類型拿到具體的實(shí)體類型。
- 3.在實(shí)際開發(fā)中,我們一般是通過方法參數(shù)將具體的類的類型傳遞進(jìn)來(lái):
public T get(Class<T> c, Serializable id)
1.5泛型的高級(jí)應(yīng)用-通配符
Demo4.java
package cn.itcast.generic;
import java.util.ArrayList;
import java.util.Collection;
public class Demo4 {
public static void main(String[] args) {
print(new ArrayList<Integer>());
print(new ArrayList<String>());
}
public static void print(Collection<?> c){
for(Object obj : c){
System.out.println(obj);
}
}
//當(dāng)使用?通配符時(shí),就不能再調(diào)用與類型相關(guān)的方法,只能調(diào)與類型無(wú)關(guān)的方法
public static void save(Collection<?> c){
//c.add("1");
c.size();
}
}
注意:記住,通配符主要用于引用對(duì)象,使用了通配符之后就只能調(diào)用對(duì)象與類型無(wú)關(guān)的方法,不能調(diào)用對(duì)象與類型有關(guān)的方法(不管什么情況下都是這樣)。比如上面的save方法中我們就不能使用add這種與類型相關(guān)的方法,但是size方法與類型無(wú)關(guān)的方法。
1.6泛型的高級(jí)應(yīng)用-有限制的通配符
限定通配符的上邊界
正確:Vector<? extends Number> x = new Vector<Integer>();
錯(cuò)誤:Vector<? extends Number> x = new Vector<String>();
限定通配符的下邊界
正確:Vector<? super Integer> x = new Vector<Number>();
錯(cuò)誤:Vector<? super Integer> x = new Vector<Byte>();
問題:以下代碼行不行?
public void add(List<? extends String> list){
list.add("abc");
}
答:顯然是不行的,因?yàn)槭褂昧送ㄅ浞圆荒苁褂煤皖愋拖嚓P(guān)的方法。
二、Annotation(注解)
2.1概述
從jdk5開始,java增加了對(duì)元數(shù)據(jù)(MetaData)的支持,也就是Annotation(注解)。
什么是Annotation,以及注解的作用?三個(gè)基本的Annotation:
@Override: 限定重寫父類方法, 該注解只能用于方法
@Deprecated: 用于表示某個(gè)程序元素(類, 方法等)已過時(shí)
@SuppressWarnings: 抑制編譯器警告.Annotation其實(shí)就是代碼里的特殊標(biāo)記,它用于替代配置文件,也就是說(shuō),傳統(tǒng)方式通過配置文件告訴類如何運(yùn)行,有了注解技術(shù)后,開發(fā)人員可以通過注解告訴類如何運(yùn)行。在java技術(shù)里注解的典型應(yīng)用是:可以通過反射技術(shù)去得到類里面的注解,以決定怎么去運(yùn)行類。這個(gè)技術(shù)一般在框架中應(yīng)用的比較多。
例1:
在servlet3.0中引入了注解,我們可以不使用xml文件進(jìn)行配置而直接使用注解即可。
package cn.itcast.annotation;
//@WebServlet(name="servlet1",patternUrl={"/servlet/Servlet1","/servlet/MyServlet"})
public class MyServlet {
}
- 例2:
Demo1.java
package cn.itcast.annotation;
import java.util.ArrayList;
import java.util.List;
@SuppressWarnings("unchecked")//對(duì)類使用
public class Demo1 {
@SuppressWarnings("unchecked") private List list;
//對(duì)字段使用
@SuppressWarnings("unchecked")//對(duì)方法和參數(shù)使用
public Demo1(@SuppressWarnings("unchecked") List list) {
super();
this.list = list;
}
@Override//表示覆蓋父類的方法
public boolean equals(Object obj) {
return super.equals(obj);
}
@Deprecated//表示過時(shí)了
public void doxx(){
}
public void doyy(){
}
@SuppressWarnings("unchecked")//表示跳過檢查,這樣便不會(huì)有警告
public void dozz(){
List list = new ArrayList();
System.out.println(list);
}
}
注意:注解只接收基本數(shù)據(jù)類型、String、Class、Annotation、枚舉類型和以上所屬類型的一維數(shù)組類型。
2.2 自定義Annotation
定義新的Annotation類型使用
@interface
關(guān)鍵字-
聲明注解的屬性
- 注解屬性的作用:原來(lái)寫在配置文件中的信息,可以通過注解的屬性進(jìn)行描述。
- Annotation的屬性聲明方式:
String name();
- 屬性默認(rèn)值的申明方式:
String name() default “xxx”;
- 特殊屬性value:如果注解中有一個(gè)名稱value的屬性,那么使用注解時(shí)可以省略value=部分,如
@MyAnnotation(“xxx”)
- 特殊屬性
value[]
例1:
MyAnnotation.java
package cn.itcast.annotation;
public @interface MyAnnotation {
//注解可以使用如下類型配置注解包含的信息
String name();//注意其字段的定義格式,這是定義一個(gè)屬性
String password() default "123";
double age() default 12;
Gender gender() default Gender.FEMALE;//一個(gè)枚舉值
Class clazz();
MyAnnotation2 my2();//把另一個(gè)注解當(dāng)作字段,對(duì)應(yīng)于xml文檔中的嵌套配置
int[] arr() default {1,2,3};//注意不能把[]放在后面
Gender[] gs();
}
Gender.java
package cn.itcast.annotation;
public enum Gender {
MALE,FEMALE;
}
MyAnnotation2.java
package cn.itcast.annotation;
public @interface MyAnnotation2 {
String name();
}
我們?cè)谑褂脮r(shí)就可以這樣:
@MyAnnotation(name="老張",age=37,gender=Gender.MALE,clazz=String.class,my2=@MyAnnotation2(name="xxx"),arr={2,3,4},gs={Gender.FEMALE,Gender.MALE})
public void doaa(){
}
注意:當(dāng)一個(gè)注解中如果只有一個(gè)名為value的字段,那么在使用時(shí)可以直接使用(即直接給出其值),不需要”value=”
如:
package cn.itcast.annotation;
public @interface MyAnnotation3 {
String[] value();//名稱為value的屬性可以直接賦值
}
使用時(shí)可以這樣:
//名稱為value的屬性可以直接賦值
@MyAnnotation3({"bb"})
public void dobb(){
}
但是如果此注解中還有其他的字段或是屬性名不是value,則不能省略”value=”。注意如果是數(shù)組則不能少了花括號(hào)。
注意:我們?cè)谑褂米远x注解時(shí),如果自定義注解中沒有給出默認(rèn)值則必須在使用時(shí)給出相關(guān)字段的值。
2.3 反射注解
我們直接通過相關(guān)例子進(jìn)行說(shuō)明:
DbInfo.java
package cn.itcast.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//java--->class--->jvm()
@Retention(RetentionPolicy.RUNTIME)//注明此注解的作用域,這里是運(yùn)行時(shí),這種用的最多
@Target({ElementType.METHOD})//指定注解用于修飾類的哪個(gè)成員,這里指修飾方法
@Inherited//表示此注解具有繼承性,就是子類可以繼承父類的注解
public @interface DbInfo {
String url() default "jdbc:mysql://localhost:3306/test";
String username() default "root";
String password() default "root";
}
Inject.java
package cn.itcast.annotation;
public @interface Inject {
String name();
int age();
}
這里我們首先定義了兩個(gè)注解。下面我們使用這兩個(gè)注解之后再反射相關(guān)方法上的注解。
JdbcUtils.java
package cn.itcast.annotation;
import com.sun.corba.se.pept.transport.Connection;
public class JdbcUtils {
@DbInfo(url="jdbc:mysql://localhost:3306/test",username="root",password="walp1314")
public static Connection getConnection(String url,String username,String password){
System.out.println(url);
System.out.println(username);
System.out.println(password);
return null;
}
@DbInfo(url="jdbc:mysql://localhost:3306/test",username="root",password="walp1314")
@Inject(name="flx",age=23)
public void aa(Person p){
//其中Person類有兩個(gè)字段name和age,這里我們對(duì)這個(gè)方法注入一個(gè)類。
}
}
Demo2.java
package cn.itcast.annotation;
import java.lang.reflect.Method;
public class Demo2 {
public static void main(String[] args) throws SecurityException, Exception {
Class clazz = JdbcUtils.class;
Method method = clazz.getMethod("getConnection", String.class,String.class,String.class);
DbInfo di = method.getAnnotation(DbInfo.class);//得到方法的注解,
//一個(gè)方法可以有多個(gè)注解,這里我們得到關(guān)于DbInfo的注解。
String url = di.url();//取得相關(guān)的字段,但是如果不指定注解
String username = di.username();//是運(yùn)行時(shí)注解,則是不會(huì)取得相
String password = di.password();//關(guān)的類和字段的。
method.invoke(null, url,username,password);
}
}
其中Person.java
private String name;
private int age;
......
說(shuō)明:
1.對(duì)于DbInfo注解類中的相關(guān)注解我們?cè)诤竺鏁?huì)詳細(xì)說(shuō)明,這些都是一些元注解信息,用來(lái)修飾注解。
2.在類
Demo.java
中我們使用反射對(duì)相關(guān)方法上的注解進(jìn)行反射,之后可以得到方法上的一些注解信息。但是注意:我們知道java類有3種狀態(tài),一種是源代碼,還有class和在jvm中,如果我們將注解的作用域修飾為前兩種(默認(rèn)為class),那么反射是不會(huì)取到任何信息的,因?yàn)槿绻饔糜驗(yàn)閖ava源代碼級(jí)別,那么編譯的時(shí)候就會(huì)拋棄注解,如果級(jí)別為class,那么在運(yùn)行時(shí)也會(huì)拋棄注解,此時(shí)反射不到任何信息。3.在開發(fā)中我們還經(jīng)常使用注解將一個(gè)類通過容器注入到某個(gè)方法中,如上面的aa方法,其實(shí)也是通過反射實(shí)現(xiàn)的。
2.4 JDK的元注解
2.4.1 元注解
元Annotation指修飾Annotation的Annotation。Jdk中定義了如下元Annotation:
-
@Retention
只能用于修飾一個(gè)Annotation的定義,用于指定該Annotation可以保留的域,包含一個(gè)RetentionPolicy
類型的成員變量,通過這個(gè)變量指定域。-
RetentionPolicy.CLASS
編譯器將把注解記錄在 class 文件中. 當(dāng)運(yùn)行 Java 程序時(shí), JVM 不會(huì)保留注解. 這是默認(rèn)值 -
RetentionPolicy.RUNTIME
編譯器將把注釋記錄在 class 文件中. 當(dāng)運(yùn)行 Java 程序時(shí), JVM 會(huì)保留注解. 程序可以通過反射獲取該注釋。這個(gè)值用的最多,一定不要忘記。 -
RetentionPolicy.SOURCE
編譯器直接丟棄這種策略的注釋
我們可以看到在DbInfo.java
中就使用了這個(gè)注解。我們?cè)诜瓷湟粋€(gè)注解時(shí)如果使用的不是RetentionPolicy.RUNTIME
,那么我們是拿不到相關(guān)的注解信息的。
-
@Target
指定注解用于修飾類的哪個(gè)成員。@Target
包含了一個(gè)名為value,類型為ElementType
的成員變量。@Documented
用于指定被該元Annotation修飾的Annotation類將被javadoc
工具提取成文檔。@Inherited
被它修飾的Annotation將具有繼承性。如果某個(gè)類使用了被@Inherited
修飾的Annotation,則其子類將自動(dòng)具有該Annotation具有的注解。在DbInfo.java
中就使用了這個(gè)注解。
2.4.2 模擬注解的實(shí)現(xiàn)
通過注解注入一個(gè)對(duì)象,前面例子中已經(jīng)使用過,現(xiàn)在我們模擬其實(shí)現(xiàn)。而這一實(shí)現(xiàn)有兩種方式:
首先我們看看使用注解的相關(guān)類
InjectPerson.java
package cn.itcast.annotation2;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)//一定不要忘記
public @interface InjectPerson {
String name();
int age();
}
PersonDao.java
package cn.itcast.annotation2;//使用注解注入一個(gè)類有兩種方式
public class PersonDao {
@InjectPerson(name="老王",age=23) private Person person;//2
public Person getPerson() {
return person;
}
@InjectPerson(name="老張",age=23)//1
public void setPerson(Person person) {
this.person = person;
}
}
方式一:
test.java
package cn.itcast.annotation2;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws Exception {
//1.得到要注入的屬性,這里使用的是內(nèi)省
PropertyDescriptor pd = new PropertyDescriptor("person",PersonDao.class);
//name=person
//propertyType=cn.itcast.annotation2.Person
//readMethod=getPerson()
//writeMethod=setPerson()
//2.得到要注入的屬性需要的類型
Class clazz = pd.getPropertyType(); //Person
//3.創(chuàng)健屬性需要的對(duì)象,實(shí)例化一個(gè)對(duì)象
Object person = clazz.newInstance();
//4.得到屬性的寫方法writeMethod=setPerson()
Method setPerosn = pd.getWriteMethod();
//5.反射出方法上聲明的注解
InjectPerson inject = setPerosn.getAnnotation(InjectPerson.class);
//6.得到注解上聲明的信息,填充person對(duì)象
//得到注解類的方法name(),age(),注意:注解類中的方法也可以叫其屬性
Method[] methods = inject.getClass().getMethods();
for(Method m : methods){
String methodName = m.getName();//方法的名字name or age
try{
//通過屬性名得到Person類的屬性
Field f = Person.class.getDeclaredField(methodName);
Object value = m.invoke(inject, null); //得到注解上配置的屬性的值,即”老張”和23
f.setAccessible(true);
f.set(person, value);//對(duì)person實(shí)例對(duì)象使用value進(jìn)行填充
}catch (Exception e) {
continue;
}
}
//7.把填充了數(shù)據(jù)的person通過setPerson方法整到personDao對(duì)象上
PersonDao dao = new PersonDao();
setPerosn.invoke(dao, person);
System.out.println(dao.getPerson().getName());
}
}
說(shuō)明:以上就是模擬容器通過注解注入的過程,基本過程就是先得到要注入的屬性,這里是一個(gè)類,然后得到屬性的寫方法(即PersonDao.java
中),然后反射出其注解上的值,再使用這些值實(shí)例化相關(guān)對(duì)象,然后使用invoke方法將實(shí)例化的對(duì)象設(shè)置到PersonDao.java
上。當(dāng)然有時(shí)候注解是配置在屬性上而不是在方法上,那么下面我們看這種情況是如何實(shí)現(xiàn)的。
方式二:
Test2.java
package cn.itcast.annotation2;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Test2 {
public static void main(String[] args) throws Exception {
//1.得到需要注入的屬性cn.itcast.annotation2.PersonDao.person
Field f = PersonDao.class.getDeclaredField("person");
//2.得到屬性需要的類型cn.itcast.Person
Class clazz = f.getType();
//3.創(chuàng)建person
Person person = (Person) clazz.newInstance();
//4.反射屬性的注解,
InjectPerson inject = f.getAnnotation(InjectPerson.class);
//5.并用注解的信息填充person
//得到注解的方法name()和age()
Method ms [] = inject.getClass().getMethods();
for(Method m : ms){
String methodName = m.getName(); //name age 看person對(duì)象上有沒有注解與之對(duì)應(yīng)的屬性
try{
PropertyDescriptor pd = new PropertyDescriptor(methodName,Person.class);
Method set = pd.getWriteMethod(); //setName setAge
set.invoke(person, m.invoke(inject, null));//注解的方法是沒有參數(shù)的,所以為null
}catch (Exception e) {
continue;
}
}
//6.把person賦給dao
PersonDao dao = new PersonDao();
f.setAccessible(true); //person
f.set(dao, person);
System.out.println(dao.getPerson().getAge());
System.out.println(dao.getPerson().getName());
}
}
說(shuō)明:相關(guān)實(shí)現(xiàn)過程基本一樣。
三、動(dòng)態(tài)代理
3.1基本概念
其實(shí)前面我們已經(jīng)講過了,這里再詳細(xì)說(shuō)明一下。
-
java提供了一個(gè)proxy類,調(diào)用它的
newInstance
方法可以生成某個(gè)對(duì)象的代理對(duì)象,使用該方法生成代理對(duì)象時(shí),需要三個(gè)參數(shù):- 1.生成代理對(duì)象使用哪個(gè)類裝載器
- 2.生成哪個(gè)對(duì)象的代理對(duì)象,通過接口指定
- 3.生成的代理對(duì)象的方法里是干什么事情的,由開發(fā)人員進(jìn)行編寫handler接口的實(shí)現(xiàn)來(lái)指定。
-
初學(xué)者必須理解并記住:
- 1.proxy類負(fù)責(zé)創(chuàng)建代理對(duì)象時(shí),如果指定了handler(處理器),那么不管用戶調(diào)用代理對(duì)象的什么方法,該方法都是調(diào)用處理器的invoke方法。
- 2.由于invoke方法被調(diào)用需要三個(gè)參數(shù):代理對(duì)象、方法、方法的參數(shù),因此不管代理對(duì)象哪個(gè)方法調(diào)用處理器的invoke方法,都必須把自己所在的對(duì)象、自己(調(diào)用invoke方法的方法)、方法的參數(shù)傳遞進(jìn)來(lái)。
下面看一個(gè)簡(jiǎn)單的例子:
Person.java
package cn.itcast.proxy;
public interface Person {
String sing(String name);
String dance(String name);
}
Liyuchun.java
package cn.itcast.proxy;
public class Liyuchun implements Person {
public String sing(String name){
System.out.println("春哥唱" + name + "歌?。?);
return "飛吻??!";
}
public String dance(String name){
System.out.println("春哥跳" + name + "舞??!");
return "多謝多謝老板??!";
}
}
LiyuchunProxy.java
package cn.itcast.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class LiyuchunProxy {
private Person chunchun = new Liyuchun();
// Person person = LiyuechunProxy.getProxy();
// person.sing("山歌") person.dance();
public Person getProxy() {
return (Person) Proxy.newProxyInstance(
LiyuchunProxy.class.getClassLoader(), // 類裝載器
chunchun.getClass().getInterfaces(),// 代理的對(duì)象的接口
new InvocationHandler() {// 需要干什么事情
/**
* proxy : 把代理對(duì)象自己傳遞進(jìn)來(lái) method:把代理對(duì)象當(dāng)前調(diào)用的方法傳遞進(jìn)來(lái)
* args:把方法參數(shù)傳遞進(jìn)來(lái)
*/
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
// 編碼指定返回的代理對(duì)象干的工作
if (method.getName().equals("sing")) {
System.out.println("拿一萬(wàn)塊錢來(lái)??!");
// 調(diào)用相關(guān)方法,把類和參數(shù)傳遞進(jìn)去
return method.invoke(chunchun, args); // 找春哥唱歌
}
if (method.getName().equals("dance")) {
System.out.println("拿2萬(wàn)塊錢來(lái)!!");
return method.invoke(chunchun, args); // 找春哥跳舞
}
return null;
}
});
}
}
測(cè)試:Test.java
package cn.itcast.proxy;
public class Test {
public static void main(String[] args) {
LiyuchunProxy proxy = new LiyuchunProxy();//new一個(gè)代理對(duì)象
Person p = proxy.getProxy();//得到一個(gè)接口對(duì)象
/*String value = p.sing("我愛你");
System.out.println(value);*/
String value = p.dance("跳舞");//使用接口去調(diào)用相關(guān)的方法
System.out.println(value);
}
}
3.2 應(yīng)用
- 在動(dòng)態(tài)代理技術(shù)里,由于不管用戶調(diào)用代理對(duì)象的什么方法,都是調(diào)用開發(fā)人員編寫的處理器的invoke方法。
- 并且,開發(fā)人員通過invoke方法的參數(shù),還可以在攔截的同時(shí),知道用戶調(diào)用的是什么方法,因此利用這個(gè)兩個(gè)特性,就可以實(shí)現(xiàn)一些特殊的需求。例如:攔截用戶的訪問請(qǐng)求,以檢查用戶是否有訪問權(quán)限、動(dòng)態(tài)為某個(gè)對(duì)象添加額外的功能。同時(shí),這種攔截手段較之前的攔截方式更為精細(xì),因?yàn)樗窃诜椒?jí)別上的攔截。下面看幾個(gè)例子(這些例子我們?cè)谶^濾器中已經(jīng)講過,這里當(dāng)作復(fù)習(xí)):
例1:對(duì)整個(gè)web的亂碼過濾器CharacterEncodingFilter.java
public class CharacterEncodingFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
request.setCharacterEncoding("UTF-8"); //post
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
//request.getParamter() requestProxy.getParameter()
//我們不能直接將響應(yīng)的數(shù)據(jù)顯示在頁(yè)面,所以需要攔截Request
chain.doFilter((ServletRequest) Proxy.newProxyInstance
(CharacterEncodingFilter.class.getClassLoader(),
request.getClass().getInterfaces(),
new InvocationHandler(){
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//若不是getParameter方法,則不需要處理
if(!method.getName().equals("getParameter")){
return method.invoke(request, args);
}
//若不是get方式,則也不需要處理
if(!request.getMethod().equalsIgnoreCase("get")){
return method.invoke(request, args);
}
//是get方式下的getParameter方法,需要處理
String value = (String) method.invoke(request, args);
if(value==null){
return null;
}
return new String(value.getBytes("iso8859-1"),"UTF-8");
}
}), response);
}
例2:壓縮過濾器GzipFilter.java
public class GzipFilter implements Filter {
public void destroy() {}
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) resp;
final ByteArrayOutputStream bout = new ByteArrayOutputStream();
final PrintWriter pw = new PrintWriter(new OutputStreamWriter(bout,"UTF-8"));
//response.getWriter().write("aaa"); responseProxy
chain.doFilter(request, (ServletResponse)Proxy.newProxyInstance
(GzipFilter.class.getClassLoader(),
response.getClass().getInterfaces(),
new InvocationHandler(){
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if(method.getName().equals("getWriter")){
return pw;
}else if(method.getName().equals("getOutputStream")){
return new MyServletOutputStream(bout);
}else{
return method.invoke(response, args);
}
}
}));
pw.close();
byte result[] = bout.toByteArray(); //拿到目標(biāo)資源的輸出
System.out.println("原始大?。? + result.length);
ByteArrayOutputStream bout2 = new ByteArrayOutputStream();
GZIPOutputStream gout = new GZIPOutputStream(bout2);
gout.write(result);
gout.close();
byte gzip[] = bout2.toByteArray(); //拿到目標(biāo)資源輸出的壓縮數(shù)據(jù)
System.out.println("壓縮大小:" + gzip.length);
response.setHeader("content-encoding", "gzip");
response.setContentLength(gzip.length);
response.getOutputStream().write(gzip);
}
public void init(FilterConfig filterConfig) throws ServletException {}
}
class MyServletOutputStream extends ServletOutputStream{
private ByteArrayOutputStream bout = null;
public MyServletOutputStream(ByteArrayOutputStream bout){
this.bout = bout;
}
@Override
public void write(int b) throws IOException {
bout.write(b);
}
}
注意:要產(chǎn)生某個(gè)類的動(dòng)態(tài)代理對(duì)象,那此類必須有一個(gè)接口,這也叫aop編程;如果一個(gè)類沒有接口,那一般的方式是不能實(shí)現(xiàn)動(dòng)態(tài)代理的,但是我們使用開源框架cglib
則可以實(shí)現(xiàn)動(dòng)態(tài)代理。但是如果一個(gè)類是final類型,則這種方式也不能完成動(dòng)態(tài)代理,因?yàn)槠涫峭ㄟ^產(chǎn)生一個(gè)需要代理對(duì)象的子類來(lái)實(shí)現(xiàn)動(dòng)態(tài)代理的,所以如果是final類型的,顯然是不能實(shí)現(xiàn)的。那此時(shí)我們只能使用靜態(tài)代理(包裝設(shè)計(jì)模式)。
四、類加載器
- 類加載器負(fù)責(zé)將.class文件(可能在磁盤上,也可能在網(wǎng)絡(luò)上)加載到內(nèi)存中,并為之生成對(duì)應(yīng)的
java.class.Class
對(duì)象。 - 當(dāng)JVM啟動(dòng)時(shí),會(huì)形成由三個(gè)類加載器組成的初始類加載器層次結(jié)構(gòu):
其中BootStrap是對(duì)JDK中的核心類進(jìn)行加載的加載器,ExtClassLoader
是擴(kuò)展包中的類進(jìn)行加載的加載器,而AppClassLoader
是對(duì)我們自己寫的類進(jìn)行加載的加載器。
4.1 BootStrap
-
bootstrap classloader
是使用c語(yǔ)言實(shí)現(xiàn)的。引導(dǎo)(原始)類加載器,它負(fù)責(zé)加載java的核心類。這個(gè)加載器是非常特殊的,它實(shí)際上不是java.lang.ClassLoader
的子類,而是由JVM自身實(shí)現(xiàn)的。可以通過執(zhí)行以下代碼來(lái)獲得此加載器加載了哪些核心類庫(kù):
URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++) {
System.out.println(urls[i].toExternalForm());
}
- 因?yàn)镴VM在啟動(dòng)的時(shí)候就自動(dòng)加載它們,所以不需要在系統(tǒng)屬性
CLASSPATH
中指定這些類庫(kù)。
4.2 ExtClassLoader
-
extension classloader
擴(kuò)展類加載器,它負(fù)責(zé)加載JRE的擴(kuò)展目錄(JAVA_HOME/jre/lib/ext
或者由java.ext.dirs
系統(tǒng)屬性指定的)中的jar包。這為引入除java核心類以外的新功能提供了一個(gè)標(biāo)準(zhǔn)機(jī)制。因?yàn)槟J(rèn)的擴(kuò)展目錄對(duì)所有從同一個(gè)jre中啟動(dòng)的JVM都是通用的,所以放入這個(gè)目錄的jar類包對(duì)所有的JVM和system classloader
都是可見的。
4.3 AppClassLoader
system classloader
系統(tǒng)(也稱為應(yīng)用)類加載器,它負(fù)責(zé)在JVM被啟動(dòng)時(shí),加載來(lái)自在命令java中的classpath
或者java.class.path
系統(tǒng)屬性或者CLASSPATH
操作系統(tǒng)屬性所指定的jar類包和類路徑。可以通過靜態(tài)方法
ClassLoader.getSystemClassLoader()
找到該類的加載器。如果沒有特別指定,則用戶自定義的任何類加載器都將該類加載器作為它父加載器。
4.4 全盤負(fù)責(zé)委托機(jī)制
classloader
加載類用到是全盤負(fù)責(zé)委托機(jī)制。全盤負(fù)責(zé):即是當(dāng)一個(gè)
classloader
加載一個(gè)class的時(shí)候,這個(gè)class所依賴的和引用的其他class通常也由這個(gè)classloader
負(fù)責(zé)載入。委托機(jī)制:先讓父類加載器進(jìn)行加載,只有在父類找不到的時(shí)候才從自己的類路徑下尋找。
類加載還采用了cache機(jī)制:如果cache中保存了這個(gè)class就直接返回它,如果沒有才從文件中讀取和轉(zhuǎn)換成class,并存入cache,這就是為什么修改了class但是必須重新啟動(dòng)JVM才能生效,并且類只加載一次的原因。
如果不實(shí)用委托機(jī)制,有時(shí)候會(huì)出現(xiàn)
classCastException
異常,如:
Demo1 d1 = new Demo1();
因?yàn)橛锌赡芏xDemo1
這個(gè)屬性和實(shí)例化Demo1
這個(gè)類不是由同一個(gè)類加載器加載的,這樣就會(huì)出現(xiàn)轉(zhuǎn)換異常,比如tomcat中就沒有使用此機(jī)制。