18【枚举、类加载器、动态代理】

一、枚举(Enmu)

1.1 枚举概述

枚举(enum),全称enumeration是JDK 1.5 中引入的新特性。Java 枚举是一个特殊的类,一般表示一组常量,比如一年的 4 个季节,一个年的 12 个月份,一个星期的 7 天,方向有东南西北等。

在JDK1.5 之前,我们定义常量都是: public static fianl。有了枚举之后,可以把相关的常量分组到一个枚举类型里,而且枚举提供了比常量更多的方法。

1.2  定义枚举类型

1.2.1 静态常量案例

我们使用静态常量来设置一个季节类:

package com.dfbz.demo01_静态常量;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Season {
    public static final Integer SPRING = 1;
    public static final Integer SUMMER = 2;
    public static final Integer AUTUMN = 3;
    public static final Integer WINTER = 4;
}

1/2/3/4分别代表不同的含义

测试类:

package com.dfbz.demo01_静态常量;

import org.junit.Test;

import java.util.Calendar;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01 {
    @Test
    public void test1() {
        method(Season.SUMMER);
        method(Season.WINTER);
        method(Season.SPRING);
    }


    public void method(Integer season) {

        switch (season) {
            case 1:
                System.out.println("春天!");          // 具体的逻辑
                break;
            case 2:
                System.out.println("夏天!");           // 具体的逻辑
                break;
            case 3:
                System.out.println("秋天!");          // 具体的逻辑
                break;
            case 4:
                System.out.println("冬天!");           // 具体的逻辑
                break;
        }
    }
}

1.2.2 枚举案例

Java 枚举类使用enum关键字来定义,各个常量使用逗号来分割。

package com.dfbz.demo02;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public enum Season {
    SPRING,SUMMER,AUTUMN,WINTER
}

Tips:定义枚举类的关键字是enum,而不是Enum,在Java中所有关键字都是小写的

其中SPRINGSUMMERAUTUMNWINTER都是枚举项它们都是本类的实例,本类一共就只有四个实例对象。并且只能通过这四个关键字获取Season类的实例对象,不能使用new来创建枚举类的对象

package com.dfbz.demo02_枚举的使用;

import org.junit.Test;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_枚举的使用 {
    @Test
    public void test1() {

        // 不能通过new来创建枚举实例
//        Season season = new Season()''

        // 通过枚举项来获取枚举实例
        Season spring = Season.SPRING;
        Season spring2 = Season.SPRING;

        // 通过相同的枚举项获取的枚举实例是一样的
        System.out.println(spring == spring2);          // true

        // 不同的枚举项获取的枚举实例是不一样的
        Season autumn = Season.AUTUMN;
        System.out.println(spring == autumn);           // false
    }
}

1.2.3 枚举与switch

使用枚举,能让我们的代码可读性更强。

package com.dfbz.demo02_枚举的使用;

import org.junit.Test;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_枚举与switch {
    @Test
    public void test()throws Exception{
        // 枚举不能通过new来创建
//        Season season = new Season();

        // 通过枚举项获取枚举实例
        Season season = Season.WINTER;

        switch (season){
            case SPRING:
                System.out.println("日出江花红胜火,春来江水绿如蓝");
                break;
            case SUMMER:
                System.out.println("小荷才露尖尖角,早有蜻蜓立上头");
                break;
            case AUTUMN:
                System.out.println("停车坐爱枫林晚,霜叶红于二月花");
                break;
            case WINTER:
                System.out.println("忽如一夜春风来,千树万树梨花开");
                break;
            default:
                System.out.println("错误的季节");
        }
    }
}

1.3 枚举的用法

1.3.1 枚举类的成员

枚举类和正常的类一样,可以有成员变量,成员方法,静态方法、静态成员变量等等

  • 定义枚举类:
package com.dfbz.demo02_枚举的使用;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public enum Lake {

    // 定义三个枚举项
    PoYangHu, DongTingHu, TaiHu, HongZeHu, ChaoHu;

    public static String LAKE_INFO = "我描述的是中国五大淡水湖";       // 静态成员变量

    private String name;            // 普通成员变量

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public static void method() {
        System.out.println("鄱阳湖、洞庭湖、太湖、洪泽湖、巢湖");
    }
}

Tips:当枚举项后面有其他成员(构造方法、成员变量、成员方法)时,最后一个枚举项必须加分号,并且枚举项需要写在第一行;

  • 测试类:
package com.dfbz.demo02_枚举的使用;

import org.junit.Test;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo03_枚举的成员 {

    @Test
    public void test() throws Exception {
        // 访问静态成员变量
        System.out.println(Lake.LAKE_INFO);

        // 访问静态成员方法
        Lake.method();

        // 通过枚举项获取实例
        Lake red = Lake.PoYangHu;

        // 通过枚举实例调用成员方法
        red.setName("鄱阳湖");

        // 通过枚举实例调用成员方法
        System.out.println(red.getName());            // 鄱阳湖
    }
}

1.3.2 枚举类的构造方法

1) 枚举的无参构造方法

枚举类也可以有构造方法,构造方法默认都是private修饰,而且只能是private。因为枚举类的实例不能让外界来创建!

默认情况下,所有的枚举项的创建都是调用枚举类的无参构造方法,并且在获取任何一个枚举实例时,其他实例都将会被创建

package com.dfbz.demo02_枚举的使用;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public enum River {
    // 在枚举常量后面还有其他成员时,分号是必须的。枚举常量必须在枚举类中所有成员的上方声明
    ChangJiang, HuangHou, ZhuJiang;

    // 枚举类的构造方法都是private修饰的,可写可不写
    River() {
        System.out.println("River实例创建了...");
    }
}

Tips:

  • 1:当枚举项后面有其他成员(构造方法、成员变量、成员方法)时,最后一个枚举项必须加分号;
  • 2:所有的枚举类的构造方法都是私有的(private关键字可加可不加)

测试类:

package com.dfbz.demo02_枚举的使用;

import org.junit.Test;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo04_枚举的无参构造方法 {

    @Test
    public void test() throws Exception {
        // 运行代码将会创建3个枚举实例
        River river = River.ChangJiang;
    }
}

