概述

本笔记主要记载笔者自身比较常见的设计模式。如果要查其他的请自行跳转搜索引擎。

该笔记目前包含以下设计模式的介绍:

创建型的:单例、原型、工厂(简单工厂、工厂方法、抽象工厂)

结构性的:代理、外观

行为型的:责任链、迭代器、模板、策略

创建型模式

单例模式

单例模式的核心是保证在程序之中唯一的创建单个对象提供使用。总所周知,Java之中创建对象的方式有四种:1.new、2.clone、3.反射、4.序列化反序列化。如果要完全保证单例模式的正确性那就势必需要保证这四种方式创建的对象,要么创建失败、要么返回同一个对象。显然的new很简单的可以通过static避免重复创建。而clone就更为简单不重写clone方法,自然就解决了。较为关键的是防止反射和序列化可能会导致的问题。

解决反射:虽然反射看起来好像很复杂

  1. 懒汉式

    /**
     * 懒汉式,通过静态来保证唯一性 [占用空间、资源多]
     */
    class LazyManSingeletonModel implements Serializable {
        private static volatile LazyManSingeletonModel instance = new LazyManSingeletonModel();
        // 私密化单例类的构造器,防止反射破坏
        private LazyManSingeletonModel() {
            if(Objects.nonNull(instance)) {
                throw new RuntimeException("禁止非法访问");
            }
        }
        public static LazyManSingeletonModel getInstance() {
            return instance;
        }
        // 编写ReadResolve方法,防止序列化破坏
        public Object readResolve() {
            return instance;
        }
    }
    
  2. 饿汉式

    比起其他的方式,饿汉式由于引用了懒加载的方式,降低资源的损耗,也带来了一些问题,多线程下可能会出现同时创造了多个目标是单例的对象导致单例模式被破坏。所以需要引入DCL双重校验volatile避免出现指令重排 (JVM) 导致的问题。

    简略介绍:

    一个对象的创建可以被拆解为以下三个步骤

    1.memory = allocate(); //分配对象内存空间

    2.instance(memory); //初始化对象

    3.instance = memory; //将创建的对象指向变量

    JVM在执行程序的时候,为了快,会将没有数据依赖的两个步骤打乱顺序来进行执行。而在上面的这三个步骤之中,2和3的顺序是可以被颠倒的。与上述的问题,广泛的存在在各种情况下。除了对象创建以外,还有很多其他场景,例如:

    Integer a,b,c;

    b = b + c;

    a = b;

    显然的,将a的指向改为b的地址和b+c的执行两件事没有发生冲突,所以在指令重排的情况下,有可能会出现另外一个线程读取到a 已经是 b指向的对象了,但是值却还是原来b的值。

    结论:这个问题其实是程序的有序性被破坏了,在多线程的情况下这种程序的执行顺序就是会被硬件级别的指令重排导致出现意想不到的问题出现。

    如需要了解更详细的信息,可以参考同站文章

    指令重排 - leticiafeng

    /**
     * 饿汉式,需要通过双重校验来保证单例唯一性不会被破坏  [占用空间少]
     */
    class HungryManSingletonModel implements Serializable{
        // volatile 保证双重校验可以看到第一个线程创建的状态 (并且防止指令重排)
        private volatile static HungryManSingletonModel instance = null;
        // 私密化单例类的构造器 (阻止反射破坏的可能)
        private HungryManSingletonModel() {
            if(Objects.nonNull(instance)) {
                throw new RuntimeException("禁止非法访问");
            }
        }
        public static HungryManSingletonModel getInstance() {
            // 双重校验
            if (instance == null) {
                // Synchronized是可以保证包裹的部分不会出现指令重排,但是无法保证之前是否会出现重排
                //    把内部的构建当做是一个ACID事务的话,在进去之前依旧可能是有人抢先执行创建,所以需要再次校验
                synchronized (HungryManSingletonModel.class) {
                    if (instance == null) {
                        instance = new HungryManSingletonModel();
                    }
                    return instance;
                }
            }
            return instance;
        }
        // 编写ReadResolve方法,防止序列化破坏
        public Object readResolve() {
            return instance;
        }
    }
    
  3. 私密静态内部类模式

    通过利用私密静态内部类,只有被使用的时候才会被迫用静态加载的特点来实现 (外部类可以使用私密静态内部类的静态属性)

    /**
     * 静态内部类,利用私密静态内部类来声明内部静态类对象,保证单例唯一性 [占用空间少]
     *  (反射无法破坏 序列化无法破坏,但是代码不好看)
     */
    class StaticInnerClassLazySingleton implements Serializable{
        // 防止反射获取 StaticInnerClassLazySingleton 的构造器,尝试调用内部类
        private StaticInnerClassLazySingleton () {
            if(Objects.nonNull(StaticInnerSingleton.staticInnerClassLazySingleton)) {
                throw new RuntimeException("禁止非法访问");
            }
        }
        private static class StaticInnerSingleton {
            // 静态不可改动对象,避免反射直接修改改变量
            public final static StaticInnerClassLazySingleton staticInnerClassLazySingleton = new StaticInnerClassLazySingleton();
        }
        private static StaticInnerClassLazySingleton getInstance() {
            return StaticInnerSingleton.staticInnerClassLazySingleton;
        }
        // 添加readResolve方法,解决序列化会破坏单例模式的问题
        private Object readResolve() {
            return StaticInnerSingleton.staticInnerClassLazySingleton;
        }
    }
    
  4. 枚举类模式(最好的方式)

    要知道枚举类实现的单例模式为什么是最好的单例实现方式,首先要明白Java单例模式的特点。

    枚举类因为他的自身的一些特殊的性质,这使得枚举类在应对单例模式的时候,有着天然的优势。面对反射和序列化和反序列化的时候,都可以不需要添加任何额外代码的前提下保证实例的唯一,不会被破坏。

    枚举类的简单使用 / 枚举类保证单一的验证 都可以参考本站的另外一篇文章枚举类详解 & 唯一性校验

    简单的来说enum类型,可以简单的理解为是一种较为特殊的类。具体可以参照以下的内容

    可以简化为一个简单的口诀:目标类名做实例,目标类对象做类变量,获取方法做默认成员方法,构造方法创对象

    /**
     * 枚举,利用枚举类类型的唯一性来实现单例模式
     *  天然的解决了反射破坏的问题
     */
    enum EnumSingleTon {
        ENUM_SINGLE_TON;            // 目标类名做实例
        private Object singleTonData;         // 目标类对象做类变量
        public Object getSingleTonData() {   // 获取方法做默认成员方法
            return singleTonData;
        }
        // 添加伪空参构造器,
        private EnumSingleTon() {        // 构造方法创对象
            singleTonData = new Object();
        }
    }
    
  5. ThreadLocal方式

    ThreadLocal也是很好理解的,表面看上去像是每一个线程提供一个新的实例,但由于ThreadLocalSingleton声明的单例对象是public static final,那就导致了他在不同的对象之中调用的时候其实调用的都是同一个实例对象。从而保证了单例唯一

    class ThreadLocalSingleton {
        private static final ThreadLocal<ThreadLocalSingleton> threadLocalSingleton = new ThreadLocal<ThreadLocalSingleton>() {
            @Override
            protected ThreadLocalSingleton initialValue() {
                return new ThreadLocalSingleton();
            }
        };
    
        private ThreadLocalSingleton() {
        }
    
        public static ThreadLocalSingleton getInstance() {
            return threadLocalSingleton.get();
        }
    
    }
    

