DAY23 单例设计模式、多例设计模式、枚举、工厂设计模式、动态代理

学习目标

java 复制代码
能够说出单例设计模式的好处
	//无论获取多少次对象,只产生一个对象
能够说出多例模式的好处
	//可以产生固定的多个对象
	扑克牌程序,一个"扑克类"会创建固定的54个对象,不能多、也不能少。
	麻将程序,一个"骰子类"会创建固定的2个对象,不能多、也不能少。
	程序中需要用到"颜色的表示",只能有三种颜色"红、绿、蓝",一个"颜色类(Color)"应该只创建三个对象,来代	表这三个颜色。

	//多例模式的作用:使某个类,在程序运行期间,只能产生固定的几个对象,不能多、也不能少。
能够说出动态代理模式的作用
	对对象进行代理,调用对象的功能
		让一部分功能允许执行
		让一部分功能不允许执行
		可以自己定义一些代理的规则
能够使用Proxy的方法生成代理对象
	Star wbqProxy = (Star)Proxy.newProxyInstance(WangBaoQiang.class.getClassLoader(),
			WangBaoQiang.class.getInterfaces(),
			new InvocationHandlerImpl(new WangBaoQiang()));	
能够使用工厂模式编写java程序
	public class AnimalFactory {
		//定义一个生产动物的方法,参数传递动物的名称,根据名称创建指定的动物
		public static Animal getInstance(String name){
			if("cat".equals(name)){
				return new Cat();
			}else if("dog".equals(name)){
				return new Dog();
			}else {
				//不是动物,返回null
				return null;
			}
		}
	}

一.单例设计模式(重点)

单例设计模式宗旨:保证一个类只产生一个对象

1.饿汉式

java 复制代码
package com.itheima.demo01Singleton;

/*
    单例:只能创建一个本类的对象
    单例设计模式:饿汉式
        形容人非常饥饿,直接把创建好的对象吃了
        无论是否有人使用本类对象,先把本类对象创建创建好了,供用户使用
    实现步骤:
        1.私有空参数构造方法,不让用户直接创建对象
        2.定义一个私有的静态Person变量,并进行初始化赋值(创建一个对象给变量赋值)
        3.定义一个公共的静态方法,返回Person对象
 */
public class Person {
    //1.私有空参数构造方法,不让用户直接创建对象
    private Person() { }

    //2.定义一个私有的静态Person变量,并进行初始化赋值(创建一个对象给变量赋值)
    private static Person p = new Person();

    //3.定义一个公共的静态方法,返回Person对象
    public static Person getInstance(){
        return p;
    }
}
java 复制代码
package com.itheima.demo01Singleton;

public class Demo01Singleton {
    public static void main(String[] args) {
        //正常情况下,客户根据类创建多个对象
        //Person p1 = new Person();
        //System.out.println(p1);//com.itheima.demo01Singleton.Person@4554617c
        //Person p2 = new Person();
        //System.out.println(p2);//com.itheima.demo01Singleton.Person@74a14482
        //Person p3 = new Person();
        //System.out.println(p3);//com.itheima.demo01Singleton.Person@1540e19d
        //Person p4 = new Person();
        //System.out.println(p4);//com.itheima.demo01Singleton.Person@677327b6

        //测试单例设计模式,无论调用多少次静态方法,获取的对象都是同一个
        for (int i = 0; i < 20; i++) {
            Person p = Person.getInstance();
            System.out.println(p);
        }
    }
}

2.懒汉式

java 复制代码
package com.itheima.demo02Singleton;

/*
    单例设计模式:懒汉式
        人非常懒,抽一鞭子动一下
        当我们要使用对象的时候,才会创建对象;一直不使用对象是不会创建的
    实现步骤:
        1.私有空参数构造方法,不让用户直接创建对象
        2.在类中定义一个私有的静态的Peson变量,不进行初始化赋值
        3.定义一个公共的静态的成员方法,返回Person对象,保证无论调用多少次方法,只返回一个对象
 */