运行结果:

Tips:一旦创建了枚举实例,便会初始化里面的所有枚举项;创建枚举项就等同于调用本类的无参构造器,所以FRONT、BEHIND、LEFT、RIGHT四个枚举项等同于调用了四次无参构造器

2) 枚举的有参构造方法

枚举项就是枚举类的实例,在创建定义枚举项时其实就是创建枚举类的实例,因此在定义枚举项就要传递实际的参数

  • 定义枚举项:
package com.dfbz.demo02_枚举的使用;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public enum Mountain {
    LuShan("庐山", "江西"), HuangShan("黄山"), YanDangShan;

    private String name;
    private String area;

    Mountain(String name, String area) {
        this.name = name;
        this.area = area;
        System.out.println("【name=" + name + ",area=" + area + "】两个参数构造方法....");
    }

    Mountain(String name) {
        this.name = name;
        System.out.println("【name=" + name + ",area=" + area + "】一个参数构造方法...");
    }

    Mountain() {
        System.out.println("【name=" + name + ",area=" + area + "】空参构造方法....");
    }

    // 普通成员方法
    public void show() {
        System.out.println("我是【" + name + "】,我在【" + area + "】");
    }

    // get/set方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getArea() {
        return area;
    }

    public void setArea(String area) {
        this.area = area;
    }
}
  • 测试类:
package com.dfbz.demo02_枚举的使用;

import org.junit.Test;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo05_枚举的有参构造方法 {
    @Test
    public void test() throws Exception {

        // 获取第一个枚举实例时,将会创建所有枚举实例
        Mountain lushan = Mountain.LuShan;
        lushan.show();

        Mountain huangShan = Mountain.HuangShan;
        huangShan.setArea("安徽");
        huangShan.show();

        Mountain yanDangShan = Mountain.YanDangShan;
        yanDangShan.setName("雁荡山");
        yanDangShan.setArea("浙江");
        yanDangShan.show();
    }
}
  • 运行效果如下:

1.3.3 枚举中的抽象方法

枚举类中可以包含抽象方法,但是在定义枚举项时必须重写该枚举类中的所有抽象方法;

我们前面说过,每一个枚举项其实都是枚举类的实例对象,因此如果当前枚举类包含抽象方法时,在定义枚举项时就需要重写此枚举类的所有抽象方法,这跟我们以前使用的匿名内部类很相似;

  • 首先定义一个抽象类(包含了抽象方法):
package com.dfbz.demo03_枚举的抽象方法;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public abstract class SeasonClass {
    public abstract void fun();
}
  • 实例化这个抽象类的时候,我们必须抽象其所有的抽象方法:
package com.dfbz.demo03_枚举的抽象方法;

import org.junit.Test;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_枚举的抽象方法 {

    /**
     * 抽象类
     *
     * @throws Exception
     */
    @Test
    public void test1() {

        SeasonClass winterSeason = new SeasonClass() {
            @Override
            public void fun() {
                System.out.println("冬天!");
            }
        };

        winterSeason.fun();

        SeasonClass summerSeason = new SeasonClass() {
            @Override
            public void fun() {
                System.out.println("夏天!");
            }
        };

        summerSeason.fun();
    }
}

当枚举类中含有抽象方法的时候,定义枚举项时,必须重写该枚举类中所有的抽象方法,像下面这种就是一种错误的定义:

package com.dfbz.demo03_枚举的抽象方法;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public enum Season {

    SPRING;         // 编译报错,定义枚举项时必须重写枚举类包含的所有抽象方法

    public abstract void fun();
}
  • 正确写法:
package com.dfbz.demo03_枚举的抽象方法;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public enum SeasonEnum {

    SPRING() {
        @Override
        public void fun() {
            System.out.println("我是春天哦!.....");
        }
    },

    WINTER() {
        @Override
        public void fun() {
            System.out.println("我是冬天哦!");
        }

        // 也可以重写其他方法
        @Override
        public void show() {
            System.out.println("我喜欢冬天,因为冬天可以打雪仗....");
        }
    };

    public void show() {
        System.out.println("我是普通方法");
    }

    public abstract void fun();
}
  • 测试类:
/**
 * 带有抽象方法的枚举
 */
@Test
public void test2() {
    SeasonEnum spring = SeasonEnum.SPRING;
    spring.fun();

    System.out.println("---------------");

    SeasonEnum winter = SeasonEnum.WINTER;
    winter.fun();
    winter.show();
}

1.4 Enum 类

1.4.1 Enum类中的方法

Java中,所有的枚举类都默认继承与java.lang.Enum类,这说明Enum中的方法所有枚举类都拥有。另外Enum也继承与Object,因此所有的枚举类都拥有与Object类一样的方法;

Enum类中的方法如下:

Tips:枚举类除了不能拥有Object中的clone、finalize方法外,其他方法都能拥有;

Enum类新增(或重写Object)的方法:

  • int compareTo(E e):比较两个枚举常量谁大谁小,其实比较的就是枚举常量在枚举类中声明的顺序(ordinal值)
  • boolean equals(Object o):比较两个枚举常量是否相等;
  • Class<E> getDeclaringClass():返回此枚举类的Class对象,这与Object中的getClass()类似;
  • int hashCode():返回枚举常量的hashCode
  • String name():返回枚举常量的名字;
  • int ordinal():返回枚举常量在枚举类中声明的序号,第一个枚举常量序号为0;
  • String toString():把枚举常量转换成字符串;
  • static T valueOf(Class enumType, String name):把字符串转换成枚举常量。

1.4.2 测试方法功能

  • 定义一个枚举类:
package com.dfbz.demo07;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public enum  Season {
    SPRING,SUMMER,AUTUMN,WINTER;
}
  • 测试类:
package com.dfbz.demo04_Enum类;