原型模式

所谓原型模式就比较简单了,就是在使用的时候每次都直接返回一个新的。原型模式一般来说直接通过不同的对象的隔离,使得不同线程 / 不同的操作的结果完全隔离保证了资源独立情况下的线程安全。

通常可以通过工厂模式 / 抽象工厂模式 / new等方式来构建。比较简单,不多介绍。

简单工厂模式

简单工程模式,将实例化对象的逻辑全部封装在一个工厂类之中,这也导致了一旦如果要修改 / 增加类型,就首先添加基本的实现实体类,最后在工厂模式的工厂类之中添加该类型的处理逻辑。

概述:简单工厂模式其实就是通过实体类抽象层,将实体类划分为一个大的类别整体,在该整体下又通过实现大类别整体的情况下,分为更多细致的小实际实体类。而对象的创建步骤,则从具体调用程序之中进行调用更改为了提供一个工厂类,并在工厂类之中通过形参来产生相对应的实体类进行返回。

代码示例

public class AbstractFactoryBean {
    public static void main(String[] args) {
    	        System.out.println(AnimalFactory.getAnimal("灵长类动物"));
        System.out.println(AnimalFactory.getAnimal("猫咪类动物"));
    }
}

/**
 * 抽象实体类
 */
interface Animal {
    String getClassial();
}
/**
 * 人类实体类
 */