public class Person {
    //1.私有空参数构造方法,不让用户直接创建对象
    private Person(){}

    //2.在类中定义一个私有的静态的Peson变量,不进行初始化赋值
    private static Person p;

    //3.定义一个公共的静态的成员方法,返回Person对象,保证无论调用多少次方法,只返回一个对象
    public static Person getInstance(){
        //增加一个判断,判断变量p的值是否为null(是null第一次调用getInstance方法),创建对象
        if(p==null){
            p = new Person();
        }
        //变量p不是null直接返回
        return p;
    }
}
java 复制代码
package com.itheima.demo02Singleton;

public class Demo01Singleton {
    public static void main(String[] args) {
        //测试单例设计模式,无论调用多少次方法,获取的都是同一个对象
        for (int i = 0; i < 20; i++) {
            Person p = Person.getInstance();
            System.out.println(p);
        }
    }
}

3.懒汉式解决多线程安全问题

java 复制代码
package com.itheima.demo03Singleton;

public class Demo01Singleton {
    public static void main(String[] args) {
        //创建两个线程,每个线程获取20次对象
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                Person p = Person.getInstance();
                System.out.println(p);
            }
        }).start();

        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                Person p = Person.getInstance();
                System.out.println(p);
            }
        }).start();
    }
}

执行结果:

java 复制代码
com.itheima.demo03Singleton.Person@47e3e4b5
com.itheima.demo03Singleton.Person@67c084e5
com.itheima.demo03Singleton.Person@67c084e5
com.itheima.demo03Singleton.Person@67c084e5
com.itheima.demo03Singleton.Person@67c084e5
com.itheima.demo03Singleton.Person@67c084e5
com.itheima.demo03Singleton.Person@67c084e5
...
java 复制代码
package com.itheima.demo03Singleton;

public class Person {
    //1.私有空参数构造方法,不让用户直接创建对象
    private Person(){}

    //2.在类中定义一个私有的静态的Peson变量,不进行初始化赋值
    private static Person p;

    //3.定义一个公共的静态的成员方法,返回Person对象,保证无论调用多少次方法,只返回一个对象
    public static synchronized Person getInstance(){
        //增加一个判断,判断变量p的值是否为null(是null第一次调用getInstance方法),创建对象
        if(p==null){
            p = new Person();
        }
        //变量p不是null直接返回
        return p;
    }
}

小结

单例模式可以保证系统中一个类只有一个对象实例。

实现单例模式的步骤:

  1. 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
  2. 在该类内部产生一个唯一的实例化对象,并且将其封装为private static类型的成员变量。
  3. 定义一个静态方法返回这个唯一对象。

二.多例设计模式(重点)

1.多例设计模式概述

多例模式,是一种常用的软件设计模式。通过多例模式可以保证系统中,应用该模式的类有固定数量的实例。多例类要自我创建并管理自己的实例,还要向外界提供获取本类实例的方法。

例如:

​ 扑克牌程序,一个"扑克类"会创建固定的54个对象,不能多、也不能少。

​ 麻将程序,一个"骰子类"会创建固定的2个对象,不能多、也不能少。

​ 程序中需要用到"颜色的表示",只能有三种颜色"红、绿、蓝",一个"颜色类(Color)"应该只创建三个对象,来代 表这三个颜色。

多例模式的作用:使某个类,在程序运行期间,只能产生固定的几个对象,不能多、也不能少。

2.获取多个通用的对象

java 复制代码
package com.itheima.demo04Multiton;

import java.util.ArrayList;
import java.util.Random;

/*
    多例设计模式:获取多个通用的对象
    需求:
        只让程序产生3个Student对象,不能多,也不能少
    实现步骤:
        1.私有空参数构造方法,不让用户直接创建对象
        2.定义一个私有的,静态的,最终的变量,值定义创建对象的总个数
        3.定义一个私有的,静态的ArrayList集合,用于存储多个对象
        4.定义一个静态代码块(只执行一次),创建3个对象,存储到集合中
        5.定义一个公共的静态方法,给用户随机在集合中取出一个对象返回
 */
