Java基础——核心设计模式

Java
242
0
0
2023-09-27

设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现实中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。

一、GOF23设计模式简介

经典设计模式是 GOF23,也就是有 23 种设计模式。对于我们学习和开发,实际上掌握 10 多个常用的就绰绰有余了。学习设计模式,在于通过学习它更深刻的理解面向对象,打开新的思维方式,体验经典设计之美!

总体来说,这23种设计模式可以分为三大类:

创建型模式

结构型模式

行为型模式

抽象工厂模式

工厂方法模式

单例模式

建造者模式

原型模式

适配器模式、桥接模式、

组合模式 、装饰模式、

外观模式、享元模式、

代理模式

责任链模式 、解释器模式、

模板方法模式、命令模式、

迭代器模式、中介者模式、

备忘录模式、观察者模式、

状态模式、策略模式、

访问者模式

用来帮助我们创建对象

帮助我们组织对象和类

关注对象之间的交互,相互通信和协作

二、单例模式

单例模式 的核心作用是:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。

常见的单例模式应用场景

1. Windows 的 Task Manager(任务管理器)就是很典型的单例模式。

2. windows 的 Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。

3. 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,每次 new 一个对象去读取。

4. 网站的 计数器 ,一般也是采用单例模式实现,否则难以同步。

5. 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。

6. 数据库连接池 的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。

7. 操作系统的文件系统,也是单例模式实现的具体例子,一个操作系统只能有一个文件系统。

8. Application 也是单例的典型应用(Servlet 编程中会涉及到)。

9. 在 Spring 中,每个 Bean 默认就是单例的,这样做的优点是 Spring 容器可以管理。

10. 在 Servlet 编程中,每个 Servlet 也是单例。

11. 在 Spring MVC 框架/Struts1 框架中,控制器对象也是单例。

2.1饿汉式

饿汉式单例模式的特点是: 线程安全,调用效率高。 但是,不能延时加载。

饿汉式单例模式有三个 要点

1. 将单例对象设置成 static ,并且在类初始化时立刻创建对象。形象的比喻成“饿汉式”,意思就是“单例对象马上就创建,立刻加载,不等不拖”。 饿汉式单例模式代码中,static 变量会在类装载时初始化,此时也不会涉及多个 线程 对象访问该对象的问题。 虚拟机 保证只会装载一次该类,肯定不会发生并发访问的问题。因此,可以省略 synchronized 关键字。

2. 私有化构造器。这样就防止外部调用构造器创建本类的多个对象。

3. getInstance ()方法是提供给外部的唯一方法,只能通过这个方法获得本类的对象。

 package cn.pxy.test;
/**
 * 测试饿汉式单例模式
 * @author 胖咸鱼
 *
 */public class SingletonDemo {
    //类初始化时,立即加载这个对象(没有延时加载)。加载类时,线程安全
    private static SingletonDemo instance=new SingletonDemo1();
    //构造器私有化,外部不能调用
    private SingletonDemo() {

    }
    //方法没有同步,调用效率高
    public static SingletonDemo getInstance() {
        return instance;
    }
} 

2.2懒汉式

如果单例对象需要占用很大的内存,那么一开始就初始化该对象,会占用大量的内存。此时,有人就想,如果不在类加载的时候初始化,而是在想用的时候再初始化该对象,像懒汉一样,只有用到的时候再初始化,行不行呢?答案是肯定的。

懒汉式单例模式的特点:线程安全,调用效率不高。 但是,可以延时加载。

懒汉式单例模式 3 个 要点

1. 单例对象作为属性,一开始为空,不进行创建。只有在调用 getInstance()方法时才创建。因此,称为“懒汉式”。

2. 构造器私有化,防止外部调用。

3. getInstance()方法作为外部唯一调用接口。为了解决并发时可能创建多个对象的情况,必须在方法上增加 synchronized 进行同步处理。因此,也造成了调用效率较低的问题。

 package cn.pxy.test;
/**
 * 测试懒汉式单例模式
 * @author 胖咸鱼
 *
 */public class SingletonDemo {
    //类初始化时,不初始化这个对象(延时加载,真正用的时候在创建)
    private static SingletonDemo instance;
    //构造器私有化
    private SingletonDemo() {

    }
    //方法同步,调用效率低
    public static synchronized SingletonDemo getInstance() {
        if(instance==null) {
            instance=new SingletonDemo();
        }
        return instance;
    }
} 

2.3静态内部类式