import org.junit.Test;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_Enum类的使用 {
    Season spring = Season.SPRING;              // ordinal:0
    Season summer = Season.SUMMER;              // ordinal:1
    Season autumn = Season.AUTUMN;              // ordinal:2
    Season winter = Season.WINTER;              // ordinal:3

    @Test
    public void test1() {
        System.out.println(spring.compareTo(spring));           // 0-0=0
        System.out.println(spring.compareTo(summer));           // 0-1=-1
        System.out.println(winter.compareTo(summer));           // 3-1=2
        System.out.println(winter.compareTo(spring));           // 3-0=3
    }

    @Test
    public void test2() {

        Season spring2 = Season.SPRING;

        System.out.println(spring == spring2);                  // true
        System.out.println(spring.equals(spring2));             // 枚举对象的equals方法比较的是内存地址值(返回true)
    }

    @Test
    public void test3() {
        Class<Season> clazz = spring.getDeclaringClass();
        Class<? extends Season> clazz2 = spring.getClass();

        System.out.println(clazz == clazz2);            // true
    }

    @Test
    public void test4() {

        int hashCode = spring.hashCode();
        System.out.println(hashCode);           // 2027961269

        String name = spring.name();
        System.out.println(name);               // SPRING

        int ordinal_spring = spring.ordinal();
        int ordinal_summer = summer.ordinal();
        int ordinal_autumn = autumn.ordinal();
        int ordinal_winter = winter.ordinal();

        System.out.println(ordinal_spring);         // 0
        System.out.println(ordinal_summer);         // 1
        System.out.println(ordinal_autumn);         // 2
        System.out.println(ordinal_winter);         // 3
    }

    @Test
    public void test5() {
        Season s = Season.valueOf("SPRING");        // 通过枚举项的名称获取枚举项
        System.out.println(s == spring);            // true(返回的还是同一个枚举项)
    }

}

1.4.3 枚举的两个特殊方法

每个枚举类都有两个静态方法,而且这两个方法不是父类中的方法。这又是枚举类特殊的地方;

  • static T[] values():返回本类所有枚举项;
  • static T valueOf(String name):通过枚举项的名字返回枚举项;

【测试类】

package com.dfbz.demo04_Enum类;

import org.junit.Test;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_枚举的特殊方法 {
    @Test
    public void test1() {

        // 获取这个枚举类中所有的枚举项
        Season[] values = Season.values();
        for (Season value : values) {
            System.out.println(value);
        }

        // 通过枚举项的名称获取枚举项
        Season spring = Season.valueOf("SPRING");
        System.out.println(spring == spring);            // true
    }
}

1.5 枚举与反射

通过反射机制,我们可以拿到Java类中的任何成员,包括私有的;这就意味着如果一个类的构造方法被私有(代表不允许外界访问),通过反射机制一样可以在外界创建对象;

1.5.1 反射类

【定义一个类】

package com.dfbz.demo05_枚举与反射;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class TestEntity {
    private TestEntity(){
        System.out.println("TestEntity构造方法调用了...");
    }
}

【通过反射来调用私有构造方法】

package com.dfbz.demo05_枚举与反射;

import com.dfbz.demo04_Enum类.Season;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_反射类的构造方法 {

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

        // 获取这个类的字节码对象
        Class<TestEntity> testEntityClass = TestEntity.class;

        // 获取无参构造方法(记得要使用getDeclaredConstructor方法)
        Constructor<TestEntity> constructor = testEntityClass.getDeclaredConstructor();

        // 开启强制访问
        constructor.setAccessible(true);

        // 调用无参构造方法创建对象
        TestEntity testEntity = constructor.newInstance();

        System.out.println(testEntity);
    }
}

1.5.2 反射枚举

1) 反射构造方法

【定义一个枚举】

package com.dfbz.demo05_枚举与反射;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public enum TestEnum {

    // 定义两个枚举项
    AAA, BBB;

    public String test_01 = "100";
    private String test_02 = "200";

    public void testMethod_01() {
        System.out.println("testMethod_01...");
    }

    private void testMethod_02() {
        System.out.println("testMethod_02...");
    }

    TestEnum() {
        System.out.println("TestEnum的空参构造被调用了...");
    }


    public String getTest_01() {
        return test_01;
    }

    public void setTest_01(String test_01) {
        this.test_01 = test_01;
    }

    public String getTest_02() {
        return test_02;
    }

    public void setTest_02(String test_02) {
        this.test_02 = test_02;
    }
}

【反射枚举构造方法】

package com.dfbz.demo05_枚举与反射;

import com.dfbz.demo04_Enum类.Season;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02 {
    public static void main(String[] args) throws Exception {
        // 获取这个枚举类的字节码对象
        Class<TestEnum> testEnumClass = TestEnum.class;

        /*
         反射获取无参构造方法
         出现异常: java.lang.NoSuchMethodException: com.dfbz.demo05_枚举与反射.TestEnum.<init>()
         在Java中是无法反射枚举类型的构造方法的
         */
        Constructor<TestEnum> constructor = testEnumClass.getDeclaredConstructor();

        // 开启强制访问
        constructor.setAccessible(true);

        // 调用无参构造方法
        TestEnum testEnum = constructor.newInstance();

        System.out.println(testEnum);
    }
}

2) 反射枚举普通成员

枚举的普通成员和普通Java类一样的反射,如果是非public修饰的加上getDeclaredXxx()方法反射,如果是public修饰的则直接反射;

package com.dfbz.demo05_枚举与反射;

import org.junit.Test;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo03_反射枚举的其他成员 {
    // 获取这个枚举类的字节码对象
    Class<TestEnum> testEnumClass = TestEnum.class;

    // 反射普通成员变量
    @Test
    public void test1() throws Exception {
        // 获取枚举项
        TestEnum aaa = TestEnum.AAA;

        Field test01Field = testEnumClass.getField("test_01");
        test01Field.set(aaa, "1000");

        System.out.println(aaa.test_01);
    }


    // 反射私有成员变量
    @Test
    public void test2() throws Exception {
        // 获取枚举项
        TestEnum bbb = TestEnum.BBB;

        // 反射私有字段
        Field test02Field = testEnumClass.getDeclaredField("test_02");
        test02Field.setAccessible(true);
        test02Field.set(bbb, "2000");

        System.out.println(bbb.getTest_02());
    }

    // 反射普通成员方法
    @Test
    public void test3() throws Exception {

        // 获取枚举项
        TestEnum aaa = TestEnum.AAA;

        // 反射普通方法
        Method testMethod01 = testEnumClass.getMethod("testMethod_01");

        // 调用方法
        testMethod01.invoke(aaa);
    }


    // 反射普通成员方法
    @Test
    public void test4() throws Exception {
        // 获取枚举项
        TestEnum aaa = TestEnum.AAA;

        // 反射普通方法
        Method testMethod02 = testEnumClass.getDeclaredMethod("testMethod_02");
        testMethod02.setAccessible(true);

        // 调用方法
        testMethod02.invoke(aaa);
    }
}