public class Student {
    //1.私有空参数构造方法,不让用户直接创建对象
    private Student(){}

    //2.定义一个私有的,静态的,最终的变量,值定义创建对象的总个数
    private static final int MAX = 3;

    //3.定义一个私有的,静态的ArrayList集合,用于存储多个对象
    private static ArrayList<Student> list = new ArrayList<>();

    //4.定义一个静态代码块(只执行一次),创建3个对象,存储到集合中
    static {
        for (int i = 0; i < MAX; i++) {
            list.add(new Student());
        }
    }

    //5.定义一个公共的静态方法,给用户随机在集合中取出一个对象返回
    public static Student getInstance(){
        //定义一个Random对象
        Random r = new Random();
        //在集合索引的范围内,随机产生一个随机数
        int index = r.nextInt(list.size());//[0,1,2]
        //通过随机的索引,在集合中取出Student对象返回
        return list.get(index);
    }
}
java 复制代码
package com.itheima.demo04Multiton;

public class Demo01Multiton {
    public static void main(String[] args) {
        //测试多例设计模式,无论获取多少次对象,只会产生固定的3个
        for (int i = 0; i < 20; i++) {
            Student s = Student.getInstance();
            System.out.println(s);
        }
    }
}

3.获取多个特定的对象

java 复制代码
package com.itheima.demo05Multiton;

public class Student {
    private String name;
    private int age;
    private String sex;

    public Student() {
    }

    public Student(String name, int age, String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
}
java 复制代码
package com.itheima.demo05Multiton;

public class Demo01Mulition {
    public static void main(String[] args) {
        //创建Student对象
        Student s = new Student();
        //给成员变量赋值
        s.setName("张三");
        s.setAge(18);
        //s.setSex("男");
        s.setSex("abc");//性别是一个字符串,随机的填写
        System.out.println(s);
    }
}

优化代码:解决用户随意填写性别问题

java 复制代码
package com.itheima.demo06Multiton;

/*
    多例设计模式:获取多个特定的对象
        只产生两个Sex对象,一个代表男的性别,一个代表女的性别
    实现步骤:
        1.私有构造方法,不让用户直接创建对象
        2.定义两个固定的对象(公共的,静态的,最终的),一个代表男的性别,一个代表女的性别
        3.重写toString方法,打印对象的属性值(地址值)
 */
public class Sex {
    private String s;

    //1.私有构造方法,不让用户直接创建对象
    private Sex(String s) {
        this.s = s;
    }

    //2.定义两个固定的对象(公共的,静态的,最终的),一个代表男的性别,一个代表女的性别
    public static final Sex MAN = new Sex("男");
    public static final Sex WOMAN = new Sex("女");

    //3.重写toString方法,打印对象的属性值(地址值)
    @Override
    public String toString() {
       return s;
    }
}
java 复制代码
package com.itheima.demo06Multiton;

public class Student {
    private String name;
    private int age;
    //使用Sex类型
    private Sex sex;

    public Student() {
    }

    public Student(String name, int age, Sex sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex=" + sex +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Sex getSex() {
        return sex;
    }

    public void setSex(Sex sex) {
        this.sex = sex;
    }
}
java 复制代码
package com.itheima.demo06Multiton;

public class Demo01Mulition {
    public static void main(String[] args) {
        //创建Student对象
        Student s = new Student();
        //给成员变量赋值
        s.setName("张三");
        s.setAge(18);
        //s.setSex("男");
        //s.setSex("abc");//性别的类型是Sex,不能随便给性别赋值,只能在Sex类中选择常量赋值
        //s.setSex(Sex.MAN);
        s.setSex(Sex.WOMAN);
        System.out.println(s);
    }
}

小结

多例模式可以保证系统中一个类有固定个数的实例, 在实现需求的基础上, 能够提高实例的复用性.

实现多例模式的步骤:

  1. 创建一个类, 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
  2. 在类中定义该类被创建的总数量
  3. 在类中定义存放类实例的list集合
  4. 在类中提供静态代码块,在静态代码块中创建类的实例
  5. 提供获取类实例的静态方法

三.枚举(重点)

1.枚举的定义

java 复制代码
package com.itheima.demo07enum;

/*
    枚举:它就是"多例设计模式:获取多个特定的对象"的一种简化写法
    需求:
        只产生两个Sex对象,一个代表男的性别,一个代表女的性别
    使用多例实现:
        public static final Sex MAN = new Sex("男");
        public static final Sex WOMAN = new Sex("女");
    使用枚举实现:
        MAN  ==>  public static final Sex MAN = new Sex();
        WOMAN  ==>  public static final Sex MAN = new Sex();
        注意:枚举中是有构造方法
   ---------------------------------------------------------------
   枚举中有也可以定义成员变量,成员方法,构造方法(私有),这三个必须写在枚举常量的下边
       MAN("男") ==> public static final Sex MAN = new Sex("男");
       WOMAN("女") ==> public static final Sex WOMAN = new Sex("女");
 */
public enum Sex {
    //MAN1,WOMAN1,
    MAN("男"),WOMAN("女");

    //定义成员变量
    private String s;

    //private Sex(){}

    //添加带参数的构造方法
    private Sex(String s) {
        this.s = s;
    }

    //重写toString方法,返回对象中的字符串

    @Override
    public String toString() {
        return s;
    }
}

2.枚举的使用

java 复制代码
package com.itheima.demo07enum;

public class Student {
    private String name;
    private int age;
    private Sex sex;

    public Student() {
    }

    public Student(String name, int age, Sex sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex=" + sex +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Sex getSex() {
        return sex;
    }

    public void setSex(Sex sex) {
        this.sex = sex;
    }
}
java 复制代码
package com.itheima.demo07enum;

public class Demo01Enum {
    public static void main(String[] args) {
        Student s1 = new Student();
        s1.setName("迪丽热巴");
        s1.setAge(18);
        s1.setSex(Sex.WOMAN);
        System.out.println(s1);

        Student s2 = new Student("马尔扎哈",30,Sex.MAN);
        System.out.println(s2);
    }
}

3.枚举的应用

枚举的作用:枚举通常可以用于做信息的分类,如性别,方向,季度等。

枚举表示性别:

java 复制代码
public enum Sex {
    MAIL, FEMAIL;
}

枚举表示方向:

java 复制代码
public enum Orientation {
    UP, RIGHT, DOWN, LEFT;
}

枚举表示季度

java 复制代码
public enum Season {
    SPRING, SUMMER, AUTUMN, WINTER;
}

小结

