当前位置:首页 > 服务端 > Java-常用设计模式

Java-常用设计模式

2022年11月09日 19:14:02服务端7

1.单例模式

  • 1、设计模式是一套代码设计经验的总结。项目中合理的运用设计模式可以巧妙的解决很多问题
  • 2、总体来说设计模式可以分为三大类:
    • 创建型模式
      • 工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
    • 结构型模式
      • 适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式
    • 行为型模式
      • 策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式
  • 3、设计模式的六大原则
    • 开闭原则(Open Close Principle)
      • 开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类。
    • 里氏代换原则(Liskov Substitution Principle)
      • 里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
    • 依赖倒转原则(Dependence Inversion Principle)
      • 这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
    • 接口隔离原则(Interface Segregation Principle)
      • 这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
    • 迪米特法则,又称最少知道原则(Demeter Principle)
      • 最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
    • 合成复用原则(Composite Reuse Principle)
      • 合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。
  • 4、小应用也能体现大原则

     Java-常用设计模式 _ JavaClub全栈架构师技术笔记

  • 5、单例模式是Java中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式
  • 6、单例模式确保在一个应用程序中某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例单例实例。单例模式只应在有真正的“单一实例”的需求时才可使用:  
    • 1)单例类只能有一个实例
    • 2)单例类必须自己创建自己的唯一实例
    • 3)单例类必须给所有其他对象提供这一实例
  • 7、Java中实现单例模式可以通过两种形式实现:
    • 懒汉模式(类加载时不初始化)
    • 饿汉模式(在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快)
  • 8、编写单例必要条件:
    • 1)构造方法变成私有
    • 2)提供一个静态方法获取单实例对象
  • 饿汉单例相对比较容易理解,一般表现为以下两种形式:
public class Singleton1 {
    //static保证全局唯一
    private static Singleton1 instance = new Singleton1();
    private String name;
    private Singleton1(){//构造方法私有化}
    public void sayHello(){System.out.print(" 我是中国人 ");}
    public String getName() {return name;}
    public void setName(String name) {this.name = name;}
    public static Singleton1 getInstance(){
        return instance;
        /*下面代码getInstance方法调用一次,就会获取一个新对象,不能达到单例的目的
          return new Singleton1();
        * */
    }
}
public class Singleton2 {
    private static Singleton2 instance = null;
    private String name;
    private Singleton2(){//构造方法私有化}
    //静态代码块的方式,实例化对象
    static {
        instance = new Singleton2();
    }
    public void sayHello(){ System.out.print(" 我是中国人 ");}
    public String getName() { return name;}
    public void setName(String name) {this.name = name;}
    public static Singleton2 getInstance(){ return instance;}
}
//Test测试类
public class SingletonTest {
    public static void main(String[] args) {
       /* Singleton1 singleton1 = new Singleton1();
       * Singleton1 私有的构造方法,不能在外部new
       * */
        Singleton1 singleton1 = Singleton1.getInstance();
        Singleton1 singleton2 = Singleton1.getInstance();
        Singleton2 singleton3 = Singleton2.getInstance();
        Singleton2 singleton4 = Singleton2.getInstance();
        
        System.out.print(singleton1 == singleton2);
        singleton1.sayHello();
        singleton1.setName("李九");
        System.out.print(singleton1.getName()+" ");
        System.out.println(singleton2.getName());

        System.out.print(singleton3 == singleton4);
        singleton3.sayHello();
        singleton3.setName("李四");
        System.out.print(singleton3.getName()+" ");
        System.out.print(singleton4.getName());
    }
}
/*
true 我是中国人 李九 李九
true 我是中国人 李四 李四
*/
  • 9、饿汉模式基于classloader机制避免了多线程的同步问题(静态初始化将保证在任何线程能够访问到域之前初始化它),不过,instance在类装载时就实例化,这时候初始化instance显然没有达到lazy loading的效果
  • 10、懒汉方式实现单例模式能够提高类加载性能,但是和饿汉模式借助与JVM的类加载内部同步机制实现了线程安全不同,需要在延迟加载时注意单例实例的线程安全性,如果简单粗暴的实现,在多线程环境中将引起运行异常,如:
//懒汉式:多线程同时访问时可能会产生多个实例
public class Singleton3 {
    //单例的实例
    private static Singleton3 instance;
    //构造方法私有化
    private Singleton3() {}
    //提供一个静态方法获取单例的实例对象
    public static Singleton3 getInstance() {
        /*
            如果instance为null,创建一个实例对象
            不为null,直接返回,不再创建
        */
        if (instance == null) {
            instance = new Singleton3();
        }return instance;
    }
    public void say() {System.out.println("say实例方法");}
}
public class SingletonTest3 {
    public static void main(String[] args) {
        //子线程调用getInstance方法
        Thread t =new Thread(() ->{
            Singleton3 instance = Singleton3.getInstance();
            instance.say();
            System.out.println(instance);
        });
        t.start();
        //子线程调用getInstance方法
        Thread t1 =new Thread(() ->{
            Singleton3 instance = Singleton3.getInstance();
            instance.say();
            System.out.println(instance);
        });
        t1.start();
    }
}
/*可能不一样
say实例方法
say实例方法
com.tjetc.sheji.Singleton3@90472a2
com.tjetc.sheji.Singleton3@1e057600
*/
/*可能一样
say实例方法
say实例方法
com.tjetc.sheji.Singleton3@90472a2
com.tjetc.sheji.Singleton3@90472a2
*/
  • 上述代码多线程同时访问时可能会产生多个示例,甚至会破坏实例,违背单例的设计原则
  • 11、可以为返回单例实例的方法设置同步用来保证线程安全性
/**
 * 懒汉式 多线程安全单例
 * 效率相对较低
 */
public class Singleton4 {
    private static Singleton4 instance;
    private Singleton4(){
    }
    //锁静态方法 锁住Singleton4的字节码。字节码全局唯一
    public synchronized static Singleton4 getInstance() {
        if (instance == null) {
            instance = new Singleton4();
        }
        return instance;
    }
    public void say() {
        System.out.println("say实例方法");
    }
}
public class SingletonTest4 {
    public static void main(String[] args) {
        Thread t =new Thread(() ->{
            Singleton4 instance = Singleton4.getInstance();
            instance.say();
            System.out.println(instance);
        });
        t.start();
        Thread t1 =new Thread(() ->{
            Singleton4 instance = Singleton4.getInstance();
            instance.say();
            System.out.println(instance);
        });
        t1.start();
    }
}
/*两个线程的instance不变
say实例方法
say实例方法
com.tjetc.sheji.Singleton4@90472a2
com.tjetc.sheji.Singleton4@90472a2
*/
  • 这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,由于整个方法被同步,因此效率相对较低
  • 12、双检查锁方式
public class Singleton5 {
    //防止指令重排序
    private volatile static Singleton5 instance;
    private Singleton5() {
    }
    public static Singleton5 getInstance() {
        /*外层if判断有没有锁,可以多个线程同时判断。
            如果,instance不为null,就不会进入外面if大括号中也就不会进入锁,直接return
        */
        if (instance == null) {
            //只能有一个线程进来
            synchronized (Singleton5.class) {
                /*内层if判断目的,外面有锁,保证一个线程进来
                    如果,instance为null,new对象;
                    不为null,不创建对象,直接返回已有对象
                */
                if (instance == null) {
                    instance = new Singleton5();
                }
            }
        }
        return instance;
    }
    public void say() {
        System.out.println("say实例方法");
    }
}
public class SingletonTest5 {
    public static void main(String[] args) {
        Thread t =new Thread(() ->{
            Singleton5 instance = Singleton5.getInstance();
            instance.say();
            System.out.println(instance);
        });
        t.start();
        Thread t1 =new Thread(() ->{
            Singleton5 instance = Singleton5.getInstance();
            instance.say();
            System.out.println(instance);
        });
        t1.start();
    }
}
/*
say实例方法
say实例方法
com.tjetc.sheji.Singleton5@7e129604
com.tjetc.sheji.Singleton5@7e129604
*/
  • (1) 为什么两次判断instance == null
    • 第一次判断没有锁,如果install不为null直接返回单实例对象,提高效率
    • 第二次判断防止多线程创建多个实例,假如A和B 两个线程同时争抢synchronized锁,A先争抢到锁,B等待,A线程instance赋值实例化对象,释放锁,B线程获取到到锁,如果没有第二次判断的话,直接又会创建对象,那么就不符合单例要求
  • (2) volatile作用:保证有序性,禁止指令重排序。
  • 原因: 在执行instance = new Singleton()语句时,一共是有三步操作的。
    • 1.堆中分配内存
    • 2.调用构造函数进行初始化
    • 3.将instance引用指向内存地址。
  • 在这三步有可能会产生指令重排序即有两种结果可能产生:123与132(不管怎么重排序,单线程程序的执行结果不会改变)
  • 如果A线程执行到instance = new Singleton(),此时2,3发生重排序,选执行3,则instance已经不为null,但是指向的对象还未初始化完成,如果此时B对象判断instance 不为null就会直接返回一个未初始化完成的对象。
  • 13、之前提到了,静态初始化将在实例被任何线程访问到之前对其进行初始化,因此,可以借助于这个特性对懒汉单例进行改造:

