黑马程序员_高新技术(下)

来源:互联网 发布:js报错缺少对象解决 编辑:程序博客网 时间:2024/05/14 12:59

-------android培训java培训、期待与您交流!-------

高新技术(下总结)

1、对hashCode()与equal()的总结

(1)定义

A、equals()反映的是对象或变量具体的值,即两个对象里面包含的值--可能是对象的引用,也可能是值类型的值。

B、hashCode()是对象或变量通过哈希算法计算出的哈希值。

(2)关系

A、equals()和hashcode()这两个方法都是从object类中继承过来的。

B、equals()是对两个对象的地址值进行的比较(即比较引用是否相同)。

C、hashCode()是一个本地方法,它的实现是根据本地机器相关的。

(3)哈希算法的核心思想

哈希算法是把一块内存分为若干个区域,每个区域用哈希码(hashCode()计算的值)标识,然后在每个区域中存储对应的对象的具体值。

(4)具体使用

util包中的带hash的集合类都是用这种存储结构:

HashMap,HashSet,hashTable他们在将对象存储时,首先需要确定区域地址,而HashCode()就是这个作用,一般都需要重载hashCode()方法。找到存储区域之后,就调用equals()方法来比较对象里面的值是否相等。

(5)用HashSet举例说明

例题1:

package com.my.day01;public class Person3{private int x;private int y;@Overridepublic int hashCode(){final int prime = 31;int result = 1;result = prime * result + x;result = prime * result + y;return result;}@Overridepublic boolean equals(Object obj){if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;final Person3 other = (Person3) obj;if (x != other.x)return false;if (y != other.y)return false;return true;}public Person3(int x, int y){super();this.x = x;this.y = y;}public int getX(){return x;}public void setX(int x){this.x = x;}public int getY(){return y;}public void setY(int y){this.y = y;}}package com.my.day01;import java.util.HashSet;public class TestPerson3{public static void main(String[] args){HashSet<Person3> hs=new HashSet<Person3>();hs.add(new Person3(2,3));hs.add(new Person3(2,3));hs.add(new Person3(2,3));System.out.println(hs.size());}}

2、注意:

(1)通过被存入对象的hashCode() 来确定对象在HashSet中的存储地址,通过equals()来确定存入的对象是否重复,hashCode(),equals()都需要自己重新定义,如果没有重新定义equals()Object类中equals()方法比较的两个对象的引用,那么同一个类产生的两个内容完全相同的对象都可以存入Set,上题打印的结果为4,因为他们是通过equals()来确定的,这样就使得HashSet失去了他的意义,所以我们要复写equals(),复写之后输出结果为1。

(2)补充知识

A、通常来说,一个类的两个实例对象用equals()方法比较的结果相等时,它们的哈希码也必须相等,但么之则不成立即equals方法比较结果不相等的对象可以有相同的哈希码,或者说哈希码相同的两个对象的equals方法比较的结果可以不相等但它们的hashCode方法返回值却相等

B、当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,从而造成内存泄露。

3、框架要解决的核心问题

(1)我在写框架(房子)时,你这个用户可能还在上小学,还不会写程序呢?我写的框架程序怎样能调用到你以后写的类(门窗)呢?

(2)因为在写才程序时无法知道要被调用的类名,所以,在程序中无法直接new 某个类的实例对象了,而要用反射方式来做

4、框架与工具类的区别

框架就是别人调用的我们的类,工具类就是我们调用别人的类。

(1)用类加载器的方式管理资源和配置文件

getRealPath()//得到某个软件的绝对位置/内部位置

InputStream  ips =ReflectTest2.class.getClassLoader().getResourceAsStream

("cn/itcast/day1/config.properties");

//从根目录下开始加载,相对于工程根目录注意:不能够在cn前面加/,自动把src目录下该配置文件放到classpath执行目录下。

InputStream  ips  =  ReflectTest2.class.getResourceAsStream

("resources/config.properties");//相对于当前类所在的包,自动调用getClassLoader()

InputStream ips= ReflectTest2.class.getResourceAsStream

("/cn/itcast/day1/resources/config.properties");//相对于工程的绝对路径

 

二、由内省引出JavaBean的讲解

1、IntroSpector-->JavaBean-->特殊的Java类如下:

class  person{

private  int x;

public Person(int x){

this.x=x}

public void set(int x)

{this.x=x;}

public int getAge()

{return x;}}

2、内省的方式操作javabean

(1)通过内省的方式设置javaBean的属性值

/** *  * 通过内省的方式设置javaBean的属性值 * @param p1 * @param propertyName * @throws IntrospectionException * @throws IllegalAccessException * @throws InvocationTargetException */private static void setProperties(Person p1, String propertyName) throws IntrospectionException, IllegalAccessException, InvocationTargetException{PropertyDescriptor pd = new PropertyDescriptor(propertyName, p1.getClass());Method m = pd.getWriteMethod();m.invoke(p1, 55);System.out.println(p1.getAge());}

(2)通过内省的方式获取javaBean的属性值
/** * 通过内省的方式获取javaBean的属性值 * @param p1 * @param propertyName * @throws IntrospectionException * @throws IllegalAccessException * @throws InvocationTargetException */private static void getProperties(Person p1, String propertyName) throws IntrospectionException, IllegalAccessException, InvocationTargetException{PropertyDescriptor pd = new PropertyDescriptor(propertyName, p1.getClass());Method personMethod = pd.getReadMethod();Object object = personMethod.invoke(p1);System.out.println(object);}

3、对Javabean专门操作的工具类beanUtils工具包。

(1)、先把工具包导入,该工具包有阿帕奇提供的。commons-beanUtils

-core.jar

举例:

//先通过调用普通java类的方法的方式获得结果,然后在这之前插入BeanUtil的get和set操作,见下面的代码。

             //System.out.println(pt1.getY());

             BeanUtils.setProperty(p11,properyName, 4442);// BeanUtils自动帮我们吧String类型转换成为int数据类型。

        System.out.println(BeanUtils.getProperty(pt1,"y"));

             BeanUtils.setProperty(pt1,"y", "99");

             System.out.println(pt1.getY());

             PropertyUtils.setProperty(pt1,"y", 999);

             System.out.println(PropertyUtils.getProperty(pt1,"y").getClass().getName())

4、BeanUtils/propertyUtils

区别:BeanUtils是以字符串形式对属性进行操作。

propertyUtils是以属性本身的类型进行操作。

5、java7的新技术:

把map转换为JavaBean

Mapmap=(name:"zxxx",age:18);

三、java中的注释

1、先通过@SuppressWarnings的应用让大家认识和了解一下注解:

通过System.runFinalizersOnExit(true);的编译警告引出@SuppressWarnings ("deprecation")

@Deprecated

直接在刚才的类中增加一个方法,并加上@Deprecated标注,在另外一个类中调用这个方法。

@Override

public booleanequals(Reflect other)方法与HashSet结合讲解

总结:

注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则等于没有某种标记,以后,javac编译器,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,看你有什么标记,就去干相应的事。标记可以加在包,类,字段,方法,方法的参数以及局部变量上。了解java提供的几个基本注解

 

2、定义类名、属性名、变量名、方法名的规则

(类名、属性名、变量名)=形容词+名词

(方法名)=动词或者动词+名词

3、注解类应用结构图


具体应用举例:

(1)新建一个MetaAnnotation注释类


package com.my.day02;public @interface MetaAnnotation{String value();}

(2)再新建一个ItcastAnnotion注释类里面调用(1)实现的注释类


package com.my.day02;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;@Retention(RetentionPolicy.RUNTIME)//保留扣留到运行时期/*@Target({ ElementType.METHOD, ElementType.TYPE })*/public @interface ItcastAnnotion{String color() default "bulue";String value();int [] arryAtt () default {1,2,3};MetaAnnotation annotationAttr() default @MetaAnnotation("lhm");}

(3)新建一个注释类的测试类AnnotationTest

package com.my.day02;@ItcastAnnotion(annotationAttr = @MetaAnnotation("flx"), color = "red", value = "233", arryAtt ={ 234, 4, 2 })public class AnnotationTest{@SuppressWarnings("deprecation")public static void main(String[] args){System.runFinalizersOnExit(true);if (AnnotationTest.class.isAnnotationPresent(ItcastAnnotion.class)){ItcastAnnotion itcastAnnotion = (ItcastAnnotion) AnnotationTest.class.getAnnotation(ItcastAnnotion.class);System.out.println(itcastAnnotion.color());System.out.println(itcastAnnotion.value());System.out.println(itcastAnnotion.arryAtt());System.out.println(itcastAnnotion.annotationAttr().value());}}@Deprecatedpublic static void sayHello(){System.out.println("你好!!");}}