class People implements Animal {
    @Override
    public String getClassial() {
        return "灵长类动物";
    }
}
/**
 * 猫咪实体类
 */
class Miumi implements Animal {
    @Override
    public String getClassial() {
        return "猫咪类动物";
    }
}
/**
 * 工厂模式工厂类
 */
class AnimalFactory {
    public static Animal getAnimal (String classical) {
        Animal animal = null;
        if ("灵长类动物".equals(classical)) {
            animal = new People();
        } else if ("猫咪类动物".equals(classical)) {
            animal = new Miumi();
        } else {
            animal = null;
        }
        return animal;
    }
}

工厂方法模式

工厂方法模式,在简单工厂模式的基础上额外添加了一个工厂的抽象层和实现层的处理,这样的处理使得我们如果想要添加 / 修改类型的话,那么我们只需要添加 / 修改多一个工厂实现类即可。这样对于我们来说,如果想要添加一个新的具体类对象的话,那么我们只需要添加一个新的具体实体类,并且添加对应的工厂类即可。

概述:工厂方法模式相对于简单工厂模式来说,主要改变在于工厂类上的改变。相比于简单工厂模式,为了能够在新增某一个具体的实体类的时候,对原先代码不进行改动,工厂方法模式添加了工厂抽象层,在面对需要新增的实体类对应的工厂类的时候,只需要实现工厂抽象层对象,并在具体工厂类之中编写对应的逻辑代码即可。

代码示例

public class AbstractFactoryBean {
    public static void main(String[] args) {
        System.out.println(new PeopleFactory().productAnimal());
        System.out.println(new MiumiFactory().productAnimal());
    }
}
/**
 * 抽象实体类
 */
interface Animal {
    String getClassial();
}
/**
 * 人类实体类
 */
class People implements Animal {
    @Override
    public String getClassial() {
        return "灵长类动物";
    }
}
/**
 * 猫咪实体类
 */
class Miumi implements Animal {
    @Override
    public String getClassial() {
        return "猫咪类动物";
    }
}
/**
 * 抽象工厂接口
 */
interface AbstractAnimalFactory {
    String productAnimal();
}
class PeopleFactory implements AbstractAnimalFactory {
    @Override
    public String productAnimal() {
        People people = new People();
        String classial = people.getClassial();
        classial = "Create By Abstract:\n" + classial;
        return classial;
    }
}
class MiumiFactory implements AbstractAnimalFactory {
    @Override
    public String productAnimal() {
        Miumi miumi = new Miumi();
        String classial = miumi.getClassial();
        classial = "Create By Abstract:\n" + classial;
        return classial;
    }
}

抽象工厂模式