 5

  • 14、JDK1.5之后引入了枚举,由于枚举的特性,可以利用其来实现单例,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象
public enum  Singleton7 {
    INSTANCE;
​
    public void say(){
        
    }
}

2.工厂模式

  • 1、工厂模式主要是为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的
  • 2、工厂模式在一般分为三类:
    • 1)简单工厂模式(Simple Factory)
    • 2)工厂方法模式(Factory Method)
    • 3)抽象工厂模式(Abstract Factory)
  • 3、这三种模式从上到下逐步抽象,并且更具一般性
  • 4、简单工厂模式又称静态工厂方法模式。从 命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的接口,先来看看它的组成:
    • 1)工厂类角色: 这是本模式的核心,含有一定的商业逻辑和判断逻辑。在java中它往往由一个具体类实现
    • 2)抽象产品角色:它一般是具体产品继承的父类或者实现的接口。在java中由接口或者抽象类来实现。
    • 3)具体产品角色:工厂类所创建的对象就是此角色的实例。在java中由一个具体类实现。
  • 5、简单工厂可以代码示例
/**
 * 定义Car接口
 */
public interface Car {
    void show();
}
/**
 * 奔驰Car
 */
public class Benz implements Car{
    @Override
    public void show() {
        System.out.println("我是Benz");
    }
}
/**
 * 宝马Car
 */
public class Bmw implements Car{
    @Override
    public void show() {
        System.out.println("我是Bmw");
    }
}
/**
 * 工厂类角色,负责创建对象
 */
public class SimpleFactory {
    public static Car produceCar(String str) {
        if ("Benz".equalsIgnoreCase(str)) {
            return new Benz();
        } else if ("Bmw".equalsIgnoreCase(str)) {
            return new Bmw();
        } else {
            throw new RuntimeException("未知类型");
        }
    }
}
/**
 * 测试类
 */
public class SimpleFactoryTest {
    public static void main(String[] args) {
        Car car = SimpleFactory.produceCar("Bmw");
        car.show();
    }
}
  • 6、使用了简单工厂模式后,我们的程序更加符合现实中的情况;而且客户端免除了直接创建产品对象的责任,而仅仅负责“消费”产品
  • 7、可以从开闭原则上来分析简单工厂模式。当工厂生产一新辆车的时候,只要符合抽象产品制定的规定,所以对产品部分来说,它是符合开闭原则的;但是工厂部分好像不太理想,因为每增加一辆新车,都要在工厂类中增加相应的业务逻辑或者判断逻辑,这显然是违背开闭原则的。可想而知对于新产品的加入,工厂类是很被动的
  • 8、工厂方法模式是简单工厂模式的进一步抽象化和推广,工厂方法模式里不再只由一个工厂类决定那一个产品类应当被实例化,这个决定被交给抽象工厂的子类去做。来看下它的组成:
    • 1)抽象工厂角色:工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现
    • 2)具体工厂角色:含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象
    • 3)抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现
    • 4)具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现
  • 9、工厂方法模式使用继承自抽象工厂角色的多个子类来代替简单工厂模式中的“上帝类”。正如上面所说,这样便分担了对象承受的压力;而且这样使得结构变得灵活 起来:当有新的产品产生时,只要按照抽象产品角色、抽象工厂角色提供的合同来生成,那么就可以被客户使用,而不必去修改任何已有的代 码。可以看出工厂角色的结构也是符合开闭原则的
  • 10、工厂方法模式示例如下:
/**
 * (抽象)工厂
 */
public interface Factory {
    Car produceCar();
}
/**
 * 宝马工厂
 */