由于加载一个类时,其内部类不会同时被加载。当且仅当内部类的某个静态成员(静态域、构造器、 静态方法 等)被调用时才会加载该内部类。

并且 JVM 会保证类加载的线程安全问题,所以利用这个特性我们可以写出兼顾效率与线程安全的优化版本,即静态内部类式单例模式。

静态内部类式单例模式特点:线程安全,调用效率高。 而且,可以延时加载。需要延时加载的情况下,这种实现方式,优于“懒汉式”。

静态内部类式单例模式有三个 要点

1. 外部类没有 static 属性,则不会像饿汉式那样立即加载对象。

2. 只有真正调用 getInstance(),才会加载静态内部类。加载类时是线程安全的。instance 是 static final 类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性。

3. 兼备了并发高效调用和延迟加载的优势!

 package cn.pxy.test;
/**
 * 测试静态内部类单例模式
 * @author 胖咸鱼
 *
 */public class SingletonDemo {
    //只有在用到内部类的静态属性时,才会加载,可实现延时加载
    private static class SingletonClassInstance{
        private static final SingletonDemo instance=new SingletonDemo1();
    }

    private SingletonDemo() {

    }
    //方法没有同步,调用效率低
    public static SingletonDemo getInstance() {
        return SingletonClassInstance.instance;
    }
} 

2.4枚举式单例

JDK 1.5 以后,在不需要延时加载的情况下,单元素的枚举类型已经成为实现 Singleton的最佳方法。

枚举式单例模式特点:线程安全,调用效率高,不能延时加载。

枚举单例的 要点 如下:

1. 实现简单。

2. 枚举本身就是单例模式。由 JVM 从根本上提供保障!避免通过反射和反序列化的漏洞!

3. 没有延时加载。

 package cn.pxy.test;
/**
 * 测试枚举式单例模式
 * @author 胖咸鱼
 *
 */public class SingletonDemo {
    //这个枚举元素,本身就是单例对象
    enum EnumSingleton{
    INSTANCE;
    public  void  doSomeThing(){
    }
    }
    //添加自己需要的操作
    public void singletonOperation() {

    }
    public static void main(String[] args) {

    }
} 

2.5四种单例模式如何选择

根据开发场景,如果单例对象占用资源少,不需要延时加载:

枚举式 优于 饿汉式(枚举式天然地可以防止反射和序列化漏洞!)。

如果单例对象占用资源大,需要延时加载:

静态内部类 优于 懒汉式 (静态内部类调用效率远远高于懒汉式)。

三、工厂模式

工厂模式 :实现了创建者和调用者的分离。详细分类: 简单工厂模式 、工厂方法模式、抽象工厂模式。 我们只需要理解简单工厂模式即可。

工厂模式应用场景如下:

1. JDK 中 Calendar 的 getInstance 方法。

2. JDBC 中 Connection 对象的获取。

3. Hibernate 中 SessionFactory 创建 Session。

4. Spring 中 IOC 容器创建管理 bean 对象。

5. XML 解析时的 Document BuilderFactory 创建解析器对象。

6. 反射中 Class 对象的 newInstance()。

简单工厂模式:

用工厂方法代替 new 操作。将选择实现类、创建对象统一管理和控制。

示例:

创建接口与实现类:

 package cn.pxy.test;

public interface Car {
    void run();
} 
 package cn.pxy.test;

public class Audi implements Car{

    @Override
    public void run() {
        System.out.println("奥迪在跑。。。");

    }

} 
 package cn.pxy.test;

public class Byd implements Car{
    public void run() {
        System.out.println("比亚迪在跑。。。");
    }
} 

创建对象(未使用简单工厂模式)

 package cn.pxy.test;
/**
 * 测试在没有工厂模式的情况下
 * @author 胖咸鱼
 *
 */public class Client {//调用者
    public static void main(String[] args) {
        Car c=new Audi();
        Car c=new Byd();
        c.run();
        c.run();
    }
} 

UML图如图所示:

我们发现调用者 Client01 需要和 Car、 Byd 、 Audi 三个类打交道,如果我们要增加奔驰类 Benz,则 Client01 也需要知道新增 Benz 这个类。显然,调用者和三个类的关系耦合性太强,这违反了“开闭原则”(对修改关闭,对扩展开放)。

那接下来看看使用简单工厂模式后,会达到怎样的效果:

 package cn.pxy.test;
/**
 * 汽车工厂类,可以创建各种汽车对象
 * @author 胖咸鱼
 *
 */public class CarFactory {
    public static Car createCar(String type){
        Car c=null;
        switch (type) {
            case "奥迪":
                c=new Audi();
               break;
            case "比亚迪":
                c=new Byd();
                break;
        }
        return c;
    }
} 
 package cn.pxy.test;