  • 枚举类在第一行罗列若干个枚举对象。(多例)
  • 第一行都是常量,存储的是枚举类的对象。
  • 枚举是不能在外部创建对象的,枚举的构造器默认是私有的。
  • 枚举通常用于做信息的标志和分类。

四.工厂设计模式(重点)

1.简单工厂设计模式

java 复制代码
package com.itheima.demo08SimpleFactory;

public abstract class Animal {
    public abstract void eat();
}
java 复制代码
package com.itheima.demo08SimpleFactory;

public class Cat extends Animal{
    @Override
    public void eat() {
        System.out.println("猫吃鱼!");
    }
}
java 复制代码
package com.itheima.demo08SimpleFactory;

public class Dog extends Animal{
    @Override
    public void eat() {
        System.out.println("狗吃肉!");
    }
}
java 复制代码
package com.itheima.demo08SimpleFactory;

/*
    定义一个生产动物的工厂
    只能生产动物,不能生产其他对象
    定义一个静态方法,根据用户传递的动物名称,创建指定的动物对象返回
 */
public class AnimalFactory {
    public static Animal getInstance(String name){
        if("cat".equals(name)){
            return new Cat();
        }else if("dog".equals(name)){
            return new Dog();
        }else{
            //不是动物返回null
            return null;
        }
    }
}
java 复制代码
package com.itheima.demo08SimpleFactory;

/*
    简单工厂设计模式:
        创建一个工厂类,在工厂类中定义一个生产对象的方法
        我们要使用对象,不在自己创建对象了,使用工厂类的方法获取
    好处:
        可以给工厂类的方法传递不同的动物名称,生产不同的动物对象
        解除耦合性,增强了扩展性
    弊端:
        胡乱传递一个动物的名称,不存在,会抛出空指针异常
    解决:
        可以使用工厂方法设计模式来解决;创建多个工厂,每个工厂生产特定的动物
 */
public class Demo01SimpleFactory {
    public static void main(String[] args) {
        //使用工厂类的方法,获取指定的动物
        //获取一个Cat对象
        Animal cat = AnimalFactory.getInstance("cat");
        cat.eat();

        //获取一个Dog对象
        Animal dog = AnimalFactory.getInstance("dog");
        dog.eat();

        //胡乱传递一个动物的名称
        Animal car = AnimalFactory.getInstance("car");
        //car.eat();//null.eat(); NullPointerException:空指针异常
    }
}

2.工厂方法设计模式

java 复制代码
package com.itheima.demo09FactoryMethod;

public abstract class Animal {
    public abstract void eat();
}
java 复制代码
package com.itheima.demo09FactoryMethod;

public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫吃鱼!");
    }
}
java 复制代码
package com.itheima.demo09FactoryMethod;

public class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("狗吃肉!");
    }
}
java 复制代码
package com.itheima.demo09FactoryMethod;

/*
    定义一个工厂接口:所有的工厂都的实现这个接口
 */
public interface Factory {
    //定义一个生产动物的抽象方法
    public abstract Animal getInstance();
}
java 复制代码
package com.itheima.demo09FactoryMethod;

/*
    生产猫的工厂,实现工厂接口
    只生产猫
 */
public class CatFactory implements Factory{
    @Override
    public Animal getInstance() {
        return new Cat();
    }
}
java 复制代码
package com.itheima.demo09FactoryMethod;

/*
    生产狗的工厂,实现工厂接口
    只生产狗对象
 */
public class DogFactory implements Factory{
    @Override
    public Animal getInstance() {
        return new Dog();
    }
}
java 复制代码
package com.itheima.demo09FactoryMethod;

/*
    工厂方法设计模式:
        可以设计多个工厂,每个工厂生产不同的对象
        猫工厂只生产猫,狗工厂只生产狗..
    好处:
        解决解决简单工厂设计模式的弊端,不让用户胡乱的传递参数了
    弊端:
        如果动物的种类过多,产生的工厂也有多个
 */
public class Demo01FactoryMethod {
    public static void main(String[] args) {
        //创建生产猫的工厂==>生产猫
        CatFactory catFactory = new CatFactory();
        Animal cat = catFactory.getInstance();
        cat.eat();
        //猫工厂可以生产多个猫
        catFactory.getInstance().eat();
        catFactory.getInstance().eat();
        catFactory.getInstance().eat();
        catFactory.getInstance().eat();

        //创建生产狗的工厂==>生产狗
        DogFactory dogFactory = new DogFactory();
        Animal dog = dogFactory.getInstance();
        dog.eat();
        //狗工厂可以生产多个狗
        dogFactory.getInstance().eat();
        dogFactory.getInstance().eat();
        dogFactory.getInstance().eat();
        dogFactory.getInstance().eat();
        dogFactory.getInstance().eat();

    }
}

五.动态代理 (原理)

1.动态代理概述

2.动态代理代码实现

Star接口

java 复制代码
package com.itheima.demo10proxy;

//培养明星的接口
public interface Star {
    //教唱歌的方法
    public abstract void changge();