二、类加载器

2.1 类加载时机

我们知道,所有的代码都是运行在内存中的,我们必须把类加载到内存中才能运行;在Java中,所有的Java类都是通过类加载器加载到内存进行执行的;

  • 一个类何时被加载?
    • 1)main方法所在的类总是被首先初始化
    • 2)创建该类对象时,首先会将内加载到内存(如果该类存在父类,那么首先加载父类到内存,创建父类的对象(super))
    • 3)访问该类的静态成员时,会将类加载到内存(该静态成员不能被fianl修饰)
    • 4)class.forName("类的全包名")

【示例代码】

package com.dfbz.demo01_类加载器的使用;

import org.junit.Test;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_类何时被加载 {
    static {
        System.out.println("Demo01类被加载了...");
    }

    @Test
    public void test1() throws ClassNotFoundException {
//        new B();          // 先加载A再加载B

//        Integer num = B.num;        // 先加载A再加载B

        Class<?> clazz = Class.forName("com.dfbz.demo01_类加载器的使用.B");        // 先加载A再加载B
    }
}

class A {
    public static Integer num = 10;

    static {
        System.out.println("A loader...");
    }
}

class B extends A {

    public static Integer num = 20;

    static {
        System.out.println("B loader...");
    }
}

Tips:不管是用什么方法加载,类从始至终只会加载一次;

2.3 类加载器

2.3.1 类加载器的种类

  • 启动类加载器Bootstrap ClassLoader: 是嵌在JVM内核中的加载器,该加载器是用C++语言写的,主要负则加载JAVA_HOME/lib下的类库,启动类加载器无法被应用程序直接使用。
  • 扩展类加载器Extension ClassLoader:该加载器器是用JAVA编写,且它的父加载器是Bootstrap,是由sun.misc.Launcher$ExtClassLoader实现的,主要加载JAVA_HOME/lib/ext目录中的类库。开发者可以这几使用扩展类加载器。
  • 系统类加载器App ClassLoader:系统类加载器,也称为应用程序类加载器,负责加载应用程序classpath目录下的所有jar和class文件(第三方jar)。它的父加载器为Ext ClassLoader。

Tips:这里的父加载器并非是Java中的继承关系,而是我们后面学习双亲委派过程中向上委派的加载器,我们将其称为父加载器;

测试类:

package com.dfbz.demo01;

import com.dfbz.demo02.Demo02;
import com.sun.java.accessibility.AccessBridge;
import org.junit.Test;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_类加载器的种类 {
    @Test
    public void test1(){
        // Bootstrap类加载器是获取不到的,为null
        System.out.println("Bootstrap ClassLoader: "+ String.class.getClassLoader());

        // jre\lib\ext\access-bridge-64.jar
        System.out.println("ExtClassLoader ClassLoader: "+ AccessBridge.class.getClassLoader());

        System.out.println("AppClassLoader ClassLoader: "+ Demo02.class.getClassLoader());
    }
}

2.3.2 双亲委派机制

从JDK1.2开始,类的加载过程采用双亲委派机制,它是一种任务委派模式。即把加载类的请求交由父加载器处理,一直到顶层的父加载器(BootstrapClassLoader);如果父加载器能加载则用父加载器加载,否则才用子加载器加载该类;

  • 示例代码:
package com.dfbz.demo01_类加载器的使用;

import org.junit.Test;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo03_类加载的过程 {

    @Test
    public void test1() {
        Class<T> tClass = T.class;
        System.out.println(tClass);
    }
}
class T {
    static {
        System.out.println("T....");
    }
}

JVM在加载类时,会调用类加载器(ClassLoader)的loadClass方法进行加载;

ClassLoader类加载源码:

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // 检查该类是否被加载过
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    // 使用父加载器加载
                    c = parent.loadClass(name, false);
                } else {
                    
                    // 如果没有父加载器则使用BootstrapClassLoader加载
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // 如果依旧没有加载,则调用自身的findClass方法进行加载
                long t1 = System.nanoTime();
                c = findClass(name);

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

findClass方法源码:

protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

可以看到,默认情况下ClassLoader的findClass方法只是抛出了一个异常而已(这个方法是留给我们写的)

  • 双亲委派机制流程图:

  • 1)从上图我们可以分析,当一个Demo.class这样的文件要被加载时,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。

  • 2)如果没有加载,那么会拿到父加载器(向上委派),然后调用父加载器的loadClass方法进行加载。AppClassLoader的父加载器为ExtClassLoader,而ExtClassLoader并没有重写loadClass方法,因此还是调用ClassLoader类的loadClass方法,相当于是一个递归的操作;

  • 3)父类中同理也会先检查自己是否已经加载过,如果没有再往上。注意是个递归的过程,直到到达Bootstrap classLoader之前,都是在检查是否加载过,并不会选择自己去加载。直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。

BootstrapClassLoader不能加载该类,因此还是为null,然后调用本类的findClass方法;这里需要注意两点:

  • 1)本类还是ExtClassLoader
  • 2)ExtClassLoader是自身没有findClass方法,但ExtClassLoader继承与URLClassLoader,并且URLClassLoader提供有findClass方法;

接下来调用到URLClassLoader类中的findClass方法来加载该类:

URLClassLoader类中的findClass方法无法加载我们传递的类,然后向上抛出了一个异常;这里需要注意:

  • 1)URLClassLoader类中的findClass方法是通过ExtClassLoader调用findClass方法进去的,因此向上抛出异常后,findClass方法后面的代码将不会执行了,并且触发的异常继续往上抛给调用者(调用loadClass的对象)
  • 2)ExtClassLoader的loadClass方法是在AppClassLoader中,通过parent.loadClass()调用进去的,因此异常被抛到了这里;