public class Client {//调用者
    public static void main(String[] args) {
        CarFactory.createCar("奥迪").run();
    }

} 

运行结果:

UML图:

我们发现,调用者 Client 02 只需要知道工厂类 CarFactory 和 Car 接口就可以了,至于下面有多少个实现类,和调用者无关。当我们需要车对象时,只需要和 CarFactory 打交道,获得新创建的对象即可。

四、装饰模式

装饰模式(Decorator)的核心作用:动态的为一个对象增加新的功能。(也叫包装器模式Wrapper)。

装饰模式是一种用于代替继承的技术,无须通过继承增加子类就能扩展对象的新功能。使用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀。

开发中的使用场景如下:

1. IO 中输入流和输出流的设计。

2. Swing 包中图形界面构件功能。

3. Servlet API 中提供了一个 request 对象的 Decorator 设计模式的默认实现类HttpServletRequestWrapper,HttpServletRequestWrapper 类,增强了request 对象的功能。

4. Struts2 中,request,response,session 对象的处理。

 package cn.pxy.test;

public interface ICar {
    void move();
}
class Car implements ICar{

    @Override
    public void move() {
        System.out.println("陆地上跑");
    }
}

class SuperCar implements ICar{
    protected ICar car;
    @Override
    public void move() {
        car.move();
    }
    public SuperCar(ICar car) {
        this.car=car;
    }
}
class FlyCar  extends  SuperCar{

    public FlyCar(ICar car) {
        super(car);
    }
    public void fly() {
        System.out.println("天上飞!");
    }
    public void move() {
        super.move();
        fly();
    }
}
class WaterCar extends SuperCar{
    public WaterCar(ICar car) {
        super(car);
        // TODO 自动生成的构造函数存根
    }
    public void swim() {
        System.out.println("水上游!");
    }
    public void move() {
        super.move();
        swim();
    }
}
class AICar extends SuperCar{

    public AICar(ICar car) {
        super(car);
    }
    public void autoMove() {
        System.out.println("自动驾驶!");
    }
    public void move() {
        super.move();
        autoMove();
    }
} 

装饰体系UML图:

Java基础——核心设计模式

概念说明


ICar

抽象构建角色。 真实角色和装饰角色需要实现这个统一的接口

Car

真实角色(被装饰的角色)

SuperCar

装饰角色(用来装饰真实角色)

FlyCar、WaterCar、AICar

具体的装饰角色

测试装饰器模式:

 package cn.pxy.test;

public class Client {
    public static void main(String[] args) {
        Car car=new Car();
        car.move();

        System.out.println("新增飞行功能");
        ICar flycar=new FlyCar(car);
        flycar.move();

        System.out.println("新增水里游功能");
        ICar waterCar=new WaterCar(car);
        waterCar.move();

        System.out.println("增加飞行、游泳功能");
        ICar waterCar=new WaterCar(new FlyCar(car));
        waterCar.move();
    }
} 

运行结果:

五、责任链模式

责任链模式的作用:将能够处理同一类请求的对象连成一条链,所提交的请求沿着链传递,链上的对象逐个判断是否有能力处理该请求,如果能则处理,如果不能则传递给链上的下一个对象。

开发中常见的场景如下:

1. Java 中,异常机制就是一种责任链模式。一个 try 可以对应多个 catch,当第一个 catch 不匹配类型,则自动跳到第二个 catch。

2. JavaScript 语言中,事件的冒泡和捕获机制。 Java 语言中,事件的处理采用观察者模式。

3. Servlet 开发中,过滤器的链式处理。

4. Struts2 中,拦截器的调用也是典型的责任链模式。

责任链模式的UML图:

示例:

 package cn.pxy.test;
/**
 * 以请假为案例,封装请假的基本信息,用来测试责任链模式
 * @author 胖咸鱼
 *
 */public class LeaveRequest {
    private String empName;
    private int leaveDays;
    private String reason;
    public LeaveRequest(String empName,int leaveDays,String reason) {
        super();
        this.empName=empName;
        this.leaveDays=leaveDays;
        this.reason=reason;
    }
    public String getEmpName() {
        return empName;
    }
    public void setEmpName(String empName) {
        this.empName = empName;
    }
    public int getLeaveDays() {
        return leaveDays;
    }
    public void setLeaveDays(int leaveDays) {
        this.leaveDays = leaveDays;
    }
    public String getReason() {
        return reason;
    }
    public void setReason(String reason) {
        this.reason = reason;
    }


} 
 package cn.pxy.test;
