• [技术干货] Java中常用的设计模式
    Java软件设计中的设计模式主要分为三大类:创建型模式、结构型模式和行为型模式。这些模式为解决常见设计问题提供了通用的解决方案,提升代码的可复用性、可维护性和可扩展性。  创建型模式 创建型模式(Creational Patterns)主要处理对象创建的方式,目的是将对象的创建与使用分离,并避免硬编码构造过程。通过使用创建型模式,可以提高代码的灵活性、可扩展性和可维护性。以下是五种常见的创建型模式的详细说明。  1. 单例模式(Singleton Pattern) 定义 确保一个类只有一个实例,并提供一个全局访问点来访问该实例。  动机 在某些情况下,整个应用程序中只需要一个类的实例,如日志管理器、数据库连接池、配置管理等。通过单例模式可以确保只有一个实例被创建,避免重复创建带来的资源浪费,并且全局访问点确保所有客户端都能访问到同一个实例。  结构 Singleton类:持有自己的静态实例,并提供一个静态方法来返回这个唯一实例。  构造方法:私有,防止外部类直接实例化对象。  示例 public class Singleton {     // 私有的静态实例     private static Singleton instance;          // 私有构造方法,防止外部实例化     private Singleton() {} ​     // 静态方法,提供全局访问点     public static Singleton getInstance() {         if (instance == null) {             instance = new Singleton();         }         return instance;     } } 应用场景 日志记录器  数据库连接池  配置管理器  优点 控制实例数量,节省资源  提供全局访问点  缺点 不支持多线程(如果不进行同步处理,可能会导致多个实例的创建)  单例对象难以扩展和测试(例如,在单元测试中需要使用多个不同实例时可能会有困难)  多线程改进(双重检查锁定) public class Singleton {     private static volatile Singleton instance;     private Singleton() {} ​     public static Singleton getInstance() {         if (instance == null) {             synchronized (Singleton.class) {                 if (instance == null) {                     instance = new Singleton();                 }             }         }         return instance;     } } 2. 工厂方法模式(Factory Method Pattern) 定义 定义一个用于创建对象的接口,但由子类决定具体要实例化的类。工厂方法让一个类的实例化延迟到子类。  动机 当代码中需要创建对象,但不想暴露具体类的构造过程,或者为了扩展方便,希望可以灵活地指定子类来实例化具体对象时,工厂方法模式提供了解决方案。  结构 抽象产品类:定义产品的接口。  具体产品类:实现抽象产品的接口,表示不同类型的产品。  抽象工厂类:定义一个抽象的工厂方法,返回抽象产品类型。  具体工厂类:实现工厂方法,返回具体的产品实例。  示例 // 产品接口 abstract class Product {     abstract void use(); } ​ // 具体产品类 class ConcreteProduct extends Product {     void use() {         System.out.println("Using ConcreteProduct");     } } ​ // 工厂接口 abstract class Creator {     abstract Product createProduct(); } ​ // 具体工厂类 class ConcreteCreator extends Creator {     Product createProduct() {         return new ConcreteProduct();     } }  应用场景 日志记录系统:根据不同的日志级别创建不同的日志处理器。  数据库访问层:使用不同的数据库(如MySQL、Oracle等)时,通过工厂方法灵活选择数据库驱动。  优点 客户端不需要知道具体产品的创建逻辑,只依赖工厂接口。  符合“开放-封闭”原则:可以通过子类扩展新产品,而无需修改现有代码。  缺点 增加了系统的复杂度,需要创建额外的工厂类。  3. 抽象工厂模式(Abstract Factory Pattern) 定义 提供一个创建一系列相关或互相依赖对象的接口,而无需指定它们具体的类。  动机 如果系统需要多个相互关联的对象,且不希望在客户端直接依赖具体类时,抽象工厂模式通过提供统一的接口,来生成相关的对象族,从而保证了对象之间的一致性和可扩展性。  结构 抽象工厂接口:定义创建一系列相关产品的接口。  具体工厂类:实现抽象工厂接口,负责生成具体的产品。  抽象产品接口:定义产品的通用接口。  具体产品类:实现抽象产品接口,表示不同的产品。  示例 // 抽象产品 interface Button {     void paint(); } ​ interface Checkbox {     void paint(); } ​ // 具体产品 class WinButton implements Button {     public void paint() {         System.out.println("Render a button in Windows style.");     } } ​ class MacButton implements Button {     public void paint() {         System.out.println("Render a button in MacOS style.");     } } ​ // 抽象工厂 interface GUIFactory {     Button createButton();     Checkbox createCheckbox(); } ​ // 具体工厂 class WinFactory implements GUIFactory {     public Button createButton() {         return new WinButton();     } ​     public Checkbox createCheckbox() {         return new WinCheckbox();     } } ​ class MacFactory implements GUIFactory {     public Button createButton() {         return new MacButton();     } ​     public Checkbox createCheckbox() {         return new MacCheckbox();     } }  应用场景 跨平台的GUI工具包:为不同操作系统(如Windows、Mac)创建风格一致的UI组件。  数据库访问:为不同数据库(如MySQL、PostgreSQL)生成一组相关的操作对象。  优点 客户端不需要直接实例化对象,易于扩展。  保证了相关产品之间的一致性。  缺点 代码复杂度增加,需要维护更多的类。  当需要扩展新产品族时,可能需要修改抽象工厂接口。  4. 建造者模式(Builder Pattern) 定义 将一个复杂对象的构建过程与它的表示分离,使得同样的构建过程可以创建不同的表示。  动机 有些对象的构建过程非常复杂,包含多个步骤(如配置文件的解析、复杂UI界面的生成)。建造者模式允许分步骤创建对象,且同样的构造过程可以构建不同的对象。  结构 Builder接口:定义构建复杂对象的步骤。  ConcreteBuilder类:实现Builder接口,完成具体步骤。  Director类:负责按顺序调用建造者的构建步骤。  Product类:复杂对象,由多个部分组成。  示例 // 产品类 class Product {     private String partA;     private String partB; ​     public void setPartA(String partA) {         this.partA = partA;     } ​     public void setPartB(String partB) {         this.partB = partB;     } ​     public void showProduct() {         System.out.println("Product with " + partA + " and " + partB);     } } ​ // 抽象建造者 interface Builder {     void buildPartA();     void buildPartB();     Product getResult(); } ​ // 具体建造者 class ConcreteBuilder implements Builder {     private Product product = new Product(); ​     public void buildPartA() {         product.setPartA("Part A");     } ​     public void buildPartB() {         product.setPartB("Part B");     } ​     public Product getResult() {         return product;     } } ​ // 指挥者 class Director {     public void construct(Builder builder) {         builder.buildPartA();         builder.buildPartB();     } }  应用场景 复杂对象的创建,如车辆、房屋、文档生成器。  当构造过程需要按照一定的顺序时,如餐厅订单的构建(前菜、主菜、甜点)。  优点 将构造过程分离,允许同样的构造过程创建不同的对象。  更加灵活,构造过程更容易扩展和维护。  缺点 增加了代码的复杂性,尤其是当产品本身并不复杂时。  5. 原型模式(Prototype Pattern) 定义 通过复制现有对象来创建新对象,而不是通过实例化类。  动机 有时创建对象的成本很高或者很复杂(如加载大量数据或依赖外部资源),直接克隆现有对象可以有效提升性能,避免冗余计算或复杂的初始化过程。  结构 Prototype接口:定义一个clone()方法,用于复制对象。  ConcretePrototype类:实现Prototype接口,负责具体的克隆操作。  示例 // 原型接口 interface Prototype {     Prototype clone(); } ​ // 具体原型 class ConcretePrototype implements Prototype {     private String state; ​     public ConcretePrototype(String state) {         this.state = state;     } ​     @Override     public Prototype clone() {         return new ConcretePrototype(state);     } }  结构型模式 结构型模式是设计模式中的一种类型,关注于如何将类或对象组合成更大的结构,以形成更复杂的功能。结构型模式帮助我们简化设计、提高系统的灵活性和可复用性,并有助于在不同的类之间建立良好的关系。  以下是几种常见的结构型模式的详细说明:  1. 适配器模式(Adapter Pattern) 定义 适配器模式允许将一个类的接口转换成客户端所期望的另一种接口,使得原本因接口不兼容而无法一起工作的类可以一起工作。  动机 在系统需要与多个接口不兼容的类交互时,适配器模式提供了一种解决方案。  实现 适配器可以是类适配器或对象适配器。类适配器通过继承来实现,而对象适配器通过组合来实现。  示例 // 目标接口 interface Target {     void request(); } ​ // 适配者类 class Adaptee {     void specificRequest() {         System.out.println("Called specificRequest");     } } ​ // 适配器类 class Adapter implements Target {     private Adaptee adaptee; ​     public Adapter(Adaptee adaptee) {         this.adaptee = adaptee;     } ​     @Override     public void request() {         adaptee.specificRequest();     } }  优点 使得接口不兼容的类可以协同工作。  提高系统的可复用性和灵活性。  缺点 可能会增加系统的复杂性,过多的适配器会导致代码难以理解。  2. 桥接模式(Bridge Pattern) 定义 桥接模式通过将抽象部分与其实现部分分离,从而使它们可以独立变化。即在抽象类和具体实现之间引入一个桥接接口。  动机 当一个类的抽象和实现之间有多个变化维度时,桥接模式能够将这些变化分离,减少系统的复杂性。  结构 抽象类:定义了高层接口。  实现类:实现了低层接口。  桥接:将抽象类与实现类连接。  示例 // 实现接口 interface Implementor {     void operationImpl(); } ​ // 具体实现 class ConcreteImplementorA implements Implementor {     @Override     public void operationImpl() {         System.out.println("ConcreteImplementorA operation");     } } ​ // 抽象类 abstract class Abstraction {     protected Implementor implementor; ​     public Abstraction(Implementor implementor) {         this.implementor = implementor;     } ​     public abstract void operation(); } ​ // 具体抽象类 class RefinedAbstraction extends Abstraction {     public RefinedAbstraction(Implementor implementor) {         super(implementor);     } ​     @Override     public void operation() {         implementor.operationImpl();     } }  优点 通过分离抽象和实现,提高了系统的灵活性。  可以在不改变抽象和实现的情况下扩展系统。  缺点 可能会增加系统的复杂性,特别是在类的数量较多时。  3. 组合模式(Composite Pattern) 定义 组合模式将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式允许客户端以统一的方式对待单个对象和组合对象。  动机 当需要表示对象的部分和整体的关系时,组合模式提供了一种简单的方法来管理这些对象。  结构 组件:定义了叶子和组合对象的公共接口。  叶子:实现了组件接口,表示树的叶子节点。  组合:实现了组件接口,表示树的组合节点。  示例 // 组件接口 interface Component {     void operation(); } ​ // 叶子类 class Leaf implements Component {     @Override     public void operation() {         System.out.println("Leaf operation");     } } ​ // 组合类 class Composite implements Component {     private List<Component> children = new ArrayList<>(); ​     public void add(Component component) {         children.add(component);     } ​     @Override     public void operation() {         for (Component child : children) {             child.operation();         }     } }  优点 客户端可以一致地使用单个对象和组合对象。  可以方便地添加新的叶子或组合。  缺点 设计上比较复杂,可能会引入不必要的复杂性。  4. 装饰模式(Decorator Pattern) 定义 装饰模式允许在不改变对象自身的情况下,动态地给一个对象添加一些额外的职责。装饰模式提供了比子类更灵活的替代方案。  动机 当需要在运行时扩展对象的功能时,装饰模式提供了一种灵活的方法。  结构 组件接口:定义了对象的接口。  具体组件:实现了组件接口的基本对象。  装饰者抽象类:实现了组件接口,持有一个组件的引用。  具体装饰者:扩展了装饰者抽象类,添加额外的职责。  示例 // 组件接口 interface Component {     void operation(); } ​ // 具体组件 class ConcreteComponent implements Component {     @Override     public void operation() {         System.out.println("ConcreteComponent operation");     } } ​ // 装饰者抽象类 abstract class Decorator implements Component {     protected Component component; ​     public Decorator(Component component) {         this.component = component;     } ​     @Override     public void operation() {         component.operation();     } } ​ // 具体装饰者 class ConcreteDecoratorA extends Decorator {     public ConcreteDecoratorA(Component component) {         super(component);     } ​     @Override     public void operation() {         super.operation();         System.out.println("ConcreteDecoratorA operation");     } }  优点 可以动态地添加职责,增加灵活性。  可以通过多个装饰者组合来扩展功能。  缺点 可能会导致系统中出现大量小的类,增加管理的复杂性。  5. 代理模式(Proxy Pattern) 定义 代理模式为其他对象提供一种代理,以控制对这个对象的访问。代理对象在客户端与真实对象之间起到中介作用。  动机 在某些情况下,需要控制对某个对象的访问,比如延迟加载、安全控制、日志记录等。  结构 主题接口:定义了真实对象和代理的公共接口。  真实对象:实现了主题接口,表示被代理的对象。  代理类:实现了主题接口,持有真实对象的引用并控制对它的访问。  示例 // 主题接口 interface Subject {     void request(); } ​ // 真实对象 class RealSubject implements Subject {     @Override     public void request() {         System.out.println("RealSubject request");     } } ​ // 代理类 class Proxy implements Subject {     private RealSubject realSubject; ​     @Override     public void request() {         if (realSubject == null) {             realSubject = new RealSubject();         }         realSubject.request();     } }  优点 控制访问真实对象的权限,可以实现懒加载。  可以在请求前后添加额外的操作(如日志、安全控制等)。  缺点 可能会引入不必要的复杂性,特别是在多层代理的情况下。  行为型模式 行为型模式是设计模式中的一种类型,主要关注对象之间的交互和职责分配。它们帮助管理复杂的控制流和对象之间的关系,以便于创建可扩展和可维护的系统。行为型模式通常关注如何使对象之间的交互更加灵活,并提供一种更好的方法来处理系统中的操作。  以下是几种常见的行为型模式的详细说明:  1. 责任链模式(Chain of Responsibility Pattern) 定义 责任链模式将请求的发送者和接收者解耦,通过一系列处理者对象形成一个链,每个处理者决定将请求处理还是将其传递给下一个处理者。  动机 在系统中,可能会有多个处理者需要处理相同的请求,责任链模式能够避免将请求直接发送给某个具体的处理者。  结构 处理者接口:定义处理请求的方法。  具体处理者:实现处理者接口,处理特定的请求。  责任链:将多个处理者连接成一个链。  示例 // 处理者接口 interface Handler {     void setNext(Handler next);     void handleRequest(String request); } ​ // 具体处理者A class ConcreteHandlerA implements Handler {     private Handler next; ​     @Override     public void setNext(Handler next) {         this.next = next;     } ​     @Override     public void handleRequest(String request) {         if (request.equals("A")) {             System.out.println("Handler A handled request");         } else if (next != null) {             next.handleRequest(request);         }     } } ​ // 具体处理者B class ConcreteHandlerB implements Handler {     private Handler next; ​     @Override     public void setNext(Handler next) {         this.next = next;     } ​     @Override     public void handleRequest(String request) {         if (request.equals("B")) {             System.out.println("Handler B handled request");         } else if (next != null) {             next.handleRequest(request);         }     } }  优点 降低了请求发送者和处理者之间的耦合度。  可以动态增加处理者。  缺点 可能会导致请求的处理时间不确定,特别是请求在链中经过多个处理者时。  2. 命令模式(Command Pattern) 定义 命令模式将请求封装为对象,从而使得用户可以将请求参数化、排队或记录请求日志,并支持可撤销的操作。  动机 在需要将操作的调用者与执行者解耦时,命令模式提供了一种灵活的解决方案。  结构 命令接口:定义命令的接口。  具体命令:实现命令接口,定义执行的操作。  调用者:调用命令对象来执行请求。  接收者:实际执行请求的对象。  示例 // 命令接口 interface Command {     void execute(); } ​ // 具体命令 class ConcreteCommand implements Command {     private Receiver receiver; ​     public ConcreteCommand(Receiver receiver) {         this.receiver = receiver;     } ​     @Override     public void execute() {         receiver.action();     } } ​ // 接收者 class Receiver {     public void action() {         System.out.println("Receiver action");     } } ​ // 调用者 class Invoker {     private Command command; ​     public void setCommand(Command command) {         this.command = command;     } ​     public void invoke() {         command.execute();     } }  优点 通过将请求封装为对象,命令模式实现了请求的参数化。  可以支持可撤销的操作。  缺点 可能会导致系统中出现大量的命令类,增加了复杂性。  3. 观察者模式(Observer Pattern) 定义 观察者模式定义了一种一对多的依赖关系,使得当一个对象状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。  动机 在需要建立对象之间的发布-订阅关系时,观察者模式提供了一种有效的解决方案。  结构 主题接口:定义注册和通知观察者的方法。  具体主题:实现主题接口,管理观察者并通知它们。  观察者接口:定义更新方法。  具体观察者:实现观察者接口,以便在主题状态变化时进行更新。  示例 // 主题接口 interface Subject {     void attach(Observer observer);     void detach(Observer observer);     void notifyObservers(); } ​ // 具体主题 class ConcreteSubject implements Subject {     private List<Observer> observers = new ArrayList<>();     private String state; ​     @Override     public void attach(Observer observer) {         observers.add(observer);     } ​     @Override     public void detach(Observer observer) {         observers.remove(observer);     } ​     @Override     public void notifyObservers() {         for (Observer observer : observers) {             observer.update(state);         }     } ​     public void setState(String state) {         this.state = state;         notifyObservers();     } } ​ // 观察者接口 interface Observer {     void update(String state); } ​ // 具体观察者 class ConcreteObserver implements Observer {     @Override     public void update(String state) {         System.out.println("Observer updated with state: " + state);     } }  优点 支持广播通信,减少了对象之间的耦合。  可以动态地添加和移除观察者。  缺点 可能会造成观察者的过度依赖,导致状态更新过于频繁。  4. 状态模式(State Pattern) 定义 状态模式允许一个对象在其内部状态改变时改变其行为。对象看起来似乎修改了其类。  动机 当一个对象的行为与其状态相关时,状态模式提供了一种有效的方法来管理这些状态和相应的行为。  结构 状态接口:定义状态的接口。  具体状态:实现状态接口,定义与具体状态相关的行为。  上下文:维护一个对某一状态对象的引用,负责状态的切换。  示例 // 状态接口 interface State {     void handle(); } ​ // 具体状态A class ConcreteStateA implements State {     @Override     public void handle() {         System.out.println("Handling State A");     } } ​ // 具体状态B class ConcreteStateB implements State {     @Override     public void handle() {         System.out.println("Handling State B");     } } ​ // 上下文 class Context {     private State state; ​     public void setState(State state) {         this.state = state;     } ​     public void request() {         state.handle();     } }  优点 简化了代码,避免了复杂的条件判断。  通过状态的封装,提高了系统的可扩展性。  缺点 状态类的数量可能会增加,导致系统的复杂性。  5. 策略模式(Strategy Pattern) 定义 策略模式定义了一系列算法,将每一个算法封装起来,并使它们可以互相替换。策略模式让算法独立于使用它的客户端。  动机 在需要在运行时选择算法或行为时,策略模式提供了一种灵活的解决方案。  结构 策略接口:定义了一个算法的接口。  具体策略:实现策略接口,封装具体的算法。  上下文:维护一个对某一策略对象的引用,负责执行策略。  示例 // 策略接口 interface Strategy {     void execute(); } ​ // 具体策略A class ConcreteStrategyA implements Strategy {     @Override     public void execute() {         System.out.println("Executing Strategy A");     } } ​ // 具体策略B class ConcreteStrategyB implements Strategy {     @Override     public void execute() {         System.out.println("Executing Strategy B");     } } ​ // 上下文 class Context {     private Strategy strategy; ​     public void setStrategy(Strategy strategy) {         this.strategy = strategy;     } ​     public void executeStrategy() {         strategy.execute();     } }  优点 提高了系统的灵活性和可扩展性。  客户端可以根据需要选择策略。  缺点 可能会导致策略类的数量增加。  6. 迭代器模式(Iterator Pattern) 定义 迭代器模式提供一种方法访问一个聚合对象的各个元素,而又不暴露该对象的内部表示。  动机 在需要遍历集合但不希望暴露集合的内部结构时,迭代器模式提供了一种清晰的解决方案。  结构 迭代器接口:定义遍历的方法(如next()、hasNext())。  具体迭代器:实现迭代器接口,管理对集合的访问。  聚合接口:定义创建迭代器的方法。  具体聚合:实现聚合接口,提供存储元素的集合。  示例 import java.util.ArrayList; import java.util.List; ​ // 迭代器接口 interface Iterator {     boolean hasNext();     Object next(); } ​ // 具体迭代器 class ConcreteIterator implements Iterator {     private List<Object> items;     private int position = 0; ​     public ConcreteIterator(List<Object> items) {         this.items = items;     } ​     @Override     public boolean hasNext() {         return position < items.size();     } ​     @Override     public Object next() {         return items.get(position++);     } } ​ // 聚合接口 interface Aggregate {     Iterator createIterator(); } ​ // 具体聚合 class ConcreteAggregate implements Aggregate {     private List<Object> items = new ArrayList<>(); ​     public void addItem(Object item) {         items.add(item);     } ​     @Override     public Iterator createIterator() {         return new ConcreteIterator(items);     } } ​ // 使用示例 public class IteratorPatternExample {     public static void main(String[] args) {         ConcreteAggregate aggregate = new ConcreteAggregate();         aggregate.addItem("Item 1");         aggregate.addItem("Item 2");         aggregate.addItem("Item 3"); ​         Iterator iterator = aggregate.createIterator();         while (iterator.hasNext()) {             System.out.println(iterator.next());         }     } }  优点 提供了一种统一的遍历方式,支持多种聚合对象。  隐藏了集合的内部实现,使得聚合对象的实现可以独立于迭代器的实现。  缺点 可能会增加系统的复杂性,特别是在需要多个迭代器时。  7. 中介者模式(Mediator Pattern) 定义 中介者模式定义一个中介对象来封装一系列对象之间的交互,使得对象之间的交互不直接,减少了对象之间的耦合。  动机 在复杂系统中,多个对象之间的交互可能会导致高耦合性,中介者模式可以降低这种耦合。  结构 中介者接口:定义各个同事对象之间的交互方法。  具体中介者:实现中介者接口,协调同事对象之间的交互。  同事接口:定义同事对象的接口。  具体同事:实现同事接口,并通过中介者与其他同事进行交互。  示例 // 中介者接口 interface Mediator {     void notify(Object sender, String event); } ​ // 具体中介者 class ConcreteMediator implements Mediator {     private ColleagueA colleagueA;     private ColleagueB colleagueB; ​     public void setColleagueA(ColleagueA colleagueA) {         this.colleagueA = colleagueA;     } ​     public void setColleagueB(ColleagueB colleagueB) {         this.colleagueB = colleagueB;     } ​     @Override     public void notify(Object sender, String event) {         if (sender == colleagueA) {             // 处理ColleagueA的事件             System.out.println("Mediator received event from ColleagueA: " + event);         } else if (sender == colleagueB) {             // 处理ColleagueB的事件             System.out.println("Mediator received event from ColleagueB: " + event);         }     } } ​ // 同事接口 interface Colleague {     void setMediator(Mediator mediator); } ​ // 具体同事A class ColleagueA implements Colleague {     private Mediator mediator; ​     @Override     public void setMediator(Mediator mediator) {         this.mediator = mediator;     } ​     public void doSomething() {         System.out.println("ColleagueA does something.");         mediator.notify(this, "Action from ColleagueA");     } } ​ // 具体同事B class ColleagueB implements Colleague {     private Mediator mediator; ​     @Override     public void setMediator(Mediator mediator) {         this.mediator = mediator;     } ​     public void doSomething() {         System.out.println("ColleagueB does something.");         mediator.notify(this, "Action from ColleagueB");     } } ​ // 使用示例 public class MediatorPatternExample {     public static void main(String[] args) {         ConcreteMediator mediator = new ConcreteMediator();                  ColleagueA colleagueA = new ColleagueA();         colleagueA.setMediator(mediator);                  ColleagueB colleagueB = new ColleagueB();         colleagueB.setMediator(mediator);                  mediator.setColleagueA(colleagueA);         mediator.setColleagueB(colleagueB);                  colleagueA.doSomething();         colleagueB.doSomething();     } }  优点 减少了对象之间的依赖,提高了系统的灵活性。  可以集中处理对象之间的交互逻辑。  缺点 可能会导致中介者变得复杂,成为系统的单点故障。  8. 备忘录模式(Memento Pattern) 定义 备忘录模式允许在不暴露对象内部状态的情况下,捕获并外部存储一个对象的内部状态,以便在以后可以恢复到该状态。  动机 在需要保存对象的历史状态以便在需要时恢复时,备忘录模式提供了一种简单的实现。  结构 备忘录:用于存储对象的状态。  发起人:创建备忘录并负责恢复状态。  管理者:管理多个备忘录的存储。  示例 // 备忘录 class Memento {     private String state; ​     public Memento(String state) {         this.state = state;     } ​     public String getState() {         return state;     } } ​ // 发起人 class Originator {     private String state; ​     public void setState(String state) {         this.state = state;     } ​     public Memento saveStateToMemento() {         return new Memento(state);     } ​     public void getStateFromMemento(Memento memento) {         state = memento.getState();     } } ​ // 管理者 class Caretaker {     private List<Memento> mementoList = new ArrayList<>(); ​     public void add(Memento state) {         mementoList.add(state);     } ​     public Memento get(int index) {         return mementoList.get(index);     } } ​ // 使用示例 public class MementoPatternExample {     public static void main(String[] args) {         Originator originator = new Originator();         Caretaker caretaker = new Caretaker(); ​         originator.setState("State #1");         caretaker.add(originator.saveStateToMemento()); ​         originator.setState("State #2");         caretaker.add(originator.saveStateToMemento()); ​         originator.setState("State #3"); ​         System.out.println("Current State: " + originator.state);         originator.getStateFromMemento(caretaker.get(0));         System.out.println("Restored State: " + originator.state);     } }  优点 可以在不暴露实现细节的情况下恢复对象的状态。  提高了对象状态管理的灵活性。  缺点 可能会增加系统的内存开销,特别是在需要存储大量状态时。  9. 访问者模式(Visitor Pattern) 定义 访问者模式允许在不改变对象结构的前提下,定义作用于这些对象的新操作。通过将操作封装在访问者中,可以对一组对象进行操作。  动机 在需要对对象结构中的元素执行一些操作时,访问者模式提供了一种清晰的解决方案。  结构 访问者接口:定义访问的方法。  具体访问者:实现访问者接口,定义具体的操作。  元素接口:定义接受访问者的方法。  具体元素:实现元素接口,接受访问者。  示例 // 访问者接口 interface Visitor {     void visit(ElementA elementA);     void visit(ElementB elementB); } ​ // 具体访问者 class ConcreteVisitor implements Visitor {     @Override     public void visit(ElementA elementA) {         System.out.println("Visiting Element A");     } ​     @Override     public void visit(ElementB elementB) {         System.out.println("Visiting Element B");     } } ​ // 元素接口 interface Element {     void accept(Visitor visitor); } ​ class ElementA implements Element {     @Override     public void accept(Visitor visitor) {         visitor.visit(this);     } } ​ // 具体元素B class ElementB implements Element {     @Override     public void accept(Visitor visitor) {         visitor.visit(this);     } } ​ // 使用示例 public class VisitorPatternExample {     public static void main(String[] args) {         ElementA elementA = new ElementA();         ElementB elementB = new ElementB(); ​         Visitor visitor = new ConcreteVisitor(); ​         elementA.accept(visitor); // 访问ElementA         elementB.accept(visitor); // 访问ElementB     } }  优点 将操作与对象结构分离,使得新增操作变得更加容易。  可以对对象结构中的元素进行多种操作,而无需修改元素的结构。  缺点 增加了新元素时的复杂性,因为需要修改访问者接口和所有的具体访问者。  对象结构中的元素与访问者的紧密耦合可能会影响可维护性。  10. 解释器模式(Interpreter Pattern) 定义 解释器模式为一种语言的文法定义一个表示,并定义一个解释器,用于处理该语言的句子。  动机 在需要解释一些特定语言或表达式的场景下,解释器模式可以简化语言处理的复杂度。  结构 抽象表达式:定义一个接口以实现解释操作。  终结符表达式:实现与文法中的终结符相关的解释操作。  非终结符表达式:实现与文法中的非终结符相关的解释操作。  上下文:存储解释器所需的状态信息。  示例 import java.util.HashMap; import java.util.Map; ​ // 抽象表达式 interface Expression {     int interpret(Map<String, Expression> variables); } ​ // 终结符表达式 class TerminalExpression implements Expression {     private String key; ​     public TerminalExpression(String key) {         this.key = key;     } ​     @Override     public int interpret(Map<String, Expression> variables) {         Expression variable = variables.get(key);         return variable.interpret(variables);     } } ​ // 非终结符表达式 class AddExpression implements Expression {     private Expression leftExpression;     private Expression rightExpression; ​     public AddExpression(Expression left, Expression right) {         leftExpression = left;         rightExpression = right;     } ​     @Override     public int interpret(Map<String, Expression> variables) {         return leftExpression.interpret(variables) + rightExpression.interpret(variables);     } } ​ // 使用示例 public class InterpreterPatternExample {     public static void main(String[] args) {         // 定义变量         Map<String, Expression> variables = new HashMap<>();         variables.put("x", new TerminalExpression("5"));         variables.put("y", new TerminalExpression("10")); ​         // 表达式:x + y         Expression expression = new AddExpression(variables.get("x"), variables.get("y"));         int result = expression.interpret(variables); ​         System.out.println("Result: " + result); // 输出:Result: 15     } }  优点 可以通过组合简单的表达式来构建复杂的表达式。 符合开闭原则,新增表达式时可以很容易地扩展。 缺点 解析复杂的文法时,可能会导致类的数量急剧增加,影响可读性。 性能问题:对于大型文法的解释可能会有性能瓶颈。 ————————————————    原文链接:https://blog.csdn.net/u012108607/article/details/142852438 
  • [技术干货] Java中23种设计模式
    一、创建型模式 1.单例模式(Singleton Pattern) 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。  1.1 饿汉式 特点:类加载时就初始化,线程安全          // 构造方法私有化     private Singleton() {              }       // 饿汉式创建单例对象     private static Singleton singleton = new Singleton();       public static Singleton getInstance() {         return singleton;     } 1.2 懒汉式  特点:第一次调用才初始化,避免内存浪费。      /*      * 懒汉式创建单例模式 由于懒汉式是非线程安全, 所以加上线程锁保证线程安全      */     private static Singleton singleton;       public static synchronized Singleton getInstance() {         if (singleton == null) {             singleton = new Singleton();         }         return singleton;     } 1.3 双重检验锁(double check lock)(DCL)  特点:安全且在多线程情况下能保持高性能      private volatile static Singleton singleton;     private Singleton (){}     public static Singleton getInstance() {         if (singleton == null) {             synchronized (Singleton.class) {                 if (singleton == null) {                     singleton = new Singleton();                 }             }         }         return singleton;     } 1.4 静态内部类  特点:效果类似DCL,只适用于静态域      private static class SingletonHolder {         private static final Singleton INSTANCE = new Singleton();     }     private Singleton (){}     public static final Singleton getInstance() {         return SingletonHolder.INSTANCE;     } 1.5 枚举  特点:自动支持序列化机制,绝对防止多次实例化  public enum Singleton {     INSTANCE; } 1.6 破坏单例的几种方式与解决方法  1.6.1 反序列化              Singleton singleton = Singleton.getInstance();             ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:/test.txt"));             oos.writeObject(singleton);             oos.flush();             oos.close();               ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:/test.txt"));             Singleton singleton1 = (Singleton)ois.readObject();             ois.close();             System.out.println(singleton);//com.ruoyi.base.mapper.Singleton@50134894             System.out.println(singleton1);//com.ruoyi.base.mapper.Singleton@5ccd43c2 可以看到反序列化后,两个对象的地址不一样了,那么这就是违背了单例模式的原则了,解决方法只需要在单例类里加上一个readResolve()方法即可,原因就是在反序列化的过程中,会检测readResolve()方法是否存在,如果存在的话就会反射调用readResolve()这个方法。  private Object readResolve() {         return singleton;     } //com.ruoyi.base.mapper.Singleton@50134894 //com.ruoyi.base.mapper.Singleton@50134894 1.6.2 反射              Singleton singleton = Singleton.getInstance();             Class<Singleton> singletonClass = Singleton.class;             Constructor<Singleton> constructor = singletonClass.getDeclaredConstructor();             constructor.setAccessible(true);             Singleton singleton1 = constructor.newInstance();             System.out.println(singleton);//com.ruoyi.base.mapper.Singleton@32a1bec0             System.out.println(singleton1);//com.ruoyi.base.mapper.Singleton@22927a81 同样可以看到,两个对象的地址不一样,这同样是违背了单例模式的原则,解决办法为使用一个布尔类型的标记变量标记一下即可,代码如下:  private static boolean singletonFlag = false;       private Singleton() {         if (singleton != null || singletonFlag) {             throw new RuntimeException("试图用反射破坏异常");         }         singletonFlag = true;     } 但是这种方法假如使用了反编译,获得了这个标记变量,同样可以破坏单例,代码如下:              Class<Singleton> singletonClass = Singleton.class;             Constructor<Singleton> constructor = singletonClass.getDeclaredConstructor();             constructor.setAccessible(true);             Singleton singleton = constructor.newInstance();             System.out.println(singleton); // com.ruoyi.base.mapper.Singleton@32a1bec0               Field singletonFlag = singletonClass.getDeclaredField("singletonFlag");             singletonFlag.setAccessible(true);             singletonFlag.setBoolean(singleton, false);             Singleton singleton1 = constructor.newInstance();             System.out.println(singleton1); // com.ruoyi.base.mapper.Singleton@5e8c92f4 如果想使单例不被破坏,那么应该使用枚举的方式去实现单例模式,枚举是不可以被反射破坏单例的。  1.7 容器式单例  当程序中的单例对象非常多的时候,则可以使用容器对所有单例对象进行管理,如下:  public class ContainerSingleton {     private ContainerSingleton() {}     private static Map<String, Object> singletonMap = new ConcurrentHashMap<>();     public static Object getInstance(Class clazz) throws Exception {         String className = clazz.getName();         // 当容器中不存在目标对象时则先生成对象再返回该对象         if (!singletonMap.containsKey(className)) {             Object instance = Class.forName(className).newInstance();             singletonMap.put(className, instance);             return instance;         }         // 否则就直接返回容器里的对象         return singletonMap.get(className);     }     public static void main(String[] args) throws Exception {         SafetyDangerLibrary instance1 = (SafetyDangerLibrary)ContainerSingleton.getInstance(SafetyDangerLibrary.class);         SafetyDangerLibrary instance2 = (SafetyDangerLibrary)ContainerSingleton.getInstance(SafetyDangerLibrary.class);         System.out.println(instance1 == instance2); // true     } }  1.8 ThreadLocal单例  不保证整个应用全局唯一,但保证线程内部全局唯一,以空间换时间,且线程安全。  public class ThreadLocalSingleton {     private ThreadLocalSingleton(){}     private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance = ThreadLocal.withInitial(() -> new ThreadLocalSingleton());     public static ThreadLocalSingleton getInstance(){         return threadLocalInstance.get();     }     public static void main(String[] args) {         new Thread(() -> {             System.out.println(Thread.currentThread().getName() + "-----" + ThreadLocalSingleton.getInstance());             System.out.println(Thread.currentThread().getName() + "-----" + ThreadLocalSingleton.getInstance());         }).start();         new Thread(() -> {             System.out.println(Thread.currentThread().getName() + "-----" + ThreadLocalSingleton.getInstance());             System.out.println(Thread.currentThread().getName() + "-----" + ThreadLocalSingleton.getInstance());         }).start(); //        Thread-0-----com.ruoyi.library.domain.vo.ThreadLocalSingleton@53ac93b3 //        Thread-1-----com.ruoyi.library.domain.vo.ThreadLocalSingleton@7fe11afc //        Thread-0-----com.ruoyi.library.domain.vo.ThreadLocalSingleton@53ac93b3 //        Thread-1-----com.ruoyi.library.domain.vo.ThreadLocalSingleton@7fe11afc     } }  可以看到上面线程0和1他们的对象是不一样的,但是线程内部,他们的对象是一样的,这就是线程内部保证唯一。  1.9 总结  适用场景:  需要确保在任何情况下绝对只需要一个实例。如:ServletContext,ServletConfig,ApplicationContext,DBPool,ThreadPool等。 优点:  在内存中只有一个实例,减少了内存开销。 可以避免对资源的多重占用。 设置全局访问点,严格控制访问。 缺点:  没有接口,扩展困难。 如果要扩展单例对象,只有修改代码,没有其它途径。 2.工厂方法模式(Factory Method) 2.1 简单工厂模式  简单工厂模式不是23种设计模式之一,他可以理解为工厂模式的一种简单的特殊实现。  2.1.1 基础版  // 工厂类 public class CoffeeFactory {     public Coffee create(String type) {         if ("americano".equals(type)) {             return new Americano();         }         if ("mocha".equals(type)) {             return new Mocha();         }         if ("cappuccino".equals(type)) {             return new Cappuccino();         }         return null;     } } // 产品基类 public interface Coffee { }   // 产品具体类,实现产品基类接口 public class Cappuccino implements Coffee { } 基础版是最基本的简单工厂的写法,传一个参数过来,判断是什么类型的产品,就返回对应的产品类型。但是这里有一个问题,就是参数是字符串的形式,这就很容易会写错,比如少写一个字母,或者小写写成了大写,就会无法得到自己想要的产品类了,同时如果新加了产品,还得在工厂类的创建方法中继续加if,于是就有了升级版的写法。  2.1.2 升级版      // 使用反射创建对象     // 加一个static变为静态工厂     public static Coffee create(Class<? extends Coffee> clazz) throws Exception {         if (clazz != null) {             return clazz.newInstance();         }         return null;     } 升级版就很好的解决基础版的问题,在创建的时候在传参的时候不仅会有代码提示,保证不会写错,同时在新增产品的时候只需要新增产品类即可,也不需要再在工厂类的方法里面新增代码了。  2.1.3 总结  适用场景:  工厂类负责创建的对象较少。 客户端只需要传入工厂类的参数,对于如何创建的对象的逻辑不需要关心。 优点:  只需要传入一个正确的参数,就可以获取你所需要的对象,无须知道创建的细节。 缺点:  工厂类的职责相对过重,增加新的产品类型的时需要修改工厂类的判断逻辑,违背了开闭原则。 不易于扩展过于复杂的产品结构。 2.2 工厂方法模式  工厂方法模式是指定义一个创建对象的接口,让实现这个接口的类来决定实例化哪个类,工厂方法让类的实例化推迟到子类中进行。  工厂方法模式主要有以下几种角色:  抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它和具体工厂之间一一对应。 2.2.1 代码实现  // 抽象工厂 public interface CoffeeFactory {     Coffee create(); } // 具体工厂 public class CappuccinoFactory implements CoffeeFactory {     @Override     public Coffee create() {         return new Cappuccino();     } } // 抽象产品 public interface Coffee { } // 具体产品 public class Cappuccino implements Coffee { }  2.2.2 总结  适用场景:  创建对象需要大量的重复代码。 客户端(应用层)不依赖于产品类实例如何被创建和实现等细节。 一个类通过其子类来指定创建哪个对象。 优点:  用户只需要关系所需产品对应的工厂,无须关心创建细节。 加入新产品符合开闭原则,提高了系统的可扩展性。 缺点:  类的数量容易过多,增加了代码结构的复杂度。 增加了系统的抽象性和理解难度。 3.抽象工厂模式(Abstract Factory) 抽象工厂模式是指提供一个创建一系列相关或相互依赖对象的接口,无须指定他们具体的类。    工厂方法模式中考虑的是一类产品的生产,如电脑厂只生产电脑,电话厂只生产电话,这种工厂只生产同种类的产品,同种类产品称为同等级产品,也就是说,工厂方法模式只考虑生产同等级的产品,但是现实生活中许多工厂都是综合型工厂,能生产多等级(种类)的产品,如上面说的电脑和电话,本质上他们都属于电器,那么他们就能在电器厂里生产出来,而抽象工厂模式就将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族,如上图所示纵轴是产品等级,也就是同一类产品;横轴是产品族,也就是同一品牌的产品,同一品牌的产品产自同一个工厂。  抽象工厂模式的主要角色如下:  抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。 3.1 代码实现  // 咖啡店 抽象工厂 public interface CoffeeShopFactory {       // 咖啡类     Coffee createCoffee();       // 甜点类     Dessert createDessert(); } // 美式风格工厂 public class AmericanFactory implements CoffeeShopFactory {     @Override     public Coffee createCoffee() {         return new Americano();     }       @Override     public Dessert createDessert() {         return new Cheesecake();     } } // 意式风格工厂 public class ItalyFactory implements CoffeeShopFactory {     @Override     public Coffee createCoffee() {         return new Cappuccino();     }       @Override     public Dessert createDessert() {         return new Tiramisu();     } }  类图 3.2 总结  产品族:一系列相关的产品,整合到一起有关联性  产品等级:同一个继承体系  适用场景:  客户端(应用层)不依赖于产品类实例如何被创建和实现等细节。 强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量重复的代码。 提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。 优点:  当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。 缺点:  当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。 4.原型模式(Prototype) 原型模式是指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。调用者不需要知道任何创建细节,不调用构造函数。  原型模式包含如下角色:  抽象原型类:规定了具体原型对象必须实现的的 clone() 方法。 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。 访问类:使用具体原型类中的 clone() 方法来复制新的对象。 @Data @AllArgsConstructor @NoArgsConstructor public class Student implements Cloneable {     private String name;     private String sex;     private Integer age;     @Override     protected Object clone() throws CloneNotSupportedException {         return super.clone();     }       public static void main(String[] args) throws Exception{         Student stu1 = new Student("张三", "男", 18);         Student stu2 = (Student)stu1.clone();         stu2.setName("李四");         System.out.println(stu1);// Student(name=张三, sex=男, age=18)         System.out.println(stu2);// Student(name=李四, sex=男, age=18)     } }  可以看到,把一个学生复制过来,只是改了姓名而已,其他属性完全一样没有改变,需要注意的是,一定要在被拷贝的对象上实现Cloneable接口,否则会抛出CloneNotSupportedException异常。  4.1 浅克隆  创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。  @Data public class Clazz implements Cloneable {     private String name;     private Student student;     @Override     protected Object clone() throws CloneNotSupportedException {         return super.clone();     } } @Data @AllArgsConstructor @NoArgsConstructor public class Student implements Serializable {     private String name;     private String sex;     private Integer age; }       public static void main(String[] args) throws Exception{         Clazz clazz1 = new Clazz();         clazz1.setName("高三一班");         Student stu1 = new Student("张三", "男", 18);         clazz1.setStudent(stu1);         System.out.println(clazz1); // Clazz(name=高三一班, student=Student(name=张三, sex=男, age=18))         Clazz clazz2 = (Clazz)clazz1.clone();         Student stu2 = clazz2.getStudent();         stu2.setName("李四");         System.out.println(clazz1); // Clazz(name=高三一班, student=Student(name=李四, sex=男, age=18))         System.out.println(clazz2); // Clazz(name=高三一班, student=Student(name=李四, sex=男, age=18))     }  可以看到,当修改了stu2的姓名时,stu1的姓名同样也被修改了,这说明stu1和stu2是同一个对象,这就是浅克隆的特点,对具体原型类中的引用类型的属性进行引用的复制。同时,这也可能是浅克隆所带来的弊端,因为结合该例子的原意,显然是想在班级中新增一名叫李四的学生,而非让所有的学生都改名叫李四,于是我们这里就要使用深克隆。  4.2 深克隆  创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。  @Data public class Clazz implements Cloneable, Serializable {     private String name;     private Student student;     @Override     protected Object clone() throws CloneNotSupportedException {         return super.clone();     }       protected Object deepClone() throws IOException, ClassNotFoundException {         ByteArrayOutputStream bos = new ByteArrayOutputStream();         ObjectOutputStream oos = new ObjectOutputStream(bos);         oos.writeObject(this);         ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());         ObjectInputStream ois = new ObjectInputStream(bis);         return ois.readObject();     } }       public static void main(String[] args) throws Exception{         Clazz clazz1 = new Clazz();         clazz1.setName("高三一班");         Student stu1 = new Student("张三", "男", 18);         clazz1.setStudent(stu1);         Clazz clazz3 = (Clazz)clazz1.deepClone();         Student stu3 = clazz3.getStudent();         stu3.setName("王五");         System.out.println(clazz1); // Clazz(name=高三一班, student=Student(name=张三, sex=男, age=18))         System.out.println(clazz3); // Clazz(name=高三一班, student=Student(name=王五, sex=男, age=18))     }  可以看到,当修改了stu3的姓名时,stu1的姓名并没有被修改了,这说明stu3和stu1已经是不同的对象了,说明Clazz中的Student也被克隆了,不再指向原有对象地址,这就是深克隆。这里需要注意的是,Clazz类和Student类都需要实现Serializable接口,否则会抛出NotSerializableException异常。  4.3 克隆破坏单例与解决办法  PS:上面例子有的代码,这里便不重复写了,可以在上面的代码基础上添加以下代码  // Clazz类 private static Clazz clazz = new Clazz(); private Clazz(){} public static Clazz getInstance() {return clazz;}       // 测试     public static void main(String[] args) throws Exception{         Clazz clazz1 = Clazz.getInstance();         Clazz clazz2 = (Clazz)clazz1.clone();         System.out.println(clazz1 == clazz2); // false     } 可以看到clazz1和clazz2并不相等,也就是说他们并不是同一个对象,也就是单例被破坏了。  解决办法也很简单,首先第一个就是不实现Cloneable接口即可,但是不实现Cloneable接口进行clone则会抛出CloneNotSupportedException异常。第二个方法就是重写clone()方法即可,如下:      @Override     protected Object clone() throws CloneNotSupportedException {         return clazz;     }     // 测试输出     System.out.println(clazz1 == clazz2) // true 可以看到,上面clazz1和clazz2是相等的,即单例没有被破坏。  另外我们知道,单例就是只有一个实例对象,如果重写了clone()方法保证单例的话,那么通过克隆出来的对象则不可以重新修改里面的属性,因为修改以后就会连同克隆对象一起被修改,所以是需要单例还是克隆,在实际应用中需要好好衡量。  4.4 总结  适用场景:  类初始化消耗资源较多。 new产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)。 构造函数比较复杂。 循环体中生产大量对象时。 优点:  性能优良,Java自带的原型模式是基于内存二进制流的拷贝,比直接new一个对象性能上提升了许多。 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,简化了创建的过程。 缺点:  必须配备克隆(或者可拷贝)方法。 当对已有类进行改造的时候,需要修改代码,违反了开闭原则。 深克隆、浅克隆需要运用得当。 5.建造者模式(Builder) 建造者模式是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。用户只需指定需要建造的类型就可以获得对象,建造过程及细节不需要了解。  建造者(Builder)模式包含如下角色:  抽象建造者类(Builder):这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的部件对象的创建。 具体建造者类(ConcreteBuilder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。 产品类(Product):要创建的复杂对象。 指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。 5.1 常规写法  //产品类 电脑 @Data public class Computer {     private String motherboard;     private String cpu;     private String memory;     private String disk;     private String gpu;     private String power;     private String heatSink;     private String chassis; } // 抽象 builder类(接口) 组装电脑 public interface ComputerBuilder {      Computer computer = new Computer();     void buildMotherboard();     void buildCpu();     void buildMemory();     void buildDisk();     void buildGpu();     void buildHeatSink();     void buildPower();     void buildChassis();     Computer build(); } // 具体 builder类 华硕ROG全家桶电脑(手动狗头) public class AsusComputerBuilder implements ComputerBuilder {     @Override      public void buildMotherboard() {         computer.setMotherboard("Extreme主板");     }     @Override     public void buildCpu() {         computer.setCpu("Inter 12900KS");     }     @Override     public void buildMemory() {         computer.setMemory("芝奇幻峰戟 16G*2");     }     @Override     public void buildDisk() {         computer.setDisk("三星980Pro 2T");     }     @Override     public void buildGpu() {         computer.setGpu("华硕3090Ti 水猛禽");     }     @Override     public void buildHeatSink() {         computer.setHeatSink("龙神二代一体式水冷");     }     @Override     public void buildPower() {         computer.setPower("雷神二代1200W");     }     @Override     public void buildChassis() {         computer.setChassis("太阳神机箱");     }     @Override     public Computer build() {         return computer;     } } // 指挥者类 指挥该组装什么电脑 @AllArgsConstructor public class ComputerDirector {     private ComputerBuilder computerBuilder;     public Computer construct() {         computerBuilder.buildMotherboard();         computerBuilder.buildCpu();         computerBuilder.buildMemory();         computerBuilder.buildDisk();         computerBuilder.buildGpu();         computerBuilder.buildHeatSink();         computerBuilder.buildPower();         computerBuilder.buildChassis();         return computerBuilder.build();     } }    // 测试     public static void main(String[] args) {         ComputerDirector computerDirector = new ComputerDirector(new AsusComputerBuilder());         // Computer(motherboard=Extreme主板, cpu=Inter 12900KS, memory=芝奇幻峰戟 16G*2, disk=三星980Pro 2T, gpu=华硕3090Ti 水猛禽, power=雷神二代1200W, heatSink=龙神二代一体式水冷, chassis=太阳神机箱)         System.out.println(computerDirector.construct());     }  上面示例是建造者模式的常规用法,指挥者类ComputerDirector在建造者模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类,但是有些情况下需要简化系统结构,可以把指挥者类和抽象建造者进行结合,于是就有了下面的简化写法。  5.2 简化写法  // 把指挥者类和抽象建造者合在一起的简化建造者类 public class SimpleComputerBuilder {     private Computer computer = new Computer();     public void buildMotherBoard(String motherBoard){         computer.setMotherboard(motherBoard);     }     public void buildCpu(String cpu){         computer.setCpu(cpu);     }     public void buildMemory(String memory){         computer.setMemory(memory);     }     public void buildDisk(String disk){         computer.setDisk(disk);     }     public void buildGpu(String gpu){         computer.setGpu(gpu);     }     public void buildPower(String power){         computer.setPower(power);     }     public void buildHeatSink(String heatSink){         computer.setHeatSink(heatSink);     }     public void buildChassis(String chassis){         computer.setChassis(chassis);     }     public Computer build(){         return computer;     } }     // 测试     public static void main(String[] args) {         SimpleComputerBuilder simpleComputerBuilder = new SimpleComputerBuilder();         simpleComputerBuilder.buildMotherBoard("Extreme主板");         simpleComputerBuilder.buildCpu("Inter 12900K");         simpleComputerBuilder.buildMemory("芝奇幻峰戟 16G*2");         simpleComputerBuilder.buildDisk("三星980Pro 2T");         simpleComputerBuilder.buildGpu("华硕3090Ti 水猛禽");         simpleComputerBuilder.buildPower("雷神二代1200W");         simpleComputerBuilder.buildHeatSink("龙神二代一体式水冷");         simpleComputerBuilder.buildChassis("太阳神机箱");         // Computer(motherboard=Extreme主板, cpu=Inter 12900K, memory=芝奇幻峰戟 16G*2, disk=三星980Pro 2T, gpu=华硕3090Ti 水猛禽, power=雷神二代1200W, heatSink=龙神二代一体式水冷, chassis=太阳神机箱)         System.out.println(simpleComputerBuilder.build());     }  可以看到,对比常规写法,这样写确实简化了系统结构,但同时也加重了建造者类的职责,也不是太符合单一职责原则,如果construct() 过于复杂,建议还是封装到 Director 中。  5.3 链式写法  // 链式写法建造者类 public class SimpleComputerBuilder {     private Computer computer = new Computer();     public SimpleComputerBuilder buildMotherBoard(String motherBoard){         computer.setMotherboard(motherBoard);         return this;     }     public SimpleComputerBuilder buildCpu(String cpu){         computer.setCpu(cpu);         return this;     }     public SimpleComputerBuilder buildMemory(String memory){         computer.setMemory(memory);         return this;     }     public SimpleComputerBuilder buildDisk(String disk){         computer.setDisk(disk);         return this;     }     public SimpleComputerBuilder buildGpu(String gpu){         computer.setGpu(gpu);         return this;     }     public SimpleComputerBuilder buildPower(String power){         computer.setPower(power);         return this;     }     public SimpleComputerBuilder buildHeatSink(String heatSink){         computer.setHeatSink(heatSink);         return this;     }     public SimpleComputerBuilder buildChassis(String chassis){         computer.setChassis(chassis);         return this;     }     public Computer build(){         return computer;     } }     // 测试     public static void main(String[] args) {         Computer asusComputer = new SimpleComputerBuilder().buildMotherBoard("Extreme主板")             .buildCpu("Inter 12900K")             .buildMemory("芝奇幻峰戟 16G*2")             .buildDisk("三星980Pro 2T")             .buildGpu("华硕3090Ti 水猛禽")             .buildPower("雷神二代1200W")             .buildHeatSink("龙神二代一体式水冷")             .buildChassis("太阳神机箱").build();         System.out.println(asusComputer);     }  可以看到,其实链式写法与普通写法的区别并不大,只是在建造者类组装部件的时候,同时将建造者类返回即可,使用链式写法使用起来更方便,某种程度上也可以提高开发效率。从软件设计上,对程序员的要求比较高。比较常见的mybatis-plus中的条件构造器就是使用的这种链式写法。  5.4 总结  适用场景:  适用于创建对象需要很多步骤,但是步骤顺序不一定固定。 如果一个对象有非常复杂的内部结构(属性),把复杂对象的创建和使用进行分离。 优点:  封装性好,创建和使用分离。 扩展性好,建造类之间独立、一定程度上解耦。 缺点:  产生多余的Builder对象。 产品内部发生变化,建造者都要修改,成本较大。 与工厂模式的区别:  建造者模式更注重方法的调用顺序,工厂模式更注重创建对象。 创建对象的力度不同,建造者模式创建复杂的对象,由各种复杂的部件组成,工厂模式创建出来的都一样。 关注点不同,工厂模式只需要把对象创建出来就可以了,而建造者模式中不仅要创建出这个对象,还要知道这个对象由哪些部件组成。 建造者模式根据建造过程中的顺序不一样,最终的对象部件组成也不一样。 与抽象工厂模式的区别:  抽象工厂模式实现对产品族的创建,一个产品族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。 建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。 建造者模式所有函数加到一起才能生成一个对象,抽象工厂一个函数生成一个对象 二、结构型模式 1.代理模式(Proxy Pattern) 代理模式是指为其他对象提供一种代理,以控制对这个对象的访问。代理对象在访问对象和目标对象之间起到中介作用。  Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。  代理(Proxy)模式分为三种角色:  抽象角色(Subject): 通过接口或抽象类声明真实角色和代理对象实现的业务方法。 真实角色(Real Subject): 实现了抽象角色中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。 代理角色(Proxy) : 提供了与真实角色相同的接口,其内部含有对真实角色的引用,它可以访问、控制或扩展真实角色的功能。 1.1 静态代理  静态代理就是指我们在给一个类扩展功能的时候,我们需要去书写一个静态的类,相当于在之前的类上套了一层,这样我们就可以在不改变之前的类的前提下去对原有功能进行扩展,静态代理需要代理对象和目标对象实现一样的接口。    public static void main(String[] args) {         // 正常播放         new Controller().excuse(new PlayAction(new Player()));         // 暂停播放         new Controller().excuse(new PauseAction(new Player()));         // 停止播放         new Controller().excuse(new StopAction(new Player()));     }  3.2 总结  适用场景:  现实语义中具备“命令”的操作(如命令菜单,shell命令...)。 请求调用者和请求接收者需要解耦,使得调用者和接收者不直接交互。 需要抽象出等待执行的行为,比如撤销操作和恢复操作等。 需要支持命令宏(即命令组合操作)。 优点:  通过引入中间件(抽象接口),解耦了命令的请求与实现。 扩展性良好,可以很容易地增加新命令。 支持组合命令,支持命令队列。 可以在现有的命令的基础上,增加额外功能。 缺点:  具体命令类可能过多。 增加 了程序的复杂度,理解更加困难。 4.职责链模式(chain of responsibility pattern) 职责链模式是将链中每一个节点看作是一个对象,每个节点处理的请求均不同,且内部自动维护一个下一节点对象。当一个请求从链式的首端发出时,会沿着链的路径依次传递给每一个节点对象,直至有对象处理这个请求为止。  职责链模式主要包含以下角色:  抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。 4.1 代码实现 多个对象可以处理同一请求,但具体由哪个对象处理则在运行时动态决定。 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。 可动态指定一组对象处理请求。 优点:  将请求与处理解耦。 请求处理者(节点对象)只需关注自己感兴趣的请求进行处理即可,对于不感兴趣的请求,直接转发给下一级节点对象。 具备链式传递处理请求功能,请求发送者无需知晓链路结构,只需等待请求处理结果。 链路结构灵活,可以通过改变链路结构动态地新增或删减责任。 易于扩展新的请求处理类(节点),符合开闭原则。 缺点:  责任链太长或者处理时间过长,会影响整体性能。 如果节点对象存在循环引用时,会造成死循环,导致系统崩溃。 5.状态模式(State Pattern) 状态模式也称为状态机模式(State Machine Pattern),是允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。  状态模式包含以下主要角色:  环境(Context)角色:也称为上下文,它定义了客户程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。 具体状态(Concrete State)角色:实现抽象状态所对应的行为。 双分派实现动态绑定的本质,就是在重载方法委派的前面加上了继承体系中覆盖的环节,由于覆盖是动态的,所以重载就是动态的了。 11.2 Spring中的解释器模式 可以看到Spring中解释器写的是比较完善的,不仅有先乘除后加减和先括号进行运算的日常计算规则,而且对于空格也并没有要求,仅需要写出完整的表达式即可运算出来。  11.3 总结 适用场景: 一些重复出现的问题可以用一种简单的语言来进行表述。 一个简单语法需要解释的场景。 优点: 扩展性强:在解释器模式中由于语法是由很多类表示的,当语法规则更改时,只需修改相应的非终结符表达式即可;若扩展语法时,只需添加相应非终结符类即可。 增加了新的解释表达式的方式。 易于实现文法:解释器模式对应的文法应当是比较简单且易于实现的,过于复杂的语法并不适合使用解释器模式。 缺点:  语法规则较复杂时,会引起类膨胀。 执行效率比较低 ————————————————  原文链接:https://blog.csdn.net/pyy542718473/article/details/127248128 
  • [技术干货] SpringMVC常见面试题总结
     1、什么是Spring MVC ?简单介绍下你对springMVC的理解? Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把Model,View,Controller分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。  2、SpringMVC的流程? (1)用户发送请求至前端控制器DispatcherServlet; (2)DispatcherServlet收到请求后,调用HandlerMapping处理器映射器,请求获取Handler; (3)处理器映射器根据请求url找到具体的处理器Handler,生成处理器对象及处理器拦截器(如果有则生成),一并返回给DispatcherServlet; (4)DispatcherServlet 调用 HandlerAdapter处理器适配器,请求执行Handler; (5)HandlerAdapter 经过适配调用 具体处理器进行处理业务逻辑; (6)Handler执行完成返回ModelAndView; (7)HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet; (8)DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析; (9)ViewResolver解析后返回具体View; (10)DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中) (11)DispatcherServlet响应用户。 前端控制器 DispatcherServlet:接收请求、响应结果,相当于转发器,有了DispatcherServlet 就减少了其它组件之间的耦合度。 处理器映射器 HandlerMapping:根据请求的URL来查找Handler 处理器适配器 HandlerAdapter:负责执行Handler 处理器 Handler:处理器,需要程序员开发 视图解析器 ViewResolver:进行视图的解析,根据视图逻辑名将ModelAndView解析成真正的视图(view) 视图View:View是一个接口, 它的实现类支持不同的视图类型,如jsp,freemarker,pdf等等 3、Springmvc的优点: (1)可以支持各种视图技术,而不仅仅局限于JSP; (2)与Spring框架集成(如IoC容器、AOP等); (3)清晰的角色分配:前端控制器(dispatcherServlet) ,请求到处理器映射(handlerMapping),处理器适配器(HandlerAdapter),视图解析器(ViewResolver)。 (4) 支持各种请求资源的映射策略。  4、SpringMVC怎么样设定重定向和转发的? (1)转发:在返回值前面加"forward:",譬如"forward:user.do?name=method4" (2)重定向:在返回值前面加"redirect:",譬如"redirect:http://www.baidu.com"  5、 SpringMVC常用的注解有哪些? @RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。 @RequestBody:注解实现接收http请求的json数据,将json转换为java对象。 @ResponseBody:注解实现将conreoller方法返回对象转化为json对象响应给客户。  6、SpingMvc中的控制器的注解一般用哪个?有没有别的注解可以替代? 答:一般用@Controller注解,也可以使用@RestController,@RestController注解相当于@ResponseBody + @Controller,表示是表现层,除此之外,一般不用别的注解代替。  7、springMVC和struts2的区别有哪些? (1)springmvc的入口是一个servlet即前端控制器(DispatchServlet),而struts2入口是一个filter过虑器(StrutsPrepareAndExecuteFilter)。  (2)springmvc是基于方法开发(一个url对应一个方法),请求参数传递到方法的形参,可以设计为单例或多例(建议单例),struts2是基于类开发,传递参数是通过类的属性,只能设计为多例。  (3)Struts采用值栈存储请求和响应的数据,通过OGNL存取数据,springmvc通过参数解析器是将request请求内容解析,并给方法形参赋值,将数据和视图封装成ModelAndView对象,最后又将ModelAndView中的模型数据通过reques域传输到页面。Jsp视图解析器默认使用jstl。  8、如何解决POST请求中文乱码问题,GET的又如何处理呢? (1)解决post请求乱码问题:在web.xml中配置一个CharacterEncodingFilter过滤器,设置成utf-8;  <filter>     <filter-name>CharacterEncodingFilter</filter-name>     <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>     <init-param>         <param-name>encoding</param-name>         <param-value>utf-8</param-value>     </init-param> </filter>   <filter-mapping>     <filter-name>CharacterEncodingFilter</filter-name>     <url-pattern>/*</url-pattern> </filter-mapping> (2)get请求中文参数出现乱码解决方法有两个:  ①修改tomcat配置文件添加编码与工程编码一致,如下:  <ConnectorURIEncoding="utf-8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>  ②另外一种方法对参数进行重新编码:  String userName = new String(request.getParamter("userName").getBytes("ISO8859-1"),"utf-8") ISO8859-1是tomcat默认编码,需要将tomcat编码后的内容按utf-8编码。  9、SpringMvc里面拦截器是怎么写的: 有两种写法,一种是实现HandlerInterceptor接口,另外一种是继承适配器类,接着在接口方法当中,实现处理逻辑;然后在SpringMvc的配置文件中配置拦截器即可:  <!-- 配置SpringMvc的拦截器 --> <mvc:interceptors>     <!-- 配置一个拦截器的Bean就可以了 默认是对所有请求都拦截 -->     <bean id="myInterceptor" class="com.zwp.action.MyHandlerInterceptor"></bean>       <!-- 只针对部分请求拦截 -->     <mvc:interceptor>        <mvc:mapping path="/modelMap.do" />        <bean class="com.zwp.action.MyHandlerInterceptorAdapter" />     </mvc:interceptor> </mvc:interceptors> 10、注解原理: (1)什么是注解:          Java 注解就是代码中的一些特殊标记(元信息),用于在编译、类加载、运行时进行解析和使用,并执行相应的处理。它本质是继承了 Annotation 的特殊接口,其具体实现类是 JDK 动态代理生成的代理类,通过反射获取注解时,返回的也是 Java 运行时生成的动态代理对象 $Proxy1。通过代理对象调用自定义注解的方法,会最终调用 AnnotationInvocationHandler 的 invoke 方法,该方法会从 memberValues 这个Map中查询出对应的值,而 memberValues 的来源是Java常量池。          注解在实际开发中非常常见,比如 Java 原生的 @Overried、@Deprecated 等,Spring的 @Controller、@Service等,Lombok 工具类也有大量的注解,不过在原生 Java 中,还提供了元 Annotation(元注解),他主要是用来修饰注解的,比如 @Target、@Retention、@Document、@Inherited 等。  @Target:标识注解可以修饰哪些地方,比如方法、成员变量、包等,具体取值有以下几种:ElementType.TYPE/FIELD/METHOD/PARAMETER/CONSTRUCTOR/LOCAL_VARIABLE/ANNOTATION_TYPE/PACKAGE/TYPE_PARAMETER/TYPE_USE @Retention:什么时候使用注解:SOURCE(编译阶段就丢弃) / CLASS(类加载时丢弃) / RUNTIME(始终不会丢弃),一般来说,我们自定义的注解都是 RUNTIME 级别的,因为大多数情况我们是根据运行时环境去做一些处理,一般需要配合反射来使用,因为反射是 Java 获取运行是的信息的重要手段 @Document:注解是否会包含在 javadoc 中; @Inherited:定义该注解与子类的关系,子类是否能使用。 (2)如何自定义注解?  ① 创建一个自定义注解:与创建接口类似,但自定义注解需要使用 @interface ② 添加元注解信息,比如 @Target、@Retention、@Document、@Inherited 等 ③ 创建注解方法,但注解方法不能带有参数 ④ 注解方法返回值为基本类型、String、Enums、Annotation 或其数组 ⑤ 注解可以有默认值; @Target(FIELD) @Retention(RUNTIME) @Documented public @interface CarName {     String value() default ""; } 11、SpringMvc怎么和AJAX相互调用的? 通过Jackson框架就可以把Java里面的对象直接转化成Js可以识别的Json对象。具体步骤如下 :  (1)加入Jackson.jar (2)在配置文件中配置json的映射 (3)在接受Ajax方法里面可以直接返回Object、List等,但方法前面要加上@ResponseBody注解。  12、Spring MVC的异常处理 ? 答:可以将异常抛给Spring框架,由Spring框架来处理;我们只需要配置简单的异常处理器,在异常处理器中添视图页面即可。  13、SpringMvc的控制器是不是单例模式?如果是,有什么问题?怎么解决? 答:是单例模式,在多线程访问的时候有线程安全问题,解决方案是在控制器里面不能写可变状态量,如果需要使用这些可变状态,可以使用ThreadLocal机制解决,为每个线程单独生成一份变量副本,独立操作,互不影响。  14、如果在拦截请求中,我想拦截get方式提交的方法,怎么配置? 答:可以在@RequestMapping注解里面加上method=RequestMethod.GET。  16、怎样在方法里面得到Request,或者Session? 答:直接在方法的形参中声明request,SpringMvc就自动把request对象传入。  16、如果想在拦截的方法里面得到从前台传入的参数,怎么得到? 答:直接在形参里面声明这个参数就可以,但必须名字和传过来的参数一样。  17、如果前端传入多个参数,并且参数都是同个对象的,如何快速得到这个对象? 答:直接在方法中声明这个对象,SpringMvc就自动会把属性赋值到这个对象里面。  18、SpringMvc中函数的返回值是什么? 答:返回值可以有很多类型,有String,ModelAndView。ModelAndView类把视图和数据都合并的一起的,但一般用String比较好。  19、SpringMvc用什么对象从后台向前台传递数据的? 答:通过ModelMap对象,可以在这个对象里面调用put方法,把对象加到里面,前端就可以通过el表达式拿到。  20、怎么样把ModelMap里面的数据放入Session里面? 答:可以在类上面加上@SessionAttributes注解,里面包含的字符串就是要放入session里面的key。 ———————————————— 原文链接:https://blog.csdn.net/a745233700/article/details/80963758 
  • [问题求助] 通话记录是否有记录落地码
    【问题来源】【必填】南网电网【问题简要】【必填】语音平台数据库会对每通来话及来话的落地码进行数据保存吗,如果保存的话应该查询那张表,可以对所有通话按照落地码进行分别统计【问题类别】【必填】CC-Gateway【AICC解决方案版本】【必填】AICC 24.200.0【期望解决时间】【选填】尽快【问题现象描述】【必填】语音平台数据库会对每通来话及来话的落地码进行数据保存吗,如果保存的话应该查询那张表,可以对所有通话按照落地码进行分别统计【日志或错误截图】【可选】无【附件】【可选】无
  • [问题求助] 金额播报问题
    【问题来源】重庆银行【问题简要】IVR播报金额为十亿时,例如1635635958,只播报了前两位就不播了【AICC解决方案版本】AICC 23.200.0【期望解决时间】尽快【问题现象描述】IVR播报金额为十亿时,例如1635635958,只播报了前两位就不播了【日志或错误截图】【附件】无
  • [问题求助] 呼叫排队数实时统计
    【问题来源】【必填】南网电网【问题简要】【必填】语音平台是否有对全部技能队列的一个当前排队数查询的接口支持,或者是平台数据库能实时统计出来的库表【问题类别】【必填】CC-Gateway【AICC解决方案版本】【必填】AICC 24.200.0【期望解决时间】【选填】尽快【问题现象描述】【必填】语音平台是否有对全部技能队列的一个当前排队数查询的接口支持,或者是平台数据库能实时统计出来的库表【日志或错误截图】【可选】【附件】【可选】无
  • [技术干货] MinIO上传和下载文件及文件完整性校验
    package com.xuecheng.media;import com.j256.simplemagic.ContentInfo;import com.j256.simplemagic.ContentInfoUtil;import io.minio.*;import io.minio.errors.*;import org.apache.commons.codec.digest.DigestUtils;import org.apache.commons.io.IOUtils;import org.junit.jupiter.api.Test;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.http.MediaType;import java.io.*;import java.util.HashMap;import java.util.Map;/** * 测试minio的sdk * * @author ManolinCoder * @date 2024-10-21 */@SpringBootTestpublic class MinioTest { // MinIO服务器地址 String url = "http://localhost:9000"; // MinIO访问密钥 String accessKey = "accessKey"; // MinIO秘密密钥 String secretKey = "secretKey"; // 待上传的文件路径 String filePath = "/Users/lcm/Movies/测试视频/1.mp4"; // MinIO存储桶名称 String bucketName = "testbucket"; // 存储桶中的对象名称 String objectName = "1.mp4"; MinioClient minioClient = MinioClient.builder().endpoint(url).credentials(accessKey, secretKey).build(); /** * 上传文件 * * @param * @return void * @author ManolinCoder * @date 2024-10-21 */ @Test public void testUpload() throws Exception { ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(".mp4"); String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE; // 通用 mimeType 字节流 if (extensionMatch != null) { mimeType = extensionMatch.getMimeType(); } try { // Make 'asiatrip' bucket if not exist. boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket("testbucket").build()); if (!found) { // Make a new bucket called 'asiatrip'. minioClient.makeBucket(MakeBucketArgs.builder().bucket("testbucket").build()); } else { System.out.println("文件桶已存在!!!"); } // 上传文件 uploadFile(minioClient, filePath, bucketName, objectName, mimeType); //上传文件完整性校验 boolean flag = checkFileIntegrity(minioClient, filePath, bucketName, objectName); System.out.println(flag ? "上传文件成功了!!!" : "上传文件失败了!!!"); } catch (MinioException e) { System.out.println("Error occurred: " + e); System.out.println("HTTP trace: " + e.httpTrace()); } } /** * 删除文件 * * @param * @return void * @author ManolinCoder * @date 2024-10-21 */ @Test public void testDelete() throws Exception { RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket("testbucket").object("1.MP4").build(); minioClient.removeObject(removeObjectArgs); } /** * 查询文件,下载到本地 * * @param * @return void * @author ManolinCoder * @date 2024-10-21 */ @Test public void testGetObject() throws Exception { GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket("testbucket").object("1.MP4").build(); FilterInputStream inputStream = minioClient.getObject(getObjectArgs); String localFileName = "/Users/lcm/Downloads/1.mp4"; // 指定输出流 FileOutputStream outputStream = new FileOutputStream(new File(localFileName)); IOUtils.copy(inputStream, outputStream); // md5完整性校验 boolean flag = checkFileIntegrity(minioClient, localFileName, bucketName, objectName); if (flag) { System.out.println("下载成功了!!!"); } else { System.out.println("下载失败了!!!"); } } /** * 上传文件 * * @param minioClient * @param filePath * @param bucketName * @param objectName * @param contentType * @return void * @author CoderManolin * @date 2024-10-25 */ public void uploadFile(MinioClient minioClient, String filePath, String bucketName, String objectName, String contentType) throws Exception { // 计算上传前本地文件MD5 String uploadLocalFileMD5 = calculateMD5(filePath); System.out.println("上传前本地文件MD5: uploadLocalFileMD5=" + uploadLocalFileMD5); Map<String, String> md5Map = new HashMap<>(); md5Map.put("md5", uploadLocalFileMD5); //上传文件到 MinIO File file = new File(filePath); minioClient.putObject( PutObjectArgs.builder() .bucket(bucketName) .object(objectName) .stream(new FileInputStream(file), file.length(), -1) .userMetadata(md5Map) .contentType(contentType) .build() ); System.out.println("File uploaded successfully: " + objectName); } /** * 计算md5 * * @param filePath * @return File * @author ManolinCoder * @date 2024-10-23 */ public String calculateMD5(String filePath) throws Exception { FileInputStream fileInputStream = new FileInputStream(filePath); return DigestUtils.md5Hex(fileInputStream); } /** * 对比本地文件和minio文件的MD5值是否一致,校验文件完整性 * * @param minioClient * @param filePath * @param bucketName * @param objectName * @return boolean * @author CoderManolin * @date 2024-10-25 */ public boolean checkFileIntegrity(MinioClient minioClient, String filePath, String bucketName, String objectName) throws Exception { // 计算本地文件的MD5 String localFileMD5 = calculateMD5(filePath); System.out.println("Local file MD5: " + localFileMD5); // 获取MinIO中对象的MD5 StatObjectResponse stat = minioClient.statObject( StatObjectArgs.builder() .bucket(bucketName) .object(objectName) .build()); String minioFileMD5 = stat.userMetadata().get("md5"); System.out.println("MinIO file MD5: " + minioFileMD5); // 比较MD5值 return localFileMD5.equals(minioFileMD5); }}
  • [技术干货] Java EasyExcel 导出报内存溢出如何解决
    使用EasyExcel进行大数据量导出时容易导致内存溢出,特别是在导出百万级别的数据时。你有遇到过这种情况吗,以下是V 哥整理的解决该问题的一些常见方法,分享给大家,欢迎一起讨论:EasyExcel大数据量导出常见方法1. 分批写入EasyExcel支持分批写入数据,可以将数据分批加载到内存中,分批写入Excel文件,避免一次性将大量数据加载到内存中。示例代码: String fileName = "large_data.xlsx"; ExcelWriter excelWriter = EasyExcel.write(fileName).build(); WriteSheet writeSheet = EasyExcel.writerSheet("Sheet1").build(); // 假设每次写入10000条数据 int batchSize = 10000; List<Data> dataList; int pageIndex = 0; do { // 分页获取数据 dataList = getDataByPage(pageIndex++, batchSize); excelWriter.write(dataList, writeSheet); } while (dataList.size() == batchSize); // 关闭资源 excelWriter.finish();2. 设置合适的JVM内存针对大数据导出场景,可以尝试增大JVM的内存分配,例如: java -Xms512M -Xmx4G -jar yourApp.jar解释:-Xms512M:设置初始堆大小为512MB。-Xmx4G:设置最大堆大小为4GB。3. 减少数据对象的复杂性导出数据时,尽量简化数据对象,避免不必要的嵌套和多余字段的加载,以减少对象占用的内存空间。4. 关闭自动列宽设置EasyExcel的自动列宽功能会占用大量内存,特别是在数据量较大的情况下。关闭自动列宽可以节省内存。示例代码: EasyExcel.write(fileName) .registerWriteHandler(new SimpleWriteHandler()) // 不使用自动列宽 .sheet("Sheet1") .doWrite(dataList);5. 使用Stream导出(适合大数据)利用OutputStream分批写入数据,减少内存消耗。通过BufferedOutputStream可以进一步提高性能。示例代码: try (OutputStream out = new BufferedOutputStream(new FileOutputStream(fileName))) { ExcelWriter excelWriter = EasyExcel.write(out).build(); WriteSheet writeSheet = EasyExcel.writerSheet("Sheet1").build(); int pageIndex = 0; List<Data> dataList; do { dataList = getDataByPage(pageIndex++, batchSize); excelWriter.write(dataList, writeSheet); } while (dataList.size() == batchSize); excelWriter.finish(); } catch (IOException e) { e.printStackTrace(); }6. 选择合适的数据导出工具如果数据量非常大,可以考虑切换到支持更高性能的导出工具(如Apache POI的SXSSFWorkbook),适合导出百万级别数据量,但配置和使用会更复杂。亮点来了,那要如何使用 POI 的 SXSSFWorkbook来导出百万级别的数据量呢?Apache POI的SXSSFWorkbook 实现百万级别数据量的导出案例使用Apache POI的SXSSFWorkbook可以处理大数据量的Excel导出,因为SXSSFWorkbook基于流式写入,不会将所有数据加载到内存中,而是使用临时文件进行缓存,这样可以显著减少内存消耗,适合百万级别数据的导出。下面我们来看一个完整的实现示例。代码如下import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.streaming.SXSSFWorkbook; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class LargeDataExportExample { public static void main(String[] args) { // 文件输出路径 String filePath = "vg_large_data_export.xlsx"; // 导出百万级数据 exportLargeData(filePath); } private static void exportLargeData(String filePath) { // 每次写入的批次大小 final int batchSize = 10000; // 数据总条数 final int totalRows = 1_000_000; // 创建SXSSFWorkbook对象,内存中只保留100行,超过的部分会写入临时文件 SXSSFWorkbook workbook = new SXSSFWorkbook(100); workbook.setCompressTempFiles(true); // 启用临时文件压缩 // 创建工作表 Sheet sheet = workbook.createSheet("Large Data"); // 创建标题行 Row headerRow = sheet.createRow(0); String[] headers = {"ID", "Name", "Age"}; for (int i = 0; i < headers.length; i++) { Cell cell = headerRow.createCell(i); cell.setCellValue(headers[i]); } int rowNum = 1; // 数据开始的行号 try { // 按批次写入数据 for (int i = 0; i < totalRows / batchSize; i++) { // 模拟获取每批数据 List<Data> dataList = getDataBatch(rowNum, batchSize); // 将数据写入到Excel中 for (Data data : dataList) { Row row = sheet.createRow(rowNum++); row.createCell(0).setCellValue(data.getId()); row.createCell(1).setCellValue(data.getName()); row.createCell(2).setCellValue(data.getAge()); } // 处理完成一批数据后,可以选择清除缓存数据,防止内存溢出 ((SXSSFSheet) sheet).flushRows(batchSize); // 清除已写的行缓存 } // 将数据写入文件 try (FileOutputStream fos = new FileOutputStream(filePath)) { workbook.write(fos); } System.out.println("数据导出完成:" + filePath); } catch (IOException e) { e.printStackTrace(); } finally { // 关闭workbook并删除临时文件 workbook.dispose(); } } /** * 模拟分页获取数据 */ private static List<Data> getDataBatch(int startId, int batchSize) { List<Data> dataList = new ArrayList<>(batchSize); for (int i = 0; i < batchSize; i++) { dataList.add(new Data(startId + i, "Name" + (startId + i), 20 + (startId + i) % 50)); } return dataList; } // 数据类 static class Data { private final int id; private final String name; private final int age; public Data(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } public int getId() { return id; } public String getName() { return name; } public int getAge() { return age; } } }来解释一下代码SXSSFWorkbook:SXSSFWorkbook(100)表示内存中最多保留100行数据,超过的部分会写入临时文件,节省内存。批次处理:通过batchSize控制每批次写入的数据量,以减少内存消耗。totalRows设置为1,000,000表示导出100万条数据。模拟数据生成:getDataBatch方法模拟分页获取数据,每次返回一批数据。清除缓存行:每次写入一批数据后,通过flushRows(batchSize)将缓存的行从内存中清除,以控制内存占用。压缩临时文件:workbook.setCompressTempFiles(true)启用临时文件压缩,进一步减少磁盘空间占用。需要注意的事项临时文件:SXSSFWorkbook会在系统临时文件夹中生成临时文件,需要确保磁盘空间足够。资源释放:完成数据写入后需要调用workbook.dispose()以清理临时文件。性能优化:可根据机器内存调整batchSize和SXSSFWorkbook缓存行数,避免频繁刷新和内存溢出。转载自https://www.cnblogs.com/wgjava/p/18510650
  • [技术干货] mvc架构详解
    MVC架构 mvc是一种架构模型,本身并没有新功能,只是对项目的一种规范,方便后期进行维护;mvc架构将模型(M),视图(V)和控制(C)割离开,这么做可以提高代码书写的效率和后期维护;  MVC分层次介绍 模型(MODEL) 模型负责封装应用的状态,并实现应用的功能; 封装的是数据源和所有对这些数据的操作; 在组件中,模型表示组件的状态和操作状态的方法。  模型通常分为数据模型和业务逻辑模型:  数据模型 数据模型主要存放业务数据,如订单,用户信息等。 业务逻辑模型 业务逻辑模型往往是对数据的处理,如订单和用户信息的修改。 mvc的三个部件中模型拥有处理的处理能力,安装上文所说,M可以连接和处理数据库。(EJBs和ColdFusion Components构建对象处理数据库)被模型返回的数据是中立的(模型于数据格式无关),这么一来一个模型可以被多个视图调用。可以减少代码的重复性。  视图(VIEW) ** 视图是将模型的内容呈现出来,用户可以通过对视图的操作进而操作模型,封装的是对数据源Model的显示;** 一个模型可以被多个视图复用,一个视图理论上也可以和多个模型进行关联; 视图从模型中获取展示的数据,然后用自己的方法展示给用户。如用户点击H5页面的登录,视图会给模型提交用户的数据,视图接收模型的数据,然后按照自己的方式展示给用户。  在实际WEB项目中,视图通常是HTML,XHTML,XML/XSL,WML代码组成的界面。 在MVC模式中,视图只是起到展示的作用,真正处理数据其实并没有发生。 控制器(CONTROLLER) ** 控制器时用来出来视图和模型关系的组件。封装的是外界对模型的操作。** 这些操作一般为转发给模型,模型处理数据,并调用模型中相应的一个或多个方法; 一般控制器是在模型和视图之间作为沟通的组件存在的。 用户填写完账号密码由控制器接收处理并转发给模型。可以让视图和控制器之间的耦合关系更加密切;严格的分工可以提高工作的效率。  当控制器接收到用户的请求之后,会将用户的数据和模型的更新相映射,也就是调用模型来实现用户请求的功能;控制器会选择响应应用的视图,把模型更新后的数据发送给视图,视图按照自己的方式展示给用户;  当点击WEB的超链接和发送HTML表单时,控制器本身并不传输任何数据,也不做任何处理。只是接收请求并决定调用哪个模型进行构建去处理请求,然后决定用哪个视图来显示模型返回的数据。 MVC的组件关系 根据上文的描述,Model和View是严格分开的,甚至他们之间互相不知道彼此。只有Controller作为中介连接他们两个。  视图里不会有逻辑的东西。控制器里没有展示的东西。一个模型会有很多的展示方法,每一个展示方法页不止一种模型。  比如:windows下打开文件夹;  文件夹的大图标小图标就是不同的视图但内容数据是一样的(模型数据一样); 点击进入其他的文件夹内,视图一样但是数据不同。  即–模型和视图之间并非一一对应的。  而Controller作为模型和视图之间的桥梁,可以保证模型和视图之间相互制约;点击视图的操作控制器立即给出相应;  MVC交互关系:  登入程序后,展示视图用户在视图上进行操作,填写业务数据 用户点击提交来提交发出的请求 视图发出的请求传到控制器,其中包含想要完成什么样的业务功能及相关数据 控制器处理用户请求并封装,选择合适的模型,请求模型来处理用户请求的数据,接收模型的更新,并选择接下来用哪一个视图进行展示这些数据 模型会处理用户请求的业务功能,同时进行模型状态的维护和更新 当模型进行更新后模型会通知相应的视图进行更新 当视图接收到模型的通知后,会向模型进行状态查询,获取需要展示的数据,然后按照视图的规则进行展示 等待用户的下一次操作,重复上面的操作 JAVA EE的MVC架构 MVC是一种横向分层的模型。采用MVC模式的用户不可以直接访问JSP视图,即使不需要调用业务逻辑功能,也应该让用户的所有操作都经过控制器,由控制器转到视图。  JAVA EE中视图层一般JSP实现一个交互性高的异步通信Web应用。 模型层:一般以JAvaBean为主体,实现Java的业务主体 控制器:用Servlet实现,  JavaEE中通常把Servlet+JSP+Javabean的模型称为Model2模型,基本划分如下:  Javabean作为模型,既可以作为数据模型来封装业务数据,也可以作为业务逻辑模型来实现业务的操作;其中数据模型用来存储和传递业务数据。业务逻辑模型接收控制器传来的模型请求更新后,执行特定的业务逻辑 处理,返回相应的执行结果; JSP作为视图,负责提供页面给用户展示数据。提供相应的表单(Form)来相应用户的请求,并在适当的时候(比如用户点击提交按钮)向控制器发送用户请求来要求模型进行更新; Servlet作为控制器,用来接收用户提交的请求,并获取请求中的数据,将其转化为业务模型中需要的数据模型,然后调用业务模型中相应的方法,请求业务模型进行更新,同时根据业务执行的结果来选择需要返回的视图。发送数据给相应的视图JSP; ————————————————                  原文链接:https://blog.csdn.net/Guo_Yuhua/article/details/119977966 
  • [技术干货] 什么是MVC架构模式
    MVC架构模式,即模型-视图-控制器(Model-View-Controller)模式,是一种软件工程中的软件架构模式。它将应用程序分为三个主要部分:模型(Model)、视图(View)和控制器(Controller),以实现代码的分层组织和关注点的分离。  模型(Model) : 模型代表应用程序的数据和业务逻辑。它负责处理数据的获取、存储和更新等操作,并且可以包含一些逻辑处理,当数据发生变化时,通知视图进行更新。模型是相对稳定的部分,重用率高,通常不直接与用户界面交互。  视图(View) : 视图负责展示数据并呈现给用户。它从模型中提取数据并将其显示在用户界面上,但不处理任何业务逻辑或数据访问操作。视图的变化不会影响模型的内容,因为模型独立于视图进行数据管理。  控制器(Controller) : 控制器作为模型和视图之间的桥梁,接收用户的输入并调用相应的模型和视图来响应这些输入。它处理所有的用户交互,如点击按钮、填写表单等,并根据需要更新模型和视图。控制器本身不输出任何结果或进行处理,仅作为中介角色。  通过这种分层设计,MVC架构模式提高了应用程序的可维护性、可扩展性和可测试性。各组件之间的职责明确,使得团队成员可以独立开发和维护不同的部分而不互相干扰。此外,由于模型和视图的分离,可以在不影响其他部分的情况下改进界面和用户交互。  总之,MVC架构模式是一种经典且灵活的设计范例,广泛应用于各种应用程序的开发中,包括Web应用、桌面应用和其他类型的应用程序。  MVC架构模式的历史发展和演变是什么? MVC(Model-View-Controller)架构模式的历史发展和演变可以追溯到20世纪70年代末期。最初,这种架构模式是由Trygve Reenskaug在Xerox PARC的Smalltalk-80项目中提出的。Smalltalk-80是当时用于开发图形用户界面应用程序的一个重要平台,而MVC模式正是为了组织这些应用程序而设计的。  在Smalltalk-80环境中,MVC架构通过将应用程序分为三个主要部分来实现:模型(Model)、视图(View)和控制器(Controller)。模型负责数据处理和业务逻辑,视图负责展示数据并允许用户交互,控制器则作为模型和视图之间的中介,处理用户的输入和请求。  随着时间的推移,MVC架构逐渐被广泛应用于不同的技术平台和编程语言中。例如,Sun公司的Java平台也采用了类似的架构模式,并且后来发展出了许多变体架构,如MVVM(Model-View-ViewModel)等。这些变体架构进一步扩展了MVC的基本概念,以适应更复杂的应用场景和需求。  此外,MVC架构也在Web应用领域得到了广泛应用。经典的MVC框架如Ruby on Rails,极大地推动了MVC模式的普及和发展。其他著名的MVC框架还包括ASP.NET MVC等,它们证明了MVC架构在提高代码可维护性和可测试性方面的优势。  尽管MVC架构在软件开发中取得了巨大成功,但其应用过程中也存在一些问题。例如,由于不同变体之间的混淆,MVC常常被误用。因此,在实际应用中需要对MVC的不同变体有清晰的理解和区分。  总结来说,MVC架构自其诞生以来已经经历了显著的发展和演变。  如何在实际项目中实现MVC架构模式以提高软件开发的效率和质量? 在实际项目中实现MVC架构模式以提高软件开发的效率和质量,需要遵循以下步骤和原则:  明确角色分工:  模型(Model) :负责数据处理和业务逻辑。它与视图(View)完全独立,确保数据逻辑不被界面展示所干扰。 视图(View) :负责数据显示和用户交互。它从模型获取数据并展示给用户,但不直接操作数据。 控制器(Controller) :作为模型和视图之间的桥梁,接收用户的输入并调用相应的模型或视图进行响应。 分离代码结构:  将应用程序分为三个主要部分:模型、视图和控制器。这种分离有助于降低代码耦合度,提高可维护性和可扩展性。 模型层处理业务逻辑和数据访问,视图层负责界面展示,控制器层处理用户输入和逻辑控制。 并行开发:  由于MVC将不同部分分离,前端开发人员可以专注于视图的实现,而后端开发人员可以独立开发模型逻辑。这样可以实现并行开发,显著提高开发效率。 测试和部署:  各个部分独立开发后,可以分别进行单元测试和集成测试,确保每个部分的功能正确无误。这不仅提高了代码质量,还减少了整体系统的调试时间。 团队协作:  MVC架构促进了前后端开发人员的协作。前端开发者不需要了解后端逻辑,只需通过控制器与后端通信;后端开发者也不需要关注前端界面的具体实现。 选择合适的技术栈:  根据项目需求选择合适的技术栈。例如,在Web开发中,可以选择JSP、Servlet和POJO等技术来实现MVC设计模式。 持续优化和迭代:  在项目实施过程中,不断收集反馈并优化各部分的设计和实现。通过持续迭代,逐步完善系统功能和性能。 MVC架构模式与其他软件架构模式(如MVP、MVVM)的比较有哪些优缺点? MVC(Model-View-Controller)架构模式是一种经典的软件架构模式,它将应用程序分为三个主要组件:模型(Model)、视图(View)和控制器(Controller)。这种模式在许多方面具有其独特的优缺点,与其他架构模式如MVP(Model-呈现阶段)和MVVM(Model-View-ViewModel)相比,各有不同的适用场景。  MVC的优点: 清晰的分层:MVC通过将业务逻辑、用户界面和控制逻辑分离到不同的组件中,有助于简化应用程序的测试和维护。 模块化程度高:当业务逻辑变更时,不需要修改View和Model,只需更换Controller即可,这使得系统的可扩展性和可维护性较高。 观察者模式支持多视图更新:MVC可以利用观察者模式实现多个视图的同时更新,从而提高系统的响应速度和用户体验。 MVC的缺点: 耦合性较高:在MVC中,View可以直接访问Model,导致View与Model之间存在高度耦合,这会降低系统的可读性、健壮性和可拓展性。 控制器职责过重:由于Controller需要处理所有与用户交互相关的逻辑,因此其职责较为复杂且容易变得过于庞大,影响开发效率和代码质量。 系统复杂度增加:实现MVC架构模式需要更多的代码和更复杂的设计,前期投入大,增加了项目的时间和成本。 MVP与MVC的比较: 耦合性:MVP通过引入Presenter来降低视图和模型之间的耦合,使得View不能直接访问Model,而是通过Presenter发出请求。这使得MVP在耦合性上优于MVC。 可维护性和测试性:由于呈现阶段的存在,MVP更容易进行单元测试和维护,因为呈现阶段隔离了视图和模型之间的交互。然而,呈现阶段可能会变得复杂,增加开发工作量。 MVVM与MVC的比较: 数据绑定:MVVM引入了数据绑定的概念,这使得View和ViewModel之间的关系更加紧密,减少了手动同步数据的需求。然而,数据绑定也可能导致性能问题,在执行复杂任务时可能会影响性能。 入口点:在MVVM中,View是应用程序的入口点,而在MVC中,Controller是入口点。这种设计上的差异决定了不同架构模式在实际应用中的适用性。 单元测试:MVVM由于其事件驱动的特性,更容易进行单元测试,而MVC的Controller与View的关系密切,可能会影响单元测试的独立性。 选择哪种架构模式取决于项目的具体需求、UI复杂度以及对测试和灵活性的要求。  在使用MVC架构模式时,如何处理数据更新和视图重定向的问题? 在使用MVC架构模式时,处理数据更新和视图重定向的问题可以参考以下方法: 数据更新: 在MVC架构中,数据更新通常涉及以下几个步骤: 控制器(Controller) :接收用户的输入并调用模型(Model)中的方法来更新数据。例如,如果用户点击了一个按钮来更新数据库中的记录,控制器会调用相应的模型方法来执行这个操作。 模型(Model) :负责实际的数据更新逻辑。这可能包括与数据库交互的代码,如存储过程或直接的SQL查询。例如,可以编写一个实体方法来直接与数据库进行更新动作。 视图重定向: 视图重定向是将用户从当前处理请求定向到另一个视图或处理请求。Spring MVC提供了两种主要的重定向方式:转发和重定向。 重定向:使用RedirectView可以实现重定向。创建一个RedirectView实例后,它会调用HttpServletResponse.sendRedirect ()方法,从而将用户重定向到指定的URL。这种方式会使之前的请求(request)失效,并进入一个新的request作用域。  转发:使用ModelAndView可以实现转发。当需要将用户从当前视图转发到另一个视图时,可以使用ModelAndView对象,并将其视为重定向的一种形式。默认情况下,ModelAndView使用的是转发方式。 ————————————————                 原文链接:https://blog.csdn.net/m0_61505785/article/details/140901532 
  • [技术干货] MVC架构总结
    MVC架构职责分析 MVC 设计模式一般指 MVC 框架,M(Model)指数据模型层,V(View)指视图层,C(Controller)指控制层。使用 MVC 的目的是将 M 和 V 的实现代码分离,使同一个程序可以有不同的表现形式。其中,View 的定义比较清晰,就是用户界面。 在 Web 项目的开发中,能够及时、正确地响应用户的请求是非常重要的。用户在网页上单击一个 URL 路径,这对 Web 服务器来说,相当于用户发送了一个请求。而获取请求后如何解析用户的输入,并执行相关处理逻辑,最终跳转至正确的页面显示反馈结果,这些工作往往是控制层(Controller)来完成的。 在请求的过程中,用户的信息被封装在 User 实体类中,该实体类在 Web 项目中属于数据模型层(Model)。 在请求显示阶段,跳转的结果网页就属于视图层(View)。 像这样,控制层负责前台与后台的交互,数据模型层封装用户的输入/输出数据,视图层选择恰当的视图来显示最终的执行结果,这样的层次分明的软件开发和处理流程被称为 MVC 模式。 总结如下: 视图层(View):负责格式化数据并把它们呈现给用户,包括数据展示、用户交互、数据验证、界面设计等功能。 控制层(Controller):负责接收并转发请求,对请求进行处理后,指定视图并将响应结果发送给客户端。 数据模型层(Model):模型对象拥有最多的处理任务,是应用程序的主体部分,它负责数据逻辑(业务规则)的处理和实现数据操作(即在数据库中存取数据)。 业务流程见下图: Servlet+JSP+JavaBean Servlet+JSP+JavaBean 中 Servlet 用于处理用户请求,JSP 用于数据显示,JavaBean 用于数据封装,适合复杂的 Web 程序。 VO:视图层对象 例子:比如User类中有id,name,age,password等20个字段。而前端登录时所需数据只要name和password,这时可封装一个UserVo对象只包含name和password两个属性。 参考: https://baike.baidu.com/item/MVC%E6%A1%86%E6%9E%B6/9241230?fromtitle=mvc&fromid=85990&fr=aladdin http://c.biancheng.net/spring_mvc/mvc.html https://www.bilibili.com/video/BV1aE41167Tu?p=2 ————————————————             原文链接:https://blog.csdn.net/qq_38256519/article/details/121895614 
  • [技术干货] SpringMVC框架
    一、SpringMVC 简介 1. 什么是 MVC MVC 是一种软件架构的思想,将软件按照模型、视图、控制器来划分 M:Model,模型层,指工程中的 JavaBean,作用是处理数据。JavaBean 分为两类: 一类称为实体类 Bean:专门存储业务数据的,如 Student、User 等 一类称为业务处理 Bean:指 Service 或 Dao 对象,专门用于处理业务逻辑和数据访问。 V:View,视图层,指工程中的 html 或 jsp 等页面,作用是与用户进行交互,展示数据 C:Controller,控制层,指工程中的 servlet,作用是接收请求和响应浏览器 MVC 的工作流程:用户通过视图层发送请求到服务器,在服务器中请求被 Controller 接收,Controller 调用相应的 Model 层处理请求,处理完毕将结果返回到 Controller,Controller 再根据请求处理的结果找到相应的 View 视图,渲染数据后最终响应给浏览器 2. 什么是 SpringMVC SpringMVC 是 Spring 的一个后续产品,是 Spring 的一个子项目 SpringMVC 是 Spring 为表述层开发提供的一整套完备的解决方案。在表述层框架历经 Strust、WebWork、Strust2 等诸多产品的历代更迭之后,目前业界普遍选择了 SpringMVC 作为 Java EE 项目表述层开发的首选方案。 注:三层架构分为表述层(或表示层)、业务逻辑层、数据访问层,表述层表示前台页面和后台 servlet  3. SpringMVC 的特点 Spring 家族原生产品,与 IOC 容器等基础设施无缝对接 基于原生的 Servlet,通过了功能强大的前端控制器 DispatcherServlet,对请求和响应进行统一处理 表述层各细分领域需要解决的问题全方位覆盖,提供全面解决方案 代码清新简洁,大幅度提升开发效率 内部组件化程度高,可插拔式组件即插即用,想要什么功能配置相应组件即可 性能卓著,尤其适合现代大型、超大型互联网项目要求 二、SpringMVC 入门案例 1. 创建 Maven 项目 创建 Maven web 项目 打包方式是 war 完善目录结构 目录结构如下:  2. 添加依赖 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0        http://maven.apache.org/xsd/maven-4.0.0.xsd">     <modelVersion>4.0.0</modelVersion>       <groupId>com.xszx</groupId>     <artifactId>springmvc_20240620</artifactId>     <version>1.0-SNAPSHOT</version>     <packaging>war</packaging>       <dependencies>         <!-- SpringMVC -->         <dependency>             <groupId>org.springframework</groupId>             <artifactId>spring-webmvc</artifactId>             <version>5.3.1</version>         </dependency>           <!-- 日志 -->         <dependency>             <groupId>ch.qos.logback</groupId>             <artifactId>logback-classic</artifactId>             <version>1.2.3</version>         </dependency>           <!-- ServletAPI -->         <dependency>             <groupId>javax.servlet</groupId>             <artifactId>javax.servlet-api</artifactId>             <version>3.1.0</version>             <scope>provided</scope>         </dependency>     </dependencies>       <build>         <plugins>             <plugin>                 <groupId>org.apache.tomcat.maven</groupId>                 <artifactId>tomcat7-maven-plugin</artifactId>                 <version>2.2</version>                 <configuration>                     <port>8080</port>                     <path>/</path>                     <uriEncoding>UTF-8</uriEncoding>                     <server>tomcat7</server>                 </configuration>             </plugin>         </plugins>     </build> </project>  注:由于 Maven 的传递性,我们不必将所有需要的包全部配置依赖,而是配置最顶端的依赖,其他靠传递性导入。   3. 配置 web.xml 注册 SpringMVC 的前端控制器 DispatcherServlet。  <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"          xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee          http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"          version="4.0">       <!-- 配置SpringMVC的前端控制器,对浏览器发起的请求进行统一处理 -->     <servlet>         <servlet-name>dispatcherServlet</servlet-name>         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>         <!-- 通过初始化参数指定SpringMVC配置文件的路径和名称 -->         <init-param>             <!-- 参数名contextConfigLocation是固定值 -->             <param-name>contextConfigLocation</param-name>             <param-value>classpath:springMVC.xml</param-value>         </init-param>         <!-- 表示服务器一启动就会加载该Servlet -->         <load-on-startup>1</load-on-startup>     </servlet>          <servlet-mapping>         <servlet-name>dispatcherServlet</servlet-name>         <!--             / 所匹配的请求可以是/login或.html或.js或.css方式的请求路径             但是/不能匹配.jsp请求路径的请求         -->         <url-pattern>/</url-pattern>     </servlet-mapping> </web-app>  注:  <url-pattern> 标签中使用 / 和 /* 的区别:  / 所匹配的请求可以是 /login 或 .html 或 .js 或 .css 方式的请求路径,但是 / 不能匹配 .jsp 请求路径的请求  因此就可以避免在访问 jsp 页面时,该请求被 DispatcherServlet 处理,从而找不到相应的页面  /* 则能够匹配所有请求,例如在使用过滤器时,若需要对所有请求进行过滤,就需要使用 \* 的写法  4. 编写 SpringMVC 的配置文件 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"        xmlns:context="http://www.springframework.org/schema/context"        xmlns:mvc="http://www.springframework.org/schema/mvc"        xsi:schemaLocation="http://www.springframework.org/schema/beans    http://www.springframework.org/schema/beans/spring-beans.xsd    http://www.springframework.org/schema/context    https://www.springframework.org/schema/context/spring-context.xsd    http://www.springframework.org/schema/mvc    https://www.springframework.org/schema/mvc/spring-mvc.xsd">       <!-- 配置扫描 -->     <context:component-scan base-package="com.xszx.controller"></context:component-scan>       <!-- 配置注解驱动 -->     <mvc:annotation-driven></mvc:annotation-driven> </beans>  5. 编写 controller 层代码 package com.xszx.controller;   import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping;   @Controller // 将该类交给SpringMVC容器管理 @RequestMapping("/hello") // 请求映射路径 public class HelloController {       @RequestMapping("/say") // 请求映射路径     public String say(){         System.out.println("say...");         return "/abc.jsp"; // 转发到根目录底下的abc.jsp页面     } } 6. 编写 abc.jsp 页面 <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head>     <title>Title</title> </head> <body>   <h1>我是abc.jsp页面中的内容!</h1> </body> </html> 原文链接:https://blog.csdn.net/bear_309/article/details/141996730
  • [技术干货] spring与springMVC的区别
    Spring是IOC和AOP的容器框架,SpringMVC是基于Spring功能之上添加的Web框架,想用SpringMVC必须先依赖Spring。简单点的话可以将SpringMVC类比于Struts。Spring是IOC和AOP的容器框架,SpringMVC是基于Spring功能之上添加的Web框架,想用SpringMVC必须先依赖Spring。Spring可以说是一个管理bean的容器,也可以说是包括很多开源项目的总称,spring mvc是其中一个开源项目,所以简单走个流程的话,http请求一到,由容器(如:tomact)解析http搞成一个request,通过映射关系(路径,方法,参数啊)被spring mvc一个分发器去找到可以处理这个请求的bean,那tomcat里面就由spring管理bean的一个池子(bean容器)里面找到,处理完了就把响应返回回去。SpringMVC是一个MVC模式的WEB开发框架;Spring是一个通用解决方案, 最大的用处就是通过Ioc/AOP解耦, 降低软件复杂性, 所以Spring可以结合SpringMVC等很多其他解决方案一起使用, 不仅仅只适用于WEB开发SSH:SSH 为 struts+spring+hibernate 的一个集成框架,是目前较流行的一种JAVA Web应用程序开源框架。 Struts  Struts是一个基于Sun J2EE平台的MVC框架,主要是采用Servlet和JSP技术来实现的。由于Struts能充分满足应用开发的需求,简单易用,敏捷迅速,在过去的一年中颇受关注。Struts把Servlet、JSP、自定义标签和信息资源(message resources)整合到一个统一的框架中,开发人员利用其进行开发时不用再自己编码实现全套MVC模式,极大的节省了时间,所以说Struts是一个非常不错的应用框架。 Spring  Spring是一个解决了许多在J2EE开发中常见的问题的强大框架。 Spring提供了管理业务对象的一致方法并且鼓励了注入对接口编程而不是对类编程的良好习惯。Spring的架构基础是基于使用JavaBean属性的Inversion of Control容器。然而,这仅仅是完整图景中的一部分:Spring在使用IOC容器作为构建完关注所有架构层的完整解决方案方面是独一无二的。 Spring提供了唯一的数据访问抽象,包括简单和有效率的JDBC框架,极大的改进了效率并且减少了可能的错误。Spring的数据访问架构还集成了Hibernate和其他O/R mapping解决方案。Spring还提供了唯一的事务管理抽象,它能够在各种底层事务管理技术,例如JTA或者JDBC事务提供一个一致的编程模型。Spring提供了一个用标准Java语言编写的AOP框架,它给POJOs提供了声明式的事务管理和其他企业事务–如果你需要–还能实现你自己的aspects。这个框架足够强大,使得应用程序能够抛开EJB的复杂性,同时享受着和传统EJB相关的关键服务。Spring还提供了可以和IoC容器集成的强大而灵活的MVC Web框架。 Hibernate  Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。 Hibernate可以应用在任何使用JDBC的场合,既可以在Java的客户端程序实用,也可以在Servlet/JSP的Web应用中使用,最具革命意义的是,Hibernate可以在应用EJB的J2EE架构中取代CMP,完成数据持久化的重任。原文链接:https://devpress.csdn.net/cloudnative/66d30095a1ed2f4c8540020b.html
  • [技术干货] SpringMVC 与 MVC的区别
    SpringMVC作为MVC框架,与Struts 2 有着异曲同工之处,首先对比一下SpringMVC与Struts2的不同之处:     1. 机制:spring mvc的入口是servlet,而struts2是filter(这里要指出,filter和servlet是不同的。以前认为filter是servlet的一种特殊),这样就导致了二者的机制不同,这里就牵涉到servlet和filter的区别了。     2. 性能:spring会稍微比struts快。spring mvc是基于方法的设计,而sturts是基于类,每次发一次请求都会实例一个action,每个action都会被注入属性,而spring基于方法,粒度更细,但要小心把握像在servlet控制数据一样。spring3 mvc是方法级别的拦截,拦截到方法后根据参数上的注解,把request数据注入进去,在spring3 mvc中,一个方法对应一个request上下文。而 struts2框架是类级别的拦截,每次来了请求就创建一个Action,然后调用setter getter方法把request中的数据注入;struts2实际上是通过setter getter方法与request打交道的;struts2中,一个Action对象对应一个request上下文。     3. 参数传递:struts是在接受参数的时候,可以用属性来接受参数,这就说明参数是让多个方法共享的。     4. 设计思想上:struts更加符合oop的编程思想, spring就比较谨慎,在servlet上扩展。     5. intercepter的实现机制:struts有以自己的interceptor机制,spring mvc用的是独立的AOP方式。这样导致struts的配置文件量还是比spring mvc大,虽然struts的配置能继承,所以我觉得论使用上来讲,spring mvc使用更加简洁,开发效率Spring MVC确实比struts2高。spring mvc是方法级别的拦截,一个方法对应一个request上下文,而方法同时又跟一个url对应,所以说从架构本身上spring3 mvc就容易实现restful url。struts2是类级别的拦截,一个类对应一个request上下文;实现restful url要费劲,因为struts2 action的一个方法可以对应一个url;而其类属性却被所有方法共享,这也就无法用注解或其他方式标识其所属方法了。spring3 mvc的方法之间基本上独立的,独享request response数据,请求数据通过参数获取,处理结果通过ModelMap交回给框架方法之间不共享变量,而struts2搞的就比较乱,虽然方法之间也是独立的,但其所有Action变量是共享的,这不会影响程序运行,却给我们编码,读程序时带来麻烦。     6. 另外,spring3 mvc的验证也是一个亮点,支持JSR303,处理ajax的请求更是方便,只需一个注解@ResponseBody ————————————————                     原文链接:https://blog.csdn.net/SpringFileld/article/details/38414929 
  • [技术干货] MVC与SpringMVC
    MVC MVC:MVC是一种设计模式 MVC的原理图:  分析: M-Model 模型(完成业务逻辑:有javaBean构成,service+dao+entity) V-View 视图(做界面的展示  jsp,html……) C-Controller 控制器(接收请求—>调用模型—>根据结果派发页面) 2、SpringMVC     SpringMVC是一个MVC的开源框架     SpringMVC=Struts2+Spring   , SpringMVC就相当于是Struts2加上Sring的整合      SpringMVC是Spring的一个后续产品,其实就是Spring在原有基础上,又提供了web应用的MVC模块,可以简单的把SpringMVC理解为是Spring的一个模块  (类似AOP,IOC这样的模块),网络上经常会说SpringMVC和Spring无缝集成,其实SpringMVC就是Spring的一个子模块,所以根本不需要同Spring进行整合。 SpringMVC的原理图: 看到这个图大家可能会有很多的疑惑,现在我们来看一下这个图的步骤:(可以对比MVC的原理图进行理解)  第一步:  用户发起请求到前端控制器(DispatcherServlet) 第二步:前端控制器请求处理器映射器(HandlerMappering)去查找处理器(Handle):通过xml配置或者注解进行查找 第三步:找到以后处理器映射器(HandlerMappering)像前端控制器返回执行链(HandlerExecutionChain) 第四步:前端控制器(DispatcherServlet)调用处理器适配器(HandlerAdapter)去执行处理器(Handler) 第五步:处理器适配器去执行Handler 第六步:Handler执行完给处理器适配器返回ModelAndView 第七步:处理器适配器向前端控制器返回ModelAndView 第八步:前端控制器请求视图解析器(ViewResolver)去进行视图解析 第九步:视图解析器像前端控制器返回View 第十步:前端控制器对视图进行渲染 第十一步:前端控制器向用户响应结果  看到这些步骤我相信大家很感觉非常的乱,这是正常的,但是这里主要是要大家理解springMVC中的几个组件:  前端控制器(DispatcherServlet):接收请求,响应结果,相当于电脑的CPU。 处理器映射器(HandlerMapping):根据URL去查找处理器 处理器(Handler):(需要程序员去写代码处理逻辑的) 处理器适配器(HandlerAdapter):会把处理器包装成适配器,这样就可以支持多种类型的处理器,类比笔记本的适配器(适配器模式的应用) 视图解析器(ViewResovler):进行视图解析,多返回的字符串,进行处理,可以解析成对应的页面  原文链接:https://blog.csdn.net/rzrenyu/article/details/78513595 
总条数:764 到第
上滑加载中