    //教跳舞的方法
    public abstract void tiaowu();

    //教演电影的方法
    public abstract String yandianying(int money);

    //教吃饭的方法
    public abstract void chifan();

    //教玩游戏的方法
    public abstract void wanyouxi();
}

CaiXuKun类

java 复制代码
package com.itheima.demo10proxy;

public class CaiXuKun implements Star {
    @Override
    public void changge() {
        System.out.println("蔡徐坤在唱歌");
    }

    @Override
    public void tiaowu() {
        System.out.println("蔡徐坤在跳舞");
    }

    @Override
    public String yandianying(int money) {
        System.out.println("蔡徐坤在演电影:"+money);
        return "电影演完了";
    }

    @Override
    public void chifan() {
        System.out.println("和蔡徐坤在一起吃饭");
    }

    @Override
    public void wanyouxi() {
        System.out.println("和蔡徐坤在一起玩游戏");
    }
}

WuYiFan类

java 复制代码
package com.itheima.demo10proxy;

/*
    快捷键: ctrl+r 查找并替换
 */
public class WuYiFan implements Star {
    @Override
    public void changge() {
        System.out.println("吴亦凡在唱歌");
    }

    @Override
    public void tiaowu() {
        System.out.println("吴亦凡在跳舞");
    }

    @Override
    public String yandianying(int money) {
        System.out.println("吴亦凡在演电影:"+money);
        return "电影演完了";
    }

    @Override
    public void chifan() {
        System.out.println("和吴亦凡在一起吃饭");
    }

    @Override
    public void wanyouxi() {
        System.out.println("和吴亦凡在一起玩游戏");
    }
}

InvocationHandler接口的实现类:

java 复制代码
package com.itheima.demo10proxy;

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

/*
    java.lang.reflect.InvocationHandler接口
        InvocationHandler 是代理实例的调用处理程序 实现的接口。
        用来生产代理人对象的动态代理的接口
        可以把被代理人(吴亦凡对象,蔡徐坤对象)传递到InvocationHandlerImpl类中,让InvocationHandler接口的实现生产明星的代理人对象
        动态代理:传递哪个明星,就生产哪个明星的代理人对象
    InvocatcionHandler接口中的方法:
        Object invoke(Object proxy, Method method, Object[] args) 在代理实例上处理方法调用并返回结果。
        作用:
            使用invoke方法对被代理人(吴亦凡对象,蔡徐坤对象)的方法进行拦截,部分方法可以运行,部分方法不能运行
        参数:
            Object proxy:内部产生的代理人对象
            Method method:invoke方法对明星的方法(changge,tiaowu...)进行拦截,方法内部使用反射技术获取到这些方法
            Object[] args:拦截到明星的方法,方法的参数 changge()  yandianying(int money)
        返回值:
           Object:就是拦截到方法的返回值
 */
public class InvocationHandlerImpl implements InvocationHandler{
    //在实现类中定义一个Star类型的变量,用于接收被代理人对象(吴亦凡对象,蔡徐坤对象)
    private Star star;

    //使用构造方法,把明星传递到类中,给成员变量star赋值
    public InvocationHandlerImpl(Star star) {
        this.star = star;
    }