抽象工厂模式,相比于工厂模式来说添加了不同大类别的实体类类别的数量,也就是说,有了更多的实体类抽象层接口,这样一来,如果我们想要直接添加一个大类别的话,我们可以通过直接添加一个实体类抽象层接口,并添加其对应的实现类。而在实体类一侧,则跟工厂模式一样,添加对应的抽象工厂接口 & 实际工厂类(或者也可以直接修改原来的工厂类接口添加类别,并在对应的实现类之中添加对应的创建方法)

概述:抽象工厂模式相比于工厂模式来说,改变则主要体现在实体类一侧,通过添加不同的实体类抽象层接口,可以将新增的实体类与原先的实体类区分开来,这样一来当我们需要添加一个有着完全不同类别的大类别及其对应的具体实体类的时候,只需要新增对应的代码即可,不用改动原先的实体类部分代码。而在实体类一侧,我们可以通过在原先的工厂抽象层添加新增的实体类抽象层接口变量,并在其对应工厂类之中补充新增实体类对象即可。(也可以添加完全新的一套实体类工厂抽象层 & 实体类工厂实现类)

代码示例

/**
 * 调用类
 */
public class AbstractFactoryBean {
    public static void main(String[] args) {
        AbstractFactoryInterface factory1 = new AbsGroupOneClassicalFactory();
        AbstractAnimal animal1 = factory1.animal();
        AbstractPlant plant1 = factory1.plant();
        AbstractFactoryInterface factory2 = new AbsGroupTwoClassicalFactory();
        AbstractAnimal animal2 = factory2.animal();
        AbstractPlant plant2 = factory2.plant();
    }
}
/**
 * 实体类抽象层1
 */
interface AbstractPlant {
    public abstract String skill();
}
/**
 * 实体类抽象层2
 */
interface AbstractAnimal {
    public abstract String skill();
}
/**
 * 类别1 -  实际实体类1
 */
class AbstractPeople implements AbstractAnimal {
    @Override
    public String skill() {
        return "this human";
    }
}
/**
 * 类别1 -  实际实体类2
 */
class AbstractXiugou implements AbstractAnimal {
    @Override
    public String skill() {
        return "this 修狗";
    }
}
/**
 * 类别2 -  实际实体类1
 */
class AbstractFengTree implements AbstractPlant {
    @Override
    public String skill() {
        return "this 枫";
    }
}
/**
 * 类别2 -  实际实体类2
 */
class AbstractCommonTree implements AbstractPlant {
    @Override
    public String skill() {
        return "this 普通树";
    }
}
/**
 * 抽象工厂接口
 */
interface AbstractFactoryInterface {
    AbstractPlant plant();
    AbstractAnimal animal();
}
/**
 * 实际类别1工厂类
 */
class AbsGroupOneClassicalFactory implements AbstractFactoryInterface {
    @Override
    public AbstractPlant plant() {
        return new AbstractFengTree();
    }
    @Override
    public AbstractAnimal animal() {
        return new AbstractPeople();
    }
}
/**
 * 实际类别2工厂类
 */
class AbsGroupTwoClassicalFactory implements AbstractFactoryInterface {
    @Override
    public AbstractPlant plant() {
        return new AbstractCommonTree();
    }
    @Override
    public AbstractAnimal animal() {
        return new AbstractXiugou();
    }
}

结构性模式

代理模式

代理模式一般来说其实只有动态代理才算是真正的代理。但是在Java的概念之中,有存在着静态代理。静态代理和动态代理的主要区别在于,静态代理在代理类上往往是在编译期间就已经写死了,所以往往只能代理一种类型的对象,但是在动态代理之中,往往代理的对象的类型是不固定的,可以代理多个对象。

在Java的程序 / 众多框架技术之中,代理模式都普遍的存在(主要是动态代理普遍存在),例如Spring的基于CGlib的AOP,IOC的实现。Mybatis的加载过程等都是经典的通过代理来实现的功能。

代理的基本结构

被代理的基本结构

首先代理需要程序上对被代理对象有最基本的两层的分层。其结构大致如下:

  1. 接口抽象层 (将需要代理的方法抽取到该接口中,从而方便统一进行代理增强)。
  2. 业务实现层,针对接口抽象成之中的对应方法进行实现,具有着业务上的实际逻辑。