public class BmwFactory implements Factory{
    @Override
    public Car produceCar() {
        return new Bmw();
    }
}
/**
 * 奔驰工厂
 */
public class BenzFactory implements Factory{
    @Override
    public Car produceCar() {
        return new Benz();
    }
}
public class FactoryMethodTest {
    public static void main(String[] args) {
        BenzFactory benzFactory = new BenzFactory();
        Car benzCar = benzFactory.produceCar();
        benzCar.show();

        BmwFactory bmwFactory = new BmwFactory();
        Car bmwCar = bmwFactory.produceCar();
        bmwCar.show();
    }
}
  • 11、使用工厂方法模式足以应付我们可能遇到的大部分业务需求。但是当产品种类非常多时,就会出现大量的与之对应的工厂类,这不应该是我们所希望的。所以建议在这种情况下使用简单工厂模式与工厂方法模式相结合的方式来减少工厂类:即对于产品树上类似的种类(一般是树的叶子中互为兄弟的)使用简单工厂模式来实现
  • 12、事实上很多时候工厂所产生的产品会划分为很多层次
    • 1)产品族:位于不同产品等级结构中,功能相关联的产品组成的家族
    • 2) 如果项目中涉及到多个产品族,则可以使用抽象工厂模式,抽象工厂模式的各个角色和工厂方法的如出一辙:
      • a、抽象工厂角色:这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现
      • b、具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。在java中它由具体的类来实现
      • c、抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现
      • d、具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现
  • 13、抽象工厂模式和工厂方法模式的区别就在于需要创建对象的复杂程度上。而且抽象工厂模式是三个里面最为抽象、最具一般性的。抽象工厂模式的用意为:给客户端提供一个接口,可以创建多个产品族中的产品对象。而且使用抽象工厂模式还要满足一下条件:
    • 1)系统中有多个产品族,而系统一次只可能消费其中一族产品
    • 2)同属于同一个产品族的产品以其使用
  • 14、在抽象工厂模式中,抽象产品可能是一个或多个,从而构成一个或多个产品族。 在只有一个产品族的情况下,抽象工厂模式实际上退化到工厂方法模式

  Java-常用设计模式 _ JavaClub全栈架构师技术笔记

  • 15、抽象工厂模式示例参见课堂案例
/**
 * 汽车工厂
 */
public interface CarFactory {
    SportCar produceSportCar();

    BusinessCar produceBusinessCar();
}
/**
 * 运动型汽车
 */
public interface SportCar extends Car {
    boolean isStrong();
}
/**
 * 商务型汽车
 */
public interface BusinessCar extends Car {
    boolean isAutoDoor();
}
/**
 * 奔驰商务汽车
 */
public class BenzBusinessCar implements BusinessCar {
    @Override
    public boolean isAutoDoor() {
        return true;
    }
    @Override
    public void show() {
        System.out.println("我是奔驰商务型汽车");
    }
}
/**
 * 奔驰运动型汽车
 */
public class BenzSportCar implements SportCar {
    @Override
    public boolean isStrong() {
        return true;
    }
    @Override
    public void show() {
        System.out.println("我是奔驰运动型汽车");
    }
}
/**
 * 宝马商务型汽车
 */
public class BmwBusinessCar implements BusinessCar {
    @Override
    public boolean isAutoDoor() {
        return true;
    }
    @Override
    public void show() {
        System.out.println("我是宝马商务型汽车");
    }
}
/**
 * 宝马运动型汽车
 */
public class BmwSportCar implements SportCar {
    @Override
    public boolean isStrong() {
        return true;
    }
    @Override
    public void show() {
        System.out.println("我是宝马运动型汽车");
    }
}
/**
 * 奔驰工厂
 */
public class BenzCarFactory implements CarFactory {
    @Override
    public SportCar produceSportCar() {
        return new BenzSportCar();
    }
    @Override
    public BusinessCar produceBusinessCar() {
        return new BenzBusinessCar();
    }
}
/**
 * 宝马工厂
 */
public class BmwCarFactory implements CarFactory {
    @Override
    public SportCar produceSportCar() {
        return new BmwSportCar();
    }
    @Override
    public BusinessCar produceBusinessCar() {
        return new BmwBusinessCar();
    }
}
/**
 * 测试抽象工厂
 */