/**
 * 抽象类
 * 抽象处理着
 * @author 胖咸鱼
 *
 */public  abstract  class Leader {
    protected String name;
    protected Leader nextLeader;//责任链上的后继对象
    public Leader(String name) {
        super();
        this.name=name;
    }
    //设定责任链上的后继对象
    public void setNextLeader(Leader nextLeader) {
        this.nextLeader=nextLeader;
    }
    /**
 * 处理请求的核心的业务方法
 */public abstract void handleRequest(LeaveRequest request);
} 
 package cn.pxy.test;
/**
 * 具体处理者
 * 主任
 * @author 胖咸鱼
 *
 */public class Director extends Leader{
    public Director(String name) {
        super(name);
    }
    @Override
    public void handleRequest(LeaveRequest request) {
        if(request.getLeaveDays()<) {
            System.out.println("主任:"+this.name+",审批通过!");
        }else {
            System.out.println("主任:天数超过权限,经理审批");
            if(this.nextLeader!=null) {
                this.nextLeader.handleRequest(request);
            }
        }

    }

} 
 package cn.pxy.test;
/**
 * 具体处理者
 * 经理
 * @author 胖咸鱼
 *
 */public class Manager extends Leader{
    public Manager(String name) {
        super(name);
    }
    @Override
    public void handleRequest(LeaveRequest request) {
        if(request.getLeaveDays()<) {
            System.out.println("经理:"+this.name+",审批通过!");
        }else {
            System.out.println("经理:天数超过权限,总经理审批");
            if(this.nextLeader!=null) {
                this.nextLeader.handleRequest(request);
            }
        }

    }

} 
 package cn.pxy.test;
/**
 * 具体处理者
 * 总经理
 * @author 胖咸鱼
 *
 */public class GeneralManager extends Leader{
    public GeneralManager(String name) {
        super(name);
    }
    @Override
    public void handleRequest(LeaveRequest request) {
        if(request.getLeaveDays()<) {
            System.out.println("总经理:"+this.name+",审批通过!");
        }else {
            System.out.println("难道:"+request.getEmpName()+"不想干了?居然请假"
            +request.getLeaveDays()+"天");
        }

    }

} 
 package cn.pxy.test;
/**
 * 测试责任链模式
 * @author 胖咸鱼
 *
 */public class Client {
    public static void main(String[] args) {
        Leader a=new Director("张三");
        Leader b=new Manager("李四");
        Leader c=new GeneralManager("王五");
        //组织责任链对象的关系
        a.setNextLeader(b);
        b.setNextLeader(c);
        //开始请假
        LeaveRequest req=new LeaveRequest("TOM",15,"回老家探亲!");
        System.out.println("员工:"+req.getEmpName()+"请假,天数:"
                       +req.getLeaveDays()+",理由:"+req1.getReason());
        System.out.println("开始审批");
        a.handleRequest(req);
    }
} 

运行结果:

六、模板方法模式(钩子方法)

模板方法模式是编程中经常用得到模式。它定义了一个操作中的算法骨架,将某些步骤延迟到子类中实现。 这样,新的子类可以在不改变一个算法结构的前提下重新定义该算法的某些特定步骤。

模板方法模式的核心:处理某个流程的代码已经都具备,但是其中某个节点的代码暂时不能确定。因此,我们采用模板方法模式,将这个节点的代码实现转移给子类完成。即:处理步骤父类中定义好,具体实现延迟到子类中定义。

那我们什么时候能用到模板方法模式呢?实现一个算法时,整体步骤很固定,但是,某些部分易变,那么易变部分可以抽象成出来,供子类实现。

模板方法模式使用非常频繁。各个框架、类库中都有他的影子。开发中常见的场景如下:

1. 数据库访问的封装。

2. Junit 单元测试。

3. Servlet 中关于 doGet/doPost 方法调用。

4. Hibernate 中模板程序。

5. Spring 中 JDBCTemplate、HibernateTemplate 等。

示例 :客户到银行办理业务:

1. 取号排队。

2. 办理具体现金/转账/企业/个人/理财业务。

3. 给银行工作人员评分。

这个场景里面,实际上整体步骤是固定的。但是,第二步具体办什么业务,因人而异,制定算法时无法估计到底是什么业务,这时候可以通过定义一个“抽象方法”来延迟到子类中实现。

 package cn.pxy.test;