代理的基本结构

  1. 被代理类 (一般是业务实现类,内部包含实际的业务代码逻辑)
  2. 代理类 (不管是静态代理还是动态代理,都是通过代理类,实现代理方法的Before、After、AfterReturning、AfterThrowing、Around来针对被代理方法的前置、后置、返回后、异常后、环绕处理)
  3. 外部调用类 (实际调用到被代理方法的源程序块)
  4. 代理类的工厂 (可有可无,不是那么关键)

静态代理

静态代理的主要缺陷是,他往往只能针对一个抽象层接口的一个方法进行代理,这就导致了我们如果想要通过静态代理的这种方式来实现针对某个类型的一些方法进行代理的时候,会变得很麻烦,往往针对一个方法,我们就需要写一个实际处理方法。但如果每一个方法都需要针对进行处理的话,那我为什么不直接写一个包装类来针对进行处理呢?由于这些狭隘性,导致了我们实际开发之中很少会使用或者见到静态代理。

示例

但不管怎么说,基本示例还是得有的

/**
 * 外部调用类
 */
public class Proxy {
    public static void main(String[] args) {
        BusinessTheme businessTheme = new BusinessTheme();
        // 调用静态代理工厂获取代理对象
        ThemeProxy proxyObject = ThemeStaticProxy.getProxyObject(businessTheme);
        // 调用方法
        proxyObject.voice();
    }
}

/**
 * 业务抽象层
 */
interface Theme {
    public void voice ();
}

/**
 * 实际业务类
 */