异常被抛到了AppClassLoader中的loadClass方法中,接着尝试使用AppClassLoader的findClass()方法来加载类;

最终交给AppClassLoader完成类的加载:

2.3.3 双亲委派的好处

我们已经了解了Java中类加载的双亲委派机制,即加载类时交给父加载器加载,如果父加载器不能加载,再交给子加载器加载;这样做有何好处呢?

  • 1)避免类的重复加载:当父类加载器已经加载了该类时,就没有必要子 ClassLoader 再加载一次。
  • 2)安全问题:有了双亲委派机制,当有人想要替换系统级别的类或者篡改他的实现时,在双亲委派机制下,在任何的Java代码运行之前,会将所有要用到的系统类提前使用BootstrapClassLoader加载进内存(而当一个类需要被加载时必定会轮到BootstrapClassLoader来加载,只是是否能加载的问题,不能加载的必定不是系统级别的类),所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。

在指定的系统包下建立指定的类(由BootstrapClassLoader、ExtClassLoader加载的系统类):

  • Object:
package java.lang;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Object {
    static {
        System.out.println("自定义的Object类被加载了....");
    }
}
  • AccessBridge:
package com.sun.java.accessibility;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class AccessBridge {
    static {
        System.out.println("自定义的AccessBridge类被加载了.....");
    }
}
  • 测试类:
package com.dfbz.demo01;

import com.sun.java.accessibility.AccessBridge;
import org.junit.Test;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo04_双亲委派的好处 {

    @Test
    public void test1() {
        // java.lang.Object 类在JVM启动时就已经被加载过了,因此不会再被加载了
        Class<Object> clazz = Object.class;

        // com.sun.java.accessibility.AccessBridge 类在JVM启动时就已经被加载过了,因此不会再被加载了
        Class<AccessBridge> accessBridgeClass = AccessBridge.class;
    }
}

Tips:根据双亲委派机制,我们自定义的Object、AccessBridge类不可能被加载;

另外,JVM的类加载器对包名的定义也有限制;不允许我们自定义系统包名

在系统包名下创建任意一个类:

@Test
public void test2() {
    //  不允许用户将类定义在受限包名下 ,Prohibited package name: java.lang
    Class<AA> clazz = AA.class;
}

运行结果:

2.3.4 URLClassLoader类加载器

在 java.net 包中,JDK提供了一个更加易用的类加载器URLClassLoader,它扩展了 ClassLoader,能够从本地或者网络上指定的位置加载类,我们可以使用该类作为自定义的类加载器使用。

URLClassLoader的构造方法:

  • public URLClassLoader(URL[] urls):指定要加载的类所在的URL地址,父类加载器默认为系统类加载器
  • public URLClassLoader(URL[] urls, ClassLoader parent):指定要加载的类所在的URL地址,并指定父类加载器。

1) 加载本地磁盘上的类:

在指定目录下准备一个Java文件并把它编译成class文件:

  • Show.java:
package com.dfbz.demo01;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Show {
    public Show(){
        System.out.println("new Show....");
    }
}
  • 编译文件:
D:\000\com\dfbz\demo01>javac Show.java

D:\000\com\dfbz\demo01>

  • 测试代码:
package com.dfbz.demo01_类加载器的功能;

import org.junit.Test;

import java.io.File;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo05_URLClassLoader {
    @Test
    public void test() throws Exception{
        File file = new File("D:\\000");

        // file   --->  URI
        URI uri = file.toURI();

        // URI    --->  URL
        URL url = uri.toURL();

        // 根据URL构建一个类加载器
        URLClassLoader classLoader = new URLClassLoader(new URL[]{url});
        System.out.println("父类加载器:" + classLoader.getParent());             // 默认父类加载器是系统类加载器

        Class clazz = classLoader.loadClass("com.dfbz.demo01.Show");

        // 实例化这个类
        clazz.newInstance();
    }
}

运行结果:

2) 加载网络上的类:

@Test
public void test2() throws Exception{
    // 构建一个网络地址
    URL url = new URL("http://www.baidu.com/class/");
    
    URLClassLoader classLoader = new URLClassLoader(new URL[]{url});
    System.out.println("父类加载器:" + classLoader.getParent());         // 默认父类加载器是系统类加载器
    Class clazz = classLoader.loadClass("com.baidu.demo.Show");
    
    // 实例化这个类
    clazz.newInstance();
}

Tips:关于加载网络上的类,等我们以后学习了服务器编程再来体验!

2.3.5 自定义类加载器

我们如果需要自定义类加载器,只需要继承ClassLoader,并覆盖掉findClass方法即可。

Tips:我们自定义的类加载器的父加载器为AppClassLoader;

  • 自定义类加载器:
package com.dfbz.demo02;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

// 1. 继承 ClassLoader
// 2. 覆盖 findClass方法
public class MyClassLoader extends ClassLoader {

    // 被加载类所在的目录
    private String dir;

    public MyClassLoader(String dir) {          // 默认父类加载器就是系统类加载器 AppClassLoader
        this.dir = dir;
    }

    public MyClassLoader(ClassLoader parent, String dir) {
        super(parent);
        this.dir = dir;
    }

    /**
     *
     * @param name
     * @return 重写findClass方法
     * @throws ClassNotFoundException
     */
    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            // 把类名转换为目录  --->  D:/000/com/dfbz/demo01/Show.class
            String file = dir + "/" + name.replace(".", "/") + ".class";

            // 从文件中读取这个Class文件
            InputStream in = new FileInputStream(file);

            // 构建一个内存输出流(将读取到的Class文件写在内存中)
            ByteArrayOutputStream baos = new ByteArrayOutputStream();

            byte[] buf = new byte[1024];

            int len ;

            while ((len = in.read(buf)) != -1) {
                baos.write(buf, 0, len);
            }

            // 读取到的流的二进制数据
            byte[] data = baos.toByteArray();
            in.close();
            baos.close();