(4)对上的实例进行解释

元信息

元注解

元数据

根据发射测试的问题,引出@Retention元注解的讲解,其三种取值:RetetionPolicy.SOURCE、RetetionPolicy.CLASS、RetetionPolicy.RUNTIME;分别对应:

java源文件-->class文件-->内存中的字节码。

 

4、每个注释对应的阶段:

@Override(源文件)、@SuppressWarnings(class文件)和@Deprecated(内存中的字节码)

5、几个常用的注释

(1)注释类型 Deprecated

用 @Deprecated注释的程序元素,不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。在使用不被赞成的程序元素或在不被赞成的代码中执行重写时,编译器会发出警告。

@suppressWarnings(“deprecation”)

(2)注释类型 Override

表示一个方法声明打算重写超类中的另一个方法声明。如果方法利用此注释类型进行注解但没有重写超类方法,则编译器会生成一条错误消息。

(3)注释类型 SuppressWarnings

指示应该在注释元素(以及包含在该注释元素中的所有程序元素)中取消显示指定的编译器警告。注意,在给定元素中取消显示的警告集是所有包含元素中取消显示的警告的超集。例如,如果注释一个类来取消显示某个警告,同时注释一个方法来取消显示另一个警告,那么将在此方法中同时取消显示这两个警告

四、泛型

泛型也是JDK1.5中的一个很重要的特性。

1、泛型的概述

没有使用泛型时,只要是对象,不管是什么类型的对象,都可以存储进同一个集合中。使用泛型集合,可以将一个集合中的元素限定为一个特定类型,集合中只能存储一个类型的对象,这样更安全并且从集合获取一个对象时,编辑器也可以指定这个对象类型,不需要对对象进行强制类型转换,这样方便。

2、使用细节讲解

(1)在JDK 1.5中,你还可以按原来的方式将各种不同类型的数据装到一个集合中,但编译器会报告unchecked警告。引入泛型以后,前面讲解反射的代码就可以改写成如下形式了,这种情况下创建实例对象时不需要类型转换:

Class<String>clazzString1 = String.class;

Constructor<String>constructor1 = clazzString1.getConstructor(StringBuffer.class);//这里先故意把<String>改成<Object>,看到编译器报错

String str3 =constructor1.newInstance(/*"abc"*/newStringBuffer("abc"));

String str4 =clazzString1.newInstance();

(2)我是怎么知道什么情况下可以用泛型的啊?

这看类的定义,只有类被定义成了泛型,才可以对其进行参数化应用。

(3)去类型化

用下面的代码查看getClass()方法返回的结果已经去掉了“类型”信息。

System.out.println(newArrayList<Integer>().getClass().getName());

System.out.println(newArrayList<String>().getClass().getName());

泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时会去除掉“类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用其add方法即可。

(4)代码讲解总结

下面程序的main方法中的第二行代码和注释中的两行代码表达的意思完全相同,注释中的两行代码不能通过编译,而第二行(采用方法调用链)却可以顺利通过编译。

public classTest

{

    public void func()

    {

         System.out.println("func");

   }

 

   public static void main(String args[])throws Exception

   {

           Object obj = new Test();

           //下面这行可以成功编译 

          ((Test)obj).getClass().newInstance().func();

           //下面这两行无法通过编译

           /*Class c = ((Test)obj).getClass();

           c.newInstance().func(); */

  

 }

}

因为Generic, 编译器可以在编译期获得类型信息所以可以编译这类代码。你将下面那两行改成

Class<?extends Test> c = ((Test)obj).getClass();

c.newInstance().func();

应该就能通过编译了。

在JDK 1.5中引入范型后,Object.getClass()方法的定义如下:

public finalClass<? extends Object> getClass()

这说明((Test)obj).getClass()语句返回的对象类型为Class<? extends Test>,而Class<T>的newInstance()方法的定义如下:

public TnewInstance() throws InstantiationException,IllegalAccessException