class BusinessTheme implements Theme{
    @Override
    public void voice() {
        System.out.println("就这?");
        try {
            Thread.sleep(3000);
            System.out.println("不会吧");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

/**
 * 实际代理类,静态代理类
 */
class ThemeProxy implements Theme {

    private Theme beProxyObject;

    public ThemeProxy(Theme beProxyObject) {
        this.beProxyObject = beProxyObject;
    }

    @Override
    public void voice() {
        // before be proxy class method
        System.out.println("你在狗叫什么?");
        long before = System.currentTimeMillis();
        // invoke proxy method
        this.beProxyObject.voice();
        // after be proxy class method
        long after = System.currentTimeMillis();
        System.out.println("就只会就这?");
        System.out.println( (after - before) / 1000.0  + "秒,就只会骂一句就这,啥水平不用多说了");
    }
}

/**
 * 静态代理工厂
 */
class ThemeStaticProxy {
    public static ThemeProxy getProxyObject (Theme object) {
        return new ThemeProxy(object);
    }
}

动态代理

动态代理其实和静态代理的区别只有一处不同,那就是动态代理代理的方法并非是固定的具体某一个,而是接口之中所有的抽象方法。这就使得我们在使用代理的时候方便许多,不用针对每一个方法都去写一个代理的处理逻辑。

以JDK动态代理来看

原理

JDK的基于InvocationHandler + Proxy的代理方式其实是通过反射的方式来拦截住了对应的方法的执行。(利用反射机制,对Interface之中的方法进行识别,找到有哪些方法需要被代理,然后将他们与 invoke 方法之中的逻辑进行编织,构建一个新的对象,然后将该对象上转型到业务抽象层接口对象中,提供调用,多态行为覆盖)

JDK - InvocationHandler实现动态代理最大的缺点在于他需要原先的接口本身实现 InvocationHandler接口,如果类本身也是动态代理或者反射的方式创建的,往往就无法通过JDK动态代理来实现。(举例:参考Mybatis在实现延迟加载的时候,不使用JDK而使用CGlib)

从基本逻辑来说,其实静态代理和动态代理是类似的。其实总的来看,只多了一个Proxy来生成代理对象的过程。

Implements InvocationHandler 实现invoke方法,实现了真正动态代理的处理逻辑 --- 等同于ThemeProxy,做实际代理的处理工作

Proxy 根据class对象,实现类实现的接口列表和以及实现类对象本身,产生代理对象

业务抽象层保持不变

业务实现层保持不变

代码示例
/**
 * 外部调用类
 */
public class DynamicProxy {
    public static void main(String[] args) {
        BussineseDynamicTheme bussineseDynamicTheme = new BussineseDynamicTheme();
        DynamicThemeProxy dynamicThemeProxy = new DynamicThemeProxy(bussineseDynamicTheme);
        DynamicTheme proxyInstance = (DynamicTheme) Proxy.newProxyInstance(BussineseDynamicTheme.class.getClassLoader(), BussineseDynamicTheme.class.getInterfaces(),
                dynamicThemeProxy);
        proxyInstance.voice();
        System.out.println("===============================================");
        System.out.println(proxyInstance.song());
    }
}

/**
 * 业务抽象层
 */
interface DynamicTheme {
    public abstract void voice();
    public abstract String song();
}

/**
 * 业务实现类
 */
class BussineseDynamicTheme implements DynamicTheme {
    @Override
    public void voice() {
        try {
            System.out.println("就这啊?");
            Thread.sleep(3000);
            System.out.println("怎么不敢叫了?");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public String song() {
        return "就叫就叫,你能把我咋地?";
    }
}

/**
 * 动态代理类
 */
class DynamicThemeProxy implements InvocationHandler {

    private DynamicTheme theme;

    public DynamicThemeProxy(DynamicTheme theme) {
        this.theme = theme;
    }

    public DynamicTheme getTheme() {
        return theme;
    }

    public void setTheme(DynamicTheme theme) {
        this.theme = theme;
    }

    /**
     * @param proxy 生成的代理对象自身
     * @param method 被代理的方法 (也就是业务抽象层中定义的抽象方法) ===> 可以进一步理解成所有被动态代理的方法都要事先在抽象层定义
     * @param args 被代理方法所需要的形参列表
     * @return user defined
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("你这么菜,能不能回家养猪啊?");
        long beforeIvoke = System.currentTimeMillis();
        Object invoke = method.invoke(theme, args);
        long afterInvoke = System.currentTimeMillis();
        System.out.println(((afterInvoke - beforeIvoke) / 1000) + "秒了,就憋出来一句,就这?九折水瓶还学别人出来骂人,真差不多得了");
        return invoke;
    }
}
调用流程

前置流程 -> 代理对象 -> 动态代理类中invoke()方法 -> 被代理对象 -> 动态代理类中invoke()方法 -> 后续流程

基于CGlib的动态代理

原理

如果仅仅从代码上来看,CGlib实现动态代理的方式跟JDK的动态代理实现方式虽然实现的具体细节上有所不同,但总体是差不多的。Proxy -> Enhancer 、InvocationHandler -> MethodInterceptor、InvocationHandler.invoke。三个部分几乎是完全一样的,但是不同的是他们的底层实现其实是不一样的。CGlib实现动态代理是通过MethodInterceptor先将方法进行拦截处理,然后同样的针对要被代理的方法,作前置、后置、返回后、异常后等处理功能。但是Cglib创建的代理对象跟JDK是不一样的,JDK创建的代理对象是通过产生一个实现业务抽象层的新实现类,再上转型到业务抽象层接口变量提供调用。CGlib则是产生三个代理类对象来实现的代理。

CGlib使用ASM字节码 FastClass 处理框架,用于转换字节码并生成的新的类。这一次的字节码文件生成,会生成直接三个代理对象类还有两个缓存Key先关的代理类。其中名字格式如下:

image-20240114221157553

代理对象类

  1. BeProxyClassName$$EnhancerByCGLib$$···.class (代理类直接子类)

    extends BeProxyClassName implement net.sf.cglib.proxy.Factory
    
  2. BeProxyClassName$$EnhancerByCGLIB$$···$$FastClassByCGLIB$$···.class (索引代理类)

    该类其实是代理直接子类的索引类,内部有两个关键的方法getIndex()和invoke()方法
    等同于 被代理类名 + $$ + EnhancerByCGLIB + $$ + 随机码 + $$ + FastClassByCGLIB $$ + ···

  3. BeProxyClassName$$FastClassByCGLIB%%···.class (索引被代理类)

    该类是被代理类的索引类,内部的关键方法和代理直接子类的一样都是getIndex()和invoke()方法
    等同于 被代理类 + $$ + FastClassByCGLIB +$$ ···

缓存KeyClass类

  1. MethodWrapper$MethodWrapperKey$$KeyFactoryByCGLIB···.class (用于生成被代理类的缓存,提供性能)

    与方法包装器(MethodWrapper)和键工厂(KeyFactory)有关。在 CGLib 中,为了提高方法调用的性能,会使用 MethodWrapper 来封装被代理类的方法,而 MethodWrapperKey 则是用于生成方法包装器的键。KeyFactoryByCGLIB 是键工厂的一部分,用于创建这些键。

  2. Enhancer$EnhancerKey$$KeyFactoryByCGLIB···.class (用于生成代理类的缓存,提供性能)

    与代理类增强器(Enhancer)和键工厂(KeyFactory)相关。EnhancerKey 是用于生成代理类增强器的键,KeyFactoryByCGLIB 是键工厂的一部分,用于创建这些键。

代码示例

相比于JDK的动态代理示例,简单了不少,不需要业务抽象层,也不需要往代理实现类之中传自身对象类型。编写代理逻辑、外加有一个业务实现类即可。

/**
 * 实际调用类
 */
public class CglibDynamicProxy {
    public static void main(String[] args) {
        BusinessDynamic businessObject = new BusinessDynamic();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(businessObject.getClass());
        enhancer.setCallback(new DynmaicMethodInterceptor());
        BusinessDynamic proxyObject =  (BusinessDynamic) enhancer.create();
        proxyObject.executeBussiness();
    }
}

/**
 * 业务类
 */
class BusinessDynamic {
    public BusinessDynamic() {
    }
    public String executeBussiness () {
        System.out.println("Nice!寄了!");
        return "太典了";
    }
}

/**
 * 方法拦截类,编写真正针对被代理方法做前置 / 后置 / 返回后 / 异常返回后处理的代码逻辑
 */
class DynmaicMethodInterceptor implements MethodInterceptor {
    /**
     * @param o  代理对象的实例,就是Enhancer代理生成的对象
     * @param method 当前被拦截的方法,可以利用它获取当前方法的参数列表、返回类型、所属类、使用的注解等信息
     *                  可以以此作为条件针对参数进行处理
     * @param objects   参数列表,被拦截方法的参数列表
     * @param methodProxy 被代理对象的实例,就是实际上调用动态代理时被代理的对象
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("芜湖,又没中标");
        Object res = methodProxy.invokeSuper(o, objects);
        System.out.println("好,太经典的,不吃瘪我都怀疑不是我");
        return res;
    }
}
调用流程

代理对象 -> 拦截器实现方法(前置处理) -> 代理类索引类getIndex() -> 代理类索引类invoke() -> 拦截器实现方法(后置处理) -> 被代理对象

小补充:使用CGlib的时候,如果jdk版本超过9,那么就会在new Enhancer的时候出现一个反射报错提示:可以通关添加VM配置选项的方式来解决这个问题。

错误提示

Exception in thread "main" java.lang.ExceptionInInitializerError
	at com.leticiafeng.review.designModel.CglibDynamicProxy.main(CglibDynamicProxy.java:15)
Caused by: net.sf.cglib.core.CodeGenerationException: java.lang.reflect.InaccessibleObjectException-->Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @4ee285c6

解决

--add-opens java.base/java.lang=ALL-UNNAMED

外观模式

外观模式往往是针对某一个较为底层的类,进行外部的包装,使得有一个更加高层的类,在提供某些功能的时候,更加方便的调用 / 使用。这一点在Tomcat的request和其对应的requestFacade类之中有所体现。requestFacade针对request做了一系列的包装调用,使得其有着更高的封装程度。

Tomcat之中对RequestFacade包装类生成流程

RequestProcessor -> request (org.apache.coyote)
AbstractProtocol.recycledProcessors(继承synchronizedStack) - (如果是空的) -> AbstractProtocol.createProcessor -> request (org.apache.coyote) -> request(org.apache.catalina) -> requestFacde

行为型模式

责任链模式

责任链模式其实很容易理解,就是针对某个对象 / 资源进行一系列的处理,这个处理有可能是多个对象 / 资源来负责的,而最终返回的是处理结果,对于调用者一方来说是一个黑盒。使用到责任链的设计模式场景有很多,比如说不同用户领取福利的结果不一样。在框架上,则有IOC针对DI处理的逻辑、SpringSecurity针对访问请求的验证处理逻辑等。

迭代器模式

迭代器模式也是一个非常简单便于理解的设计模式,一种不用了解集合内部的数据结构但又可以简单的对集合的数据进行遍历迭代的设计。在Java之中就是List Set Map等类所实现的Iterator接口,实现的迭代。另外在JDBC之中的ResultSet对象,为了将查询结果进行遍历处理的话,也是需要迭代器模式的存在进行处理的。

模板模式

模板模式其实在前面的动态代理之中已经出现过了,为了更好的展示在编写静态代理的示例代码和jdk的动态代理之中,都有将需要被代理的方法抽取到业务抽象层之中,并通过对业务抽象层代码的实现,这个过程也属于是模板模式。

除了平时编写的代码抽取逻辑以外,还有平时使用的中间件的功能模板也有一些常见的模板。例如:redisTemplate、jdbcTemplate、kafkaTemplate。

一般来说在模板模式之中为了能够更好的统筹其子类依靠继承关系的方式实现功能需求,往往会包含一下三种不同类型的方法:
  1. 抽象方法,完全依靠子类继承复写来定义具体的行为
  2. 钩子方法,方法内部是完全空的,如果子类需要该方法提供可以实现也可以不实现的方法来提供该功能。
  3. 直接方法,提供标准的实现某个目标功能的方法,如果子类没有需要修改内容的情况下,可以通过直接使用该方法

策略模式

策略模式其实简单的来说就是按照一定的条件来做不同的处理*(普通的if-else / switch不算是策略模式)*,策略模式基本都会包括以下结构构成:

  • 策略接口:类似于抽象工厂类,是所有策略类的上层抽象层,利用上转型就可以算法进行互换
  • 具体策略类:实际上具体策略算法的类,一个类往往对应着一种实际的策略
  • 上下文策略切换类:实现策略切换的类,使用该类可以做具体的算法策略切换

简单的来说,就是依靠上转型将具体的策略传递到抽象层依靠同一个抽象接口变量实现不同策略的调用。策略模式虽然相比 if-else / swich来说增加了一定的资源损耗,但可读性得到一定的提升。(当然也有其他将 if-else / switch 做优化的方式,比如通过 函数式接口 + map / 状态机 来优化)

策略接口示例

public interface Strategy {
   public int doOperation(int num1, int num2);
}

具体策略类示例

public class OperationAdd implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 + num2;
   }
}

public class OperationMultiply implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 * num2;
   }
}

上下文策略切换类

public class Context {
   private Strategy strategy;
 
   public Context(Strategy strategy){
      this.strategy = strategy;
   }
 
   public int executeStrategy(int num1, int num2){
      return strategy.doOperation(num1, num2);
   }
}

使用示例

public class StrategyPatternDemo {
   public static void main(String[] args) {
      Context context = new Context(new OperationAdd());  
      System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
 
      context = new Context(new OperationMultiply());  
      System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
   }
}