            /*
            defineClass: 根据类的全包名和内存中的数据流来加载一个类
             - 参数1: 需要加载类的全包名
             - 参数2: 已经加载到内存中的数据流
             - 参数3: 从指定的数据下表开始读取
             - 参数4: 读取到什么位置
             */
            return defineClass(name, data, 0, data.length);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
  • 测试类:
package com.dfbz.demo02;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_自定义类加载器的使用 {
    public static void main(String[] args) throws Exception{
        
        // 创建我们自己的类加载器
        MyClassLoader classLoader = new MyClassLoader("d:/000");
        
        // 使用loadClass加载类
        Class<?> clazz = classLoader.loadClass("com.dfbz.demo01.Show");
        
        clazz.newInstance();
    }
}

2.3.6 打破双亲委派

我们前面自定义了类加载器,观察下面代码:

package com.dfbz.demo02_自定义类加载器;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_打破双亲委派_01 {
    public static void main(String[] args) throws Exception {
        MyClassLoader classLoader = new MyClassLoader("d:/000");
        MyClassLoader classLoader2 = new MyClassLoader("d:/000");
        
        // 使用loadClass方法(进行双亲委派)
        Class<?> clazz = classLoader.loadClass("com.dfbz.demo01.Show");
        Class<?> clazz2 = classLoader2.loadClass("com.dfbz.demo01.Show");

        System.out.println(clazz == clazz2);                // true

        System.out.println(clazz.getClassLoader());         // sun.misc.Launcher$AppClassLoader@18b4aac2
        System.out.println(clazz2.getClassLoader());        // sun.misc.Launcher$AppClassLoader@18b4aac2
    }
}

运行结果:

根据我们之前学习双亲委派机制,上面两个类加载器在加载Show类时,都会判断有没有加载这个类,没有加载则使用父加载器加载,MyClassLoader的父加载器是AppClassLoader,而AppClassLoader正好可以加载这个类;所以其实这两次的加载都是由AppClassLoader来加载的,而AppClassLoader在加载时会判断是否已经加载过,加载过了则不加载;因此Show类只会加载一次;


但是需要注意的是,双亲委派机制的逻辑是写在ClassLoader类的loadClass方法中的,通过一系列逻辑判断最终执行findClass方法来加载类;如果我们加载类直接使用findClass方法呢?那就相当于避开了双亲委派;(当然也可以重写loadClass方法,重新自定义loadClass规则)

  • 测试类:
package com.dfbz.demo02;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo03_打破双亲委派_02 {
    public static void main(String[] args) throws Exception{
        MyClassLoader classLoader = new MyClassLoader("d:/000");
        MyClassLoader classLoader2 = new MyClassLoader("d:/000");

        // 不使用loadClass来加载类,直接使用findClass方法去加载类,每一次调用findClass都相当于是加载一次新的类
        Class<?> clazz = classLoader.findClass("com.dfbz.demo01.Show");
        Class<?> clazz2 = classLoader2.findClass("com.dfbz.demo01.Show");

        System.out.println(clazz == clazz2);                // false

        System.out.println(clazz.getClassLoader());         // com.dfbz.demo02.MyClassLoader@135fbaa4
        System.out.println(clazz2.getClassLoader());        // com.dfbz.demo02.MyClassLoader@330bedb4
    }
}

运行结果:

2.2 类的加载过程

2.2.1 类的生命周期

一个Java类从开始到结束整个生命周期会经历7个阶段:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)。

其中验证、准备、解析三个部分又统称为连接(Linking)。

1)加载

加载过程就是把class字节码文件载入到虚拟机中,至于从哪儿加载,虚拟机设计者并没有限定,你可以从文件、压缩包、网络、数据库等等地方加载class字节码。

类加载的方式有:

  • 1)通过类的全限定名来获取定义此类的二进制字节流
  • 2)将此二进制字节流所代表的静态存储结构转化成方法区的运行时数据结构(加载到内存)
  • 3)在内存中生成代表此类的java.lang.Class对象(在堆中),作为该类访问入口;

2)连接

连接阶段的开始,并不一定等到加载阶段结束。加载阶段与连接阶段的部分内容(如一部分字节码文件格式验证动作)是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始,但这些夹杂在加载阶段之中的动作任然属于连接阶段,加载和连接这两个阶段的开始顺序是固定的。

  • 验证:验证节点主要确保Class文件的格式正确,运行时不会危害JVM的安全;包含文件格式验证、元数据验证、字节码验证、符号引用验证等;
  • 准备:准备阶段会为类变量(被static修饰的变量)分配内存并设置类变量的初始值,这些变量所使用的内存都将在方法区中分配。
    假如有一个变量private static int value = 123;那么value在准备阶段过后值是0,而不是123;因为这个时候尚未执行任何java方法,而把value赋值为123的动作在初始化阶段才会执行。
    但是如果上面的变量被final修饰,变为:private static final int value = 123;编译时javac会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为123。
  • 解析:解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

Tips:

  • 符号引用:在编译的时候每个java类都会被编译成一个class文件,但在编译的时候虚拟机并不知道所引用类的地址,所以就用符号引用来代替,而在这个解析阶段就是为了把这个符号引用转化成为真正的地址的阶段。
  • 直接引用:直接引用可以直接指向目标的指针,如果有了直接引用,那引用的目标必定已经在内存中存在。

解析动作主要针对类或接口、字段、类方法、接口方法、方法类型方法句柄和调用点限定符7类符号引用进行。

3)初始化

类初始化是类加载过程的最后一步,这一步会真正开始执行类中定义的Java程序代码(或者说字节码)。在准备阶段,变量已经被赋过一次系统要求的初始值,在初始化阶段,变量会再次赋值为程序员设置的值。比如变量:private static int value = 123;那么value在准备阶段过后值是0,初始化阶段后值是123。

会导致 类加载 的情况

  • 1)main方法所在的类总是被首先初始化
  • 2)创建该类对象时,首先会将内加载到内存(如果该类存在父类,那么首先加载父类到内存,创建父类的对象(super))
  • 3)访问该类的静态成员时,会将类加载到内存(该静态常量不能被final修饰的基本类型和字符型)
  • 4)class.forName("类的全包名")

