开发者日记 - Java
type: channel
link: https://blog.web3er.cn/tag/java/
items: 设计模式6
items: 设计模式5
items: 设计模式4
items: 设计模式3
items: 设计模式2
items: 设计模式1
items: RabbitMQ开发需要考虑到的问题
items: SpringBoot RabbitMQ开发流程
items: RabbitMQ使用
items: JAVA中创建多模块项目

设计模式6
type: item
date: 2024-01-27T23:01:00+08:00
description: 6，行为型模式6.11 解释器模式6.11.1 概述如上图，设计一个软件用来进行加减计算。我们第一想法就是使用工具类，提供对应的加法和减法的工具方法。//用于两个整数相加 public static int add(int a,int b){ return a + b; } //用于两个整数相加 public static int add(int a,int b,int c){ return a + b + c; } //用于n个整数相加 public static int add(Integer ... arr) { int sum = 0; for (Integer i : arr) { sum += i; } return sum; }上面的形式比较单一、有限，如果形式变化非常多，这就不符合要求，因为加法和减法运算，两个运算符与数值可以有无限种组合方式。比如 1+2+3+4+5、1+2+3-4等等。 显然，现在需要一种翻译识别机器，能够解析由数字以及 + - 符号构成的合法的运算序列。如果把运算符和数字都看作节点的话，能够逐个节点的进行读取解析运算，这就是解释器模式的思维。定义：给定一个语言，定义它的文法表示，并定义一个解释器，这个解释器使用该标识来解释语言中的句子。在解释器模式中，我们需要将待解决的问题，提取出规则，抽象为一种“语言”。比如加减法运算，规则为：由数值和+-符号组成的合法序列，“1+3-2” 就是这种语言的句子。解释器就是要解析出来语句的含义。但是如何描述规则呢？文法（语法）规则：文法是用于描述语言的语法结构的形式规则。expression ::= value | plus | minus plus ::= expression ‘+’ expression minus ::= expression ‘-’ expression value ::= integer注意： 这里的符号“::=”表示“定义为”的意思，竖线 | 表示或，左右的其中一个，引号内为字符本身，引号外为语法。上面规则描述为 ：表达式可以是一个值，也可以是plus或者minus运算，而plus和minus又是由表达式结合运算符构成，值的类型为整型数。抽象语法树：在计算机科学中，抽象语法树（AbstractSyntaxTree，AST），或简称语法树（Syntax tree），是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构，树上的每个节点都表示源代码中的一种结构。用树形来表示符合文法规则的句子。6.11.2 结构解释器模式包含以下主要角色。抽象表达式（Abstract Expression）角色：定义解释器的接口，约定解释器的解释操作，主要包含解释方法 interpret()。终结符表达式（Terminal Expression）角色：是抽象表达式的子类，用来实现文法中与终结符相关的操作，文法中的每一个终结符都有一个具体终结表达式与之相对应。非终结符表达式（Nonterminal Expression）角色：也是抽象表达式的子类，用来实现文法中与非终结符相关的操作，文法中的每条规则都对应于一个非终结符表达式。环境（Context）角色：通常包含各个解释器需要的数据或是公共的功能，一般用来传递被所有解释器共享的数据，后面的解释器可以从这里获取这些值。客户端（Client）：主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树，然后调用解释器的解释方法，当然也可以通过环境角色间接访问解释器的解释方法。6.11.3 案例实现【例】设计实现加减法的软件代码如下：//抽象角色AbstractExpression public abstract class AbstractExpression { public abstract int interpret(Context context); } //终结符表达式角色 public class Value extends AbstractExpression { private int value; public Value(int value) { this.value = value; } @Override public int interpret(Context context) { return value; } @Override public String toString() { return new Integer(value).toString(); } } //非终结符表达式角色 加法表达式 public class Plus extends AbstractExpression { private AbstractExpression le…

