深圳高端建设网站,如何申请网站,python做网站验证码,邯郸网站建设设计模式
面向过程的编程
面向过程的编程#xff08;也称为过程式编程#xff09;是一种编程范式#xff0c;其中程序被分解为一系列独立的、可重复的步骤或过程。每个步骤执行一个特定的任务#xff0c;并产生一定的输出。
以下是面向过程编程的一些关键特征#xff1…设计模式
面向过程的编程
面向过程的编程也称为过程式编程是一种编程范式其中程序被分解为一系列独立的、可重复的步骤或过程。每个步骤执行一个特定的任务并产生一定的输出。
以下是面向过程编程的一些关键特征
模块化程序被分解为小的、独立的函数或过程每个都有明确的输入和输出。这使得代码更易于理解、测试和重用。
顺序执行在过程式编程中代码按照特定的顺序执行通常是自上而下、从左到右。这意味着在任何给定时间只有一个操作正在执行。
避免全局状态在过程式编程中状态通常被限制在函数或过程的范围内。这意味着没有全局变量所有的状态变化都是局部的。
数据驱动过程式编程强调数据在程序中的流动。数据被视为从输入源流入经过一系列处理步骤然后流出到输出目的地。
面向过程编程的缺点主要包括
代码可重用性差由于每个过程都是独立的且通常针对特定的问题或任务编写所以代码的可重用性较低。
可维护性差随着项目规模的扩大代码的复杂性和维护成本可能会迅速增加。
缺乏抽象面向过程编程通常关注具体的算法和过程而不是抽象的概念。这使得代码难以适应变化且难以理解和修改。
全局状态管理困难在面向过程的编程中全局状态的管理通常较为困难因为状态的改变可能会影响到程序的多个部分。
不适合大型项目面向过程的编程更适合小型、短期的项目因为它难以管理大型、长期项目的复杂性。 尽管存在这些缺点但面向过程的编程在某些情况下仍然是有用的特别是在需要精确控制和高效性能的领域如系统编程、嵌入式系统开发或性能关键的应用程序中。然而面向对象的编程OOP和函数式编程等其他范式也在许多应用中取得了成功。每种范式都有其优点和适用场景选择哪种范式取决于具体的需求和约束。
面向对象的编程
面向对象编程OOP是一种编程范式它基于对象的概念。在OOP中对象是数据和与之相关的操作的集合。以下是面向对象编程的一些主要特点封装、继承、多态、抽象、组合
面向对象的具体特征
封装特征
封装概述
是面向对象三大特征之一封装继承多态是面向对象编程语言对客观世界的模拟客观世界里成员变量都是隐藏在对象内部的外界是无法直接操作的 封装原则
将类的某些信息隐藏在类内部不允许外部程序直接访问而是通过该类提供的方法来实现对隐藏信息的操作和访问
成员变量private提供对应的getXxx()/setXxx()方法 面向对象的封装例子 封装好处
通过方法来控制成员变量的操作提高了代码的安全性
把代码用方法进行封装提高了代码的复用性
Tips: 在多线程环境中类中的变量是属于状态必须要采用 threadlocal的方式或者采用 锁的机制来进行处理。 继承
继承是从已存在的类派生出新类。新类子类可以继承原始类父类的属性和方法并且可以定义自己的新属性和方法。这有助于代码的复用并建立类之间的层次结构 2.1 继承
继承就是子类继承父类的特征和行为 2.2 继承的作用
通过继承可以快速创建新的类实现代码的重复利用提高开发的效率。 2.3 继承后成员访问规则
继承的构造方法调用 创建对象时调用构造方法子类会先调用父类的构造方法。
子类没有构造方法则会默认调用父类的空参构造方法。
可以隐式地实现也可以用关键字super来实现super应在方法中的第一行。
父类私有变量调用
因为子类继承父类非私有变量和方法可以调用父类的私有成员获取方法来获取父类中的私有变量。 父类变量
子类的静态变量和静态方法会隐藏父类的静态变量和静态方法?
子类中如果没有定义同名的变量和方法则调用父类变量和方法。 super关键字使用
super可以访问父类成员属性成员方法构造方法。
super.父类成员变量
super.成员方法名();
super();//父类构造方法 注意 子类不能选择性继承父类
Java不支持多重继承但一个类可以实现多个接口从而克服单继承的缺点
构造方法不会被子类继承但可以从子类中调用父类的构造方法。
父类的私有成员子类不能直接调用。
2.4 方法重写
方法重写声明不变重新实现。子类中出现和父类一样的方法返回值方法名参数列表都相同。这样会将父类方法覆盖也叫重写。
方法重写后的权限不能比父类方法小如父类project方法子类不能是private。父类是static子类也要是static。 随便找一个例子
多态
多态是指允许一个接口被多个类实现或者一个接口可以有多种实现方式。多态可以提高代码的灵活性和可扩展性因为它允许程序在运行时确定对象的类型和行为。 是指同一行为对于不同的对象具有多个不同表现形式。
程序中多态: 是指同一方法,对于不同的对象具有不同的实现.
父类引用指向子类对象格式体现为
父类类型 变量名 new 子类对象
变量名.方法名();
父类类型指子类对象继承的父类类型或者实现的父接口类型。 多态是面向对象的程序可扩展的非常重要的一个能力在单体之外有微服务架构与它想对应 后期绑定它的含义就是在运行时根据对象的类型进行绑定。 也叫做动态绑定或运行时绑定。编译器一直不知道对象的类型但是方法调用机制能找到正确的方法体并加以调用。 Java中除了static方法和final方法private方法属于final方法之外其他所有的方法都是后期绑定。 Java多态是指同一个方法在不同情况下有不同的实现方式。它是Java面向对象中最重要的概念之一可以提高代码的可读性可扩展性和可维护性。在Java中多态性可以通过继承、接口和重载来实现。其中多态的实现原理和关键是动态链接。
动态链接实现Java多态
动态链接是Java实现多态的关键。动态链接是指在程序运行时动态地决定方法或函数的绑定过程。这通常涉及到多态性的实现即子类可以覆盖父类的方法从而在运行时根据对象的实际类型来确定调用哪个方法。这种机制使得程序更加灵活和扩展性强。
在面向对象编程中的应用
在面向对象编程中动态链接主要体现在以下几个方面
多态性
多态性允许子类重写父类的方法。
在运行时根据对象的实际类型来决定调用哪个版本的方法。
这种机制使得程序能够更加灵活地处理不同类型的对象。
虚方法
在 Java 中所有的非 static 方法都是虚方法这意味着它们都可以被子类重写。
当调用一个对象的方法时实际调用的是对象实际类型中定义的方法。
这种绑定是在运行时完成的称为动态绑定。
接口
接口定义了一组方法签名实现接口的类必须提供这些方法的具体实现。
这种机制使得不同的类可以共享相同的接口从而实现多态性。
动态链接与静态链接不同静态链接是指在程序编译期间就确定调用哪个方法的实现例如C中的函数重载就是静态链接。而Java中的动态链接是在程序运行时才确定方法的实际执行代码因此可以实现多态。
Tips动态编译的概念与动态链接的概念不一样。
动态编译是指在程序运行时对代码进行编译的过程。这种编译通常是为了优化性能或者处理动态生成的代码。它有以下特点
即时编译JITJava 虚拟机JVM中的即时编译器会在运行时将热点代码编译成本地机器码。
动态代理利用反射和代理技术在运行时生成类。
脚本引擎使用脚本引擎在运行时执行动态生成的脚本代码。
Java多态的实现方式
java的多态可以通过继承和接口来实现。具体而言继承实现多态可以用子类来重写父类的方法当调用方法时依据对象的实际类型来执行相应的实现。
在面向对象编程中动态链接Dynamic Linking主要体现在多态性和虚方法的实现上。下面通过一个简单的 Java 示例来展示动态链接的应用。
示例代码
1. 定义基类 Animal
public abstract class Animal { public abstract void makeSound(); public void displayInfo() { System.out.println(This is an animal.); }
}
2. 定义子类 Dog
public class Dog extends Animal { Override public void makeSound() { System.out.println(Woof!); } public void displayInfo() { System.out.println(This is a dog.); }
}
3. 定义子类 Cat
public class Cat extends Animal { Override public void makeSound() { System.out.println(Meow!); } public void displayInfo() { System.out.println(This is a cat.); }
}
4. 主程序 Main
public class Main { public static void main(String[] args) { // 创建 Animal 类型的引用变量 Animal animal1 new Dog(); Animal animal2 new Cat(); // 调用 makeSound 方法 animal1.makeSound(); // 输出 Woof! animal2.makeSound(); // 输出 Meow! // 调用 displayInfo 方法 animal1.displayInfo(); // 输出 This is a dog. animal2.displayInfo(); // 输出 This is a cat. // 调用通用方法 makeSound(animal1); // 输出 Woof! makeSound(animal2); // 输出 Meow! } // 通用方法接受 Animal 类型的参数 public static void makeSound(Animal animal) { animal.makeSound(); }
}
解释
多态性
Animal 是一个抽象类定义了 makeSound 方法。
Dog 和 Cat 分别继承了 Animal 类并重写了 makeSound 方法。
在 main 方法中我们创建了 Animal 类型的引用变量 animal1 和 animal2分别指向 Dog 和 Cat 对象。
动态绑定
当调用 makeSound 方法时实际调用的是对象的实际类型中定义的方法。
animal1.makeSound() 实际上调用的是 Dog 类中的 makeSound 方法输出 Woof!。
animal2.makeSound() 实际上调用的是 Cat 类中的 makeSound 方法输出 Meow!。
通用方法
makeSound 方法接受 Animal 类型的参数可以处理任何继承自 Animal 的对象。
这种设计使得程序更加灵活可以处理不同类型的对象。
总结
在面向对象编程中动态链接主要体现在多态性和虚方法的实现上。通过子类重写父类的方法程序在运行时根据对象的实际类型来决定调用哪个版本的方法。这种机制使得程序更加灵活和扩展性强。
通过上述示例可以看到动态链接如何在 Java 中实现并且如何通过多态性来提高程序的灵活性和可扩展性。 向下转型向上转型
类型转换
将一个类型转换成另一个类型的过程被称为类型转换。我们所说的对象类型转换一般是指两个存在继承关系的对象而不是任意类型的对象。如果两个类型之间没有继承关系就不允许进行类型转换否则会抛出强制类型转换异常(java.lang.ClassCastException)。 Java语言允许某个类型的引用变量引用子类的实例而且可以对这个引用变量进行类型转换。Java中引用类型之间的类型转换(前提是两个类是直接或间接的父子关系)主要有两种分别是向上转型(upcasting)和向下转型(downcasting)。 向上转型
Tomcat中的ServletRequest
向上转型描述的比较抽象。采用具体的例子
Servlet是一个接口。接口service方法中
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
Request继承于ServletRequest
public class Request implements ServletRequest{}
但是Request中有自己的方法不仅仅是父类方法的覆盖。 调用Sevlet的service方法需要把参数转为ServletRequest。
Servlet servlet null;
try {
servlet (Servlet) myClass.newInstance();
servlet.service((ServletRequest) request,(ServletResponse) response);
}
catch (Exception e) {
System.out.println(e.toString());
} 这个时候就是向上转型。因为本身是request对象但是要转为父类参数被接口调用。
总结向上转型就是把子类对象直接赋给父类引用不用强制转换。使用向上转型可以调用父类类型中的所有成员不能调用子类类型中特有成员最终运行效果看子类的具体实现(如果子类没有实现那么就采用父类的默认实现)。 如果Servlet的程序员可以把ServletRequest对象向下转型为Request对象就可以拿到它不属于父类定义的方法而是子类自己的方法。
向下转型
与向上转型相反子类对象指向父类引用为向下转型语法格式如下
sonClass obj (sonClass) fatherClass;
其中fatherClass 是父类名称obj 是创建的对象sonClass 是子类名称。
向下转型可以调用子类类型中所有的成员不过需要注意的是如果父类引用对象指向的是子类对象那么在向下转型的过程中是安全的也就是编译是不会出错误。但是如果父类引用对象是父类本身那么在向下转型的过程中是不安全的编译不会出错但是运行时会出现我们开始提到的 Java 强制类型转换异常一般使用 instanceof 运算符来避免出此类错误。
例如Animal 类表示动物类该类对应的子类有 Dog 类使用对象类型表示如下
成功的方式
Animal animal new Dog(); // 向上转型把Dog类型转换为Animal类型当前的父类animal引用变量 指向了子类。
Dog dog (Dog) animal; // 向下转型把Animal类型转换为Dog类型
失败的方式
Animal animal new Animal (); // 向上转型把Dog类型转换为Animal类型当前的父类animal引用变量 指向了子类。
Dog dog (Dog) animal; // 向下转型把Animal类型转换为Dog类型
父类引用的对象是父类本身那么在向下转型的过程中是不安全的编译不会出错但是运行时会出现java.lang.ClassCastException错误
总结
父类引用可以指向子类对象子类引用不能指向父类对象。
如不能写成 Son sonnew Father();
2、把子类对象直接赋给父类引用叫upcasting向上转型向上转型不用强制转型。
如Father father new Son();
upcasting 会丢失子类特有的方法,但是子类overriding 父类的方法子类方法有效
向上转型的作用体现了面向接口编程的特点父类为参数调有时用子类作为参数就是利用了向上转型。这样使代码的可扩展性很强。体现了面向对象编程的优势。 3、把指向子类对象的父类引用赋给子类引用叫向下转型downcasting要强制转型。
如father就是一个指向子类对象的父类引用把father赋给子类引用son 即
Son son Sonfather
其中father前面的Son必须添加进行强制转换。
指向父类对象的父类引用是无法进行向下转型的。 向下转型不安全
体现在2个方面
编译不报错执行会报错
向下转型时如果父类的引用一开始就指向子类对象那么转型之后是安全的若果父类的引用一开始指向的不是子类对象那么转型成子类对象时编译不会出错运行时会显示ClassCastException。我们可以通过关键字instanceof来检测安全性
导致安全问题
原来是向上转型的但是框架的使用者知道这个实际上指向了 具体的子类对象。所以主动进行向下转型。导致 框架中该子类的私有方法被暴露。 “封装”通过合并特征和行为来创建新的数据类型。“实现隐藏”则通过将细节“私有化”把接口和实现分离开来。多态的作用则是消除类型之间的耦合关系。 避免 向框架转型的时候不安全的情况发生 适配器模式
原理
适配器模式是一种结构型设计模式它允许将不兼容的对象转换成可兼容的接口。主要目的是解决在不改变现有代码的情况下使不兼容的接口之间能够正常工作通过创建一个中间转换的适配器来将一个对象转换成我们所需要的接口。
结构图如下 角色组成
客户端Client调用自己需要的接口Target
目标接口target需要适配的标准接口。
适配器对象adapter充当中间转换角色该对象将源对象转换成目标接口。转换的时候也可能会补充一些逻辑。
源对象source需要被适配的不兼容对象。一般情况下已经存在的接口功能能满足客户端的需求但是接口与客户端的接口不一致方法本身功能方法名传递参数返回类型异常类型有可能有一个不一致需要被适配。
下面是一个简单的Java代码示例展示了适配器设计模式Adapter Pattern的
// Target接口
public interface Target { void request();
} // Adaptee类
public class Adaptee { public void specificRequest() { System.out.println(Adaptee specific request); }
}
// 类适配器
public class ClassAdapter implements Target { private Adaptee adaptee; public ClassAdapter(Adaptee adaptee) { this.adaptee adaptee; } Override public void request() { adaptee.specificRequest(); System.out.println(Adapter class converts specificRequest to request); }
}
public class AdapterPatternDemo { public static void main(String[] args) { // 类适配器示例 Target classAdapter new ClassAdapter(new Adaptee()); classAdapter.request(); }
}
在这个例子中Adaptee类具有一个specificRequest方法但是客户端需要的是Target接口的request方法。通过使用适配器我们可以将Adaptee的specificRequest方法适配为Target接口的request方法从而满足客户端的需求。 运行AdapterPatternDemo的main方法你将看到输出显示了每种适配器模式如何将Adaptee的specificRequest方法适配为Target接口的request方法。
源码中实际案例
http://localhost:8080/myApp/Primitive
参考Tomcat9的代码
模版设计模式
模板设计模式概念
模板模式是一种行为型设计模式用于定义算法的框架结构将具体步骤的实现延迟到子类中。这种模式促使子类在不改变算法结构的情况下重新定义算法中的某些步骤。 模板模式通常包含以下几个要素
AbstractClass抽象类 定义了算法的框架结构其中包含了一个或多个抽象方法用于被子类实现以及一个模板方法用于定义算法的框架。
ConcreteClass具体类 实现了抽象类中定义的抽象方法完成了算法中的具体步骤。具体类可以有多个每个具体类对应着不同的算法实现。
模版设计模式类图 模板方法模式的功能
模板方法模式的核心功能是固定算法的骨架同时允许子类扩展具体的算法实现。
这种模式在设计框架级功能时非常有用因为框架可以定义算法的步骤并在适当的点提供扩展点让开发者实现具体的算法。
例如在DAO数据访问对象的实现中可以设计通用的增删改查功能模板方法模式可以在这里发挥作用。
模板方法模式还提供了控制子类扩展的能力。由于算法步骤在父类中定义只在特定的点调用被子类实现的方法因此只允许在这些点扩展功能。这些可以被子类覆盖以扩展功能的方法通常被称为“钩子”方法。
模版设计模式使用时候关注点
1. **抽象类与接口的关系** - 接口是一种特殊的抽象类其中所有属性自动是public final static的所有方法必须是抽象的。 - 抽象类是用abstract修饰的类它可以包含抽象方法也可以包含具体实现的方法。 - 抽象类和接口的主要区别在于抽象类可以有具体实现的方法而接口中的方法都是抽象的。 2. **何时使用抽象类** - 在需要约束子类行为的同时又想为子类提供公共功能时使用抽象类是合适的。 - 模板方法模式正是基于这样的原则通过定义算法的骨架公共行为并使用抽象方法来允许子类扩展具体步骤的实现。模板方法模式使用了抽象方法而不是接口。 3. **模板方法模式的实现** - 模板方法模式通过将算法骨架定义在抽象类中为所有子类提供公共功能。 - 同时模板方法模式通过抽象方法要求子类实现具体的步骤从而约束子类的行为。 - 这种设计使得系统有更好的复用性和扩展性。 4. **变与不变的思考** - 程序设计中重要的一点是区分哪些功能是不变的哪些是可变的。 - 模板方法模式体现了这一点将不变的算法骨架抽象出来而将可变的部分通过抽象方法延迟到子类中实现。 5. **好莱坞法则** - 好莱坞法则是一种控制结构其原则是“不要找我们我们会联系你”。 - 在模板方法模式中父类在需要时调用子类的方法体现了好莱坞法则即父类控制何时调用子类的方法而不是子类主动调用父类的方法。 6. Java中模板方法模式能够实现其功能的理论依据即Java的动态绑定机制 6.1. **Java动态绑定后期绑定** - Java的动态绑定技术指的是在编译时方法调用是基于数据类型的而在运行时则是基于实际的对象类型。 - 这意味着即使在编写代码时使用的是父类的引用类型实际运行时如果创建的是子类实例那么调用的方法将是子类覆盖的方法。 6.2. **模板方法模式与动态绑定** - 在模板方法模式中父类定义了算法的骨架和一些抽象方法这些抽象方法需要子类去实现。 - 当使用模板方法模式时尽管声明的变量类型是父类类型但实际创建的是子类的实例。 - 在运行时通过动态绑定机制父类中调用的抽象方法会被绑定到子类的具体实现上从而实现了父类对子类的反向控制。 6.3. **new操作符与方法调用** - 文件中提到了“new谁就调用谁的方法”意味着使用new关键字创建对象时指定的是哪个类的构造器就会调用哪个类的方法。 - 这表明即使父类中定义了抽象方法子类通过覆盖这些方法提供了具体实现运行时Java虚拟机JVM会根据实际创建的对象类型来调用相应的方法。 Java的动态绑定机制是模板方法模式能够实现其设计目的的关键。通过这种机制父类可以在运行时动态地调用子类覆盖的方法从而实现算法骨架的固定和子类行为的约束。 角色划分
写抽象类角色定义了算法的框架结构
写子类角色根据框架实现不同的子类
客户端角色调用子类 源码中的设计模式 回调设计模式
除了继承方式Java回调技术也可以实现类似的功能通过在接口中定义的方法调用具体实现类中的方法。
Java回调技术利用Java的动态绑定技术可以在运行时调用实现类中的方法。通过使用匿名内部类可以实现回调方法而无需单独创建实现类。
应用Java回调实现模板方法模式这种实现方式在实际开发中非常常见可以视为模板方法模式的一种变形。
为了使用Java回调实现模板方法模式首先需要定义一个回调接口其中包含所有可扩展的方法。
比较使用继承和Java回调技术实现模板方法模式的两种方式 Java回调技术可以作为一种替代继承的方式来实现模板方法模式中的算法骨架扩展。通过定义回调接口和使用匿名内部类可以在运行时动态地调用具体实现类的方法从而实现算法步骤的扩展和定制。 1. **继承方式实现模板方法模式** - 抽象方法和具体实现的关系在编译期间静态决定是类级的关系。 - 这种方式比较简单因为父类提供了实现的方法子类如果不想扩展可以不进行任何操作。 2. **Java回调方式实现模板方法模式** - 抽象方法和具体实现的关系在运行期间动态决定是对象级的关系。 - 使用回调机制会更灵活因为Java是单继承的使用回调可以基于接口不受单继承限制。
3在回调模式中的模版类的方法中方法传递的参数会增加一个回调的接口。纯粹的模版设计模式不需要回调接口。
4. 在回调模式中的模版类不需要定义子类因为业务有可能变化很多无法预先把所有的子类定义完。
5客户端调用的时候回调模式的客户端需要写匿名的类的实现。这样可以保证足够的灵活性。即实现接口的业务逻辑在调用的时候写。纯粹的模版模式实现的业务逻辑是在子类实现的时候静态完成了欠缺灵活性。 1. **两种方式的优缺点** - 使用继承方式的优点是简单缺点是子类不能继承其他对象且耦合度较高。 - 使用回调机制的优点是灵活性更高缺点是实现时需要定义所有可能被扩展的方法即使不扩展也需要实现这些方法。 2. **回调机制的特点** - 回调机制通过委托方式组合功能耦合度较低提供更多灵活性。 - 在回调实现时可以不调用模板中的方法而是调用其他实现中的某些功能功能组织更灵活。 使用继承和Java回调技术实现模板方法模式的两种方式各有优缺点选择哪种方式取决于具体的需求和场景。继承方式简单但不够灵活而回调机制灵活但实现起来较为复杂。此外命令模式提供了一种使用Java回调实现模板方法模式的替代方案。 角色划分
模版设计者组织框架步骤并在方法中增加回调接口
模版继承者没有这个角色
回调接口接口定义角色
客户调用者实现调用并同时进行接口的实现。事情变多但是灵活性变强。
源码中的设计模式
模板定义
JdbcTemplate
public T T queryForObject(String sql, RowMapperT rowMapper) throws DataAccessException { ListT results this.query(sql, rowMapper); return DataAccessUtils.nullableSingleResult(results);
}
接口定义 public interface RowMapperT { Nullable T mapRow(ResultSet rs, int rowNum) throws SQLException;
}
客户端调用实现这个匿名类
public static void main(String[] args) { DataSource dataSource DataSourceConfig.createDataSource(); JdbcExample example new JdbcExample(dataSource); // 查询操作 String querySql SELECT * FROM users WHERE id ?; User user example.queryForObject(querySql, (rs, rowNum) - { int id rs.getInt(id); String name rs.getString(name); return new User(id, name); }, 1); System.out.println(user);
}
回调的本质是面向接口动态链接的过程
策略设计模式
策略设计模式原理 策略模式的结构图 Strategy(策略)接口: 定义了所有支持的算法的公共接口。
Concrete Strategies(具体策略)类: 实现了策略接口提供了具体的算法实现。
Context(上下文)类: 维持一个对策略对象的引用使用策略对象的算法
主要优点包括
易于扩展和维护: 策略模式将算法的实现封装在独立的类中这意味着增加新的算法或修改现有算法变得非常容易而不会影响到使用这些算法的客户端代码。这使得系统更易于维护和扩展。
提高代码的复用性: 通过将算法封装在独立的策略类中相同的算法可以在多个地方重用减少了代码重复提高了代码的复用性。
减少条件语句: 在没有使用策略模式的情况下不同的算法可能通过一系列的条件语句如if-else或switch-case来选择和执行。策略模式通过将算法的选择逻辑封装在上下文中避免了复杂的条件语句使代码更加清晰和简洁。
提高灵活性: 策略模式允许在运行时动态地选择和改变算法这增加了系统的灵活性。例如可以在不修改客户端代码的情况下根据外部条件或用户选择来改变算法。
解耦合: 策略模式通过将算法的实现与使用算法的客户端解耦使得算法的变更不会影响到客户端。这有助于降低系统的耦合度提高了模块之间的独立性。
易于理解和测试: 每个策略类只关注一个算法的实现这使得代码更易于理解和维护。同时独立的策略类也便于单元测试因为它们通常具有单一的责任。
综上所述策略设计模式通过封装和解耦算法的实现提高了代码的可读性、可维护性和灵活性同时也简化了条件逻辑促进了代码的复用是设计可扩展和可维护系统的一种有效手段。
源码中的设计模式
SpringMVC中HandlerAdapter
每一个都有supports(Object handler)的类型
有多个类
调用方在DispatcherServlet中
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { if (this.handlerAdapters ! null) { Iterator var2 this.handlerAdapters.iterator(); while(var2.hasNext()) { HandlerAdapter adapter (HandlerAdapter)var2.next(); if (adapter.supports(handler)) { return adapter; } } } throw new ServletException(No adapter for handler [ handler ]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler); }
针对handlerAdapters的集合循环之后选择一个合适的。
执行的动作在另外一个地方进行分离
传递的参数 责任链设计模式
责任链设计模式原理
职责链模式特点如下
分离发送者和接收者将发送者和接收者解耦发送者只需要将请求发送给职责链的起始节点而不需要知道具体的接收者。接收者也不需要知道请求的发送者是谁。
多个处理节点处理请求请求会在职责链上的多个处理节点中依次传递每个节点都有机会处理请求。这样可以将复杂的处理逻辑分解成多个独立的节点。
请求的处理可以终止每个处理节点都可以决定是否继续传递请求给下一个节点或终止处理。这样可以灵活地控制请求的处理流程。 多个Filter的执行顺序 | 职责链模式应用_多个filter执行顺序-CSDN博客 详细动作的分解
配置文件将每个独立的类采用配置化的方式增加扩展性
定时扫描如果规则增加能动态扩展单独的一个线程
能否全部权限
规则类每个独立的业务处理单元。可以设置条件根据条件来判断
建链类将规则按照链的方式组合在一起
上下文传递到链条中的参数这个上下文是否可以写
类之间转移在每个类里面设置采用模板模式或者采用单独的一个类来设置转移的过程。
调用方遍历这个集合或者访问链首 Tomcat框架中的责任链
内部使用的责任链
规则类在内部内称为阀valve
Pipeline-Valve使用的责任链模式和普通的责任链模式有些不同区别主要有以下两点 每个Pipeline都有特定的Valve而且是在管道的最后一个执行这个Valve叫做BaseValveBaseValve是不可删除的。在StandardEngine的构造函数中设置了pipeline.setBasic(new StandardXXXValve()) 在上层容器的管道的BaseValve中会调用下层容器的管道。
Tomcat架构详解-CSDN博客 规则配置
Server.xml阀配置文件的格式
Engine nameCatalina defaultHostlocalhost Host namelocalhost appBasewebapps unpackWARstrue autoDeploytrue !-- 默认 Valve -- Valve classNameorg.apache.catalina.valves.AccessLogValve directorylogs prefixlocalhost_access_log suffix.txt !-- maxDays5 -- pattern%h %l %u %t %r %s %b / !-- 自定义 valve -- Valve classNameorg.apache.catalina.valves.WswAccessValve/ /Host/Engine 规则类
public interface Valve { Valve getNext(); void setNext(Valve valve); void backgroundProcess(); void invoke(Request request, Response response) throws IOException, ServletException; boolean isAsyncSupported();
} 自定义Value继承于ValveBase
public class WswAccessValve extends ValveBase { Override public void invoke(Request request, Response response) { String uri request.getRequestURI(); System.out.println(uri uri); getNext().invoke(request, response); }} 建立链的时间点
在启动程序的时候通过Diagest方式来建立规则类之间的关系。建立4条链每个链都在容器中。 建链类
Pipeline接口
StandardPipeline接口的实现
Tomcat中容器有层级关系每一个容器都有一个 Pipeline 对象只要触发这个 Pipeline 的第一个 Valve这个容器里 Pipeline 中的 Valve 就都会被调用到。但是不同容器的 Pipeline 是怎么链式触发的呢比如 Engine 中 Pipeline 需要调用下层容器 Host 中的 Pipeline。
在tomcat启动的时候执行xml的解析组成了
设置基础阀的方法这个方法在每个容器的构造函数中调用
public StandardEngine() { super(); pipeline.setBasic(new StandardEngineValve());}
public StandardHost() { super(); pipeline.setBasic(new StandardHostValve());}
public StandardContext() { super(); pipeline.setBasic(new StandardContextValve());}
public StandardWrapper() { super(); pipeline.setBasic(new StandardWrapperValve());} 这是因为 Pipeline 中还有个 getBasic 方法。这个 BasicValve 处于 Valve 链表的末端它是 Pipeline 中必不可少的一个 Valve负责调用下层容器的 Pipeline 里的第一个 Valve。通过一张图来解释。
在Catalina中有4种容器每个容器都有自己的Pipeline组件每Pipeline组件上至少会设定一个Valve(阀门)这个Valve称之为BaseValve基础阀。基础阀的作用是连接当前容器的下一个容器(通常是自己的自容器),可以说基础阀是两个容器之间的桥梁。 Pipeline定义对应的接口Pipeline,标准实现了StandardPipeline。Valve定义对应的接口Valve,抽象实现类ValveBase,4个容器对应基础阀门分别是 StandardEngineValve,StandardHostValve,StandardContextValve,StandardWrapperValve。 调用链时间点
整个调用过程由连接器中的 Adapter 触发的它会调用 Engine 的第一个 Valve
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) { // 此处调用 Pipeline Value 的 invoke 方法。Engine是最顶层容器 connector.getService() .getContainer() .getPipeline() .getFirst() .invoke(request, response); }} 在第一个StandardEngineValve规则类中 Overridepublic final void invoke(Request request, Response response) { // 根据 当前 request 找到合适的 host通过 MappingData Host host request.getHost(); // 没有找到 host不走了 if (host null) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, sm.getString(standardEngine.noHost)); return; } // 执行另外一条链即执行host中的链条的第一个 Valve host.getPipeline().getFirst().invoke(request, response);} Wrapper 容器的最后一个 Valve 会创建一个 Filter 链也是一个责任链并调用 doFilter() 方法最终会调到 Servlet 的 service 方法。
执行链
上下文
Requestresponse
配置文件变化
没有动态监控server.xml文件的变化每次变化之后需要重现启动Tomcat
开发给程序员的责任链 规则配置
web.xml配置文件的格式
参考网上的内容
FilterDef
FilterDef与xml配置的保持一致。但是并没有实例化
FilterConfig接口
ApplicationFilterConfig类
在StandardContext.java类中startInternal方法中调用了filterStart()
public boolean filterStart() {
。。。
synchronized (filterConfigs) {
//对配置文件中的每一个filterdef循环处理放入到map中 for (EntryString, FilterDef entry : filterDefs.entrySet()) { String name entry.getKey(); ApplicationFilterConfig filterConfig new ApplicationFilterConfig(this, entry.getValue())
{ this.filter filterDef.getFilter(); context.getInstanceManager().newInstance(filter);
//初始化Filter initFilter(){。。。
filter.init(this)
}
} filterConfigs.put(name, filterConfig);
。。。
} }
规则类
Filter是一个接口有3个方法。它的实例化在前面配置类的时候进行
public interface Filter { default void init(FilterConfig filterConfig) throws ServletException { } void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException; default void destroy() { }
} 建立链的时间点 建链类
FilterChain接口
只是定义了执行方法并没有定义如何添加链。
public interface FilterChain { void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException;
}
ApplicationFilterChain 接口的实现
继承了接口同时扩展了链的实现
接口的实现
public final class ApplicationFilterChain implements FilterChain { //初始化的配置文件
private ApplicationFilterConfig[] filters new ApplicationFilterConfig[0];
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
//通过类变量中的pos及n来遍历整个链
//链的顺序也在这个地方处理
}
//添加链到数值中
void addFilter(ApplicationFilterConfig filterConfig) {
}
构建链 用户请求的时候建立链条。StandardWrapperValve类中的invoke方法
ApplicationFilterChain filterChain ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
在这个方法中把代码错误检查及异常处理的步骤去掉保留核心的逻辑
public static ApplicationFilterChain createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet) { // If there is no servlet to execute, return null if (servlet null) { return null; } // 创建一个链Create and initialize a filter chain object // Create and initialize a filter chain object
将filterChain链这个类放入到request属性中 ApplicationFilterChain filterChain null; if (request instanceof Request) { Request req (Request) request; if (filterChain null) { filterChain new ApplicationFilterChain(); req.setFilterChain(filterChain); }
//继续设置链的Servlet属性 filterChain.setServlet(servlet); filterChain.setServletSupportsAsync(wrapper.isAsyncSupported()); // Acquire the filter mappings for this Context StandardContext context (StandardContext) wrapper.getParent(); FilterMap filterMaps[] context.findFilterMaps(); String servletName wrapper.getName(); // Add the relevant path-mapped filters to this filter chain for (FilterMap filterMap : filterMaps) { filterChain.addFilter(filterConfig); } // Return the completed filter chain
//根据访问的URL确定了链中的fliter数组是从webxml中进行了筛选 return filterChain; }
调用链时间点
Wrapper 容器的最后一个 Valve 会创建一个 Filter 链也是一个责任链并调用 doFilter() 方法最终会调到 Servlet 的 service 方法。
也是在StandardWrapperValve类中的invoke方法中。 执行链
这个链是一个单独的对象在这个对象里面有一个filter的数组。这个与责任链有一些差异。调用方遍历这个集合或者访问链首。
也是在StandardWrapperValve类中的invoke方法中。
filterChain.doFilter(request.getRequest(), response.getResponse()){
{
internalDoFilter(ServletRequest request, ServletResponse response)
{
类之间转移在每个类里面设置采用模板模式或者采用单独的一个类来设置转移的过程。
} }
}
每个filter必须要调用一下 //把请求转发给后续的过滤器或者Web组件 chain.doFilter(request, response);
由这个类来决定下一个是哪一个filter 到最后一个filter之后执行servlet
然后继续执行filter下面的内容 上下文
ServletReuqest,ServletResponse可以修改但是通过Wrapper类
传递到链条中的参数这个上下文是否可以写
配置文件动态变化
容器类的基类ContainerBase 拥有一个重要属性backgroundProcessorDelay默认为-1在StandardEngine的构造函数中设置成了10其余的容器如StandardContext、StandardHost都仍然为-1只有该值属性为正数才能启动ContainerBackgroundProcessor线程
StandardEngine类下
Override protected synchronized void startInternal() throws LifecycleException { // Standard container startup super.startInternal(); }
ContainerBase类下
Override protected synchronized void startInternal() throws LifecycleException { //延迟initialDelay时间后第一次执行command任务,上次执行command任务终止后延迟delay时间再次执行command任务
//public ScheduledFuture? scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit) {}
产生一个定时任务的线程池这个线程池采用Tomcat的包装类暂时不知道为什么要这么处理,第一次延迟0秒任务结束后延迟60秒执行ContainerBackgroundProcessorMonitor任务。 60s执行一次 // Start our thread if (backgroundProcessorDelay 0) { monitorFuture Container.getService(ContainerBase.this).getServer().getUtilityExecutor() .scheduleWithFixedDelay(new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS); } }
//每隔60秒
protected class ContainerBackgroundProcessorMonitor implements Runnable { Override public void run() { if (getState().isAvailable()) { threadStart(); } } }
//每隔10s执行一次
protected void threadStart() { if (backgroundProcessorDelay 0 (getState().isAvailable() || LifecycleState.STARTING_PREP.equals(getState())) (backgroundProcessorFuture null || backgroundProcessorFuture.isDone())) { if (backgroundProcessorFuture ! null backgroundProcessorFuture.isDone()) { // There was an error executing the scheduled task, get it and log it try { backgroundProcessorFuture.get(); } catch (InterruptedException | ExecutionException e) { log.error(sm.getString(containerBase.backgroundProcess.error), e); } } backgroundProcessorFuture Container.getService(this).getServer().getUtilityExecutor() .scheduleWithFixedDelay(new ContainerBackgroundProcessor(), backgroundProcessorDelay, backgroundProcessorDelay, TimeUnit.SECONDS); } } 调用processChildren继续跟踪该方法会看到调用Engine、Host、Context、Wrapper各容器组件及与它们相关的其它组件的backgroundProcess方法。
protected class ContainerBackgroundProcessor implements Runnable { Override public void run() { processChildren(ContainerBase.this); } protected void processChildren(Container container) { ClassLoader originalClassLoader null; try { if (container instanceof Context) { Loader loader ((Context) container).getLoader(); // Ensure background processing for Contexts and Wrappers // is performed under the web apps class loader originalClassLoader ((Context) container).bind(false, null); } container.backgroundProcess(); Container[] children container.findChildren(); for (Container child : children) { if (child.getBackgroundProcessorDelay() 0) { processChildren(child); } } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.error(sm.getString(containerBase.backgroundProcess.error), t); } finally { if (container instanceof Context) { ((Context) container).unbind(false, originalClassLoader); } } } } 在StandardContext下
Loader loader
public void backgroundProcess() { Loader loader getLoader(); //Webapploader.java loader.backgroundProcess(); Manager manager getManager(); manager.backgroundProcess(); WebResourceRoot resources getResources(); resources.backgroundProcess(); InstanceManager instanceManager getInstanceManager(); instanceManager.backgroundProcess(); super.backgroundProcess(); } Webapploader.java
Override public void backgroundProcess() { if (reloadable modified()) { Thread currentThread Thread.currentThread(); try { currentThread.setContextClassLoader(WebappLoader.class.getClassLoader()); if (context ! null) { context.reload(); } } finally { if (context ! null context.getLoader() ! null) { currentThread.setContextClassLoader(context.getLoader().getClassLoader()); } } } } 更新的频率
在开发状态下
ContainerBackgroundProcessor
文件的变化 // 校验 WebappClassLoader 加载的资源是否有修改过, 若有文件修改过, 则进行热部署
public boolean modified() { if (log.isDebugEnabled()) { log.debug(modified()); }
// 1. 遍历已经加载的资源 for (EntryString,ResourceEntry entry : resourceEntries.entrySet()) { long cachedLastModified entry.getValue().lastModified; long lastModified resources.getClassLoaderResource( entry.getKey()).getLastModified();
// 2. 对比 file 的 lastModified的属性 // 3. 若修改时间不对, 则说明文件被修改过, StandardContext 需要重新部署 if (lastModified ! cachedLastModified) { if( log.isDebugEnabled() ) { log.debug(sm.getString(webappClassLoader.resourceModified, entry.getKey(), new Date(cachedLastModified), new Date(lastModified))); } return true; } } // Check if JARs have been added or removed WebResource[] jars resources.listResources(/WEB-INF/lib); // Filter out non-JAR resources int jarCount 0; for (WebResource jar : jars) {
// 4. 比较 /WEB-INF/lib 下的 jar 包是否有修改/增加/减少 if (jar.getName().endsWith(.jar) jar.isFile() jar.canRead()) {
// 5. 记录 /WEB-INF/lib 下的 jar 的个数 jarCount; Long recordedLastModified jarModificationTimes.get(jar.getName()); if (recordedLastModified null) { // Jar has been added log.info(sm.getString(webappClassLoader.jarsAdded, resources.getContext().getName())); return true; }
// 6. 比较一下这次的文件修改时间 与 上次文件的修改时间是否一样, 不一样的话, 直接返回 true, StandardContext 需要重新部署 if (recordedLastModified.longValue() ! jar.getLastModified()) { // Jar has been changed log.info(sm.getString(webappClassLoader.jarsModified, resources.getContext().getName())); return true; } } }
// 7. 判断 WebappClassloader文件是够有增加/减少, 若有变化的话, 直接返回 true, StandardContext 需要重新部署 if (jarCount jarModificationTimes.size()){ log.info(sm.getString(webappClassLoader.jarsRemoved, resources.getContext().getName())); return true; } // No classes have been modified return false; } 发现是线程 ContainerBackgroundProcessor 网上查了一下是处理 tomcat的动态reload的机制的定时扫描class的变化开发环境需要关闭于是修改tomcat的配置即可 Host namelocalhost appBase/app unpackWARstrue autoDeploytrue Context path docBase./picturebook-teacher reloadablefalse / //改成false即可 ...... /Host StandardContext 重写了 backgroundProcess 方法
StandardContext.java Override
public void backgroundProcess() { if (!getState().isAvailable()) return; // 热加载 class或者 jsp Loader loader getLoader(); if (loader ! null) { loader.backgroundProcess(); } // 清理过期Session Manager manager getManager(); if (manager ! null) { manager.backgroundProcess(); } // 清理资源文件的缓存 WebResourceRoot resources getResources(); if (resources ! null) { resources.backgroundProcess(); } // 清理对象或class信息缓存 InstanceManager instanceManager getInstanceManager(); if (instanceManager instanceof DefaultInstanceManager) { ((DefaultInstanceManager)instanceManager).backgroundProcess(); } // 调用子容器的 backgroundProcess 任务 super.backgroundProcess(); FAQ在IDEA中如何跟踪另外一个Loader的代码因为Tomcat的web是另外一个Webload执行的 Webload的实现与new有什么差别
可以将问tomcat的问题问孙卫琴及孙鑫 如何判断文件价被修改看classload中的modified的变量 Tomcat中的责任链有Valve和Filter它们的区别是
Valve 是 tomcat 的私有机制与 tomcat 的基础架构 /API 是紧耦合的。Servlet API 是公有的标准所有的 Web 容器包括 Jetty 都支持 Filter 机制。另一个重要的区别是 Valve 工作在 Web 容器级别拦截所有应用的请求而 Servlet Filter 工作在应用级别只能拦截某个 Web 应用的所有请求。如果想做整个 Web 容器的拦截器必须通过 Valve 来实现。Valve不能动态扫描配置文件
https://lvxueyang.vip/post/f9108ba4.html 代理模式
代理模式Proxy Pattern定义
给某一个对象提供一个代理并由代理对象控制对原对象的引用。代理模式的英文叫做Proxy或Surrogate它是一种对象结构型模式。 代理模式UML结构图 各角色介绍 Subject抽象角色 是真实角色和代理角色的共同接口基类这样在任何使用真实角色的地方都可以使用代理角色多态特性客户端需要针对抽象角色进行编程。 Proxy代理角色/代理对象 代理角色内部包含了对真实角色的引用从而可以在任何时候操作真实角色对象。在代理角色中提供了一个与真实角色相同的接口即基类的抽象接口以便在任何时候都可以替代真实角色。代理角色还可以控制真实就角色的使用负责在需要的时候创建和删除真实角色并对真实角色对象的使用加以约束。代理角色通常在客户端调用所引用的真实角色操作之前或之后还需要执行其他操作而不仅仅是单纯地调用真实角色对象的操作。 RealSubject真实角色/目标对象 真实角色定义了代理角色所代表的真实对象在真实角色中实现真实的业务操作客户端可以通过代理角色间接调用真实角色中定义的方法。
如果根据字节码的创建时机来分类可以分为静态代理和动态代理 ●所谓静态也就是在程序运行前就已经存在代理类的字节码文件代理类和真实主题角色的关系在运行前就确定了。 ●而动态代理的源码是在程序运行期间由JVM根据反射等机制动态的生成所以在运行前并不存在代理类的字节码文件。动态代理在Java中有着广泛的应用比如Spring AOP、Hibernate数据查询、测试框架的后端mock、RPC远程调用、Java注解对象获取、日志、用户鉴权、全局性异常处理、性能监控事务处理等
Java实现静态代理
静态代理我们通过手动的方式在不修改目标对象的基础上扩展代理对象对目标对象进行一些功能的附加和增强实现对目标对象的增强。需要有一个通用的抽象接口代理对象和目标对象实现抽象接口客户端编写抽象接口进行编程但是其实际应用场景非常非常少日常开发几乎看不到使用静态代理的场景。 优点 封装性强代理类可以对真实对象进行封装客户端不需要知道真实对象的具体实现细节只需要和代理类交互从而达到解耦的效果。
扩展性好通过代理类可以在不修改真实对象的情况下对其进行功能扩展或增强例如添加额外的操作、控制访问权限等。
访问控制代理类可以控制客户端对真实对象的访问实现访问权限的管理例如在访问某些敏感方法前后进行权限验证或日志记录。
保护真实对象代理类可以充当真实对象的保护层可以控制对真实对象的直接访问防止恶意操作或错误调用。
缺点 编码复杂每一个需要代理的对象都需要单独编写代理类如果真实对象很多会导致代理类的数量激增增加了系统的复杂度。
静态类型静态代理在编译期间就已经确定了代理对象和真实对象的关系在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件因此无法在运行时动态改变代理对象灵活性较差。
功能局限静态代理只能代理固定类型的对象无法代理不同类型的对象因此对于不同类型的真实对象需要编写不同的代理类增加了开发成本。
维护成本高当真实对象的接口发生变化时代理类的接口也需要相应地进行修改维护起来相对麻烦。
1. 定义接口
首先我们需要定义一个接口这个接口将被原始类和代理类共同实现。
public interface Subject { void request();
}
2. 实现原始类
接下来我们实现这个接口创建一个具体的类称为“真实主题”RealSubject。
public class RealSubject implements Subject { Override public void request() { System.out.println(RealSubject: Handling request.); }
}
3. 实现代理类
然后我们创建一个代理类也实现相同的接口并持有真实主题的一个引用。
public class Proxy implements Subject { private RealSubject realSubject; public Proxy(RealSubject realSubject) { this.realSubject realSubject; } Override public void request() { System.out.println(Proxy: Before request.); realSubject.request(); System.out.println(Proxy: After request.); }
}
4. 测试代码
最后我们编写一个测试类来验证代理模式的效果。
public class ProxyTest { public static void main(String[] args) { RealSubject realSubject new RealSubject(); Proxy proxy new Proxy(realSubject); proxy.request(); }
}
运行结果
运行上述测试代码输出结果如下
Proxy: Before request.
RealSubject: Handling request.
Proxy: After request. 在这个例子中Proxy 类实现了 Subject 接口并持有一个 RealSubject 的实例。当调用 Proxy 的 request 方法时它先执行了一些前置操作这里是打印一条消息然后调用 RealSubject 的 request 方法最后再执行一些后置操作这里也是打印一条消息。这样就实现了在不修改 RealSubject 的情况下添加了额外的行为。 Java中的动态代理jdk动态代理
4.1、说明
相较于静态代理实现我们不需要为每个目标对象都创建一个代理类动态代理类的字节码在程序运行时由 Java 反射机制动态生成无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作而且提高了软件系统的可扩展性因为 Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的 Proxy 类和InvocationHandler 接口提供了生成动态代理类的能力。 其在日常场景中应用还是比较少的但是在框架中几乎是很常见的例如AOP、RPC框架等等。 优点 灵活性高与静态代理相比动态代理更加灵活可以在运行时动态地生成代理类无需提前编写大量的代理类。
代码简洁由于动态代理是在运行时生成的因此可以大大减少代码量使代码更加简洁、清晰。
维护成本低由于动态代理不需要为每个被代理的类编写单独的代理类因此当原始类的接口发生变化时对代码的影响较小维护成本低。
适用范围广动态代理可以代理任意实现了接口的类不限于特定的类或接口类型因此适用范围更广泛。
缺点 性能稍低相比静态代理动态代理在运行时需要动态生成代理类因此可能会稍微降低程序的运行效率。
复杂度高动态代理涉及到反射机制和动态生成字节码等技术因此相对于静态代理而言实现和理解的难度较高。
不支持对类的直接代理动态代理只能代理实现了接口的类无法直接代理类这在某些情况下可能会限制其使用。
难以调试动态代理生成的代理类通常是在运行时动态生成的字节码因此在调试时可能会增加一定的难度不如静态代理那样直观。 动态代理允许我们在运行时动态地创建一个实现指定接口的代理对象从而可以在不修改原始类的情况下添加额外的功能或行为。
1. 定义接口
首先我们需要定义一个接口这个接口将被原始类和代理类共同实现。
public interface MyService { void doSomething();
}
2. 实现原始类
接下来我们创建一个实现了上述接口的具体类。
public class RealService implements MyService { Override public void doSomething() { System.out.println(Doing something...); }
}
3. 创建InvocationHandler
然后我们需要创建一个实现了InvocationHandler接口的类这个类将处理所有对代理对象方法的调用
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; public class MyInvocationHandler implements InvocationHandler { private Object target; public MyInvocationHandler(Object target) { this.target target; } Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(Before method call...); Object result method.invoke(target, args); System.out.println(After method call...); return result; }
}
4. 使用动态代理创建对象
最后我们将使用Proxy类来创建一个动态代理对象并通过这个代理对象来调用方法。
public class DynamicProxyDemo { public static void main(String[] args) { MyService realService new RealService(); MyService proxyInstance (MyService) Proxy.newProxyInstance( MyService.class.getClassLoader(), new Class[]{MyService.class}, new MyInvocationHandler(realService) ); proxyInstance.doSomething(); }
}
以上就是一个完整的Java动态代理示例。在这个例子中我们首先定义了一个接口MyService然后创建了它的实现类RealService。接着定义了一个MyInvocationHandler类来处理代理对象的方法调用。最后通过Proxy.newProxyInstance方法创建了一个动态代理对象并通过它调用了方法 运行结果
运行上述测试代码输出结果如下
Before method call...
Doing something...
After method call... 在这个例子中MyInvocationHandler类实现了 InvocationHandler 接口并持有一个 RealService的实例。当调用 proxy 的 request 方法时实际上是通过 invoke 方法执行的。在 invoke 方法中我们先执行了一些前置操作这里是打印一条消息然后调用 RealService的 doSomething方法最后再执行一些后置操作这里也是打印一条消息。这样就实现了在不修改 RealService的情况下添加了额外的行为。
通过这种方式我们可以在运行时动态地创建代理对象并添加额外的行为提高了代码的灵活性和可扩展性。 动态代理根据接口凭空生成了一个代理类。 可以在场景实现中看到整个动态代理的过程主要涉及到一个接口InvocationHandler和一个类Proxy。 Proxy类通过静态方法newProxyInstance来生成一个代理对象实例
newProxyInstance方法入参详细说明
ClassLoader loader这是目标对象的类加载器用于加载代理对象。
Class?[] interfaces这是被代理对象需要实现的接口列表。如果目标对象实现了多个接口生成的代理对象会实现所有的接口。
InvocationHandler h这是 Java 动态代理机制中的一个接口定义了代理对象调用方法时的处理方式。 method.invoke() 方法有两个参数
target这是要调用方法的目标对象。
args这是传递给方法的实际参数数组。 我们来看一下源码中的动态代理
Mybatis的动态代理
public class MapperProxyT implements InvocationHandler, Serializable {}
public class MapperProxyFactoryT { protected T newInstance(MapperProxyT mapperProxy) { return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy); }
} // 代理对象的内部实现 public class $Proxy0 extends UserMapper implements InvocationHandler { private SqlSession sqlSession;
public $Proxy0(SqlSession sqlSession) { this.sqlSession sqlSession;
} Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 处理 SQL 执行逻辑 String statementId com.example.mapper.UserMapper.getUserById; Object result sqlSession.selectOne(statementId, args[0]); return result;
}
Java中的cglib动态代理
5.1、说明
上述jdk动态代理只能代理实现接口的类如果想要对类实现代理我们可以通过cglib动态代理来解决关于类的动态代理。
CGLIB(Code Generation Library)是一个基于ASM的字节码生成库它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。很多知名的开源框架都使用到了CGLIB 例如 Spring 中的 AOP 模块中如果目标对象实现了接口则默认采用 JDK 动态代理否则采用 CGLIB 动态代理。
优点 性能高 CGLIB 直接对字节码进行操作相比于 JDK 动态代理的反射调用性能更高。因为它通过生成子类的方式来代理目标类而不是通过实现接口的方式。
不需要目标对象实现接口 JDK 动态代理要求目标对象必须实现接口而 CGLIB 可以代理没有实现接口的类。
更强大的功能 CGLIB 不仅可以代理类的方法还可以代理类的属性。
更灵活 CGLIB 可以代理没有公共构造方法的类以及被 final 修饰的类的方法。
缺点 性能相对 JDK 动态代理更低 尽管 CGLIB 的性能较高但相比于直接调用目标方法仍然存在一定的性能开销。而且生成的代理类会增加类加载的时间和内存消耗。
类加载器敏感 CGLIB 动态代理生成的代理类是目标类的子类因此可能会受到类加载器的限制。在一些场景下例如使用不同的类加载器加载目标类和代理类时可能会出现类转换异常。
无法代理 final 方法和 private 方法 CGLIB 无法代理 final 方法和 private 方法因为它是通过生成子类来代理目标类的方法而 final 方法和 private 方法无法被子类重写。
Debugging困难 由于 CGLIB 是在运行时生成字节码来创建代理类因此调试起来可能会比较困难不如 JDK 动态代理那样直观。 下面是一个使用 CGLIBCode Generation Library实现动态代理的例子。CGLIB 是一个强大的代码生成库它可以用来创建一个类的子类。与 JDK 动态代理不同的是CGLIB 不需要目标类实现接口而是通过继承的方式实现动态代理。
1. 添加 CGLIB 依赖
首先确保你的项目中添加了 CGLIB 的依赖。如果你使用 Maven可以在 pom.xml 文件中添加以下依赖
dependencies dependency groupIdcglib/groupId artifactIdcglib/artifactId version3.3.0/version /dependency
/dependencies
2. 定义目标类
假设我们有一个目标类 RealSubject不需要实现任何接口。
public class RealSubject { public void request() { System.out.println(RealSubject: Handling request.); }
}
3. 创建 CGLIB 代理类
使用 CGLIB 创建动态代理类需要实现 MethodInterceptor 接口。
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class CglibProxy implements MethodInterceptor { private Object target; public CglibProxy(Object target) { this.target target; } public Object getProxy() { Enhancer enhancer new Enhancer(); enhancer.setSuperclass(target.getClass()); enhancer.setCallback(this); return enhancer.create(); } Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println(CglibProxy: Before request.); Object result methodProxy.invokeSuper(proxy, args); System.out.println(CglibProxy: After request.); return result; }
}
4. 测试代码
编写一个测试类来验证 CGLIB 动态代理的效果。
public class CglibProxyTest { public static void main(String[] args) { RealSubject realSubject new RealSubject(); CglibProxy cglibProxy new CglibProxy(realSubject); RealSubject proxy (RealSubject) cglibProxy.getProxy(); proxy.request(); }
}
运行结果
运行上述测试代码输出结果如下
CglibProxy: Before request.
RealSubject: Handling request.
CglibProxy: After request.
在这个例子中CglibProxy 类实现了 MethodInterceptor 接口并持有一个 RealSubject 的实例。当调用 proxy 的 request 方法时实际上是通过 intercept 方法执行的
Javassist Mybatis的动态代理
MyBatis 中的接口代理类机制MyBatis 框架中使用了动态代理的设计模式让我们可以不用写对应XxxMapper.java 接口的实现类而是通过动态代理的方式让MyBatis 自动为我们生成对应实现了该 XxxMapper.java接口的实现类这个动态代理实现的类我们可以直接使用。
我们可以深入分析其源码中的关键部分。以下是 MyBatis 动态代理机制的核心流程和相关类的解析
1. SqlSessionFactory 和 SqlSession
SqlSessionFactory负责创建 SqlSession。
SqlSessionFactory 是通过 SqlSessionFactoryBuilder 构建的。
SqlSessionFactory 的主要职责是创建 SqlSession 实例。
SqlSession负责执行 SQL 语句。
SqlSession 提供了 getMapper 方法来获取 Mapper 接口的代理对象。
2. SqlSession.getMapper()
getMapper 方法用于获取 Mapper 接口的代理对象。
这个方法内部调用了 configuration.newMapperProxy 方法来创建代理对象。
3. Configuration.newMapperProxy()
newMapperProxy 方法创建 Mapper 接口的代理对象。
这个方法利用了 Java 的动态代理机制 (java.lang.reflect.Proxy)。
4. Java 动态代理机制
java.lang.reflect.Proxy 类提供了创建动态代理对象的方法。
通过 Proxy.newProxyInstance 方法创建代理对象。
代理对象实现了传入的接口类型并且指定了一个 InvocationHandler。
5. MapperProxyFactory
MapperProxyFactory负责创建具体的 Mapper 代理对象。
每个 Mapper 接口都有一个对应的 MapperProxyFactory 实例。
MapperProxyFactory 的 newInstance 方法用于创建代理对象。
具体实现
SqlSessionFactory 创建过程 SqlSessionFactory sqlSessionFactory new SqlSessionFactoryBuilder().build(inputStream); SqlSession 创建过程 SqlSession sqlSession sqlSessionFactory.openSession(); getMapper 方法 public T T getMapper(ClassT type, SqlSession sqlSession) { if (type.isInterface()) { return (T) mapperRegistry.getMapper(type, sqlSession); } throw new BindingException(Type type is not a valid mapper interface); } Configuration.getMapper 方法 public T T getMapper(ClassT type, SqlSession sqlSession) { if (type.isInterface()) { return (T) mapperRegistry.getMapper(type, sqlSession); } throw new BindingException(Type type is not a valid mapper interface); } MapperRegistry 类 public T T getMapper(ClassT type, SqlSession sqlSession) { final MapperProxyFactoryT mapperProxyFactory (MapperProxyFactoryT) knownMappers.get(type); if (mapperProxyFactory null) { throw new BindingException(Type type is not known to the MapperRegistry.); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException(Error getting mapper instance. Cause: e, e); } } MapperProxyFactory 类 public T newInstance(SqlSession sqlSession) { final MapperProxyT mapperProxy new MapperProxy(sqlSession, mapperInterface, methodHandler); return mapperProxy.newInstance(); } private class MapperProxyT implements InvocationHandler { private final SqlSession sqlSession; private final ClassT mapperInterface; private final MapperMethod.MethodHandler methodHandler; public MapperProxy(SqlSession sqlSession, ClassT mapperInterface, MapperMethod.MethodHandler methodHandler) { this.sqlSession sqlSession; this.mapperInterface mapperInterface; this.methodHandler methodHandler; } Override public T newInstance() { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, this); } Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else { return methodHandler.invoke(sqlSession, method, args); } } } 通过以上步骤MyBatis 利用 Java 动态代理机制 (java.lang.reflect.Proxy) 创建了 Mapper 接口的代理对象。这些代理对象在调用方法时会通过 InvocationHandler 来执行实际的 SQL 操作。这种方式使得 MyBatis 能够灵活地处理 SQL 执行逻辑并且与 Java 应用程序无缝集成。 JPA动态代理类的生成
Jpa的DynamicUpdate
传入user只有3个属性。其余7个属性为null
将id传入到服务端查询出来user10个属性。
如果这个时候直接进行update那么就会将null 设置覆盖掉。 增加一行代码查询到的10个属性的usersrc 将user传入的参数非空的属性复制到usersrc中
然后利用jpa每个属性的比较特性。这个时候就不会进行覆盖了因为其他的属性是没有编号的。 经过测试如下
Jpa的DynamicUpdate
如果与数据库一样的字段内容那么就不会在update语句中。
如传入3个字段数据库中有10个字段。其中2个字段与数据库字段一样那么update后就有8个字段。 然后会对比8个字段其中7个在传入的时候都是null它也认为更新。所以数据库中被更新了。 会导致一个问题如果用户传递的不全。会将数据库中的字段覆盖。 这个与约定的开发习惯不一样只有传递的字段才会更新 明天做一个16章的查询性能的方案 反射的技术原理
4.2 相关 Java 基础知识…. .… .
4.2.1 简单实例 . . .
4.2.2 类装载器 lassLoader ……
4.2.3 Java 反射机制 . 83 事件机制
在项目中经常碰到事件机制
事件机制
事件的设计模式
1、观察者设计模式
详细的内容参考
本质是动态编译面向接口编程
观察者模式Observer Pattern也叫做发布订阅模式Publish/subscribe,它是一个在项目中经常使用的模式其定义如下
Define a one-to-many dependency between objects so that when one object changes state,all itsdependents are notified and updated automatically.定义对象间一种一对多的依赖关系使得每当一个对象改变状态则所有依赖于它的对象都会得到通知并被自动更新。
观察者模式的通用类图如图22-5所示。 我们先来解释一下观察者模式的几个角色名称
● Subject被观察者
定义被观察者必须实现的职责它必须能够动态地增加、取消观察者。它一般是抽象类。或者是实现类仅仅完成作为被观察者必须实现的职责管理观察者并通知观察者。
•一个目标可以被多个观察者现察.
• 目标提供对观棋者注册和退订的维护.
• 当目标的状态发生变化时目标负责通知所有注册的、有效的观察者.
● Observer观察者
观察者接收到消息后即进行update更新方法操作对接收到的信息进行处理。
定义观察者的接口提供目标通知时对应的更新方法这个更新方法进行相应的业务处理可以在这个方法里面回调目标对象. 以获取目标对象的做据.
● ConcreteSubject具体的被观察者
定义被观察者自己的业务逻辑同时定义对哪些事件进行通知。
具体的目标实理对象用来维护目标状态当目标对象的状态发生改变时通知所有世二册的、有效的观察者让观察者执行相应的处理.
● ConcreteObserver具体的观察者
每个观察在接收到消息后的处理反应是不同各个观察者有自己的处理逻辑。现察者的具体实现对象用来接收目标的通知并进行相应的后续处理比如更新自身的状态以保持和目标的相应状态一致. 如果是Java有obserable.不需要自己定义接口。
在实现的时候采用setchange 2、详细动作的分解
一个事件发送事件是一个动词是一个过程但是在面向对象设计过程中要分解为多个步骤。 事件名称。一般为Event。事件可以是多层次的
事件响应者的注册
一般是在List 的add方法
事件响应者的remove
一般是在
事件触发者
一般采用fireevent本质是是调用List的数组进行循环处理。循环处理的时候可以考虑进行异步处理。 事件的触发时机
一般情况下是在完成了状态维护后触发因为通知会传递数据不能够先通知后改量据1 这很容易出问题会导观察者和目标对象的状态不一致.
另外是否在状态变化的时候不一定触发由 被观察者尽量自己做主如增加一个boolean变量判断这个状态的变化是否要进行通知 事件的容器
事件的响应者同步响应异步响应
事件传递的上下文
根据观察者对象应用的数据情况如果简单一起把所有的数据提供过去。 优点
现察者模式实现了观察者和目标之间的抽象辑合
原本目标对象在状态草生改变的时候需要直拨词用所有的现察者对象但是
抽象出观察者按口U后日岳和观察者就只是在抽象居面上牺合了也就是说
目标只是知道观察者接口 并不知道具体的观察者的类从而实现目标类和具
体的现察者类之间解祸. 在应用观察者模式的时候需要注意的是
避免循环观察。
在某些应用中可陆会出现目标和现察者相互观察的情况. 11 么意思眼?比如有两茸观察者模式的应用。其中一套观察者扭式的实现是A 对象、8 对象观察C 对象· 在另一王若观察者模式的实现里面实现的是B 对忽、C 对忽观察A 对象那么A 对象和C 对忽就是在相互观察.换句话说.A 对忽的状态变化会引起C 对象的联功操作反过来.C 时象的状态变化也会引起A 对象的联副操作. 肘子出现这种状阻要特别小心处理因为可能会出现死插环的情况.
通知的顺序
从理论上来说当目标对象的状态变化后通知所有观察者的时候顺序是不确定的
因此观察者实现的功能坦对不要依赖于通知的阳序. 也就是说多个观察者之间的功能是平行的相直不应该有先后的依蛐关系.
异步的时间
同步的时间太长应该采用异步的处理方式。那么是否需要在返回通知呢 框架中采用的事件
Tomcat框架中的事件
事件的定义
继承与Java的EventObject。在构造函数中传入Lifecycle、Type-事件的类型、Data public final class LifecycleEvent extends EventObject { //...... public LifecycleEvent(Lifecycle lifecycle, String type, Object data) { super(lifecycle); this.type type; this.data data; } //......
}
其中事件的类型在接口中定义无法对事件类型进行扩展
public interface Lifecycle { /** * The LifecycleEvent type for the component after init event. */ public static final String BEFORE_INIT_EVENT before_init; /** * The LifecycleEvent type for the component after init event. */ public static final String AFTER_INIT_EVENT after_init; /** * The LifecycleEvent type for the component start event. */ public static final String START_EVENT start; //......
}
事件监听接口
事件监听接口LifecycleListener定义了lifecycleEvent方法用来传递监听者感兴趣的LifecycleEvent对象监听者使用LifecycleEvent参数用来在tomcat的各个阶段处理进行相应处理。
public interface LifecycleListener { public void lifecycleEvent(LifecycleEvent event);
} 这里使用ContextConfig类为例可以看到它实现了LifecycleListener接口。这个类在解析server.xml的时候用来监听StandardContext的各个阶段的事件并做出相应处理
public void lifecycleEvent(LifecycleEvent event) { // Identify the context we are associated with try { context (Context) event.getLifecycle(); } catch (ClassCastException e) { log.error(sm.getString(contextConfig.cce, event.getLifecycle()), e); return; } // Process the event that has occurred if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) { configureStart(); } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) { beforeStart(); } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) { // Restore docBase for management tools if (originalDocBase ! null) { context.setDocBase(originalDocBase); } } else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) { configureStop(); } else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) { init(); } else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) { destroy(); } }
事件的注册、移除
辅助类LifecycleBase
LifecycleBase是我们需要了解的主要对象它是监听对象的一个管理类在它的继承体系下所有子类都可以进行事件的注册移除等功能。
public abstract class LifecycleBase implements Lifecycle { /** * The list of registered LifecycleListeners for event notifications. */ private final List LifecycleListener lifecycleListeners new CopyOnWriteArrayList (); /** * The current state of the source component. */ private volatile LifecycleState state LifecycleState.NEW; /** * {inheritDoc} */ Override public void addLifecycleListener(LifecycleListener listener) { lifecycleListeners.add(listener); } /** * {inheritDoc} */ Override public LifecycleListener[]findLifecycleListeners() { return lifecycleListeners.toArray(new LifecycleListener[0]); } /** * {inheritDoc} */ Override public void removeLifecycleListener(LifecycleListener listener) { lifecycleListeners.remove(listener); } /** * Allow sub classes to fire {link Lifecycle} events. * * param type Event type * param data Data associated with event. */ protected void fireLifecycleEvent(String type, Object data) { LifecycleEvent event new LifecycleEvent(this, type, data); for (LifecycleListener listener: lifecycleListeners) { listener.lifecycleEvent(event); } }
}
事件的注册类
子类中的StandardContext类中这个方法调用了addLifecycleListener方法添加生命周期事件 public Wrapper createWrapper() { // synchronized (wrapperLifecyclesLock) { for (String wrapperLifecycle : wrapperLifecycles) { try { Class? clazz Class.forName(wrapperLifecycle); LifecycleListener listener (LifecycleListener) clazz.getConstructor().newInstance(); wrapper.addLifecycleListener(listener); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.error(sm.getString(standardContext.createWrapper.listenerError), t); return null; } } } 事件的产生时机
本质上是什么时候发生这个事件应该是什么时候new这个事件
StandardContext为例触发的事件为Lifecycle.CONFIGURE_START_EVENT。在调用fire方法的类时候产生事件。
/** * Allow sub classes to fire {link Lifecycle} events. * * param type Event type * param data Data associated with event. */
protected void fireLifecycleEvent(String type, Object data) { LifecycleEvent event new LifecycleEvent(this, type, data); for (LifecycleListener listener : lifecycleListeners) { listener.lifecycleEvent(event); }
}
这个事件在这里是写在fire的函数中也可以放到fire之外即先产生这个事件然后在执行fire。这样更容易理解。 事件的触发源
触发源用于产生事件。仍然以StandardContext为例触发的事件源为StandardContext即调用fire的源头
//发布监听对象
Override
protected synchronized void startInternal() throws LifecycleException {
// Notify our interested LifecycleListeners
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
} 事件触发的时机
产生事件之后立即触发fire动作
事件触发传递的内容
从Fire的地方可以看到传递的内容是一个事件。但是在实例化事件的时候将源作为事件的一个成员变量传入进去了。
LifecycleEvent event new LifecycleEvent(this, type, data);
事件的同异步处理方式
在Tomcat容器中事件的处理方式是同步处理。即调用线程一致等待结果返回。
使用方式
因为事件的枚举固定无法扩展。事件的触发时间也在代码中固定所以程序在扩展接口的时候只能是监听接口代码。 Spring框架中的事件
事件的定义
EventObject只有一个构造函数必须传入让事件发生的源头。
public class EventObject implements java.io.Serializable { ... /** * The object on which the Event initially occurred. */ protected transient Object source; /** * Constructs a prototypical Event. * * param source The object on which the Event initially occurred. * exception IllegalArgumentException if source is null. */ public EventObject(Object source) { if (source null) throw new IllegalArgumentException(null source); this.source source; } ...
}
它的类图如下 事件监听接口
ApplicationListener 接口只定义了一个方法onApplicationEvent(Event) 该方法接
收ApplicationEvent 事件对象在该方法中编写事件的响应处理逻辑
事件的注册、移除
辅助类
在Spring的事件机制中将维护监听者列表的功能单独定义了一个接口即ApplicationEventMulticaster接口。这也体现了单一责任原则的设计思想。
public interface ApplicationEventMulticaster { /** * Add a listener to be notified of all events. * param listener the listener to add */
void addApplicationListener(ApplicationListener? listener); /** * Add a listener bean to be notified of all events. * param listenerBeanName the name of the listener bean to add */
void addApplicationListenerBean(String listenerBeanName); /** * Remove a listener from the notification list. * param listener the listener to remove */
void removeApplicationListener(ApplicationListener? listener); /** * Remove a listener bean from the notification list. * param listenerBeanName the name of the listener bean to remove */
void removeApplicationListenerBean(String listenerBeanName); /** * Remove all listeners registered with this multicaster. * pAfter a remove call, the multicaster will perform no action * on event notification until new listeners are registered. */
void removeAllListeners(); /** * Multicast the given application event to appropriate listeners. * pConsider using {link #multicastEvent(ApplicationEvent, ResolvableType)} * if possible as it provides better support for generics-based events. * param event the event to multicast */
void multicastEvent(ApplicationEvent event); /** * Multicast the given application event to appropriate listeners. * pIf the {code eventType} is {code null}, a default type is built * based on the {code event} instance. * param event the event to multicast * param eventType the type of event (can be {code null}) * since 4.2 */
void multicastEvent(ApplicationEvent event, Nullable ResolvableType eventType); }
事件的注册
Spring 根据反射机制从BeanDefinitionRegistry 中找出所有实现org.springframework . context. ApplicationListener 的Bean 将它们注册为容器的事件监昕器 实际操作就是将其添加到事件广播器所提供的事件监昕器注册表中。
AbstractApplicationContext在refresh这个容器启动方法中通过以下3 个步骤搭建了事件的基础设施
Override
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Check for listener beans and register them. registerListeners(); // Last step: publish corresponding event. finishRefresh(); }
}
首先spring初始化事件的广播器用户可以在配置文件中为容器定义一个自定义的事件广播器只要实现ApplicationEventMulticaster就可以Spring会通过反射机制将其注册为容器的事件广播器。如果没有找到配置的外部事件广播器则Spring自动使用SimpleApplicationEventMulticaster作为事件广播器。
Spring 根据反射机制从BeanDefinitionRegistry 中找出所有实现org.springframework . context. ApplicationL istener 的Bean 将它们注册为容器的事件监昕器实际操作就是将其添加到事件广播器所提供的事件监昕器注册表中。
或者采用注解的方式添加事件
容器启动完成 调用事件发布接口向容器中所有的监听器发布事件。在publishEvent 内部可以看到 Spring 委托ApplicationEventMulticaster 将事件通知给事件监昕器。
事件的产生时机
指new 这个事件的时间。一般new之后会立即执行for循环语句来进行执行。 事件的触发源
在finishRefresh中的
protected void finishRefresh() {
// Publish the final event.
publishEvent(new ContextRefreshedEvent(this));
} 事件的触发时机
意思是什么时候New event这个事件。加载配置文件的时候
finishRefresh()方法这其中调用了publishEvent(new ContextRefreshedEvent(this))方法发布了ContextRefreshedEvent这一对象。
protected void finishRefresh() { //...... // Publish the final event. publishEvent(new ContextRefreshedEvent(this));
} protected void publishEvent(Object event, Nullable ResolvableType eventType) { //...... getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType); //......
}
publishEvent方法通过调用一个默认的多播器SimpleApplicationEventMulticaster的multicastEvent方法来发布各种事件 SimpleApplicationEventMulticaster
public void multicastEvent(final ApplicationEvent event, Nullable ResolvableType eventType) { ResolvableType type (eventType ! null ? eventType : resolveDefaultEventType(event)); //通过getApplicationListeners获取了所有监听器然后通过invokeListener方法循环发布事件 for (final ApplicationListener? listener : getApplicationListeners(event, type)) { Executor executor getTaskExecutor(); if (executor ! null) { executor.execute(() - invokeListener(listener, event)); } else { invokeListener(listener, event); } }
} protected void invokeListener(ApplicationListener? listener, ApplicationEvent event) { //...... doInvokeListener(listener, event);
} private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) { //...... listener.onApplicationEvent(event);
}
也就是说在spring容器中发布ApplicationListener所关注的对象是通过SimpleApplicationEventMulticaster这个类来管理的。 事件的同异步处理方式
Spring加载事件的时候支持同步及异步的处理方式
protected void publishEvent(Object event, Nullable ResolvableType eventType) {
//
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
//
} Override
public void multicastEvent(final ApplicationEvent event, Nullable ResolvableType eventType) { ResolvableType type (eventType ! null ? eventType : resolveDefaultEventType(event)); Executor executor getTaskExecutor(); for (ApplicationListener? listener : getApplicationListeners(event, type)) { if (executor ! null) { executor.execute(() - invokeListener(listener, event)); } else { invokeListener(listener, event); } }
} 事件触发传递的内容
在publishEvent的时候传递的参数为Object eventResolvableType
eventType
使用方式
自定义事件类型
Data
public class UserDefineEvent extends ApplicationEvent { public UserDefineEvent(Object source) { super(source); }
} 采用扩展方式 事件监听的实现
Component
public class UserDefineListener implements ApplicationListenerUserDefineEvent { Override public void onApplicationEvent(UserDefineEvent userDefineEvent) { System.out.println(监听器: UserDefineListener); System.out.println(线程: Thread.currentThread().getName()); System.out.println(事件: userDefineEvent); System.out.println(事件的数据: userDefineEvent.getSource()); }
} 事件的触发
Component
public class UserPublisher { Autowired ApplicationContext applicationContext; public void UserPublish(String message){ System.out.println(发布器所在线程Thread.currentThread().getName()); applicationContext.publishEvent(new UserDefineEvent(message)); }
} 测试一下结果
RestController
//localhost:8080/userpublish
public class UserDefineController { Autowired UserPublisher userPublisher; GetMapping(userpublish) public String userpublish(){ userPublisher.UserPublish(hello); return success userpublish; }
} 发布器所在线程http-nio-8080-exec-4
监听器: UserDefineListener
线程: http-nio-8080-exec-4
事件: org.jeecg.event.UserDefineEvent[sourcehello]
事件的数据: hello NoteC#语言采用的事件机制采用了委托事件它的处理方式本质上是一致的。都可以分为事件的定义、监听接口、产生的时机、触发源、触发时间、触发传递的内容等角度来理解。 软件开发
紧盯目标形成期望中文描述经常拿过来进行比较
测试先行
迭代前进尽短的迭代周期
重构模式应用
支撑能力强有CICD的能力 AI写的内容 Spring 事件体系详解
事件体系的基本概念
在 Spring 事件体系中主要包括以下几个关键角色
事件源Event Source事件的产生者任何 EventObject 都必须拥有一个事件源。
事件监听器注册表Event Listener Registry组件或框架必须提供一个地方保存事件监听器。
事件广播器Event Broadcaster负责把事件通知给事件监听器。
事件类结构
事件类
ApplicationEvent事件基类构造函数为 ApplicationEvent(Object source)。
ApplicationContextEvent容器事件包括容器启动、刷新、停止及关闭的事件。
RequestHandledEvent与 Web 应用相关的事件处理 HTTP 请求后产生的事件。
事件监听器接口
ApplicationListener定义了 onApplicationEvent(Event event) 方法。
SmartApplicationListenerSpring 3.0 新增定义了 supportsEventType 和 supportsSourceType 方法。
GenericApplicationListenerSpring 4 新增增强了对泛型事件类型的支持。
事件广播器
ApplicationEventMulticaster接口定义。
AbstractApplicationEventMulticaster抽象实现类。
SimpleApplicationEventMulticaster具体实现类。
事件体系的实现
事件体系的搭建
AbstractApplicationContext 完成了事件体系的搭建。
refresh() 方法中通过以下步骤搭建了事件的基础设施
initApplicationEventMulticaster()初始化事件广播器。
registerListeners()注册事件监听器。
finishRefresh()完成容器启动并发布事件。
具体实现
用户可以通过配置文件定义自定义的事件广播器只要实现 ApplicationEventMulticaster 接口即可。
如果没有配置外部事件广播器默认使用 SimpleApplicationEventMulticaster。
通过反射机制找出所有实现 ApplicationListener 接口的 Bean并注册为容器的事件监听器。
实例演示
下面是一个简单的实例演示事件发布和事件监听的过程
事件类 MailSendEvent package com.smart.event; import org.springframework.context.ApplicationContext; import org.springframework.context.event.ApplicationContextEvent; public class MailSendEvent extends ApplicationContextEvent { private String to; public MailSendEvent(ApplicationContext source, String to) { super(source); this.to to; } public String getTo() { return this.to; } } 事件监听器 MailSendListener package com.smart.event; import org.springframework.context.ApplicationListener; public class MailSendListener implements ApplicationListenerMailSendEvent { Override public void onApplicationEvent(MailSendEvent event) { System.out.println(MailSendListener event.getTo() 发送完一封邮件); } } 事件发布者 MailSender package com.smart.event; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; public class MailSender implements ApplicationContextAware { private ApplicationContext ctx; Override public void setApplicationContext(ApplicationContext ctx) throws BeansException { this.ctx ctx; } public void sendMail(String to) { System.out.println(MailSender 模拟发送邮件...); MailSendEvent mse new MailSendEvent(ctx, to); ctx.publishEvent(mse); } } Spring 配置文件 ApplicationContext ctx new ClassPathXmlApplicationContext(com/smart/event/beans.xml); MailSender mailSender ctx.getBean(mailSender, MailSender.class); mailSender.sendMail(aaabbb.com); 运行以上代码输出以下信息
MailSender 模拟发送邮件...
MailSendListener aaabbb.com 发送完一封邮件
小结
事件体系包括事件源、事件监听器注册表和事件广播器。
事件类结构ApplicationEvent、ApplicationContextEvent 和 RequestHandledEvent。
事件监听器接口ApplicationListener、SmartApplicationListener 和 GenericApplicationListener。
事件广播器ApplicationEventMulticaster 及其实现类。
实例演示通过 MailSendEvent、MailSendListener 和 MailSender 展示事件发布和监听的过程。
通过深入分析 Spring 事件体系可以更好地理解其设计思想并应用于实际开发中。
工作流框架中的事件
当前Flowable工作流应用的比较广泛采用了flowable的6.7.2版本
分析包的入口 事件的定义
事件在中文中有时候被人主观的感觉是一个动作。在计算机软件设计体系中将该体系分解为一系列名词加动词的组合。其中事件本身是一个名词。在事件定义中必须要有事件的类型有时候会包含事件同时需要传递的数据。
自定义了一个事件接口
public interface FlowableEvent { FlowableEventType getType();
}
它是众多实现的一个该实现中包含了事件的类型
public class FlowableEventImpl implements FlowableEvent { protected FlowableEventType type; public FlowableEventImpl(FlowableEventType type) { if (type null) { throw new FlowableIllegalArgumentException(type is null); } else { this.type type; } } public FlowableEventType getType() { return this.type; } public String toString() { return this.getClass() - this.type; }
} 其中事件的类型在接口中定义由枚举类继承接口后定义事件类型无法对事件类型进行扩展
public interface FlowableEventType { String name();
} public enum FlowableEngineEventType implements FlowableEventType { ENTITY_CREATED, ENTITY_INITIALIZED, ENTITY_UPDATED, ENTITY_DELETED, ENTITY_SUSPENDED, ENTITY_ACTIVATED, TIMER_SCHEDULED, TIMER_FIRED, JOB_CANCELED, JOB_EXECUTION_SUCCESS, JOB_EXECUTION_FAILURE, JOB_RETRIES_DECREMENTED, JOB_REJECTED, JOB_RESCHEDULED, JOB_MOVED_TO_DEADLETTER, CUSTOM, ENGINE_CREATED, ENGINE_CLOSED, ACTIVITY_STARTED, ACTIVITY_COMPLETED, ACTIVITY_CANCELLED, MULTI_INSTANCE_ACTIVITY_STARTED, MULTI_INSTANCE_ACTIVITY_COMPLETED, MULTI_INSTANCE_ACTIVITY_COMPLETED_WITH_CONDITION, MULTI_INSTANCE_ACTIVITY_CANCELLED, ACTIVITY_SIGNAL_WAITING, ACTIVITY_SIGNALED, ACTIVITY_COMPENSATE, ACTIVITY_CONDITIONAL_WAITING, ACTIVITY_CONDITIONAL_RECEIVED, ACTIVITY_ESCALATION_WAITING, ACTIVITY_ESCALATION_RECEIVED, ACTIVITY_MESSAGE_WAITING, ACTIVITY_MESSAGE_RECEIVED, ACTIVITY_MESSAGE_CANCELLED, ACTIVITY_ERROR_RECEIVED, HISTORIC_ACTIVITY_INSTANCE_CREATED, HISTORIC_ACTIVITY_INSTANCE_ENDED, SEQUENCEFLOW_TAKEN, VARIABLE_CREATED, VARIABLE_UPDATED, VARIABLE_DELETED, TASK_CREATED, TASK_ASSIGNED, TASK_COMPLETED, TASK_OWNER_CHANGED, TASK_PRIORITY_CHANGED, TASK_DUEDATE_CHANGED, TASK_NAME_CHANGED, PROCESS_CREATED, PROCESS_STARTED, PROCESS_COMPLETED, PROCESS_COMPLETED_WITH_TERMINATE_END_EVENT, PROCESS_COMPLETED_WITH_ERROR_END_EVENT, PROCESS_COMPLETED_WITH_ESCALATION_END_EVENT, PROCESS_CANCELLED, HISTORIC_PROCESS_INSTANCE_CREATED, HISTORIC_PROCESS_INSTANCE_ENDED, CASE_STARTED, CHANGE_TENANT_ID;
...
} 事件监听接口
事件监听接口FlowableEventListener定义了onEvent方法用来传递监听者感兴趣的FlowableEvent对象监听者使用FlowableEvent参数用来在工作流的各个阶段处理进行相应处理。
public interface FlowableEventListener { void onEvent(FlowableEvent var1); 这里使用AbstractFlowableEngineEventListener类为例可以看到它实现了onEvent接口。这个类在用来监听工作流的各个阶段的事件并做出相应处理 public void onEvent(FlowableEvent flowableEvent) { if (flowableEvent instanceof FlowableEngineEvent) { FlowableEngineEvent flowableEngineEvent (FlowableEngineEvent)flowableEvent; FlowableEngineEventType engineEventType (FlowableEngineEventType)flowableEvent.getType(); if (this.types null || this.types.contains(engineEventType)) { switch (engineEventType) { case ENTITY_CREATED: this.entityCreated((FlowableEngineEntityEvent)flowableEngineEvent); break; case ENTITY_INITIALIZED: this.entityInitialized((FlowableEngineEntityEvent)flowableEngineEvent); break; case ENTITY_UPDATED: this.entityUpdated((FlowableEngineEntityEvent)flowableEngineEvent); break; case ENTITY_DELETED: this.entityDeleted((FlowableEngineEntityEvent)flowableEngineEvent); break; case ENTITY_SUSPENDED: this.entitySuspended((FlowableEngineEntityEvent)flowableEngineEvent); break; case ENTITY_ACTIVATED: this.entityActivated((FlowableEngineEntityEvent)flowableEngineEvent); break;
...
} 事件的注册、移除
辅助类FlowableEventSupport
FlowableEventSupport是我们需要了解的主要对象它是监听对象的一个管理支持类在它的内部有一个FlowableEventListener的集合变量。事件的注册移除等功能都操作该集合变量注意该集合处于多线程环境下要采用同步保护措施。
列出增加事件监听者的的实现方式 protected ListFlowableEventListener eventListeners new CopyOnWriteArrayList(); public synchronized void addEventListener(FlowableEventListener listenerToAdd) { if (listenerToAdd null) { throw new FlowableIllegalArgumentException(Listener cannot be null.); } else { Collection? extends FlowableEventType types listenerToAdd.getTypes(); if (types.isEmpty()) { if (!this.eventListeners.contains(listenerToAdd)) { this.eventListeners.add(listenerToAdd); } } else { Iterator var3 types.iterator(); while(var3.hasNext()) { FlowableEventType type (FlowableEventType)var3.next(); this.addTypedEventListener(listenerToAdd, type); } } } }
移除监听者的实现方法类似的节省篇幅就不贴代码了。
事件的注册类
FlowableEventDispatcher接口定义了事件的注册移除等动作。FlowableEventDispatcherImpl具体实现了这个动作。 public class FlowableEventDispatcherImpl implements FlowableEventDispatcher { protected FlowableEventSupport eventSupport new FlowableEventSupport(); public void addEventListener(FlowableEventListener listenerToAdd) { this.eventSupport.addEventListener(listenerToAdd); } public void removeEventListener(FlowableEventListener listenerToRemove) { this.eventSupport.removeEventListener(listenerToRemove); } public void dispatchEvent(FlowableEvent event, String engineType) { if (this.enabled) { this.eventSupport.dispatchEvent(event); } CommandContext commandContext Context.getCommandContext(); if (commandContext ! null) { AbstractEngineConfiguration engineConfiguration (AbstractEngineConfiguration)commandContext.getEngineConfigurations().get(engineType); if (engineConfiguration ! null engineConfiguration.getAdditionalEventDispatchActions() ! null) { Iterator var5 engineConfiguration.getAdditionalEventDispatchActions().iterator(); while(var5.hasNext()) { EventDispatchAction eventDispatchAction (EventDispatchAction)var5.next(); eventDispatchAction.dispatchEvent(commandContext, this.eventSupport, event); } } }
事件注册类一般由配置文件中包含事件然后中工作流组件启动的时候进行解析在解析过程中调用这个事件注册方法。
事件的产生时机
本质上是什么时候发生这个事件应该是什么时候new这个事件
AbstractEntityManager为例首先是有一个事件的实例 protected FlowableEntityEvent createEntityEvent(FlowableEngineEventType eventType, Entity entity) { return new FlowableEntityEventImpl(entity, eventType); } 事件的触发源
触发源用于产生事件。仍然以AbstractEntityManager为例触发的事件源为fireEntityDeletedEvent即调用fire的源头
protected void fireEntityDeletedEvent(Entity entity) { FlowableEventDispatcher eventDispatcher this.getEventDispatcher(); if (eventDispatcher ! null eventDispatcher.isEnabled()) { eventDispatcher.dispatchEvent(this.createEntityEvent(FlowableEngineEventType.ENTITY_DELETED, entity), this.engineType); } }
事件触发的时机
产生事件之后通过构造函数传递给事件分发者同步的方式立即触发fire动作
事件触发传递的内容
从Fire的地方可以看到传递的内容是一个事件实例。但是在实例化事件的时候构造函数中包含了2个参数1个是事件源的类型一个是传输的参数entity; protected FlowableEntityEvent createEntityEvent(FlowableEngineEventType eventType, Entity entity) { return new FlowableEntityEventImpl(entity, eventType); };
事件的同异步处理方式
在Flowable工作流事件的处理方式是同步处理。即调用线程一致等待结果返回。
public class FlowableEventDispatcherImpl implements FlowableEventDispatcher { protected FlowableEventSupport eventSupport new FlowableEventSupport(); public void dispatchEvent(FlowableEvent event, String engineType) { if (this.enabled) { this.eventSupport.dispatchEvent(event); } CommandContext commandContext Context.getCommandContext(); if (commandContext ! null) { AbstractEngineConfiguration engineConfiguration (AbstractEngineConfiguration)commandContext.getEngineConfigurations().get(engineType); if (engineConfiguration ! null engineConfiguration.getAdditionalEventDispatchActions() ! null) { Iterator var5 engineConfiguration.getAdditionalEventDispatchActions().iterator(); while(var5.hasNext()) { EventDispatchAction eventDispatchAction (EventDispatchAction)var5.next(); eventDispatchAction.dispatchEvent(commandContext, this.eventSupport, event); } } } } }
使用方式
因为事件的枚举固定无法扩展。事件的触发时间也在代码中固定所以程序在扩展接口的时候只能是监听接口代码。 重要的原理-设计模式
源码中用到的设计模式这个内容很多列举3个比较典型的
设计模式的6个特征 模板设计模式
责任链设计模式
构建一个事件机制
事件及时间的状态
事件的触发
事件的接受
事件的处理-同步异步
文章转载自: http://www.morning.fnssm.cn.gov.cn.fnssm.cn http://www.morning.zmyzt.cn.gov.cn.zmyzt.cn http://www.morning.qxlyf.cn.gov.cn.qxlyf.cn http://www.morning.chehb.com.gov.cn.chehb.com http://www.morning.pskjm.cn.gov.cn.pskjm.cn http://www.morning.pjxlg.cn.gov.cn.pjxlg.cn http://www.morning.qzpw.cn.gov.cn.qzpw.cn http://www.morning.mrtdq.cn.gov.cn.mrtdq.cn http://www.morning.rrcxs.cn.gov.cn.rrcxs.cn http://www.morning.lsfrc.cn.gov.cn.lsfrc.cn http://www.morning.zdydj.cn.gov.cn.zdydj.cn http://www.morning.qpmwb.cn.gov.cn.qpmwb.cn http://www.morning.zlrsy.cn.gov.cn.zlrsy.cn http://www.morning.wjzzh.cn.gov.cn.wjzzh.cn http://www.morning.pyzt.cn.gov.cn.pyzt.cn http://www.morning.qsy36.cn.gov.cn.qsy36.cn http://www.morning.skscy.cn.gov.cn.skscy.cn http://www.morning.c7495.cn.gov.cn.c7495.cn http://www.morning.lxdbn.cn.gov.cn.lxdbn.cn http://www.morning.jnhhc.cn.gov.cn.jnhhc.cn http://www.morning.nggry.cn.gov.cn.nggry.cn http://www.morning.phwmj.cn.gov.cn.phwmj.cn http://www.morning.mzcsp.cn.gov.cn.mzcsp.cn http://www.morning.ljngm.cn.gov.cn.ljngm.cn http://www.morning.qkrz.cn.gov.cn.qkrz.cn http://www.morning.lcmhq.cn.gov.cn.lcmhq.cn http://www.morning.pybqq.cn.gov.cn.pybqq.cn http://www.morning.nfks.cn.gov.cn.nfks.cn http://www.morning.xxwl1.com.gov.cn.xxwl1.com http://www.morning.dybth.cn.gov.cn.dybth.cn http://www.morning.bzwxr.cn.gov.cn.bzwxr.cn http://www.morning.lkgqb.cn.gov.cn.lkgqb.cn http://www.morning.bfcrp.cn.gov.cn.bfcrp.cn http://www.morning.lmzpk.cn.gov.cn.lmzpk.cn http://www.morning.wftrs.cn.gov.cn.wftrs.cn http://www.morning.fnbtn.cn.gov.cn.fnbtn.cn http://www.morning.wkknm.cn.gov.cn.wkknm.cn http://www.morning.pplxd.cn.gov.cn.pplxd.cn http://www.morning.ghxsn.cn.gov.cn.ghxsn.cn http://www.morning.wnjrf.cn.gov.cn.wnjrf.cn http://www.morning.dpplr.cn.gov.cn.dpplr.cn http://www.morning.nsyzm.cn.gov.cn.nsyzm.cn http://www.morning.yqpzl.cn.gov.cn.yqpzl.cn http://www.morning.tturfsoc.com.gov.cn.tturfsoc.com http://www.morning.rkwlg.cn.gov.cn.rkwlg.cn http://www.morning.rkwwy.cn.gov.cn.rkwwy.cn http://www.morning.hwpcm.cn.gov.cn.hwpcm.cn http://www.morning.mfbcs.cn.gov.cn.mfbcs.cn http://www.morning.yjqkk.cn.gov.cn.yjqkk.cn http://www.morning.glnxd.cn.gov.cn.glnxd.cn http://www.morning.mxhgy.cn.gov.cn.mxhgy.cn http://www.morning.sjzsjsm.com.gov.cn.sjzsjsm.com http://www.morning.knqck.cn.gov.cn.knqck.cn http://www.morning.chfxz.cn.gov.cn.chfxz.cn http://www.morning.knlbg.cn.gov.cn.knlbg.cn http://www.morning.tralution.cn.gov.cn.tralution.cn http://www.morning.wfyqn.cn.gov.cn.wfyqn.cn http://www.morning.qkcyk.cn.gov.cn.qkcyk.cn http://www.morning.nwllb.cn.gov.cn.nwllb.cn http://www.morning.zqzhd.cn.gov.cn.zqzhd.cn http://www.morning.tnfyj.cn.gov.cn.tnfyj.cn http://www.morning.llmhq.cn.gov.cn.llmhq.cn http://www.morning.mrbzq.cn.gov.cn.mrbzq.cn http://www.morning.kpbgp.cn.gov.cn.kpbgp.cn http://www.morning.gyjld.cn.gov.cn.gyjld.cn http://www.morning.ywrt.cn.gov.cn.ywrt.cn http://www.morning.tfrlj.cn.gov.cn.tfrlj.cn http://www.morning.xqknl.cn.gov.cn.xqknl.cn http://www.morning.ckctj.cn.gov.cn.ckctj.cn http://www.morning.dfhkh.cn.gov.cn.dfhkh.cn http://www.morning.yrnrr.cn.gov.cn.yrnrr.cn http://www.morning.rhlhk.cn.gov.cn.rhlhk.cn http://www.morning.wwxg.cn.gov.cn.wwxg.cn http://www.morning.lrprj.cn.gov.cn.lrprj.cn http://www.morning.thrgp.cn.gov.cn.thrgp.cn http://www.morning.hous-e.com.gov.cn.hous-e.com http://www.morning.5-73.com.gov.cn.5-73.com http://www.morning.kltmt.cn.gov.cn.kltmt.cn http://www.morning.zrnph.cn.gov.cn.zrnph.cn http://www.morning.mtrrf.cn.gov.cn.mtrrf.cn