Java设计模式

狂神说Java设计模式


目录

1、设计模式概述

23种设计模式

1.1什么是设计模式

设计模式是前辈们对代码的总结,是解决问题的一系列套路。

提高代码的可复用性,可维护性,可读性,稳健性及安全性的解决方案。

1995 四人帮

1.2学习设计模式的意义

  • 设计模式的本质是面向对象设计原则的实际应用,是对类的封装性、继承性和多态性以及关联关系和组合关系的充分理解。

优点:

  1. 可以提高程序员的思维能力、编程能力、设计能力
  2. 使程序设计更加标准化,代码编程更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期
  3. 使设计的代码可重用性高,可读性强,可靠性高,灵活性好、可维护性强

1.3设计模式的基本要素

  • 模式名称
  • 问题
  • 解决方案
  • 效果

GOF 23

  • 一种思维 一种态度 一种进步
  1. 创建型模式
    • 单列模式、工厂模式、抽象工厂模式、建造者模式、原型模式
  2. 结构性模式
    • 适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
  3. 行为型模式
    • 模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘路模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式

2、OOP七大原则

  1. 开闭原则:对扩展开放,对修改关闭
  2. 里氏替换原则:继承必须确保超类所拥有的的性质在子类中仍然存在
  3. 依赖倒置原则:要面向接口编程,不要面向实现编程。
  4. 单一职责原则: 控制类的粒度大小、将对象解耦、提高其内聚性。
  5. 接口隔离原则: 要为各个类建立它们需要的专用接口。
  6. 迪米特法则:只与你的朋友交谈,不跟“陌生人”说话。
  7. 合成复用原则:尽量先用组合或聚合等关联关系实现,其次才考虑用继承关系实现。

3、单例模式

概念:

java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例、饿汉式单例、登记式单例。

单例模式有以下特点:

  1. 单例只能有一个实例。
  2. 单例类必须自己创建自己的唯一实例。
  3. 单列类必须给其他所有对象提供这一实例。

单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。

3.1懒汉式单例

//懒汉式单例类.在第一次调用的时候实例化自己
public class Singleton {
    private Singleton() {//防止被外部实例化
    }

    private static Singleton single = null;

    //静态工厂方法
    public static Singleton getInstance() {
        if (single == null) {
            single = new Singleton();
        }
        return single;
    }
}

Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。

(事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。)

但是以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全,如果你第一次接触单例模式,对线程安全不是很了解,可以先跳过下面这三小条,去看饿汉式单例,等看完后面再回头考虑线程安全的问题:

1、在getInstance方法上加同步

public static synchronized Singleton getInstance() {
         if (single == null) {  
             single = new Singleton();
         }  
        return single;
}

2、双重检查锁定

public static Singleton getInstance() {
        if (singleton == null) {  
            synchronized (Singleton.class) {  
               if (singleton == null) {  
                  singleton = new Singleton(); 
               }  
            }  
        }  
        return singleton; 
    }

3、静态内部类

public class Singleton {  
    private static class LazyHolder {  
       private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
       return LazyHolder.INSTANCE;  
    }  
} 
//这种比上面1、2都好一些,既实现了线程安全,又避免了同步带来的性能影响。

3.2 饿汉式单例


//饿汉式单例类.在类初始化时,已经自行实例化 
public class Singleton1 {
    private Singleton1() {}
    private static final Singleton1 single = new Singleton1();
    //静态工厂方法 
    public static Singleton1 getInstance() {
        return single;
    }
}//饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。

3.3 **登记式单例(可忽略)