设计模式5
type: item
date: 2024-01-27T22:59:06+08:00
description: 6，行为型模式6.5 状态模式6.5.1 概述【例】通过按钮来控制一个电梯的状态，一个电梯有开门状态，关门状态，停止状态，运行状态。每一种状态改变，都有可能要根据其他状态来更新处理。例如，如果电梯门现在处于运行时状态，就不能进行开门操作，而如果电梯门是停止状态，就可以执行开门操作。类图如下：代码如下：public interface ILift { //电梯的4个状态 //开门状态 public final static int OPENING_STATE = 1; //关门状态 public final static int CLOSING_STATE = 2; //运行状态 public final static int RUNNING_STATE = 3; //停止状态 public final static int STOPPING_STATE = 4; //设置电梯的状态 public void setState(int state); //电梯的动作 public void open(); public void close(); public void run(); public void stop(); } public class Lift implements ILift { private int state; @Override public void setState(int state) { this.state = state; } //执行关门动作 @Override public void close() { switch (this.state) { case OPENING_STATE: System.out.println("电梯关门了。。。");//只有开门状态可以关闭电梯门，可以对应电梯状态表来看 this.setState(CLOSING_STATE);//关门之后电梯就是关闭状态了 break; case CLOSING_STATE: //do nothing //已经是关门状态，不能关门 break; case RUNNING_STATE: //do nothing //运行时电梯门是关着的，不能关门 break; case STOPPING_STATE: //do nothing //停止时电梯也是关着的，不能关门 break; } } //执行开门动作 @Override public void open() { switch (this.state) { case OPENING_STATE://门已经开了，不能再开门了 //do nothing break; case CLOSING_STATE://关门状态，门打开: System.out.println("电梯门打开了。。。"); this.setState(OPENING_STATE); break; case RUNNING_STATE: //do nothing 运行时电梯不能开门 break; case STOPPING_STATE: System.out.println("电梯门开了。。。");//电梯停了，可以开门了 this.setState(OPENING_STATE); break; } } //执行运行动作 @Override public void run() { switch (this.state) { case OPENING_STATE://电梯不能开着门就走 //do nothing break; case CLOSING_STATE://门关了，可以运行了 System.out.println("电梯开始运行了。。。"); this.setState(RUNNING_STATE);//现在是运行状态 break; case RUNNING_STATE: //do nothing 已经是运行状态了 break; case STOPPING_STATE: System.out.println("电梯开始运行了。。。"); this.setState(RUNNING_STATE); break; } } //执行停止动作 @Override public void stop() { switch (this.state) { case OPENING_STATE: //开门的电梯已经是是停止的了(正常情况下) //do nothing break; case CLOSING_STATE://关门时才可以停止 System.out.println("电梯停止了。。。"); this.setState(STOPPING_STATE); break; case RUNNING_STATE://运…

设计模式4
type: item
date: 2024-01-27T22:47:13+08:00
description: 5，结构型模式5.6 组合模式5.6.1 概述​ 对于这个图片肯定会非常熟悉，上图我们可以看做是一个文件系统，对于这样的结构我们称之为树形结构。在树形结构中可以通过调用某个方法来遍历整个树，当我们找到某个叶子节点后，就可以对叶子节点进行相关的操作。可以将这颗树理解成一个大的容器，容器里面包含很多的成员对象，这些成员对象即可是容器对象也可以是叶子对象。但是由于容器对象和叶子对象在功能上面的区别，使得我们在使用的过程中必须要区分容器对象和叶子对象，但是这样就会给客户带来不必要的麻烦，作为客户而已，它始终希望能够一致的对待容器对象和叶子对象。定义：​ 又名部分整体模式，是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象，用来表示部分以及整体层次。这种类型的设计模式属于结构型模式，它创建了对象组的树形结构。5.6.2 结构组合模式主要包含三种角色：抽象根节点（Component）：定义系统各层次对象的共有方法和属性，可以预先定义一些默认行为和属性。树枝节点（Composite）：定义树枝节点的行为，存储子节点，组合树枝节点和叶子节点形成一个树形结构。叶子节点（Leaf）：叶子节点对象，其下再无分支，是系统层次遍历的最小单位。5.6.3 案例实现【例】软件菜单如下图，我们在访问别的一些管理系统时，经常可以看到类似的菜单。一个菜单可以包含菜单项（菜单项是指不再包含其他内容的菜单条目），也可以包含带有其他菜单项的菜单，因此使用组合模式描述菜单就很恰当，我们的需求是针对一个菜单，打印出其包含的所有菜单以及菜单项的名称。要实现该案例，我们先画出类图：代码实现：不管是菜单还是菜单项，都应该继承自统一的接口，这里姑且将这个统一的接口称为菜单组件。//菜单组件 不管是菜单还是菜单项，都应该继承该类 public abstract class MenuComponent { protected String name; protected int level; //添加菜单 public void add(MenuComponent menuComponent){ throw new UnsupportedOperationException(); } //移除菜单 public void remove(MenuComponent menuComponent){ throw new UnsupportedOperationException(); } //获取指定的子菜单 public MenuComponent getChild(int i){ throw new UnsupportedOperationException(); } //获取菜单名称 public String getName(){ return name; } public void print(){ throw new UnsupportedOperationException(); } }这里的MenuComponent定义为抽象类，因为有一些共有的属性和行为要在该类中实现，Menu和MenuItem类就可以只覆盖自己感兴趣的方法，而不用搭理不需要或者不感兴趣的方法，举例来说，Menu类可以包含子菜单，因此需要覆盖add()、remove()、getChild()方法，但是MenuItem就不应该有这些方法。这里给出的默认实现是抛出异常，你也可以根据自己的需要改写默认实现。public class Menu extends MenuComponent { private List<MenuComponent> menuComponentList; public Menu(String name,int level){ this.level = level; this.name = name; menuComponentList = new ArrayList<MenuComponent>(); } @Override public void add(MenuComponent menuComponent) { menuComponentList.add(menuComponent); } @Override public void remove(MenuComponent menuComponent) { menuComponentList.remove(menuComponent); } @Override public MenuComponent getChild(int i) { return menuComponentList.get(i); } @Override public void print() { for (int i = 1; i <…

设计模式3
type: item
date: 2024-01-27T21:59:00+08:00
description: 5，结构型模式结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式，前者采用继承机制来组织接口和类，后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低，满足“合成复用原则”，所以对象结构型模式比类结构型模式具有更大的灵活性。结构型模式分为以下 7 种：代理模式适配器模式装饰者模式桥接模式外观模式组合模式享元模式5.1 代理模式5.1.1 概述由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时，访问对象不适合或者不能直接引用目标对象，代理对象作为访问对象和目标对象之间的中介。Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成，而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。5.1.2 结构代理（Proxy）模式分为三种角色：抽象主题（Subject）类： 通过接口或抽象类声明真实主题和代理对象实现的业务方法。真实主题（Real Subject）类： 实现了抽象主题中的具体业务，是代理对象所代表的真实对象，是最终要引用的对象。代理（Proxy）类 ： 提供了与真实主题相同的接口，其内部含有对真实主题的引用，它可以访问、控制或扩展真实主题的功能。5.1.3 静态代理我们通过案例来感受一下静态代理。【例】火车站卖票如果要买火车票的话，需要去火车站买票，坐车到火车站，排队等一系列的操作，显然比较麻烦。而火车站在多个地方都有代售点，我们去代售点买票就方便很多了。这个例子其实就是典型的代理模式，火车站是目标对象，代售点是代理对象。类图如下：代码如下：//卖票接口 public interface SellTickets { void sell(); } //火车站 火车站具有卖票功能，所以需要实现SellTickets接口 public class TrainStation implements SellTickets { public void sell() { System.out.println("火车站卖票"); } } //代售点 public class ProxyPoint implements SellTickets { private TrainStation station = new TrainStation(); public void sell() { System.out.println("代理点收取一些服务费用"); station.sell(); } } //测试类 public class Client { public static void main(String[] args) { ProxyPoint pp = new ProxyPoint(); pp.sell(); } }从上面代码中可以看出测试类直接访问的是ProxyPoint类对象，也就是说ProxyPoint作为访问对象和目标对象的中介。同时也对sell方法进行了增强（代理点收取一些服务费用）。5.1.4 JDK动态代理接下来我们使用动态代理实现上面案例，先说说JDK提供的动态代理。Java中提供了一个动态代理类Proxy，Proxy并不是我们上述所说的代理对象的类，而是提供了一个创建代理对象的静态方法（newProxyInstance方法）来获取代理对象。代码如下：//卖票接口 public interface SellTickets { void sell(); } //火车站 火车站具有卖票功能，所以需要实现SellTickets接口 public class TrainStation implements SellTickets { public void sell() { System.out.println("火车站卖票"); } } //代理工厂，用来创建代理对象 public class ProxyFactory { private TrainStation station = new TrainStation(); public SellTickets getProxyObject() { //使用Proxy获取代理对象 /* newProxyInstance()方法参数说明： ClassLoader loader ： 类加载器，用于加载代理类，使用真实对象的类加载器即可 Class<?>[] interfaces ： 真实对象所实现的接口，代理模式真实对象和代理对象实现相同的接口 InvocationHandler h ： 代理对象的调用处理程序 */ SellTickets sellTickets = (SellTickets) Proxy.newProxyInstan…

设计模式2
type: item
date: 2024-01-27T21:49:00+08:00
description: 4，创建型模式4.2 工厂模式4.2.1 概述需求：设计一个咖啡店点餐系统。 设计一个咖啡类（Coffee），并定义其两个子类（美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】）；再设计一个咖啡店类（CoffeeStore），咖啡店具有点咖啡的功能。具体类的设计如下：在java中，万物皆对象，这些对象都需要创建，如果创建的时候直接new该对象，就会对该对象耦合严重，假如我们要更换对象，所有new对象的地方都需要修改一遍，这显然违背了软件设计的开闭原则。如果我们使用工厂来生产对象，我们就只和工厂打交道就可以了，彻底和对象解耦，如果要更换对象，直接在工厂里更换该对象即可，达到了与对象解耦的目的；所以说，工厂模式最大的优点就是：解耦。在本教程中会介绍三种工厂的使用简单工厂模式（不属于GOF的23种经典设计模式）工厂方法模式抽象工厂模式4.2.2 简单工厂模式简单工厂不是一种设计模式，反而比较像是一种编程习惯。4.2.2.1 结构简单工厂包含如下角色：抽象产品 ：定义了产品的规范，描述了产品的主要特性和功能。具体产品 ：实现或者继承抽象产品的子类具体工厂 ：提供了创建产品的方法，调用者通过该方法来获取产品。4.2.2.2 实现现在使用简单工厂对上面案例进行改进，类图如下：工厂类代码如下：public class SimpleCoffeeFactory { public Coffee createCoffee(String type) { Coffee coffee = null; if("americano".equals(type)) { coffee = new AmericanoCoffee(); } else if("latte".equals(type)) { coffee = new LatteCoffee(); } return coffee; } }工厂（factory）处理创建对象的细节，一旦有了SimpleCoffeeFactory，CoffeeStore类中的orderCoffee()就变成此对象的客户，后期如果需要Coffee对象直接从工厂中获取即可。这样也就解除了和Coffee实现类的耦合，同时又产生了新的耦合，CoffeeStore对象和SimpleCoffeeFactory工厂对象的耦合，工厂对象和商品对象的耦合。后期如果再加新品种的咖啡，我们势必要需求修改SimpleCoffeeFactory的代码，违反了开闭原则。工厂类的客户端可能有很多，比如创建美团外卖等，这样只需要修改工厂类的代码，省去其他的修改操作。4.2.2.4 优缺点优点：封装了创建对象的过程，可以通过参数直接获取对象。把对象的创建和业务逻辑层分开，这样以后就避免了修改客户代码，如果要实现新产品直接修改工厂类，而不需要在原代码中修改，这样就降低了客户代码修改的可能性，更加容易扩展。缺点：增加新产品时还是需要修改工厂类的代码，违背了“开闭原则”。4.2.2.3 扩展静态工厂在开发中也有一部分人将工厂类中的创建对象的功能定义为静态的，这个就是静态工厂模式，它也不是23种设计模式中的。代码如下：public class SimpleCoffeeFactory { public static Coffee createCoffee(String type) { Coffee coffee = null; if("americano".equals(type)) { coffee = new AmericanoCoffee(); } else if("latte".equals(type)) { coffee = new LatteCoffee(); } return coffe; } }4.2.3 工厂方法模式针对上例中的缺点，使用工厂方法模式就可以完美的解决，完全遵循开闭原则。4.2.3.1 概念定义一个用于创建对象的接口，让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。4.2.3.2 结构工厂方法模式的主要角色：抽象工厂（Abstract Factory）：提供了创建产品的接口，调用者通过它访问具体工厂的工厂方法来创建产品。具体工厂（ConcreteFactory）：主要是实现抽象工厂中的抽象方法，完成具体产品的创建。抽象产品（Product）：定义了产品的规范，描述了产品的主要特性和功能。具体产品（ConcreteProduct）：实现了抽象产品角色所定义的接口，由具体工厂来创建，它同具体工厂之间一一对应。4.2.3.3 实现使用工厂方法模式对上例进行改进，类图如下：代码如下：抽象工厂：public interface CoffeeFactory { Coffee createCof…

设计模式1
type: item
date: 2024-01-27T15:40:00+08:00
description: 1，设计模式概述1.1 软件设计模式的产生背景"设计模式"最初并不是出现在软件设计中，而是被用于建筑领域的设计中。1977年美国著名建筑大师、加利福尼亚大学伯克利分校环境结构中心主任克里斯托夫·亚历山大（Christopher Alexander）在他的著作《建筑模式语言：城镇、建筑、构造》中描述了一些常见的建筑设计问题，并提出了 253 种关于对城镇、邻里、住宅、花园和房间等进行设计的基本模式。1990年软件工程界开始研讨设计模式的话题，后来召开了多次关于设计模式的研讨会。直到1995 年，艾瑞克·伽马（ErichGamma）、理査德·海尔姆（Richard Helm）、拉尔夫·约翰森（Ralph Johnson）、约翰·威利斯迪斯（John Vlissides）等 4 位作者合作出版了《设计模式：可复用面向对象软件的基础》一书，在此书中收录了 23 个设计模式，这是设计模式领域里程碑的事件，导致了软件设计模式的突破。这 4 位作者在软件开发领域里也以他们的“四人组”（Gang of Four，GoF）著称。1.2 软件设计模式的概念软件设计模式（Software Design Pattern），又称设计模式，是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题，以及该问题的解决方案。也就是说，它是解决特定问题的一系列套路，是前辈们的代码设计经验的总结，具有一定的普遍性，可以反复使用。1.3 学习设计模式的必要性设计模式的本质是面向对象设计原则的实际运用，是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。正确使用设计模式具有以下优点。可以提高程序员的思维能力、编程能力和设计能力。使程序设计更加标准化、代码编制更加工程化，使软件开发效率大大提高，从而缩短软件的开发周期。使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。1.4 设计模式分类创建型模式用于描述“怎样创建对象”，它的主要特点是“将对象的创建与使用分离”。GoF（四人组）书中提供了单例、原型、工厂方法、抽象工厂、建造者等 5 种创建型模式。结构型模式用于描述如何将类或对象按某种布局组成更大的结构，GoF（四人组）书中提供了代理、适配器、桥接、装饰、外观、享元、组合等 7 种结构型模式。行为型模式用于描述类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务，以及怎样分配职责。GoF（四人组）书中提供了模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器等 11 种行为型模式。2，UML图统一建模语言（Unified Modeling Language，UML）是用来设计软件的可视化建模语言。它的特点是简单、统一、图形化、能表达软件设计中的动态与静态信息。UML 从目标系统的不同角度出发，定义了用例图、类图、对象图、状态图、活动图、时序图、协作图、构件图、部署图等 9 种图。2.1 类图概述类图(Class diagram)是显示了模型的静态结构，特别是模型中存在的类、类的内部结构以及它们与其他类的关系等。类图不显示暂时性的信息。类图是面向对象建模的主要组成部分。2.2 类图的作用在软件工程中，类图是一种静态的结构图，描述了系统的类的集合，类的属性和类之间的关系，可以简化了人们对系统的理解；类图是系统分析和设计阶段的重要产物，是系统编码和测试的重要模型。2.3 类图表示法2.3.1 类的表示方式在UML类图中，类使用包含类名、属性(field) 和方法(method) 且带有分割线的矩形来表示，比如下图表示一个Employee类，它包含name,age和address这3个属性，以及work()方法。属性/方法名称前加的加号和减号表示了这个属性/方法的可见性，UML类图中表示可见性的符号有三种：+：表示public-：表示private：表示protected属性的完整表示方式是： 可见性 名称 ：类型 [ = 缺省值] 方法的完整表示方式是： 可见性 名称(参数列表) [ ： 返回类型]注意：​ 1，中括号中的内容表示是可选的​ 2，也有将类型放在变量名前面，返回值类型放在方法名前面举个栗子：上图Demo类定义了三个方法：method()方法：修饰符为public，没有参数，没有返回值。method1()方法：修饰符为private，没有参数，返回值类型为String。method2()方法：修饰符为protected，接收两个参数，第一个参数类型为int，第二个参数类型为String，返回值类型是int。2.3.2 类与类之间关系的表示方式2.3.2.1 关联关系关联关系是对象之间的一种引用关系，用于表示一类对象与另一类对象之间的联系，…

RabbitMQ开发需要考虑到的问题
type: item
date: 2023-11-28T22:36:26+08:00
description: 前言：仅提供解决问题的思路具体可以按照自己的业务代码进行调整需要使用消费失败处理1.在消息重试几次后业务消息还是执行报错失败时，可以将失败消息发送到error.derect这个交换机中，在用交换机反送到队列，可以将错误进行人工处理等方法2.我们可以在交易服务设置定时任务，定期查询订单支付状态。这样即便MQ通知失败，还可以利用定时任务作为兜底方案，确保订单支付状态的最终一致性幂等性问题1例如多次减少余额2.多次删除（例如删除购物车，不影响）3.可以使用流水表解决比如创建一个mysql的流水表，在发送队列消息的时候插入到流水表中，下次插入时需要查询表中是否有记录，无记录才能插入，可以使用redis做记录不用mysql也可以

SpringBoot RabbitMQ开发流程
type: item
date: 2023-11-28T22:35:00+08:00
description: 使用生产者/消费者确认机制生产者确认机制，确保消息可以成功发送到队列spring: rabbitmq: host: 192.168.134.171 # rabbitMQ的ip地址 port: 5673 # 端口,默认是5672这里我用的docker映射的5673 username: hmall password: 123 virtual-host: /hmall connection-timeout: 1s #设置mq超时时间 template: retry: initial-interval: 1000ms #失败后的初始等待时间 multiplier: 1 #下载失败hou等待时长倍数 max-attempts: 3 #最大重试次数 enabled: true # 开启重试机制 #下面配置用于在消息发送者中使用用于监听回调信息 #publisher-confirm-type: correlated #开启publisher-confirm 并设置类型为correlated || 一般不用会降低性能 #他有三种类型 #none 关闭 / simple 同步阻塞 / correlated mq异步方式返回回执消息 publisher-returns: true #开启这个publisher-returns机制，一般用于返回 路由是否存在的，如果不存在是开发者自己导致的一般无需开启消费者确认机制，确保消息可以成功执行spring: rabbitmq: host: 192.168.134.170 # rabbitMQ的ip地址 port: 5673 # 端口 username: hmall password: 123 virtual-host: /hmall listener: simple: prefetch: 1 #这里就是使用预取一次只能取一个，提高性能，让性能低的需要处理完才能接受下一个，不影响其他的使用 acknowledge-mode: auto #确认机制 #如果确认机制使用none的时候，等监听到这个消息后就算后面发生异常了也会把消息删除了，如果使用auto则是如果遇到异常自动再次从新发送消息，如果一直异常可能会一直抛出异常 retry: enabled: true #开启重试机制 connection-timeout: 1s #设置mq超时时间 template: retry: initial-interval: 1000ms #失败后的初始等待时间 multiplier: 1 #下载失败hou等待时长倍数 max-attempts: 3 #最大重试次数 enabled: true # 开启重试机制 #stateless: true #true 无状态 false有状态。如果业务中包含事务，这里改为false需要使用消息重试【加入幂等判断防止消息重复消费】如果在并发情况下可能会多次更新数据状态，就需要保证业务幂等性，就比如多次执行这个sql操作不会影响结果，比如多次删除购物车需要使用消费失败处理在消息重试几次后业务消息还是执行报错失败时，可以将失败消息发送到error.derect这个交换机中，在用交换机反送到队列，可以将错误进行人工处理等方法需要考虑业务幂等性在业务中进行判断可以在业务中进行业务幂等的判断，比如用户下单时，已经支付了，进行通知mq将数据库中改为已支付，就可以判断订单是否为 未支付 ，只要是未支付，就说明数据库没被修改，可以进行修改，但是如果之前已经收到消息将状态已经改为已支付了，就无需做修改数据库的操作了或者使用一条sql语句也可以实现，和上面可以实现同样效果update order set status = 2 where id = ? AND status = 1使用延迟消息比如订单状态需要给用户30分钟的支付时间，等三十分钟后在进行发送mq消息，进行执行更新状态语句，如果支付了就给更新，未支付就不更新使用死信交换机实现消息延时实际上就是将原交换机设置死信时间，在将原交换机的死信后消息发送至死信交换机中，在接受死信交换机的内容，达到接受延时消息的方法流程当队列消息没有consumer接受时，将会被传到死信交换机中，配置一个交换机和队列，将其队列绑定至死信交换机中延时消息插件下载消息延时插件1.打开官网下载插件https://www.rabbitmq.com/community-plugins.html2.下载延时插件需要指定版本下载到plugins文件目录中，这个文件可以自行找你下载的rabbitmq的文件中我这里已docker启动的rabbitmq为例docker run -d --hostname my-rabbit -v /mq/plugins:/plugins --name rabb…

RabbitMQ使用
type: item
date: 2023-11-28T22:27:16+08:00
description: rabbitmq的优点可以异步执行，等等，优点很多这里不过多废话，请直接进入实际开发流程使用docker命令安装docker run -d --hostname my-rabbit -v /mq/plugins:/plugins --name rabbit -p 15672:15672 -p 5672:5672 rabbitmq-d 后台运行容器； --name 指定容器名； -p 指定服务运行的端口（5672：应用访问端口；15672：控制台Web端口号）； -v 映射目录或文件； --hostname 主机名（RabbitMQ的一个重要注意事项是它根据所谓的 “节点名称” 存储数据，默认为主机名）； -e 指定环境变量；（RABBITMQ_DEFAULT_VHOST：默认虚拟机名；RABBITMQ_DEFAULT_USER：默认的用户名； RABBITMQ_DEFAULT_PASS：默认用户名的密码）启动rabbit管理页面，不然无法进入管理页面docker exec -it rabbitmq rabbitmq-plugins enable rabbitmq_management错误提示信息Management API returned status code 500修改方法为：1、进入docker2、执行如下命令cd /etc/rabbitmq/conf.d/ echo management_agent.disable_metrics_collector = false > management_agent.disable_metrics_collector.conf3、重启RabbitMQ网页端登陆地址 ip:15672登陆默认账号/密码 guest注意：默认的用户使用java连接的时候连接地址只能填写localhost，可以在额外创建一个账户，并设置admin权限使用springboot进行实战演示先引入必备包 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency>知识点// 测试交换机的收发 - 所有的交换机都需要输入key，但是fanout可以输入null的key也就是所有绑定的都可以收到消息 @Test void testSend() throws InterruptedException { String bluemsg = "blue msg"; CorrelationData cd = new CorrelationData(UUID.randomUUID().toString()); cd.getFuture().addCallback(new ListenableFutureCallback<CorrelationData.Confirm>() { @Override public void onFailure(Throwable ex) { log.error("消息回调失败",ex); //基本上不会发生 } @Override public void onSuccess(CorrelationData.Confirm confirm) { log.debug("收到confirm callback包回执"); System.out.println(confirm.isAck()); if (confirm.isAck()) { // 消息发送成功 log.info("消息发送成功，收到ack"); } else { // 这里应该重发消息, log.error("消息发送失败，收到nack"); } } }); //消息到达了交换机,但是发送到队列的时候出错了(如果key不存在),返回ack但是有return返回值 //消息没有到达mq交换机会返回nack ,没有return返回值(代码问题，如路由不存在) //只要到达mq交换机都是ack，列队没收到返回返回ack+路由错误 rabbitTemplate.convertAndSend("nbs…

JAVA中创建多模块项目
type: item
date: 2023-11-23T10:14:16+08:00
description: JAVA中创建多模块项目1.创建父模块删除多余文件，只留下这三个文件2.创建子模块，删除多余文件和启动文件删除多余文件只留下必须文件同时在创建一个web模块3.配置父文件中pom，设置子模块，设置parent设置打包方式 <packaging>pom</packaging> <modules> <module>web</module> <module>sesrvice</module> </modules>设置parent <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.17</version> <relativePath/> </parent>4.配置子模块中的parent子模块中继承关系要改为父模块的 <parent> <groupId>com.example</groupId> <artifactId>rabbat-mq</artifactId> <version>0.0.1-SNAPSHOT</version> </parent>同时web中可以引入service模块 <dependency> <groupId>com.example</groupId> <artifactId>sesrvice</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>写一个测试接口访问进行验证访问验证可能出现的问题：1.注意删除相对路径、父模块中配置主启动文件<mainClass>com.dake.shop.ShopOrderApplication</mainClass>2.编译失败：程序包xxx不存在解决方案：在common子工程下的pom.xml，添加以下配置<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <classifier>exec</classifier> </configuration> </plugin> </plugins> </build> 原因分析：common也是SpringBoot工程，SpringBoot工程打包编译时，会生成两种jar包，一种是普通的jar，另一种是可执行jar。默认情况下，这两种jar的名称相同，在不做配置的情况下，普通的jar先生成，可执行jar后生成，造成可执行jar会覆盖普通的jar。而projectA工程无法依赖common工程的可执行jar，所以编译失败：程序包xxx不存在。添加配置后截图如下，其中common-1.0-SNAPSHOT-exec.jar为可执行jar，common-1.0-SNAPSHOT.jar为普通jar包。3.使用scanBasePackages将service的包手动导入进来，防止在web中使用@autowirte进行引入service中方法时无法使用