public class AbstractFactory {
    public static void main(String[] args) {
        //创建Bmw工厂
        CarFactory BwmCarFactory = new BmwCarFactory();
        //通过Bmw工厂对象创建Bmw商务汽车
        BusinessCar BmwBusinessCar = BwmCarFactory.produceBusinessCar();
        System.out.println("常务宝马汽车是否为自动门:" + BmwBusinessCar.isAutoDoor());
        BmwBusinessCar.show();
        //通过Bmw工厂对象创建Bmw运动汽车
        SportCar BmwSportCar = BwmCarFactory.produceSportCar();
        System.out.println("运动宝马汽车是否马力大:" + BmwSportCar.isStrong());
        BmwSportCar.show();
        //创建Benz工厂
        CarFactory BenzCarFactory = new BenzCarFactory();
        //通过Benz工厂对象创建Benz商务汽车
        BusinessCar BenzBusinessCar = BenzCarFactory.produceBusinessCar();
        System.out.println("常务奔驰汽车是否为自动门:" + BenzBusinessCar.isAutoDoor());
        BenzBusinessCar.show();
        //通过Benz工厂对象创建Benz运动汽车
        SportCar BenzSportCar = BenzCarFactory.produceSportCar();
        System.out.println("运动奔驰汽车是否马力大:" + BenzSportCar.isStrong());
        BenzSportCar.show();
    }
}
/*
常务宝马汽车是否为自动门:true
我是宝马商务型汽车
运动宝马汽车是否马力大:true
我是宝马运动型汽车
常务奔驰汽车是否为自动门:true
我是奔驰商务型汽车
运动奔驰汽车是否马力大:true
我是奔驰运动型汽车
*/

3.观察者模式

  • 1、假设现在有A、B、C、D等四个独立的对象,其中B、C、D这三个对象想在A对象发生改变的第一时间知道这种改变,以便做出相应的响应。这就是观察者模式。
  • 2、观察者与被观察者也不是对立的,一个对象可以观察其他对象,也可以被其他对象观察
  • 3、Java中通过Observable 类和 Observer 接口实现了观察者模式。Observer对象监视着Observable对象的变化,当Observable对象发生变化时,Observer得到通知,就可以进行相应的工作
  • 4、Observable类的两个方法比较重要:
方法 功能
setChanged() 用来设置一个内部标志位注明数据发生了变化
notifyObservers() / notifyObservers(Object data) 通知所有的Observer数据发生了变化,这时所有的Observer会自动调用复写好的update(Observable observable, Object data)方法来做一些处理,通知Observer有两个方法,一个无参,一个有参。那么这个参数有什么作用呢? 其中一个作用:不想通知所有的Observer,而只想其中一个指定的Observer做一些处理,那么就可以传一个参数作为ID,然后在所有的Observer中判断,每个Observer判断只有接收到底参数ID是属于自己的才做一些处理。
  • 5、通过生成被观察者的实例,来调用addObserver()方法让观察者达到观察被观察者的目的
/**
 * 被观察者
 */
public class SimpleObservable extends Observable {
    private int data = 0;

    public int getData() {
        return data;
    }

    public void setData(int data) {
        if (this.data != data) {
            this.data=data;
            //调用setChanged方法表示发生改变
            this.setChanged();
            //只有在setChange()被调用后,notifyObservers()才会去调用update(),否则什么都不干。
            notifyObservers();
        }
    }
}

/**
 * 观察者
 */
public class SimpleObserver implements Observer {
    public SimpleObserver(SimpleObservable simpleObservable) {
        simpleObservable.addObserver(this);
    }

    @Override
    public void update(Observable o, Object args) {
        System.out.println("Data属性被修改为新值:" + ((SimpleObservable) o).getData());
    }
}

/**
 * 测试
 */
public class TestSimple {
    public static void main(String[] args) {
        //被观察者
        SimpleObservable simpleObservable = new SimpleObservable();
        //观察者
        SimpleObserver simpleObserver = new SimpleObserver(simpleObservable);
        //被观察者调用setData方法改变数据
        simpleObservable.setData(1);
        simpleObservable.setData(2);
        simpleObservable.setData(3);
    }
}