public class Singleton3 {
    private static Map<String,Singleton3> map = new HashMap<String,Singleton3>();
    static{
        Singleton3 single = new Singleton3();
        map.put(single.getClass().getName(), single);
    }
    //保护的默认构造子
    protected Singleton3(){}
    //静态工厂方法,返还此类惟一的实例
    public static Singleton3 getInstance(String name) {
        if(name == null) {
            name = Singleton3.class.getName();
            System.out.println("name == null"+"--->name="+name);
        }
        if(map.get(name) == null) {
            try {
                map.put(name, (Singleton3) Class.forName(name).newInstance());
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        return map.get(name);
    }
    //一个示意性的商业方法
    public String about() {    
        return "Hello, I am RegSingleton.";    
    }    
    public static void main(String[] args) {
        Singleton3 single3 = Singleton3.getInstance(null);
        System.out.println(single3.about());
    }
}
//类似Spring里面的方法,将类名注册,下次从里面直接获取。

说明:
登记式单例实际上维护了一组单例类的实例,将这些实例存放在一个Map(登记薄)中,对于已经登记过的实例,则从Map直接返回,对于没有登记的,则先登记,然后返回。

这里我对登记式单例标记了可忽略,我的理解来说,首先它用的比较少,另外其实内部实现还是用的饿汉式单例,因为其中的static方法块,它的单例在类被装载的时候就被实例化了。
------------------

3.4饿汉式和懒汉式区别

饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。

另外从以下两点再区分以下这两种方式:

  1. 线程安全:
    饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,

    懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是上面的1、2、3, 这三种实现在资源加载和性能方面有些区别。

  2. 资源加载和性能
    饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,

而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。

至于1、2、3这三种实现又有些区别,

第1种,在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的,

第2种,在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗

第3种,利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般我倾向于使用这一种。

什么是线程安全?

如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

或者说:一个类或者程序所提供的接口对于线程来说是原子操作,或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题,那就是线程安全的
应用
以下是一个单例类使用的例子,以懒汉式为例,这里为了保证线程安全,使用了双重检查锁定的方式:

public class TestSingleton {
	String name = null;
 
        private TestSingleton() {
	}
 
	private static volatile TestSingleton instance = null;
 
	public static TestSingleton getInstance() {
           if (instance == null) {  
             synchronized (TestSingleton.class) {  
                if (instance == null) {  
                   instance = new TestSingleton(); 
                }  
             }  
           } 
           return instance;
	}
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
 
	public void printInfo() {
		System.out.println("the name is " + name);
	}

volatile

简介
volatile是Java提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile 变量的同步性较差(有时它更简单并且开销更低),而且其使用也更容易出错。

并发编程的三个基本概念

  1. 原子性
    定义: 即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

    原子性是拒绝多线程操作的,不论是多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作。简而言之,在整个操作过程中不会被线程调度器中断的操作,都可认为是原子性。例如 a=1是原子性操作,但是a++和a +=1就不是原子性操作。Java中的原子性操作包括:

    • (1)基本类型的读取和赋值操作,且赋值必须是值赋给变量,变量之间的相互赋值不是原子性操作。
    • (2)所有引用reference的赋值操作
    • (3)java.concurrent.Atomic.* 包中所有类的一切操作
  2. 可见性
    定义:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

    在多线程环境下,一个线程对共享变量的操作对其他线程是不可见的。Java提供了volatile来保证可见性,当一个变量被volatile修饰后,表示着线程本地内存无效,当一个线程修改共享变量后他会立即被更新到主内存中,其他线程读取共享变量时,会直接从主内存中读取。当然,synchronize和Lock都可以保证可见性。synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。
    3.有序性
    定义:即程序执行的顺序按照代码的先后顺序执行。

    Java内存模型中的有序性可以总结为:如果在本线程内观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无序的。前半句是指“线程内表现为串行语义”,后半句是指“指令重排序”现象和“工作内存主主内存同步延迟”现象。

    在Java内存模型中,为了效率是允许编译器和处理器对指令进行重排序,当然重排序不会影响单线程的运行结果,但是对多线程会有影响。Java提供volatile来保证一定的有序性。最著名的例子就是单例模式里面的DCL(双重检查锁)。另外,可以通过synchronized和Lock来保证有序性,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。

锁的互斥和可见性

锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility)。

(1)互斥即一次只允许一个线程持有某个特定的锁,一次就只有一个线程能够使用该共享数据。

(2)可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的。也即当一条线程修改了共享变量的值,新值对于其他线程来说是可以立即得知的。如果没有同步机制提供的这种可见性保证,线程看到的共享变 量可能是修改前的值或不一致的值,这将引发许多严重问题。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:

a.对变量的写操作不依赖于当前值。

b.该变量没有包含在具有其他变量的不变式中。

实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。事实上就是保证操作是原子性操作,才能保证使用volatile关键字的程序在并发时能够正确执行。

Java的内存模型JMM以及共享变量的可见性

JMM决定一个线程对共享变量的写入何时对另一个线程可见,JMM定义了线程和主内存之间的抽象关系:共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存保存了被该线程使用到的主内存的副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。

https://img-blog.csdn.net/20180613171711160

对于普通的共享变量来讲,线程A将其修改为某个值发生在线程A的本地内存中,此时还未同步到主内存中去;而线程B已经缓存了该变量的旧值,所以就导致了共享变量值的不一致。解决这种共享变量在多线程模型中的不可见性问题,较粗暴的方式自然就是加锁,但是此处使用synchronized或者Lock这些方式太重量级了,比较合理的方式其实就是volatile。

需要注意的是,JMM是个抽象的内存模型,所以所谓的本地内存,主内存都是抽象概念,并不一定就真实的对应cpu缓存和物理内存


4、抽象工厂模式

定义:抽象工厂模式提供了一个创建一系列相关或者相互依赖的接口,无需指定他们具体的类。

适用场景:

  • 客户端(应用层) 不依赖产品类实例如何被创建、实现的细节。
  • 强调一系列相关的产品对象一起使用创建对象需要大量的重复代码
  • 提供一个产品类的库,所有的产品以同样的接口出现,从而使得客户不依赖与具体的实现。

优点:

  • 具体产品在应用层的代码隔离,无需关心创建的细节
  • 将一个系列的产品统一到一起创建

缺点 :

  • 规定乐所有可能被创建的产品集合,产品簇中扩展新的产品难。
  • 增加了系统的抽象性和理解难度。

https://images.cnblogs.com/cnblogs_com/blogs/696945/galleries/2007665/o_210802020249%E4%BA%A7%E5%93%81%E7%AD%89%E7%BA%A7.png

4.1三种模式

核心本质:

  • 实例化对象不使用NEW,用工厂方法代替;

  • 将选择实现类,创建对象统一管理和控制。从而将调用者与我们的实现类解耦。

三种模式

  • 简单工厂模式

  • 工厂方法模式

  • 抽象工厂模式

    • 围绕着一个超级工厂建立其他工厂,该超级工厂又称为其他工厂的工厂。

4.2类图

https://images.cnblogs.com/cnblogs_com/blogs/696945/galleries/2007665/o_210809155328%E6%8A%BD%E8%B1%A1%E5%B7%A5%E5%8E%82%E8%AE%BE%E8%AE%A1%E7%B1%BB%E5%9B%BE.png

csdn 抽象工厂

4.3定义

抽象工厂模式提供了一个创建一系列相关或者相互依赖对象的接口,无需指定他们具体的类

优点:

  1. 具体产品在应用层的代码隔离,无需关系创建的细节
  2. 将一个系列的产品统一到一起创建

缺点:

  1. 规定了所有可能被创建的产品集合,产品族中扩展新的产品困难;
  2. 增加了系统的抽象性和理解难度

适用场景:

  1. 客户端(应用层)不依赖与产品类示例如何被创建、实现等细节;
  2. 强调一系列相关的产品对象(数据同一产品族)一起使用创建对象需要大量的重复代码;
  3. 提供一个产品类的库,所有的产品以同样的接口出现,使得客户端不依赖于具体实现

代码:

router:
  1. 接口:
public interface RouterProduct {
    void start();
    void shutdowm();
    void openWIFY();
    void setting();
}
  1. 华为router
public class HuaweiRouter implements RouterProduct{
    @Override
    public void start() {
        System.out.println("开启华为router");
    }

    @Override
    public void shutdowm() {
        System.out.println("关闭华为router");

    }

    @Override
    public void openWIFY() {
        System.out.println("打开华为WIFY");

    }

    @Override
    public void setting() {
        System.out.println("设置华为WIFY");

    }
}
  1. 小米router
public class XiaomiRouter implements RouterProduct{
    @Override
    public void start() {
        System.out.println("开启小米router");
    }

    @Override
    public void shutdowm() {
        System.out.println("关闭小米router");

    }

    @Override
    public void openWIFY() {
        System.out.println("打开小米WIFY");

    }

    @Override
    public void setting() {
        System.out.println("设置小米WIFY");

    }
}
phone:
  1. 接口
public interface PhoneProduct {
    void start();
    void shutdown();
    void callUp();
    void sendEmail();
}
  1. 华为Phone
public class HuaweiPhone implements PhoneProduct{
    @Override
    public void start() {
        System.out.println("开启华为手机");
    }

    @Override
    public void shutdown() {
        System.out.println("关闭华为手机");

    }

    @Override
    public void callUp() {
        System.out.println("用华为手机打电话");

    }

    @Override
    public void sendEmail() {
        System.out.println("用华为手机发邮件");

    }
}
  1. 小米Phone
public class XiaomiPhone implements PhoneProduct{


    @Override
    public void start() {
        System.out.println("开启小米手机");
    }

    @Override
    public void shutdown() {
        System.out.println("关闭小米手机");

    }

    @Override
    public void callUp() {
        System.out.println("用小米手机打电话");

    }

    @Override
    public void sendEmail() {
        System.out.println("用小米手机发邮件");

    }
}
Factory:
  1. 接口

/**
 * @author 20825
 *抽象产品工厂
 */
public interface ProductFactory {
    /**
     * fetch data by rule id
     *
     * @param
     * @param
     * @param
     * @return Result<XxxxDO>
     */
    PhoneProduct phoneFactory();
    RouterProduct routerFactory();
}
  1. 华为工厂
/**
 * @author 青岚
 */
public class HuaweiFactory implements ProductFactory{
    @Override
    public PhoneProduct phoneFactory() {
        return new HuaweiPhone();
    }

    @Override
    public RouterProduct routerFactory() {
        return new HuaweiRouter();
    }
}
  1. 小米工厂
public class XiaomiFactory implements ProductFactory{
    @Override
    public PhoneProduct phoneFactory() {
        return new XiaomiPhone();
    }

    @Override
    public RouterProduct routerFactory() {
        return new XiaomiRouter();
    }
}
客户端
public class Client {
    public static void main(String[] args) {
        System.out.println("========小米系列产品===========");
        //小米工厂
        XiaomiFactory xiaomiFactory = new XiaomiFactory();
        PhoneProduct xiaomiphoneProduct= xiaomiFactory.phoneFactory();
        RouterProduct xiaomirouterProduct = xiaomiFactory.routerFactory();
        xiaomiphoneProduct.start();
        xiaomirouterProduct.start();
        xiaomirouterProduct.setting();
        xiaomirouterProduct.openWIFY();
        xiaomiphoneProduct.callUp();
        xiaomiphoneProduct.sendEmail();
        xiaomiphoneProduct.shutdown();
        xiaomirouterProduct.shutdowm();



        System.out.println("=========华为系列产品=============");
        HuaweiFactory huaweiFactory = new HuaweiFactory();
        PhoneProduct huaweiphoneProduct = huaweiFactory.phoneFactory();
        RouterProduct huaweirouterProduct = huaweiFactory.routerFactory();
        huaweiphoneProduct.start();
        huaweirouterProduct.start();
        huaweirouterProduct.setting();
        huaweirouterProduct.openWIFY();
        huaweiphoneProduct.callUp();
        huaweiphoneProduct.sendEmail();
        huaweiphoneProduct.shutdown();
        huaweirouterProduct.shutdowm();
    }
}

5、建造者模式

  • 建造者模式也属于创建型模式,他是一种创建对象的最佳模式

定义

  • 将一个复杂对象的构建与他的表示分离,使得同样的构建过程可以创建不同的表示。

主要作用:

  • 在用户不知道对象创建过程和细节的情况下就可以直接创建复杂的对象。

实例

  • 工厂(建造者模式):负责制造汽车,
  • 汽车购买者:用户只要提供汽车型号即可

类图:

建造者模式

  • Product产品类:通常是实现了模板方法模式,也就是有模板方法和基本方法
  • Builder抽象建造者:规范产品的组建,一般是由子类实现。
  • ConcreteBuilder具体建造者:实现抽象类定义的所有方法,并且返回一个组建好的对象。
  • Director导演类:负责安排已有模块的顺序,然后告诉Builder开始建造

5.1 代码

抽象建造者:

public abstract class Builder {
    public abstract Builder builderA(String msg);//汉堡

    public abstract Builder builderB(String msg);//可乐

    public abstract Builder builderC(String msg);//薯条

    public abstract Builder builderD(String msg);//甜点

    public abstract Product getProduct();
}

产品:

package com.q.k_builder;

public class Product {
    private String builderA = "汉堡";
    private String builderB = "可乐";
    private String builderC = "薯条";
    private String builderD = "甜点";

    public String getBuilderA() {
        return builderA;
    }

    public void setBuilderA(String builderA) {
        this.builderA = builderA;
    }

    public String getBuilderB() {
        return builderB;
    }

    public void setBuilderB(String builderB) {
        this.builderB = builderB;
    }

    public String getBuilderC() {
        return builderC;
    }

    public void setBuilderC(String builderC) {
        this.builderC = builderC;
    }

    public String getBuilderD() {
        return builderD;
    }

    public void setBuilderD(String builderD) {
        this.builderD = builderD;
    }

    @Override
    public String toString() {
        return "Product{" +
                "builderA='" + builderA + '\'' +
                ", builderB='" + builderB + '\'' +
                ", builderC='" + builderC + '\'' +
                ", builderD='" + builderD + '\'' +
                '}';
    }
}

具体的建造者:

package com.q.k_builder;

public class Worker extends Builder{
    private Product product;
    public Worker() {
        product = new Product();
    }

    @Override
    public Builder builderA(String msg) {
        product.setBuilderA(msg);
        return this;
    }

    @Override
    public Builder builderB(String msg) {
        product.setBuilderB(msg);
        return this;
    }

    @Override
    public Builder builderC(String msg) {
        product.setBuilderC(msg);
        return this;
    }

    @Override
    public Builder builderD(String msg) {
        product.setBuilderD(msg);
        return this;
    }

    @Override
    public Product getProduct() {
        return product;
    }
}

测试:

public class Test {
    public static void main(String[] args) {
        Worker worker = new Worker();
        Product product = worker.getProduct();
        worker.builderA("香辣汉堡");
        System.out.println(product);
    }
}

5.2 使用场景

  • 相同的方法,不同的执行顺序,产生不同的事件结果时,可以采用建造者模式。
  • 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时,则可 以使用该模式。
  • 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,这个时候使用建 造者模式非常合适。
  • 在对象创建过程中会使用到系统中的一些其他对象,这些对象在产品对象的创建过程 中不易得到时,也可以采用建造者模式封装该对象的创建过程。

注意事项:

  • 建造者模式关注的是零件类型和装配工艺(顺序),这是它与工厂方法模式最大不同的 地方,虽然同为创建类模式,但是注重点不同。

6、原型模式

定义: 用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。
克隆:(拷贝) 以某个对象为原型,进行复制,提高效率

  • Prototype
  • Cloneable
  • clone()方法

6.1 类图

原型模式

原型模式主要用于对象的复制,它的核心是就是类图中的原型类Prototype。Prototype类需要具备以下两个条件

实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。
重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此,Prototype类需要将clone方法的作用域修改为public类型。
原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式。在实际应用中,原型模式很少单独出现。经常与其他模式混用,他的原型类Prototype也常用抽象类来替代。

6.2 代码

克隆(浅克隆):

package com.q.prototype;

import java.util.Date;

public class Video implements Cloneable{//克隆别人的视频
    private String name;
    private Date createTime;

    public Video() {
    }

    public Video(String name, Date createTime) {
        this.name = name;
        this.createTime = createTime;
    }

    public String getName() {
        return name;
    }

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

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    @Override
    public String toString() {
        return "Video{" +
                "name='" + name + '\'' +
                ", createTime=" + createTime +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

测试:

package com.q.prototype;

import java.util.Date;

/*
* 客户端: 克隆*/
public class Bilibili {
    public static void main(String[] args) throws CloneNotSupportedException {
       Video video1 = new Video("qinglan",new Date());
        System.out.println(video1);
        System.out.println(video1.hashCode());


        System.out.println("==========================");
        Video video2 = (Video) video1.clone();
        System.out.println(video2);
        System.out.println(video2.hashCode());
    }
}

深克隆:

 @Override
    protected Object clone() throws CloneNotSupportedException {
        Object obj = super.clone();
        Video video = (Video) obj;
        video.createTime =(Date) this.createTime.clone();
        return obj;
    }

7、结构型模式

作用:

  • 从程序的结构上实现松耦合,从而扩大整体的类结构,用来解决更大的问题。

7.1 适配器模式

定义:

  • 将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。

代码

public class Adapee {
    public void request(){
        System.out.println("连接网线上网");

    }
}
public class Adapter extends Adapee implements NetToUSB{

    @Override
    public void handRequest() {
        super.request();
    }
}

public class Adapter2 implements NetToUSB{
    private Adapee adapee;
    public Adapter2(Adapee adapee){
        this.adapee=adapee;
    }
    @Override
    public void handRequest() {
        adapee.request();
    }
}

public class Computer {
    public void net(NetToUSB adapter){
        //上网功能的具体实现
        adapter.handRequest();
    }

    public static void main(String[] args) {
        Computer computer = new Computer();
        Adapee adapee = new Adapee();
        Adapter adapter = new Adapter();
        computer.net(adapter);

        Adapter2 adapter2 = new Adapter2(adapee);
        computer.net(adapter2);
    }
}

public interface NetToUSB {
    public void handRequest();
}

7.2 桥接模式(Bridge)

桥接模式是将它的抽象部分与它的实现部分分离,使它们都可以独立的变化。它是一种对象结构型模式,又称为柄体模式或接口模式。

单一职责原则

减少继承的出现。

代码

public interface Brand {
    void info();
}
public class Apple implements Brand{
    @Override
    public void info() {
        System.out.println("苹果");
    }
}
package com.q.bridge;

public abstract class Computer {
    protected Brand brand;
    public Computer(Brand brand){
        this.brand = brand;
    }
    public void info(){
        brand.info();
    }
}
package com.q.bridge;

public class Desktop extends Computer{
    public Desktop(Brand brand) {
        super(brand);
    }

    @Override
    public void info() {
        super.info();
        System.out.println("台式机");
    }
}
package com.q.bridge;

public class Laptop extends Computer{

    public Laptop(Brand brand) {
        super(brand);
    }

    @Override
    public void info() {
        super.info();
        System.out.println("笔记本");
    }
}
package com.q.bridge;
//联想
public class Lenovo implements Brand {

    @Override
    public void info() {
        System.out.println("联想");
    }
}
package com.q.bridge;

public class Test {
    public static void main(String[] args) {
        //苹果 联想笔记本
        Computer computer = new Desktop(new Apple());
        Computer computer1 = new Laptop(new Apple());
        computer.info();
        computer1.info();
    }
}

8、静态代理模式

8.1 代理模式

代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。简单的说就是,我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。

代理模式

8.2 静态代理

静态代理:

由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口、被代理类、代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。

静态代理简单实现

根据上面代理模式的类图,来写一个简单的静态代理的例子:假如一个班的同学要向老师交班费,但是都是通过班长把自己的钱转交给老师。这里,班长代理学生上交班费,班长就是学生的代理。

1.确定创建接口具体行为

首先,我们创建一个Person接口。这个接口就是学生(被代理类),和班长(代理类)的公共接口,他们都有上交班费的行为。这样,学生上交班费就可以让班长来代理执行。

/**

 * 创建Person接口
   */
    public interface Person {
   //上交班费
   void giveMoney();
    }
2.被代理对象实现接口,完成具体的业务逻辑
Student类实现Person接口。Student可以具体实施上交班费的动作:
public class Student implements Person {
    private String name;
    public Student(String name) {
        this.name = name;
    }
    

    @Override
    public void giveMoney() {
       System.out.println(name + "上交班费50元");
    }

}
3.代理类实现接口,完成委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。

StudentsProxy类,这个类也实现了Person接口,但是还另外持有一个学生类对象。由于实现了Peson接口,同时持有一个学生对象,那么他可以代理学生类对象执行上交班费(执行giveMoney()方法)行为。

/**
 * 学生代理类,也实现了Person接口,保存一个学生实体,这样既可以代理学生产生行为
 * @author Gonjan
 *
    */
  public class StudentsProxy implements Person{
    //被代理的学生
    Student stu;
    
    public StudentsProxy(Person stu) {
        // 只代理学生对象
        if(stu.getClass() == Student.class) {
            this.stu = (Student)stu;
        }
    }
    
    //代理上交班费,调用被代理学生的上交班费行为
    public void giveMoney() {
        stu.giveMoney();
    }
  }
4.客户端使用操作与分析
public class StaticProxyTest {
  public static void main(String[] args) {
      //被代理的学生张三,他的班费上交有代理对象monitor(班长)完成
      Person zhangsan = new Student("张三");
      
      //生成代理对象,并将张三传给代理对象
      Person monitor = new StudentsProxy(zhangsan);
      
      //班长代理上交班费
      monitor.giveMoney();
  }
}

这里并没有直接通过张三(被代理对象)来执行上交班费的行为,而是通过班长(代理对象)来代理执行了,这就是代理模式。

代理模式最主要的就是有一个公共接口(Person),一个具体的类(Student),一个代理类(StudentsProxy),代理类持有具体类的实例,代为执行具体类实例方法。上面说到,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。就这个例子来说,加入班长在帮张三上交班费之前想要先反映一下张三最近学习有很大进步,通过代理模式很轻松就能办到:

public class StudentsProxy implements Person{
    //被代理的学生
    Student stu;
    
    public StudentsProxy(Person stu) {
        // 只代理学生对象
        if(stu.getClass() == Student.class) {
            this.stu = (Student)stu;
        }
    }
    
    //代理上交班费,调用被代理学生的上交班费行为
    public void giveMoney() {
        System.out.println("张三最近学习有进步!");
        stu.giveMoney();
    }
}

只需要在代理类中帮张三上交班费之前,执行其他操作就可以了。这种操作,也是使用代理模式的一个很大的优点。最直白的就是在Spring中的面向切面编程(AOP),我们能在一个切点之前执行一些操作,在一个切点之后执行一些操作,这个切点就是一个个方法。这些方法所在类肯定就是被代理了,在代理过程中切入了一些其他操作。其实也是切面思想的主要思路,这个后面会出一篇博客,并且举例怎么使用,想法是:对controller层出入参进行检验和必要的操作等,Spring源码里面也有许多这样的例子,后期有时间整理。

8.3 动态代理

(一)动态代理

代理类在程序运行时创建的代理方式被成为动态代理。

我们上面静态代理的例子中,代理类(studentProxy)是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。 比如说,想要在每个代理的方法前都加上一个处理方法:

public void giveMoney() {
        //调用被代理方法前加入处理方法
        beforeMethod();
        stu.giveMoney();
    }

这里只有一个giveMoney方法,就写一次beforeMethod方法,但是如果除了giveMonney还有很多其他的方法,那就需要写很多次beforeMethod方法,麻烦。所以建议使用动态代理实现。

(二)动态代理简单实现

在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。

1.确定创建接口具体行为

首先,我们创建一个Person接口。这个接口就是学生(被代理类),和班长(代理类)的公共接口,他们都有上交班费的行为。这样,学生上交班费就可以让班长来代理执行。

/**
 * 创建Person接口
 */
 public interface Person {
    //上交班费
    void giveMoney();
 }
2.被代理对象实现接口,完成具体的业务逻辑(此处增加一些方法用于检测后面使用动态代理用于区分)

Student类实现Person接口。

public class Student implements Person {
    private String name;
    public Student(String name) {
        this.name = name;
    }
    
    @Override
    public void giveMoney() {
        try {
            //假设数钱花了一秒时间
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
       System.out.println(name + "上交班费50元");
    }
}

再定义一个检测方法执行时间的工具类,在任何方法执行前先调用start方法,执行后调用finsh方法,就可以计算出该方法的运行时间,这也是一个最简单的方法执行时间检测工具。

public class MonitorUtil {
    
    private static ThreadLocal<Long> tl = new ThreadLocal<>();
    
    public static void start() {
        tl.set(System.currentTimeMillis());
    }
    
    //结束时打印耗时
    public static void finish(String methodName) {
        long finishTime = System.currentTimeMillis();
        System.out.println(methodName + "方法耗时" + (finishTime - tl.get()) + "ms");
    }
}
3.代理类实现接口,完成委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。

创建StuInvocationHandler类,实现InvocationHandler接口,这个类中持有一个被代理对象的实例target。InvocationHandler中有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法。在invoke方法中执行被代理对象target的相应方法。在代理过程中,我们在真正执行被代理对象的方法前加入自己其他处理。这也是Spring中的AOP实现的主要原理,这里还涉及到一个很重要的关于java反射方面的基础知识。

public class StuInvocationHandler<T> implements InvocationHandler {
    //invocationHandler持有的被代理对象
    T target;
    
    public StuInvocationHandler(T target) {
       this.target = target;
    }
    
    /**
     * proxy:代表动态代理对象
     * method:代表正在执行的方法
     * args:代表调用目标方法时传入的实参
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理执行" +method.getName() + "方法");  
        //代理过程中插入监测方法,计算该方法耗时
        MonitorUtil.start();
        Object result = method.invoke(target, args);
        MonitorUtil.finish(method.getName());
        return result;
    }
}
4.客户端使用操作与分析
public class ProxyTest {
    public static void main(String[] args) {
        
        //创建一个实例对象,这个对象是被代理的对象
        Person zhangsan = new Student("张三");
        
        //创建一个与代理对象相关联的InvocationHandler
        InvocationHandler stuHandler = new StuInvocationHandler<Person>(zhangsan);
        
        //创建一个代理对象stuProxy来代理zhangsan,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
        Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);
     
       //代理执行上交班费的方法
        stuProxy.giveMoney();
    }
}

创建了一个需要被代理的学生张三,将zhangsan对象传给了stuHandler中,我们在创建代理对象stuProxy时,将stuHandler作为参数了的,上面也有说到所有执行代理对象的方法都会被替换成执行invoke方法,也就是说,最后执行的是StuInvocationHandler中的invoke方法。

动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了。例如,这里的方法计时,所有的被代理对象执行的方法都会被计时,然而我只做了很少的代码量。

动态代理的过程,代理对象和被代理对象的关系不像静态代理那样一目了然,清晰明了。因为动态代理的过程中,我们并没有实际看到代理类,也没有很清晰地的看到代理类的具体样子,而且动态代理中被代理对象和代理对象是通过InvocationHandler来完成的代理过程的,其中具体是怎样操作的,为什么代理对象执行的方法都会通过InvocationHandler中的invoke方法来执行。带着这些问题,我们就需要对java动态代理的源码进行简要的分析,弄清楚其中缘由。

四、动态代理原理分析

我们利用Proxy类的newProxyInstance方法创建了一个动态代理对象,查看该方法的源码,发现它只是封装了创建动态代理类的步骤:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,
                          InvocationHandler h) throws IllegalArgumentException {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
     
        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);
     
        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
     
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

重点是这四处位置:

final Class<?>[] intfs = interfaces.clone();
Class<?> cl = getProxyClass0(loader, intfs);
final Constructor<?> cons = cl.getConstructor(constructorParams);
return cons.newInstance(new Object[]{h});

最应该关注的是 Class<?> cl = getProxyClass0(loader, intfs);这句,这里产生了代理类,后面代码中的构造器也是通过这里产生的类来获得,可以看出,这个类的产生就是整个动态代理的关键,由于是动态生成的类文件,我这里不具体进入分析如何产生的这个类文件,只需要知道这个类文件时缓存在java虚拟机中的,我们可以通过下面的方法将其打印到文件里面,一睹真容:

byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0",Student.class.getInterfaces());
String path = "G:/javacode/javase/Test/bin/proxy/StuProxy.class";
try(FileOutputStream fos = new FileOutputStream(path)) {
    fos.write(classFile);
    fos.flush();
    System.out.println("代理类class文件写入成功");
} catch (Exception e) {
    System.out.println("写文件错误");
}

对这个class文件进行反编译,我们看看jdk为我们生成了什么样的内容:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.Person;

public final class $Proxy0 extends Proxy implements Person {
  private static Method m1;
  private static Method m2;
  private static Method m3;
  private static Method m0;

  /**
  *注意这里是生成代理类的构造方法,方法参数为InvocationHandler类型,看到这,是不是就有点明白
  *为何代理对象调用方法都是执行InvocationHandler中的invoke方法,而InvocationHandler又持有一个
  *被代理对象的实例,不禁会想难道是....? 没错,就是你想的那样。
  *
  *super(paramInvocationHandler),是调用父类Proxy的构造方法。
  *父类持有:protected InvocationHandler h;
  *Proxy构造方法:
  *    protected Proxy(InvocationHandler h) {
  *         Objects.requireNonNull(h);
  *         this.h = h;
  *     }
    *
    */
    public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
    {
    super(paramInvocationHandler);
    }

  //这个静态块本来是在最后的,我把它拿到前面来,方便描述
   static
  {
    try
    {
      //看看这儿静态块儿里面有什么,是不是找到了giveMoney方法。请记住giveMoney通过反射得到的名字m3,其他的先不管
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m3 = Class.forName("proxy.Person").getMethod("giveMoney", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }

  /**
  * 
    *这里调用代理对象的giveMoney方法,直接就调用了InvocationHandler中的invoke方法,并把m3传了进去。
    *this.h.invoke(this, m3, null);这里简单,明了。
    *来,再想想,代理对象持有一个InvocationHandler对象,InvocationHandler对象持有一个被代理的对象,
    *再联系到InvacationHandler中的invoke方法。嗯,就是这样。
    */
    public final void giveMoney()
    throws 
    {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
    }

  //注意,这里为了节省篇幅,省去了toString,hashCode、equals方法的内容。原理和giveMoney方法一毛一样。

}

jdk为我们的生成了一个叫$Proxy0(这个名字后面的0是编号,有多个代理类会一次递增)的代理类,这个类文件时放在内存中的,我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建的代理实例。通过对这个生成的代理类源码的查看,我们很容易能看出,动态代理实现的具体过程。

我们可以对InvocationHandler看做一个中介类,中介类持有一个被代理对象,在invoke方法中调用了被代理对象的相应方法。通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。

代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能。

总结:生成的代理类:$Proxy0 extends Proxy implements Person,我们看到代理类继承了Proxy类,所以也就决定了java动态代理只能对接口进行代理,Java的继承机制注定了这些动态代理类们无法实现对class的动态代理。上面的动态代理的例子,其实就是AOP的一个简单实现了,在目标对象的方法执行之前和执行之后进行了处理,对方法耗时统计。Spring的AOP实现其实也是用了Proxy和InvocationHandler这两个东西的。

五、InvocationHandler接口和Proxy类详解

InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法。
看下官方文档对InvocationHandler接口的描述:

{@code InvocationHandler} is the interface implemented by
     the <i>invocation handler</i> of a proxy instance.
     <p>Each proxy instance has an associated invocation handler.
     When a method is invoked on a proxy instance, the method
     invocation is encoded and dispatched to the {@code invoke}
     method of its invocation handler.

当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用,看如下invoke方法:

    /**
    * proxy:代理类代理的真实代理对象com.sun.proxy.$Proxy0
    * method:我们所要调用某个对象真实的方法的Method对象
    * args:指代代理对象方法传递的参数
    */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
Proxy类就是用来创建一个代理对象的类,它提供了很多方法,但是我们最常用的是newProxyInstance方法。

public static Object newProxyInstance(ClassLoader loader, 
                                            Class<?>[] interfaces, 
                                            InvocationHandler h)
这个方法的作用就是创建一个代理类对象,它接收三个参数,我们来看下几个参数的含义:

loader:一个classloader对象,定义了由哪个classloader对象对生成的代理类进行加载
interfaces:一个interface对象数组,表示我们将要给我们的代理对象提供一组什么样的接口,如果我们提供了这样一个接口对象数组,那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。
h:一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用。

六、JDK动态代理和CGLIB动态代理代码示例比较与总结

(一)定义创建用户管理接口

/**

  • 用户管理接口
    */
    public interface UserManager {
    //新增用户抽象方法
    void addUser(String userName, String password);
    //删除用户抽象方法
    void delUser(String userName);
    }
(二)用户管理实现类,实现用户管理接口(被代理的实现类)

/**

  • 用户管理实现类,实现用户管理接口(被代理的实现类)
    */
    public class UserManagerImpl implements UserManager{

    //重写用户新增方法
    @Override
    public void addUser(String userName, String password) {
    System.out.println("调用了用户新增的方法!");
    System.out.println("传入参数:\nuserName = " + userName +", password = " + password);
    }

    //重写删除用户方法
    @Override
    public void delUser(String userName) {
    System.out.println("调用了删除的方法!");
    System.out.println("传入参数:\nuserName = "+userName);
    }
    }

(三)采用JDK代理实现:JDK动态代理实现InvocationHandler接口

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

/**

  • JDK动态代理实现InvocationHandler接口
    */
    public class JdkProxy implements InvocationHandler {

    private Object targetObject; //需要代理的目标对象

    //定义获取代理对象的方法(将目标对象传入进行代理)
    public Object getJDKProxy(Object targetObject){
    //为目标对象target赋值
    this.targetObject = targetObject;
    //JDK动态代理只能针对实现了接口的类进行代理,newProxyInstance 函数所需参数就可看出
    Object proxyObject = Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(),this);
    //返回代理对象
    return proxyObject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("JDK动态代理,监听开始!");
    // 调用invoke方法,result存储该方法的返回值
    Object result = method.invoke(targetObject,args);
    System.out.println("JDK动态代理,监听结束!");
    return result;
    }

// public static void main(String[] args) {
// JdkProxy jdkProxy = new JdkProxy(); //实例化JDKProxy对象
// UserManager user = (UserManager) jdkProxy.getJDKProxy(new UserManagerImpl()); //获取代理对象
// user.addUser("admin","123456");
// }
}

(四)采用CGLIB代理实现:需要导入asm版本包,实现MethodInterceptor接口

import com.proxy.UserManager;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**

  • Cglib动态代理:
  • (需要导入两个jar包,asm-5.0.3.jar,cglib-3.1.jar 版本可自行选择)
    */

//Cglib动态代理,实现MethodInterceptor接口
public class CglibProxy implements MethodInterceptor {
private Object target;//需要代理的目标对象

//重写拦截方法
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    System.out.println("Cglib动态代理,监听开始!");
    Object result = method.invoke(target,args);//方法执行参数:target 目标对象 arr参数数组
    System.out.println("Cglib动态代理,监听结束!");
    return result;
}
 
//定义获取代理对象的方法
public UserManager getCglibProxy(Object targetObject) {
    this.target = targetObject;//为目标对象target赋值
    Enhancer enhancer = new Enhancer();
    //设置父类,因为Cglib是针对指定的类生成一个子类,所以需要指定父类
    enhancer.setSuperclass(targetObject.getClass()); //UserManagerImpl
    enhancer.setCallback(this);//设置回调
    Object result = enhancer.create();//创建并返回代理对象
    return (UserManager) result;
}

// public static void main(String[] args) {
// CglibProxy cglibProxy = new CglibProxy(); //实例化CglibProxy对象
// UserManager user = cglibProxy.getCglibProxy(new UserManagerImpl());//获取代理对象
// user.delUser("admin");
// }

}

(五)客户端调用测试与结果

import com.proxy.CglibProxy.CglibProxy;
import com.proxy.JDKProxy.JdkProxy;

public class ClientTest {
public static void main(String[] args) {

    JdkProxy jdkProxy = new JdkProxy();  //实例化JDKProxy对象
    UserManager userJdk = (UserManager) jdkProxy.getJDKProxy(new UserManagerImpl());   //获取代理对象
    userJdk.addUser("admin","123456");
 
    CglibProxy cglibProxy = new CglibProxy(); //实例化CglibProxy对象
    UserManager userCglib = cglibProxy.getCglibProxy(new UserManagerImpl());//获取代理对象
    userCglib.delUser("admin");
}

}
运行结果:K动态代理,监听开始!
调用了用户新增的方法!
传入参数:
userName = admin, password = 123456
JDK动态代理,监听结束!
Cglib动态代理,监听开始!
调用了删除的方法!
传入参数:
userName = admin
Cglib动态代理,监听结束!

(六)JDK和CGLIB动态代理总结

JDK动态代理只能对实现了接口的类生成代理,而不能针对类 ,使用的是 Java反射技术实现,生成类的过程比较高效。
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 ,使用asm字节码框架实现,相关执行的过程比较高效,生成类的过程可以利用缓存弥补,因为是继承,所以该类或方法最好不要声明成final
JDK代理是不需要第三方库支持,只需要JDK环境就可以进行代理,使用条件:实现InvocationHandler + 使用Proxy.newProxyInstance产生代理对象 + 被代理的对象必须要实现接口
CGLib必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类,覆盖其中的方法,是一种继承但是针对接口编程的环境下推荐使用JDK的代理;

posted @ 2021-08-14 13:26  致非  阅读(65)  评论(0)    收藏  举报