    /*
        使用invoke方法对被代理人(吴亦凡对象,蔡徐坤对象)的方法进行拦截,部分方法可以运行,部分方法不能运行
            调用明星的chagnge,tiaowu,yandianying的方法放行==>可以运行
            调用明星的chifan,wanyouxi的方法拦截==>不让方法运行
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //获取到方法的名称
        String methodName = method.getName();
        //对方法名称进行判断
        if("chifan".equals(methodName)){
            throw new RuntimeException("不和你吃饭!");
        }
        if("wanyouxi".equals(methodName)){
            throw new RuntimeException("不和你玩游戏!");
        }
        //其他的方法,使用Method对象中的方法invoke运行方法
        Object v = method.invoke(star, args);
        return v;
    }
}

测试类:

java 复制代码
package com.itheima.demo10proxy;

import java.lang.reflect.Proxy;

/*
    动态代理:
        创建代理人对象,对明星进行代理,对明星的方法进行拦截
            调用明星的chagnge,tiaowu,yandianying的方法放行==>可以运行
            调用明星的chifan,wanyouxi的方法拦截==>不让方法运行
    想要实现动态代理:使用Proxy类生成代理人对象
        java.lang.reflect.Proxy:
        Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
        Proxy类中的静态方法:可以产生代理人对象
            static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
                返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
            参数:
                ClassLoader loader:传递类加载器
                Class<?>[] interfaces:传递被代理人实现的所有接口  代理人(蔡徐坤,吴亦凡)实现的接口 Star.class
                InvocationHandler h:生成代理人的接口,传递InvocationHandler接口的实现类对象
            返回值:
                Object:返回的就是创建的代理人对象
 */
public class Demo01Proxy {
    public static void main(String[] args) {
        //没有使用动态代理
        CaiXuKun cxk  = new CaiXuKun();
        //cxk.chifan();
        //cxk.tiaowu();
        //cxk.chifan();

        //使用Proxy类中的静态方法newProxyInstance获取蔡徐坤的代理人对象
        Star cxkProxy = (Star) Proxy.newProxyInstance(CaiXuKun.class.getClassLoader(),
                CaiXuKun.class.getInterfaces(),new InvocationHandlerImpl(cxk));
        cxkProxy.changge();
        cxkProxy.tiaowu();
        String s = cxkProxy.yandianying(100);
        System.out.println(s);
        //cxkProxy.chifan();

        //使用Proxy类中的静态方法newProxyInstance获取吴亦凡的代理人对象
        Star wyfProxy = (Star)Proxy.newProxyInstance(WuYiFan.class.getClassLoader(),
                WuYiFan.class.getInterfaces(),new InvocationHandlerImpl(new WuYiFan()));
        wyfProxy.changge();
        wyfProxy.tiaowu();
        //wyfProxy.wanyouxi();
    }
}

3.演示Java类似的动态代理方法

java 复制代码
package com.itheima.demo11proxy;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/*
    java.util.Collections:操作集合的工具类
        static <T> List<T> unmodifiableList(List<? extends T> list) 返回指定列表的不可修改视图。
        此方法允许模块为用户提供对内部列表的"只读"访问。
        在返回的列表上执行的查询操作将"读完"指定的列表。
        试图修改返回的列表(不管是直接修改还是通过其迭代器进行修改)
        将导致抛出 UnsupportedOperationException(运行时异常:不支持操作异常)。
    unmodifiableList:模拟动态代理
        传递List接口的实现类对象,方法内部对List接口的实现类对象进行代理
        返回一个被代理后的List接口的实现类对象(相当于返回了代理人对象)
        使用unmodifiableList方法代理了List接口之后
            如果使用List接口的size,get方法,没有对集合的进行修改,就可以运行方法
            如果使用List接口的add,remove,set方法,对象集合进行了修改,则会抛出UnsupportedOperationException异常
 */
public class Demo01Proxy {
    public static void main(String[] args) {
        //创建List集合对象
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        //使用Collections中的方法unmodifiableList对List集合进行代理
        List<String> listProxy = Collections.unmodifiableList(list);
        //如果使用List接口的size,get方法,没有对集合的进行修改,就可以运行方法
        System.out.println(listProxy.get(1));//b
        System.out.println(listProxy.size());//3

        //如果使用List接口的add,remove,set方法,对象集合进行了修改,则会抛出UnsupportedOperationException异常
        //listProxy.add("d");//UnsupportedOperationException
        //listProxy.remove(0);//UnsupportedOperationException
        listProxy.set(2,"www");//UnsupportedOperationException
    }
}

4.动态代理综合案例

需求:

使用动态代理模拟unmodifiableList方法,对List接口进行代理

调用List接口的方法会被拦截

如果使用的size,get方法,没有对集合进行修改,则允许执行

如果使用的add,remove,set方法,对集合进行了修改,则抛出运行时异常

分析:

1.定义一个代理方法proxyList

参数:传递List集合

返回值:被代理之后的List集合

2.方法内部可以使用Proxy类中的方法实现动态代理

代码实现:

java 复制代码
package com.itheima.demo11proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

/*
    需求:
	使用动态代理模拟unmodifiableList方法,对List接口进行代理
		调用List接口的方法会被拦截
		如果使用的size,get方法,没有对集合进行修改,则允许执行
		如果使用的add,remove,set方法,对集合进行了修改,则抛出运行时异常

    分析:
        1.定义一个代理方法proxyList
            参数:传递List集合
            返回值:被代理之后的List集合
        2.方法内部可以使用Proxy类中的方法实现动态代理
 */
@SuppressWarnings("all")//注解的作用是抑制警告,不让警告出现
public class Demo02Proxy {
    public static void main(String[] args) {
        //创建List集合对象
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");

        List<String> proxyList = proxyList(list);
        System.out.println(proxyList.size());//3
        System.out.println(proxyList.get(1));//b
        //proxyList.remove(0);//UnsupportedOperationException: remove no run!
        //proxyList.add("d");//UnsupportedOperationException: add no run!
        //proxyList.set(1,"www");//UnsupportedOperationException: set no run!
    }