4.代理模式概念

  • 1、代理模式:代理模式给某一个对象提供一个代理对象,并由代理对象控制对源对象的引用。代理就是一个人或一个机构代表另一个人或者一个机构采取行动。某些情况下,客户不想或者不能够直接引用一个对象,代理对象可以在客户和目标对象直接起到中介的作用。客户端分辨不出代理主体对象与真实主体对象。代理模式可以并不知道真正的被代理对象,而仅仅持有一个被代理对象的接口,这时候代理对象不能够创建被代理对象,被代理对象必须有系统的其他角色代为创建并传入
  • 2、大部分中国人小时候都享受过代理模式带来的福利
    • 母亲:代理客户端,本应该自己完成买菜业务,交由孩子代劳
    • 孩子:代理实例,虽然跑腿付出了体力,但是会得到好吃的
  • 3、在上例中,如果孩子买劣质白菜克扣菜钱一直没有被发现,母亲每次都让他去代为买菜,形成了客户端和代理的一一绑定关系,我们称为这种代理为静态代理,这种代理方式实现非常简单
    • 如果母亲经常更换代为买菜的目标,那么客户端和代理实例之间会进行动态关联,这种代理我们称为动态代理
  • 4、代理的本质:在不改变目标类方法的代码的情况下对目标类的方法进行增强.
  • 5、静态代理:
    • 1)由程序员创建代理类在程序运行前代理类的.class文件就已经存在了.
    • 2)静态代理需要以下角色
      • a、接口:目标类和代理类都要实现该接口
      • b、目标类:被代理的类
      • c、代理类:代理目标类的类
      • d、测试类:创建目标对象;创建代理对象(注入目标对象);调用代理类的方法

image-20210507222309017

/**
 * 用户操作接口
 */
public interface UserOperation {
    /**
     * 添加方法
     */
    void add();

    /**
     * 删除方法
     */
    void delete();
}

/**
 * 用户操作实现类
 */
public class UserOperationImpl implements UserOperation {

    @Override
    public void add() {
        System.out.println("UserImpl.add()");
    }

    @Override
    public void delete() {
        System.out.println("UserImpl.delete()");
    }
}

/**
 * 用户操作的代理类
 */
public class UserOperationProxy implements UserOperation {
    private UserOperation obj;

    public UserOperationProxy(UserOperation obj) {
        this.obj = obj;
    }

    @Override
    public void add() {
        System.out.println("add()前增强功能");
        this.obj.add();
        System.out.println("add()后增强功能");
    }

    @Override
    public void delete() {
        System.out.println("delete()前增强功能");
        this.obj.add();
        System.out.println("delete()后增强功能");
    }
}

/**
 * 测试
 */
public class TestProxy {
    public static void main(String[] args) {
        //1、创建目标对象
        UserOperation userOperation = new UserOperationImpl();
        //2、创建代理对象
        UserOperationProxy proxy = new UserOperationProxy(userOperation);
        //3、调用代理对象的方法
        proxy.add();
        proxy.delete();
    }
}
  • 静态代理缺点:
    • 1.接口增加方法,目标类和代理类都要实现方法,增加了代码维护的复杂度
    • 2.如果有很多方法需要方法(增强),会有大量重复代码.
  • 6、动态代理
    • 1)在程序运行时运用反射机制动态创建而成,不是提前写好的,是后期动态生成的.
    • 2)java中动态代理主要有JDK和CGLIB两种方式。
    • 3)区别主要是jdk是代理接口,而cglib是代理类。
    • 4)JDK动态代理是代理模式的一种实现方式,其只能代理接口
    • 5)如果被代理的目标对象实现了至少一个接口,则会使用JDK动态代理。所有该目标类型实现的接口都将被代理。
    • 6)若该目标对象没有实现任何接口,则创建一个CGLIB代理。
  • 7、JDK动态代理

image-20210507224335807

由上图可以简单的知道调用代理类中的所有方法实际上都是调用中间类的invoke方法,而在invoke方法中才是真正去调用对应的目标类的目标方法;这个比静态代理多了一层结构

    • 1)JDK动态代理使用步骤:
      • a、 新建一个接口
      • b、 为接口创建一个实现类(就是被代理的类)
      • c、 创建中间类实现java.lang.reflect.InvocationHandler接口
      • d、 测试(利用jdk创建动态代理类对象,调用代理对象的方法实现对被代理对象方法的增强)
/**
 * 用户操作接口
 */
public interface UserOperation {
    /**
     * 添加方法
     */
    void add();

    /**
     * 删除方法
     */
    void delete();
}

