30、Java基礎(chǔ)增強(qiáng)2(JavaEE筆記)

主要內(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):
類加載器.png

其中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ī)制。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 本文章涉及代碼已放到github上annotation-study 1.Annotation為何而來(lái) What:A...
    zlcook閱讀 29,311評(píng)論 15 116
  • 什么是注解(Annotation):Annotation(注解)就是Java提供了一種元程序中的元素關(guān)聯(lián)任何信息和...
    九尾喵的薛定諤閱讀 3,228評(píng)論 0 2
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,560評(píng)論 25 708
  • 上午八點(diǎn)鐘,我和孩子出門去往藝術(shù)學(xué)校學(xué)習(xí)古箏。最近一周放暑假,她沒有主動(dòng)練習(xí)古箏而我也疏于管她。路上簡(jiǎn)單地和她談?wù)?..
    愛閱沈陽(yáng)閱讀 219評(píng)論 0 0
  • 早晨起來(lái)就一直下雨,下了一上午,雨停了,也一直陰沉著天… 下午放學(xué)回來(lái),先讓女兒吃飯,做了她愛吃青椒土豆...
    倆千金的媽閱讀 266評(píng)論 0 0