即对于编译器看来,Class<Test>的newInstance()方法的对象类型为Test,而((Test)obj).getClass()返回的为对象类型为Class<?extends Test>,所以,编译器认为((Test)obj).getClass().newInstance()返回的对象类型为Test。

下面这两行代码之所以无法通过编译

           Class c = ((Test)obj).getClass();

           c.newInstance().func();

是因为((Test)obj).getClass()返回的为对象类型为Class<? extends Test>,但是我们在第一行将结果强制转换成了Class,然后再去调用Class的newInstance方法,而不是去调用Class<Test>的newInstance方法,编译器当然不再认为Class的newInstance方法返回的对象为Test了。

 

 

3、了解泛型

(1)ArrayList(E)类定义和ArrayList<Integer>类引用中涉及如下术语:

整个称为ArrayList<E>泛型类型

ArrayList<E>中的E称为类型变量或类型参数

整个ArrayList<Integer>称为参数化的类型

ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数

ArrayList<Integer>中的<>念着typeof

ArrayList称为原始类型

(2参数化类型与原始类型的兼容性:

参数化类型可以引用一个原始类型的对象,编译报告警告,例如,

Collection<String>c = new Vector();//可不可以,不就是编译器一句话的事吗?

原始类型可以引用一个参数化类型的对象,编译报告警告,例如,

Collection c =new Vector<String>();//原来的方法接受一个集合参数,新的类型也要能传进去)

(3)参数化类型不考虑类型参数的继承关系:

Vector<String>v = new Vector<Object>(); //错误!

//不写<Object>没错,写了就是明知故犯

Vector<Object>v = new Vector<String>(); //也错误!

(4)编译器不允许创建泛型变量的数组。

即在创建数组实例时,数组的元素不能使用参数化的类型,例如,下面语句有错误:

     Vector<Integer>vectorList[] = new Vector<Integer>[10];

4、泛型的?通配符

(1)定义一个方法,该方法用于打印出任意参数化类型的集合中的所有数据,该方法如何定义呢?

错误方式:

public staticvoid printCollection(Collection<Object> cols) {

             for(Object obj:cols) {

                    System.out.println(obj);

             }

             /* cols.add("string");//没错

              cols = new HashSet<Date>();//会报告错误!*/

}

正确方式:

public staticvoid printCollection(Collection<?> cols) {

             for(Object obj:cols) {

                    System.out.println(obj);

             }

             //cols.add("string");//错误,因为它不知自己未来匹配就一定是String

             cols.size();//没错,此方法与类型参数没有关系

              cols = new HashSet<Date>();

(2)总结:

使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。

自定义类型不可以给<?>

(3)泛型中的?通配符的扩展

限定通配符的上边界:

正确:Vector<?extends Number> x = new Vector<Integer>();

错误:Vector<?extends Number> x = new Vector<String>();

限定通配符的下边界:

正确:Vector<?super Integer> x = new Vector<Number>();

错误:Vector<?super Integer> x = new Vector<Byte>();

提示:

限定通配符总是包括自己。

?只能用作引用,不能用它去给其他变量赋值

      Vector<? extends Number> y = newVector<Integer>();

      Vector<Number> x = y;

      上面的代码错误,原理与Vector<Object> x11 = new Vector<String>();相似,

      只能通过强制类型转换方式来赋值。

5、定义泛型方法

(1)Java的泛型方法没有C++模板函数功能强大,java中的如下代码无法通过编译:

<T> Tadd(T x,T y) {

             return (T) (x+y);

             //return null;

      }

用于放置泛型的类型参数的尖括号应出现在方法的其他所有修饰符之后和在方法的返回类型之前,也就是紧邻返回值之前。按照惯例,类型参数通常用单个大写字母表示。

(2)交换数组中的两个元素的位置的泛型方法语法定义如下:

static <E>void swap(E[] a, int i, int j) {

      E t = a[i];

      a[i] = a[j];

      a[j] = t;

}//或用一个面试题讲:把一个数组中的元素的顺序颠倒一下

只有引用类型才能作为泛型方法的实际参数,swap(new int[3],3,5);语句会报告编译错误。

(3)除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符,例如,Class.getAnnotation()方法的定义。并且可以用&来指定多个边界,如<V extends Serializable & cloneable> void method(){}

(4)普通方法、构造方法和静态方法中都可以使用泛型。

也可以用类型变量表示异常,称为参数化的异常,可以用于方法的throws列表中,但是不能用于catch子句中。

(5)在泛型中可以同时有多个类型参数,在定义它们的尖括号中用逗号分,例如:

public static<K,V> V getValue(K key) { return map.get(key);}

6、类型参数的推断

编译器判断范型方法的实际类型参数的过程称为类型推断,类型推断是相对于知觉推断的,其实现方法是一种非常复杂的过程。

根据调用泛型方法时实际传递的参数类型或返回值的类型来推断,具体规则如下:

(1)当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如:

       swap(new String[3],3,4)      static <E> void swap(E[] a, int i, int j)

(2)当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应同一种类型来确定,这很容易凭着感觉推断出来,例如:

       add(3,5)   static <T> T add(T a, T b)

(3)当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,且没有使用返回值,这时候取多个参数中的最大交集类型,例如,下面语句实际对应的类型就是Number了,编译没问题,只是运行时出问题:

       fill(new Integer[3],3.5f)    static <T> void fill(T[] a, T v)

(4)当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,并且使用返回值,这时候优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改为float,对比eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误:

       intx =(3,3.5f)    static <T> T add(Ta, T b)

(5)参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没有问题,而第二种情况则根据参数化的Vector类实例将类型变量直接确定为String类型,编译将出现问题:

      copy(new Integer[5],new String[5]) static <T> void copy(T[] a,T[]  b);

      copy(new Vector<String>(), newInteger[5])  static <T> void copy(Collection<T> a , T[] b);

7、泛型的综合应用

(1)通过反射获取泛型的类型参数的类型

//通过反射获取泛型中实际的参数类型

Method method = GnnericTest.class.getMethod("appliyVector", Vector.class);Type[] types = method.getGenericParameterTypes();ParameterizedType pType = (ParameterizedType)types[0];System.out.println(pType.getRawType());//获取原始类型System.out.println(pType.getActualTypeArguments()[0]);//获得实际参数类型,指的是泛型里面的实际类型public static void appliyVector(Vector<Date> date){//测试函数}

(2)泛型的DAO应用

package com.my.day02;import java.util.Set;/** * data accese object -->crud 数据访问对象 */public class GenericDAO<E>{/** * 1 增加一个对象 */public void add(E e){}/** * 2 按ID号查找一个对象 *  * @param id * @return */public E finaByID(int id){return null;}/** * 3 删除一个对象 *  * @param e */public void delete(E e){}/** * 4 根据对象的id删除一个对象 *  * @param e */public void deleteByID(int id){}/** * 5 更新一个对象 *  * @param e */public void update(E e){}/** * 6 根据传入的值查找一个集合 *  * @param where * @return */public Set<E> findContains(String where){return null;}/** * 根据用户名查找对象 *  * @param userName * @return */public E findByUserName(String userName){return null;}}

五、类加载器

1、ava虚拟机中该类加载器的关系

BootStrap-->ExtClassLoader-->AppClassLoader。从父到子的顺序排列。关系如下图


2、说明不同位置的类有不同的类加载器加载

(1)逐一编写如下代码来说明放置在不同位置的类确实由不同的类加载器加载的:

      System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());

       //将上面语句的测试类改为System则抛NullPointerException,这两个类存放位置不同

      System.out.println(System.class.getClassLoader().getClass().getName());

      改为System.out.println(System.class.getClassLoader());打印的结果为null。

(2)用下面的代码让查看类加载器的层次结构关系

             ClassLoader loader =ClassLoaderTest.class.getClassLoader();

             //打印出当前的类装载器,及该类装载器的各级父类装载器

             while(loader != null)

             {

                    System.out.println(loader.getClass().getName());

                    loader = loader.getParent();

             }

 

 

3、类加载器及其委托机制的深入分析

(1)当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?

首先当前线程的类加载器去加载线程中的第一个类。

如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。

还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。

(2)每个类加载器加载类时,又先委托给其上级类加载器。

当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?

对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext目录下的itcast.jar包中后,运行结果为ExtClassLoader的原因。

委托机制类加载器先从父类开始查找Class字节码,然后逐级向下。防止内存出现多份字节码。

4、自定义类加载器

(1) 自定义一个类加载器: 思路:

A、制作一个加密器

   1,从调用类的main方法上面接收收入流的路径(指的是编译好的二进制数据),还有一个输出流的路径(指的是加密二进制数据之后存储的文件路径)

   2,定义一个输入流与输出流,然后把接收进来的两个路径字符分别传入他们的构造方法,给他们初始化

   3,定义一个cypher()用于加密的函数,接收输入流与输出流作为他们的形参,加密算法:把输入流的二进制数据,经过异或0xff然后存储输出流对应的文件中。

   B、具体实现类加载器步骤 1、让MyClassLoader去继承ClassLoader类

   2、定义一个构造方法,实现把要被解码的ClassLoaderAttachment.class的目录的名字作为参数。

   3、复写findClass()实现解密。工作如下:

 String

    classFileName=classDir+"\\"+name+".class";FileInputStream fis=new

    FileInputStream(classFileName);ByteArrayOutPutStream bos=new

    *ByteArrayOutPutStream(); cypher(fis,bos);fis.close; Byte []

 bytes=bos.toByteArray(); returnfindClass(bytes,0,bytes.length)

实现代码:略

 

5、类加载器的高级问题分析

(1)编写一个能打印出自己的类加载器名称和当前类加载器的父子结构关系链的MyServlet,正常发布后,看到打印结果为

org.apache.catalina.loader.WebappClassLoader

org.apache.catalina.loader.StandardClassLoader

sun.misc.Launcher$AppClassLoader

sun.misc.Launcher$ExtClassLoader

(2)把MyServlet.class文件打jar包,放到ext目录中,重启tomcat,发现找不到HttpServlet的错误。

原因是:MyServlet.class由引用到了servlet.jar。解决方案如下:

把servlet.jar也放到ext目录中,问题解决了,打印的结果是ExtclassLoader 。

父级类加载器加载的类无法引用只能被子级类加载器加载的类,原理如下图:



(3)思考在这两种情况下HttpServlet分别由谁加载?

第一种情况:由WebApplassLoader加载器加载

第二种情况:由ExtClassLoader加载器加载

六、代理类与AOP的应用

1、程序中的代理

要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等,你准备如何做?

编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。(参看下页的原理图)

如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。

2、代理的框架图


3、AOP

(1)概述

交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的。使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。

4、动态代理技术

要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!写成百上千个代理类,是不是太累!

(1)JVM可以在运行期动态生成出类的字节码,这种动态生成类往往被用作代理类,及动态代理类。

(2)JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类

只鞥用作具有相同接口的目标类的代理。

(3)CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,

所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。

(4)代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标的返回接口外,还可以在代理方法中的如下四个位置加上系统功能代码:

A、在调用目标方法之前

B、在调用目标方法之后

C、在调用目标方法前后

D、在处理目标方法异常的catch快中。

5、创建动态类及查看其方法列表信息

A、分析JVM动态生成的类

(1)创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass()方法的各个参数。

(2)编码列出动态类中的所有构造方法和参数签名(参数列表)

(3)编码列出动态类中的所有方法和参数签名(参数列表)

(4)创建动态类的实例对象

a、用反射获取构造方法

b、编写一个最简单的invocationHandler类

c、打印创建的对象和调用对象的没有返回值的方法和getClass方法,演示调用其他有返回值的方法报告了异常。

d、将创建动态类的实例而对象的代理改成匿名内部类的形式编写。

5、实现AOP功能的封装与配置

(1)动态代理的工作原理图


(2)工厂类BeanFactory负责创建目标类或代理类实例对象,并通过配置文件实例切换。其getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则,返回该类实例对象的getProxy方法返回对象。

BeanFactory的构造方法接收代表配置文件的输入流对象,配置文件格式如下:

      #xxx=java.util.ArrayList

      xxx=cn.itcast.ProxyFactoryBean

      xxx.target=java.util.ArrayList

      xxx.advice=cn.itcast.MyAdvice

ProxyFacotryBean充当封装生成动态代理的工厂,需要为工厂类提供哪些配置参数信息?

目标

通知

编写客户端应用:

编写实现Advice接口的类和在配置文件中进行配置

调用BeanFactory获取对象

 


-------android培训java培训、期待与您交流!-------

原创粉丝点击