public abstract class BankTemplateMethod {
    //具体方法
    public void takeNumber() {
        System.out.println("取号排队");
    }
    public abstract void transact();//办理具体的业务-----钩子方法

    public void evaluate() {
        System.out.println("反馈评分");
    }

    //模板方法,把基本操作组合到一起,子类一般不能重写
    public final void process() {
        this.takeNumber();
        this.transact();
        this.evaluate();
    }
} 
 package cn.pxy.test;
/**
 * 测试模板方法
 * @author 胖咸鱼
 *
 */public class Client {
    public static void main(String[] args) {
        BankTemplateMethod btm=new DrawMoney();
        btm.process();
        System.out.println("---------------");
        //采用匿名内部类
        BankTemplateMethod btm=new BankTemplateMethod() {
            public void transact() {
                System.out.println("我要存钱!!!");
            }
        };
        btm.process();
        System.out.println("-------------");
        BankTemplateMethod btm=new BankTemplateMethod() {
            public void transact() {
                System.out.println("我要理财,我有万");
            }
        };
        btm.process();
    }
}
class DrawMoney extends BankTemplateMethod{

    @Override
    public void transact() {
        System.out.println("我要取钱!!!");

    }

} 

运行结果:

七、观察者模式

在 JavaSE 中,提供了 java.util.Observable 和 java.util.Observer 来帮助我们方便的实现观察者模式。

观察者模式在开发中常见的场景如下所示:

1. 聊天室程序中,服务器将消息转发给所有客户端。

2. 网络游戏(多人联机对战)场景中,服务器将客户端的状态进行分发。

3. 邮件订阅。

4. Servlet 中,监听器的实现。

5. Android 中,广播机制。

6. JDK 的 AWT 中事件处理模型,基于观察者模式的委派事件模型(DelegationEvent Model)。

事件源—————-目标对象

事件监听器————观察者

 package cn.pxy.test;

import java.util.Observable;

/**
 * 观察者模式典型用法:目标对象
 * @author 胖咸鱼
 *
 */public class ConcreteSubject extends Observable{
    private int state;//目标对象的状态
    public void set(int s) {
        state=s;//目标对象的状态发生了改变
        setChanged();//表示目标对象已经做了改变
        notifyObservers(state);//通知所有观察者
    }
    public int getState() {
        return state;
    }
} 
 package cn.pxy.test;

import java.util.Observable;
import java.util.Observer;

/**
 * 观察者模式典型用法:观察者
 * @author 胖咸鱼
 *
 */public class ObserverA implements Observer{
    private int myState;

    @Override
    public void update(Observable o, Object arg) {
        myState=((ConcreteSubject)o).getState();

    }
    public int getMyState() {
        return myState;
    }
    public void setMyState(int myState) {
        this.myState=myState;
    }

} 
 package cn.pxy.test;
/**
 * 测试观察者模式
 * @author 胖咸鱼
 *
 */public class Client {
    public static void main(String[] args) {
        //创建目标对象Obserable
        ConcreteSubject subject=new ConcreteSubject();
        //创建观察者
        ObserverA obs=new ObserverA();
        ObserverA obs=new ObserverA();
        ObserverA obs=new ObserverA();
        //将上面三个观察者对象添加到目标对象subject的观察者容器里
        subject.addObserver(obs);
        subject.addObserver(obs);
        subject.addObserver(obs);
        //改变subject对象的状态
        subject.set();
        System.out.println("------目标对象,状态修改为:!");
        //观察者的状态发生了变化
        System.out.println("观察者objs的myState状态:"+obs1.getMyState());
        System.out.println("观察者objs的myState状态:"+obs2.getMyState());
        System.out.println("观察者objs的myState状态:"+obs3.getMyState());

        subject.set();

        System.out.println("------目标对象,状态修改为:!");
        //观察者的状态发生了变化
        System.out.println("观察者objs的myState状态:"+obs1.getMyState());
        System.out.println("观察者objs的myState状态:"+obs2.getMyState());
        System.out.println("观察者objs的myState状态:"+obs3.getMyState());
    }
} 

八、代理模式(动态)

代理模式的核心作用:

1. 通过代理,控制对对象的访问!

2. 可以详细控制访问某个(某类)对象的方法,在调用这个方法前做前置处理,调用这个方法后做后置处理。(即:AOP 面向切面编程的核心实现!)

代理模式的应用场景及其广泛,可以说任何一个框架都用到了代理模式。如下列举一些常见的场景:

1. Struts2 中拦截器的实现(Servlet 的过滤器是装饰器模式,不是代理模式)。

2. 数据库连接池关闭处理。

3. Hibernate 中延时加载的实现。

4. Mybatis 中实现拦截器插件。

5. AspectJ 的实现。

6. Spring 中 AOP 的实现。

7. 声明式事务处理。

8. Web Service。

9. RMI 远程方法调用。

代理模式又分了两种:静态代理模式和动态代理模式。所谓静态代理就是要为要代理的类写一个代理类,或者用工具为其生成一个代理类,总之,就是程序运行前就已经存在的编译好的代理类,这样有时候会觉得非常麻烦,也导致程序非常的不灵活;相比静态代理,动态代理具有更强的灵活性,因为它不用在我们设计实现的时候就指定某一个代理类来代理哪一个被代理对象,我们可以把这种指定延迟到程序运行时由 JVM 来实现,这就用到了反射机制。

示例:以明星—经纪人的例子测试代理模式:

 package cn.pxy.test;
/**
 * 定义同一接口
 * @author 胖咸鱼
 *
 */public interface Star {
    void signContract();//签合同
    void sing();//唱歌
    void collectMoney();//收钱
} 
 package cn.pxy.test;
/**
 * 真正的明星类
 * @author 胖咸鱼
 *
 */public class RealStar implements Star{

    @Override
    public void signContract() {
        System.out.println("(明星本人)签字");

    }

    @Override
    public void sing() {
        System.out.println("(明星本人)唱歌");

    }

    @Override
    public void collectMoney() {
        System.out.println("(明星本人)收钱");

    }

} 
 package cn.pxy.test;

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

public class StarHandler implements InvocationHandler{

    Star realStar;

    public StarHandler(Star realStar) {
        this.realStar=realStar;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object object=null;

        System.out.println("真正的方法执行前!");

        //只有唱歌处理,其他方法不做处理
        if(method.getName().equals("sing")) {
            object=method=(Method) method.invoke(realStar, args);
        }else {
            System.out.println("代理处理:"+method.getName());

        }
        System.out.println("真正的方法处理后!");
        return object;
    }

} 

属性 realStar 就是真正的“明星对象”;invoke()方法是核心处理方法,他的 3 个参数表示的含义是:

1. proxy:表示代理对象

2. method:表示代理对象调用的方法对象

3. args:表示代理对象调用的方法的参数

在整个 invoke()方法中,可以轻松获得 proxy 代理对象、realStar(被代理的对象),也可以轻松调用他们的方法。我们根据反射机制做出处理,如果调用的是 sing()方法,则由“明星本人 realStar”亲自处理。其他方法,则不做处理。

 package cn.pxy.test;

import java.lang.reflect.Proxy;

/**
 * 测试动态代理模式
 * @author 胖咸鱼
 *
 */public class Client {
    public static void main(String[] args) {
        //将明星注册到处理核心类里面
        Star realStar=new RealStar();
        StarHandler handler=new StarHandler(realStar);

        //动态创造代理类和代理对象
        Star proxy=(Star) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), 
                                             new Class[] {Star.class}, handler);

        //生成的代理对象的任何方法里面都是调用了:handler.invoke()方法
        proxy.sing();
        System.out.println("-------------------");
        proxy.collectMoney();
    }
} 

运行结果:

问: 为什么调用代理对象的方法,最终会进入核心类handler.invoke()方法呢?

解析

 Star proxy = (Star) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), 
                                           new Class[]{Star.class}, handler); 

这行代码的核心作用是:根据 Star 接口生成代理类,这是由 JDK 提供的 Proxy 类动态生成的类(这个类存在于内存中但是硬盘上没有),代码结构示意如下:

 public class StarProxy$ implements Star { 
  InvocationHandler handler; 
  @Override 
  public void collectMoney() { 
    handler.invoke(this,method对象,参数); 
  }
  @Override 
  public void signContract() { 
    handler.invoke(this,method对象,参数); 
  }
  @Override 
  public void sing() { 
    handler.invoke(this,method对象,参数); 
  } 
} 

我们发现动态生成的代理类的所有方法,实际上内部都调用了 handler.invoke()方法!这就是本质,所以,无论调用代理对象的什么方法,都会最终进入到 handler.invoke()方法中。

动态生成代理类的方法有下面 4 种常见方式

1. JDK 自带的动态代理(本节课就是采用这种方式)。

2. javaassist 字节码操作库实现。

3. CGLIB。

4. ASM(底层使用指令,可维护性较差)。