不会导致 类加载 的情况

  • 1)访问 类的 static final 静态变量(基本类型和字符型)不会触发初始化
  • 2)类对象.class 不会触发初始化
  • 3)创建该类的数组不会触发初始化
  • 4)Class.forName 的参数2 为 false 时

测试类:

package com.dfbz.demo03_类的初始化流程;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_测试类 {
    public static void main(String[] args) {
        System.out.println(Test.A);
        System.out.println(Test.B);
        System.out.println(Test.C);
        System.out.println(Test.D);
        System.out.println(Test.OBJ);
    }
}


class Test {
    static {
        System.out.println("A加载了");
    }

    public static final Integer A = 1;						// 会加载
    public static final int B = 1;							// 不会加载
    public static final String C = new String();     		//  会加载
    public static final String D = "1";						// 不会加载

    public static final Obj OBJ = new Obj();				// 会加载
}

class Obj{}

三、动态代理模式

代理模式有两种:动态代理,静态代理。

现实生活中的代理

调用者 代理角色 真实角色
买电脑的人 电脑代理商 生产电脑的厂商
买火车票 黄牛 火车站,12306
租房子 房子中介 房东

我们发现代理角色真正角色的目标都具有相同的功能(卖电脑/卖票),代理商可以在中间赚取差价(修改原有的功能)。

3.1 代理模式的作用

用于对真实角色的功能进行修改,代理真实角色实现它的功能。

代理的目的就是为了增强原有的功能;

3.2 代理场景

  • 房东:资源的真实拥有者,中介代理房子的时候最终还是要调用房东的方法,因为房东才是资源的拥有者
  • 中介:针对房东的房子进行代理,我们的实际调用者,我们要租房只能借助中介来租房,而中介在代理房子的过程中可以收手续费,相当于对原有方法的增强。
  • 合同:约定房东的房子都能被中介代理到;在Java中的体现就是房东和中介都要实现合同这个接口,保证房东和中介都能有相同的方法;

3.2.1 静态代理实现

  • 约定接口(合同):
package com.dfbz.demo01_代理的使用;

/**
 * @author lscl
 * @version 1.0
 * @intro: 房子合同,用来确保房东的房子中介都能代理到(房东有什么方法,中介就需要有什么方法)
 */
public interface FangZiHeTong {

    /**
     * 公寓
     */
    void GongYu();

    /**
     * 江景房
     */
    void JiangJingFang();

    /**
     * 海景房
     */
    void HaiJingFang();
}
  • 定义房东对象(资源的真实拥有者)
package com.dfbz.demo01_代理的使用;

/**
 * @author lscl
 * @version 1.0
 * @intro: 目标对象(资源的真实拥有者)
 */
public class FangDong implements FangZiHeTong {

    @Override
    public void GongYu() {
        System.out.println("公寓1000....");
    }

    @Override
    public void JiangJingFang() {
        System.out.println("江景房3000块钱....");
    }

    @Override
    public void HaiJingFang() {
        System.out.println("海景房价值6000块钱....");
    }
}
  • 定义中介,对房子进行代理:
package com.dfbz.demo01_代理的使用;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class ZhongJie implements FangZiHeTong {

    private FangDong fangDong;

    // 创建中介的时候必须传递房东进来,因为中介最终还是调用房东的方法
    public ZhongJie(FangDong fangDong) {
        this.fangDong = fangDong;
    }

    @Override
    public void GongYu() {
        System.out.println("多收100快");
        fangDong.GongYu();
        System.out.println("押金再多收2000");
    }

    @Override
    public void JiangJingFang() {
        System.out.println("多收300");
        fangDong.JiangJingFang();
        System.out.println("押金再多收6000");
    }

    @Override
    public void HaiJingFang() {

        System.out.println("多收600");
        fangDong.HaiJingFang();
        System.out.println("押金再多收12000");
    }
}
  • 测试代码:
package com.dfbz.demo01_代理的使用;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_静态代理的实现 {
    public static void main(String[] args) {

        // 创建房东
        FangDong fangDong = new FangDong();
        // 调用房东的功能(原始功能)
        fangDong.HaiJingFang();
        System.out.println("-----------------");

        // 创建中介
        ZhongJie zhongJie = new ZhongJie(new FangDong());
        zhongJie.HaiJingFang();
        System.out.println("-----------------");
        zhongJie.GongYu();
        System.out.println("-----------------");
    }
}

执行效果:

3.2.2 动态代理的实现

动态代理的主要功能是在不修改源码的情况下对原有对象(目标对象)进行动态的代理,对原有对象的方法进行增强最终返回一个代理对象,此代理对象包含对象原有的功能和自己另加的功能

动态代理类相应的API如下:

【Proxy类】

static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 作用:生成一个代理对象
loader 和目标对象一类的类加载器
interfaces 目标对象所有实现的接口
h 是一个接口,传一个匿名内部类做为实现类,并且重写其中的方法来实现代理的功能
返回值 返回代理对象

【InvocationHandler接口】

Object invoke(Object proxy, Method method, Object[] args) 作用:这个接口中的方法会调用多次,每个方法都会调用一次,用来实现代理方法的功能
proxy 代表生成的代理对象,不建议在方法中直接调用,不然会出现递归调用。
method 代理的方法对象
args 调用方法时传递的参数数组
返回 返回当前这个方法调用的返回值

【示例代码】

  • 定义一个调度类:
package com.dfbz.demo01_代理的使用;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class ProxyHouseInvocationHandler implements InvocationHandler {

    // 资源的真实拥有者
    private FangDong fangDong;

    public ProxyHouseInvocationHandler(FangDong fangDong) {
        this.fangDong = fangDong;
    }

    /**
     * 代理对象执行的所有方法都会执行invoke方法
     *
     * @param proxy:  代理对象
     * @param method: 代理对象执行的那个方法
     * @param args:   代理对象执行那个方法时传递的参数
     * @return: 该方法的返回值就是代理对象调用任何方法的返回值
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // 获取代理对象执行的那个方法的名称
        String name = method.getName();

        if (name.equals("GongYu")) {

            // 调用之前多收100
            System.out.println("多收100");
            // 调用房东的资源
            method.invoke(fangDong);

            // 调用之后再收押金
            System.out.println("押金再多收2000");
        } else if (name.equals("JiangJing")) {
            // 调用之前多收100
            System.out.println("多收300");
            // 调用房东的资源
            method.invoke(fangDong);

            // 调用之后再收押金
            System.out.println("押金再多收6000");
        } else if (name.equals("HaiJingFang")) {
            // 调用之前多收100
            System.out.println("多收600");
            // 调用房东的资源
            method.invoke(fangDong);

            // 调用之后再收押金
            System.out.println("押金再多收12000");
        }

        return null;
    }
}
  • 测试类:
package com.dfbz.demo01_代理的使用;

import java.lang.reflect.Proxy;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_动态代理的实现 {
    public static void main(String[] args) {

        // 目标对象(资源的实际拥有者)
        FangDong fangDong = new FangDong();

        // 通过JDK的API帮我们生成一个代理对象
        FangZiHeTong proxy = (FangZiHeTong) Proxy.newProxyInstance(
                FangDong.class.getClassLoader(),                        // 与目标对象一样的类加载器
                FangDong.class.getInterfaces(),                         // 目标对象实现的所有接口的字节码对象
                new ProxyHouseInvocationHandler(fangDong)               // 代理对象的调度器
        );

        // 代理对象执行的所有方法都会执行调度类的invoke方法
        proxy.HaiJingFang();
    }
}

3.3 代理练习

3.3.1 静态代理练习

  • 约定接口:
package com.dfbz.demo02_代理的练习;

/**
 * @author lscl
 * @version 1.0
 * @intro: 解决User的业务
 */
public interface UserService {
    void add();
    void delete(Integer id);
    String query(Integer id);
}
  • 目标对象:
package com.dfbz.demo02_代理的练习;

/**
 * @author lscl
 * @version 1.0
 * @intro: 目标对象(资源的拥有者), 被代理对象
 */
public class UserServiceImpl implements UserService {
    @Override
    public void add() {
        System.out.println("添加用户...");
    }

    @Override
    public void delete(Integer id) {
        System.out.println("删除..." + id);
    }

    @Override
    public String query(Integer id) {
        System.out.println("查询..." + id);
        return "ok";
    }
}
  • 代理对象:
package com.dfbz.demo02_代理的练习;

/**
 * @author lscl
 * @version 1.0
 * @intro: 代理对象
 */
public class UserServiceProxy implements UserService {

    // 传递目标对象进来
    private UserService userService;

    public UserServiceProxy(UserService userService) {
        this.userService = userService;
    }

    @Override
    public void add() {
        System.out.println("添加日志....");
        userService.add();
        System.out.println("添加日志....");
    }

    @Override
    public void delete(Integer id) {
        System.out.println("删除日志....");
        userService.delete(id + 100);
        System.out.println("删除日志....");
    }

    @Override
    public String query(Integer id) {
        System.out.println("查询日志....");
        String res = userService.query(id);
        return res + "【query】";
    }
}
  • 测试代码:
package com.dfbz.demo02_代理的练习;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo01_静态代理 {
    public static void main(String[] args) {
        // 直接使用目标对象
        UserServiceImpl userService = new UserServiceImpl();
        userService.add();
        userService.delete(10);
        String result = userService.query(10);
        System.out.println(result);

        System.out.println("----------");

        // 使用代理对象
        UserServiceProxy userServiceProxy = new UserServiceProxy(userService);
        userServiceProxy.add();
        userServiceProxy.delete(10);
        String proxyResult = userServiceProxy.query(10);
        System.out.println(proxyResult);
    }
}

3.3.2 动态代理练习

  • 定义代理调度类:
package com.dfbz.demo02_代理的练习;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
class ProxyUserServiceInvocationHandler implements InvocationHandler {

    // 接收目标对象
    private UserService userService;

    public ProxyUserServiceInvocationHandler(UserService userService) {
        this.userService = userService;
    }

    /**
     * @param proxy:  代理对象
     * @param method: 代理对象执行的方法对象
     * @param args:   代理对象执行方法时传递的参数
     * @throws Throwable
     * @return: 代理对象执行方法时的返回值
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // 获取代理对象执行的那个方法的名称
        String name = method.getName();

        if (name.equals("add")) {
            // 说明代理对象执行的是add方法

            System.out.println("新增日志...");
            method.invoke(userService);
            System.out.println("新增日志...");

        } else if (name.equals("delete")) {
            // 说明代理对象执行的是delete方法

            System.out.println("删除日志...");
            // 执行目标对象的delete方法,传递代理对象调用方法的参数
            method.invoke(userService, args);
            System.out.println("删除日志...");
        } else if (name.equals("query")) {
            // 说明代理对象执行的是query方法


            System.out.println("查询日志...");
            // 执行目标对象的query方法,获取目标对象的query方法的返回值
            Object result = method.invoke(userService, args);

            // 对目标对象方法的结果集进行改变
            return result + ": Proxy";
        }

        return null;
    }
}
  • 测试代码:
package com.dfbz.demo02_代理的练习;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public class Demo02_动态代理 {
    public static void main(String[] args) {

        // 创建目标对象
        UserServiceImpl userService = new UserServiceImpl();
        userService.add();
        userService.delete(10);
        String result = userService.query(10);
        System.out.println(result);

        System.out.println("------------");

        // 通过JDK的API生成一个代理对象
        UserService userServiceProxy = (UserService) Proxy.newProxyInstance(
                UserServiceImpl.class.getClassLoader(),
                UserServiceImpl.class.getInterfaces(),
                new ProxyUserServiceInvocationHandler(userService)
        );

        userServiceProxy.add();
        userServiceProxy.delete(10);
        String proxyResult = userServiceProxy.query(10);
        System.out.println(proxyResult);
    }
}

小结:JDK提供的动态代理是基于接口的,把目标对象所实现的所有接口的字节码对象传入方法中,JDK能在内存中动态的帮我们创建一个对象,该对象实现的目标对象实现的所有方法。即保证目标对象的所有方法代理对象都能代理到

posted @ 2023-02-09 13:44  绿水长流*z  阅读(240)  评论(0)    收藏  举报