    //1.定义一个代理方法proxyList
    public static List<String> proxyList(List<String> list){
        //2.方法内部可以使用Proxy类中的方法实现动态代理
        List<String> listProxy = (List<String>)Proxy.newProxyInstance(list.getClass().getClassLoader(), list.getClass().getInterfaces(),
                new InvocationHandler() {
                    //invoke方法获取List集合中的方法,对这些方法进行判断,让部分方法可以运行,让部分方法被拦截,抛出异常,不让方法运行
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //获取方法的名称
                        String name = method.getName();
                        //如果使用的add,remove,set方法,对集合进行了修改,则抛出运行时异常
                        if("add".equals(name)){
                            throw new UnsupportedOperationException("add no run!");
                        }
                        if("remove".equals(name)){
                            throw new UnsupportedOperationException("remove no run!");
                        }
                        if("set".equals(name)){
                            throw new UnsupportedOperationException("set no run!");
                        }
                        //如果使用的size,get方法,没有对集合进行修改,则允许执行
                        Object v = method.invoke(list, args);
                        return v;
                    }
                });
        return listProxy;
    }
}

动态代理案例流程图

5.总结

动态代理非常的灵活,可以为任意的接口实现类对象做代理

动态代理可以为被代理对象的所有接口的所有方法做代理,动态代理可以在不改变方法源码的情况下,实现对方法功能的增强,

动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。

动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。

动态代理同时也提高了开发效率。

缺点:只能针对接口的实现类做代理对象,普通类是不能做代理对象的。

相关推荐
武昌库里写JAVA2 小时前
Java设计模式之工厂模式
java·vue.js·spring boot·后端·sql
赛姐在努力.4 小时前
SpringMVC中的常用注解及使用方法
java·spring
让我上个超影吧5 小时前
黑马点评秒杀优化和场景补充
java
寻星探路5 小时前
Java EE初阶启程记06---synchronized关键字
java·java-ee
沉木渡香5 小时前
【VSCode中Java开发环境配置的三个层级之Maven篇】(Windows版)
java·vscode·maven
EnCi Zheng6 小时前
Spring Boot 4.0.0-SNAPSHOT @Configuration 问题解决指南
java·spring boot·spring
海上生明月丿6 小时前
在IDEA中使用Git
java·git·intellij-idea
托比-马奎尔7 小时前
Redis7内存数据库
java·redis·后端
鹅是开哥7 小时前
Redis的零食盒满了怎么办?详解缓存淘汰策略
java·redis·缓存·bootstrap