/**
 * 用户操作实现类
 */
public class UserOperationImpl implements UserOperation {

    @Override
    public void add() {
        System.out.println("UserImpl.add()");
    }

    @Override
    public void delete() {
        System.out.println("UserImpl.delete()");
    }
}

public class UserInvocationHandler implements InvocationHandler {
    //被代理对象
    private Object obj;

    public UserInvocationHandler(Object obj) {
        this.obj = obj;
    }

    /**
     * @param proxy  代理对象
     * @param method 传进来的方法对象
     * @param args   传进来的方法参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("调用方法前增强");
        Object result = method.invoke(obj, args);
        System.out.println("调用方法后增强");
        return result;
    }
}


public class TestJdkProxy {
    public static void main(String[] args) {
        //1、创建目标对象
        UserOperation userOperation = new UserOperationImpl();
        //2、创建代理对象
        //创建自定义的UserInvocationHandler对象
        UserInvocationHandler userInvocationHandler = new UserInvocationHandler(userOperation);
        //jdk的Proxy.newProxyInstance方法需要三个参数,一个classloader 一个是接口信息 一个自定义的InvocationHandler
        UserOperation userOperationProxy = (UserOperation) Proxy.newProxyInstance(userOperation.getClass().getClassLoader(),
                userOperation.getClass().getInterfaces(), userInvocationHandler);
        //3、调用代理对象
        userOperationProxy.add();
        userOperationProxy.delete();
    }
}
  • 8、分析jdk生成的代理类对象
    • 1)运行java自带的工具HSDB
    • 在cmd命令下执行:

      java -classpath "%JAVA_HOME%/lib/sa-jdi.jar" sun.jvm.hotspot.HSDB

    • 2) 查看正在运行的java类进程号:将java程序在合适的地方打下断点,直接在cmd命令行下,使用jps命令(java自带的命令)。

image-20210507232928897

image-20210507233010951

    • 3)将自己正在运行的程序进程id输入到HSDB中image-20210507233134935

image-20210507233232220

    • 4)然后在点击tools -> Class Browser,就可以搜索一下jvm正在运行的类了。

image-20210507233346042

    • 5)将jvm中的代理类输出到文件

点击感兴趣的类,然后点击 Create .class File ,就会在你的系统目录下生成.class文件。(注意:window一般在C:\Users\用户\自己用户名\com\子包名 等类似于这样的目录下)

Java-常用设计模式 _ JavaClub全栈架构师技术笔记

我的目录

Java-常用设计模式 _ JavaClub全栈架构师技术笔记

    • 6)把这个类拖进idea开发工具中,就可以利用idea反编译成java类
  • 7、反编译结果
public final class $Proxy0 extends Proxy implements UserOperation {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m4;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) {
        super(var1);
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.tjetc.proxy.UserOperation").getMethod("add");
            m4 = Class.forName("com.tjetc.proxy.UserOperation").getMethod("delete");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

    public final void add() {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final boolean equals(Object var1) {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void delete() {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
}
  • 9、CGLIB动态代理

CGLib动态代理是代理类去继承目标类,然后重写其中目标类的方法,这样也可以保证代理类拥有目标类的同名方法

代理类去继承目标类,每次调用代理类的方法都会被方法拦截器拦截,在拦截器中才是调用目标类的该方法的逻辑

image-20210507234038974

    • 1)开发步骤
      • a、创建目标类:没有接口
      • b、在pom.xml添加cglib的依赖,写方法的拦截器
      • c、测试类
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
public class PersonOperation {
    public void add(){
        System.out.println("PersonOperation.add()");
    }

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

public class MyMethodInterceptor implements MethodInterceptor {
    /**
     * 拦截方法
     *
     * @param o           CGLib动态生成的代理类实例
     * @param method      被代理(目标对象)的方法
     * @param objects     参数列表
     * @param methodProxy 代理类方法对象 ,对method进行增强,性能好
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("调用方法前增强");
        //调用代理类实例上的父类方法
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("调用方法后增强");
        return result;
    }
}

public class TestCglibProxy {
    public static void main(String[] args) {
        //创建目标对象
        PersonOperation personOperation = new PersonOperation();
        //创建Enhancer对象
        Enhancer enhancer = new Enhancer();
        //设置enhancer的父类
        enhancer.setSuperclass(PersonOperation.class);
        //设置回调对象(相当于jdk的中间类)
        enhancer.setCallback(new MyMethodInterceptor());
        //通过字节码技术动态创建目标类的子类对象就是代理对象
        PersonOperation personOperationProxy = (PersonOperation) enhancer.create();
        personOperationProxy.add();
        personOperationProxy.delete();
    }
}

作者:carat9588
来源链接:https://www.cnblogs.com/seventeen9588/p/16249806.html

版权声明:
1、JavaClub(https://www.javaclub.cn)以学习交流为目的,由作者投稿、网友推荐和小编整理收藏优秀的IT技术及相关内容,包括但不限于文字、图片、音频、视频、软件、程序等,其均来自互联网,本站不享有版权,版权归原作者所有。

2、本站提供的内容仅用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯相关权利人及本网站的合法权利。
3、本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站(javaclubcn@163.com),我们将第一时间核实后及时予以删除。


本文链接:https://www.javaclub.cn/server/69125.html

分享给朋友:

“Java-常用设计模式” 的相关文章

Java空指针异常解决java.lang.NullPointerException解决心得

Java空指针异常解决java.lang.NullPointerException解决心得

今天做课设的时候运行程序报出以下错误 java.lang.NullPointerException 首先要理解的是此错误并不会在 程序中报错,只会在运行的时候报错。 是由于某个参数(集合,数组等数据)可能出现一个null值而导致后面的程序不能运行时...

常用设计模式系列(三)—抽象工厂模式

常用设计模式系列(三)—抽象工厂模式

一、前言 各位大佬好,又是一个冷嗖嗖的日子,这个城市的天气最近一直都不太好,说下雪吧也不下,天气也不晴,让人甚是难受。前段时间周围的城市都下雪了,盼了好久的雪也没盼到,只等来了冷风作祟。基于我的心情,我来吟诗一首: ​ 《盼雪》 昨日已别大雪, 吾昼夜盼雪至。...

深入理解 Java 并发锁

深入理解 Java 并发锁

📦 本文以及示例源码已归档在 javacore 一、并发锁简介 确保线程安全最常见的做法是利用锁机制(Lock、sychronized)来对共享数据做互斥同步,这样在同一个时刻,只有一个线程可以执行某个方法或者某个代码块,那么操作必然是原子性的,线程安全的...

java比较语句常犯错误和三个数比较大小

1.忘了大括号 解决: 任何if else语句后面加大括号,哪怕只有一句 2.忘了分号 if后面不能有分号 3.代码分格 Scanner in=new Scanner(System.in); int x; int y; int z;...

关于设计模式

掌握设计模式并不是件很难的事情,关键在于多思考,多实践,不要听到人家说懂几个设计模式就很“牛”,只要用心学习,设计模式也就那么回事,你也可以很“牛”的,一定要有信心。 在学习每一个设计模式时至少应该掌握如下几点:这个设计模式的意图是什么,它...

炒冷饭系列:设计模式 装饰模式

炒冷饭系列:设计模式 装饰模式

炒冷饭系列:设计模式 装饰模式 摘要: 原创出处: http://www.cnblogs.com/Alandre/ 泥沙砖瓦浆木匠 希望转载,保留摘要,谢谢! 钢琴弹得好是艺术,文章写的好也是艺术。 一、什么是装...

java提高篇(十六)

java提高篇(十六)

     Java的基本理念是“结构不佳的代码不能运行”!!!!!       大成若缺,其用不弊。       大...

java空指针异常:java.lang.NullPointException

一.什么是java空指针异常     我们都知道java是没有指针的,这里说的"java指针"指的就是java的引用,我们不在这里讨论叫指针究竟合不合适,而只是针对这个异常本身进行分析。空指针就是空引用,java空指针异常就是引用本身为空,却调用了方...

Java 基础之详解 Java IO

Java 基础之详解 Java IO

Java IO 基本概念 Java IO:即 Java 输入 / 输出系统。 区分 Java 的输入和输出:把自己当成程序, 当你从外边读数据到自己这里就用输入(InputStream/Reader), 向外边写数据就用输出(OutputStream/Writer)。...

Java常用日志框架介绍

Java常用日志框架介绍

目录 Java日志概述 Java常用日志框架类别 Java常用日志框架历史 java常用日志框架关系 Commons Logging与Slf4j实现机制对比 Commons Logg...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。