Java 设计模式
Java后端常用设计模式总览表
模式 | 核心思想 | Spring / Spring Boot应用 | 手写实现核心 |
---|---|---|---|
单例模式 (Singleton) | 一个类只有一个实例,提供全局访问点 | Spring容器中的默认Bean都是单例管理 | volatile + synchronized 双重检查锁定,懒加载单例 |
工厂模式 (Factory) | 统一管理和创建对象,不暴露具体创建逻辑 | ApplicationContext/BeanFactory 统一管理Bean创建 | 定义工厂类,使用if-else 或注册表管理产品实例化 |
建造者模式 (Builder) | 分步骤、链式构建复杂对象,分离构建和表示 | Lombok的@Builder注解,客户端配置如OkHttpClient | 写Builder内部静态类,链式调用设置属性,最后build |
原型模式 (Prototype) | 复制已有对象,避免重新new实例 | @Scope("prototype") 让Bean每次getBean都生成新实例 | 实现Cloneable接口,重写clone() 方法深/浅拷贝 |
代理模式 (Proxy) | 通过代理对象控制访问、扩展目标对象功能 | Spring AOP(@Transactional、@Aspect等) | JDK动态代理(接口)/ CGLIB子类代理(继承+增强) |
装饰器模式 (Decorator) | 动态添加功能,不修改原对象结构 | Servlet Filter链、Spring MVC拦截器链(增强请求) | 抽象组件接口+抽象装饰器类+具体装饰器层层包裹 |
适配器模式 (Adapter) | 将一个接口转换成另一个兼容的新接口 | Spring MVC HandlerAdapter适配不同类型Controller | 定义适配器类,包装老接口对象,实现新接口 |
外观模式 (Facade) | 提供统一高层接口,简化复杂子系统调用 | Service层聚合调用多个子系统服务(FacadeService) | 定义一个Facade类,组合子系统并统一暴露简洁接口 |
策略模式 (Strategy) | 封装一系列可互换的算法或行为,动态切换 | Spring Security中多种认证策略,支付策略选择 | 策略接口+多策略实现+上下文Context动态持有策略 |
模板方法模式 (Template Method) | 固定流程结构,子类实现细节步骤 | JdbcTemplate/RestTemplate/RedisTemplate等 | 抽象父类定义流程+抽象方法留给子类实现 |
责任链模式 (Chain of Responsibility) | 请求沿链传递,每个节点决定处理或传递 | Servlet FilterChain、HandlerInterceptor链 | Handler接口+设置next+handle递归链式传递 |
观察者模式 (Observer) | 一对多通知机制,主题变化自动通知观察者 | Spring事件机制(ApplicationEventPublisher+@EventListener) | Subject接口+Observer接口+列表管理观察者,通知更新 |
✅ 1. 单例模式(Singleton Pattern)
✏️ 基本概念
单例模式的定义是:
保证一个类在整个系统中只有一个实例,并且提供一个全局访问点。
Spring容器默认就是单例模式,通过IoC管理Bean,确保一个Bean一个实例,方便全局统一调度。
简单说,就是一个类只new一次,后面都复用同一个对象。
✏️ 为什么要用单例模式?
-
节省内存(避免反复创建同一对象)
-
保持全局统一性(比如日志类、缓存管理器、数据库连接池)
-
控制共享资源(保证线程安全)
✏️ 在Spring中的应用
在Spring中:
-
默认情况下,所有@Bean都是单例模式管理的。
-
只要一个类被注册到Spring容器里(比如加了
@Component
、@Service
、@Controller
), -
Spring容器在启动的时候就创建一个实例,整个容器里共享这一份实例。
✏️ 示例讲解
@Service
public class UserService {public void print() {System.out.println("UserService实例: " + this);}
}
调用代码:
@Autowired
private UserService userService1;@Autowired
private UserService userService2;public void test() {userService1.print();userService2.print();
}
输出结果:
UserService实例: com.example.UserService@1b6d3586
UserService实例: com.example.UserService@1b6d3586
- 说明
userService1
和userService2
是同一个实例。
✏️手写示例
volatile
+ synchronized
(双重检查锁定)
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 Pattern)
✏️ 基本概念
工厂模式的定义是:
定义一个用于创建对象的接口,让子类决定实例化哪一个类。
简单说,就是把对象的创建过程交给工厂统一管理,而不是自己new对象。
✏️ 为什么要用工厂模式?
-
解耦对象的创建过程和使用过程
-
统一集中管理对象创建,便于维护
-
可以根据不同参数返回不同子类实例(多态)
✏️ 在Spring中的应用
在Spring中:
-
BeanFactory是Spring容器的最基本工厂接口。
-
ApplicationContext是BeanFactory的子接口,提供了更强大的功能。
Spring容器就是一个超大的工厂,你只需要调用getBean()
,容器就帮你创建对象并注入依赖了。
✏️ 示例讲解
Spring工厂使用例子:
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);UserService userService = context.getBean(UserService.class);
userService.print();
-
AnnotationConfigApplicationContext
是工厂(Factory)。 -
getBean()
就是工厂方法,通过Bean的名字或者类型拿到对象。 -
创建、初始化、依赖注入,这些细节都隐藏了。
内容 | 对应 |
---|---|
接口:定义了创建对象的方法 | ApplicationContext 定义了 getBean() 等方法 |
子类:决定具体如何创建对象 | AnnotationConfigApplicationContext 决定通过注解扫描来实例化哪些Bean |
使用时,只关心接口,不关心细节 | 调用 context.getBean(UserService.class) ,不用关心UserService是怎么被创建出来的 |
✏️手写示例
public class CarFactory {public static Car getCar(String type) {if ("bmw".equals(type)) {return new BmwCar();} else if ("audi".equals(type)) {return new AudiCar();}return null;}
}
-
外部只管调用
CarFactory.getCar("bmw")
-
不关心Car是怎么new出来的
✅ 3. 建造者模式(Builder Pattern)
✏️ 基本概念
建造者模式的定义是:
将一个复杂对象的构建过程分步进行,并且允许构建过程和对象本身解耦。
简单说,就是链式调用,一步步设置参数,最后一次性build出对象。
✏️ 为什么要用建造者模式?
-
对象属性太多,构造函数太复杂时,避免构造方法参数过长问题
-
使对象创建过程清晰有条理
-
支持不同顺序、不同属性构建对象(灵活)
✏️ 在Spring Boot/Lombok中的应用
Spring Boot项目里常常结合Lombok的@Builder注解来使用建造者模式。
比如定义一个实体类:
@Data
@Builder
public class User {private String name;private Integer age;private String email;
}
调用时:
User user = User.builder().name("Tom").age(18).email("tom@example.com").build();
优势:
-
不需要自己写冗长的构造器
-
不需要记得属性顺序
-
可读性极强
✏️手写示例
public class User {private String name;private Integer age;private User(Builder builder) {this.name = builder.name;this.age = builder.age;}public static class Builder {private String name;private Integer age;public Builder name(String name) { this.name = name; return this; }public Builder age(Integer age) { this.age = age; return this; }public User build() { return new User(this); }}
}
✅ 4. 原型模式(Prototype Pattern)
✏️ 基本概念
原型模式的定义是:
通过复制已有的对象来创建新的对象,而不是通过new新的实例。
Spring通过@Scope("prototype")实现了原型模式,让Bean可以按需每次创建一个新实例,而不是单例复用。
简单说,就是复制一份已有对象的副本。
✏️ 为什么要用原型模式?
-
new一个新对象成本高时,用复制可以节省资源。
-
对象初始化复杂时,可以直接clone已有对象。
-
Spring容器中,有时候需要每次getBean()都返回新的实例。
✏️ 在Spring中的应用
在Spring中,如果一个Bean标注了@Scope("prototype")
:
-
每次从容器拿Bean,Spring都会新建一个实例。
-
而不是像单例那样复用。
示例:
@Component
@Scope("prototype")
public class OrderService {
}
调用:
@Autowired
private OrderService orderService1;@Autowired
private OrderService orderService2;
输出对比:
orderService1 = com.example.OrderService@1b6d3586
orderService2 = com.example.OrderService@2a1f58b6
可以看到,orderService1
和orderService2
是不同实例!
✏️手写示例
public class User implements Cloneable {private String name;@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}
通过clone()
方法复制对象,而不是new。
✅ 5. 代理模式(Proxy Pattern)
代理模式的本质是:
不直接访问目标对象,而是通过代理对象间接访问目标对象,并且在访问前后可以增加一些额外操作。
代理模式通过代理对象统一管理对目标对象的访问,实现功能增强和解耦。Spring AOP就是最典型的应用。
-
代理对象和目标对象实现相同接口或继承相同父类。
-
客户端不感知目标对象变化,所有增强逻辑都由代理完成。
-
本质是控制访问 + 功能增强。
✏️ 为什么要用代理模式?
-
在目标对象执行前后加入通用逻辑(日志、权限控制、事务管理)
-
隐藏目标对象的细节,隔离调用者和被调用者
-
延迟加载、远程代理、保护访问等
✏️ Spring 中的应用
Spring AOP(面向切面编程)核心就是基于代理模式!
当你在Spring中使用如@Transactional、@Async、@Cacheable、@Aspect等功能时, 实际上Spring会:
-
创建一个代理对象来代替你的目标对象。
-
所有方法调用都会先进入代理,再决定是否增强(比如加事务、异步执行等)。
AOP代理的具体实现
类型 | 说明 |
---|---|
JDK动态代理 | 基于接口代理,只能代理接口方法 |
CGLIB代理 | 继承目标类,重写方法代理,没有接口也能代理 |
例子(SpringBoot中):
调用过程实际上是:
代理对象帮我们织入了事务开启/提交的逻辑。
✏️手写示例
方法一:JDK动态代理(基于接口)
定义接口:
public interface UserService {void addUser();
}
实现接口:
public class UserServiceImpl implements UserService {@Overridepublic void addUser() {System.out.println("新增用户");}
}
定义代理逻辑:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class UserServiceProxy {public static UserService createProxy(UserService target) {return (UserService) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("方法执行前增强逻辑...");Object result = method.invoke(target, args);System.out.println("方法执行后增强逻辑...");return result;}});}
}
使用代理:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class UserServiceProxy {public static UserService createProxy(UserService target) {return (UserService) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("方法执行前增强逻辑...");Object result = method.invoke(target, args);System.out.println("方法执行后增强逻辑...");return result;}});}
}
public class Main {public static void main(String[] args) {UserService target = new UserServiceImpl();UserService proxy = UserServiceProxy.createProxy(target);proxy.addUser();}
}
输出结果:
方法执行前增强逻辑...
新增用户
方法执行后增强逻辑...
方法二:自己手写静态代理(和CGLIB动态代理相似)
静态代理 | CGLIB 动态代理 | |
---|---|---|
生成代理类的时机 | 编译时期(代码阶段就有) | 运行时期(根据目标类即时生成字节码) |
是否需要手写代理类 | 是,开发者需要显式写一个代理类 | 否,由框架(如 CGLIB)自动生成 |
代理类的数量 | 代理一个类就要写一个代理类 | 可以动态为任意类生成代理,不需要事先知道 |
为什么你觉得 CGLIB 像静态代理?
因为 CGLIB 的底层原理是:
-
给目标类创建一个子类,
-
然后重写父类的方法,
-
再在方法前后加逻辑(比如AOP切面),
-
最后调用
super.method()
来执行原来的逻辑。
这种重写+调用 super
的模式,看起来和我们手动写静态代理类很像,比如手写:
public class UserServiceProxy extends UserService {@Overridepublic void save() {System.out.println("before...");super.save();System.out.println("after...");}
}
CGLIB底层生成的字节码跟这种继承加增强的思路非常像。
但是生成动作是运行时动态完成的,而手写静态代理是编译期就固定好的源代码,这是根本区别!
✅ 6. 装饰器模式(Decorator Pattern)
装饰器模式的本质是:
在不修改原有类的基础上,动态地为对象添加新的功能。
-
装饰器和原对象实现相同接口。
-
装饰器持有原对象引用,并在调用原方法前后增加逻辑。
-
可以灵活叠加多个装饰器,功能组合不受限制。
✏️ 为什么要用装饰器模式?
-
需要动态添加、扩展类的功能。
-
不改变原有对象结构,不影响已有代码。
-
职责单一,每个装饰器只专注于一类功能增强。
✏️ Spring / Spring Boot中的应用示例
(1)Servlet Filter链(FilterChain)
Servlet 容器(如 Tomcat)处理一个 HTTP 请求时,会:
-
创建一个
FilterChain
对象(内部保存所有配置好的Filter
列表)。 -
按照顺序调用每个 Filter 的
doFilter()
方法。 -
每个
Filter
自己选择:-
是否继续调用
chain.doFilter(request, response)
; -
在调用前后加逻辑。
-
典型Filter结构:
public class MyFilter implements Filter {public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {System.out.println("Filter 前置处理");chain.doFilter(request, response); // 继续调用下一个Filter或目标ServletSystem.out.println("Filter 后置处理");}
}
底层原理(Tomcat中)
Tomcat 中 ApplicationFilterChain
类的源码大概逻辑是:
public class ApplicationFilterChain implements FilterChain {private List<Filter> filters;private int pos = 0; // 当前执行到第几个Filterpublic void doFilter(ServletRequest request, ServletResponse response) {if (pos < filters.size()) {Filter currentFilter = filters.get(pos++);currentFilter.doFilter(request, response, this);} else {// 所有Filter执行完了,真正调用目标Servletservlet.service(request, response);}}
}
每个Filter调用完自己的逻辑后,再调用chain.doFilter
,递归推进,形成前后增强。
-
每个Filter不修改原Servlet;
-
可以动态添加新的Filter增强功能;
-
Filter和Servlet都基于ServletRequest/ServletResponse接口约定,保证了统一性。
(2)Spring MVC的HandlerInterceptor链
Spring MVC 收到请求后,会执行:
-
DispatcherServlet -> HandlerMapping -> 获取到一个 HandlerExecutionChain
-
HandlerExecutionChain 内部保存了很多
HandlerInterceptor
-
调用每个拦截器的
preHandle()
方法。 -
如果全部返回 true,继续执行真正的 Controller。
-
Controller执行完成后,依次倒序调用拦截器的
postHandle()
和afterCompletion()
。
拦截器基本结构:
public class MyInterceptor implements HandlerInterceptor {public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("前置拦截逻辑");return true; // 继续流程}public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("后置拦截逻辑");}public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("请求完成后的清理逻辑");}
}
✏️手写示例
// 抽象组件(统一接口)
public interface Component {void operation();
}// 具体组件(核心功能)
public class ConcreteComponent implements Component {public void operation() {System.out.println("核心功能");}
}// 抽象装饰器(持有Component引用)
public abstract class Decorator implements Component {protected Component component;public Decorator(Component component) {this.component = component;}
}// 具体装饰器A
public class DecoratorA extends Decorator {public DecoratorA(Component component) {super(component);}public void operation() {System.out.println("功能A增强之前");component.operation(); // 调用原对象方法System.out.println("功能A增强之后");}
}// 具体装饰器B
public class DecoratorB extends Decorator {public DecoratorB(Component component) {super(component);}public void operation() {System.out.println("功能B增强之前");component.operation();System.out.println("功能B增强之后");}
}
-
核心组件(ConcreteComponent)负责最基础的逻辑;
-
装饰器(ConcreteDecorator)持有原组件引用,在调用前后加功能;
-
可以嵌套多个装饰器动态叠加功能。
✅ 7. 适配器模式(Adapter Pattern)
适配器模式的本质是:
将一个类的接口转换成客户希望的另一个接口,解决接口不兼容问题。
-
适配器是一个中间层。
-
外部系统调用的是统一接口,不需要关心内部老接口、旧实现。
-
包装原对象,把它“适配”成新的标准。
✏️ 为什么要用适配器模式?
-
兼容老系统和新系统(让不同接口风格能互通)
-
重用已有类(不改动老类源码)
-
降低代码耦合度(通过中间适配)
✏️ 在Spring / Spring Boot中的应用示例
Spring MVC 中的 HandlerAdapter
-
Spring MVC允许开发者用不同风格的Controller编写业务逻辑(比如传统的Controller类、Rest风格、HttpRequestHandler等)。每种Controller风格调用方式不同,但DispatcherServlet要统一调用。
-
DispatcherServlet调用HandlerMapping(处理器映射器)找到Handler。通常,Handler是一个Controller方法,即:一个带有
@RequestMapping
、@PostMapping
等注解的方法。 -
拿到Handler之后,DispatcherServlet调用HandlerAdapter(处理器适配器)。HandlerAdapter负责真正执行Handler。因为Handler可能是不同风格的(比如普通Controller,还是Rest风格Controller),需要一个适配器统一调用。
核心接口:
public interface HandlerAdapter {boolean supports(Object handler);ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
}
✏️手写示例
老系统的播放器只能播放MP3 (OldPlayer
)
新系统标准接口要求播放MP4 (NewPlayer
)
定义老接口(不改动它):
public interface OldPlayer {void playMp3(String fileName);
}
老播放器实现:
public class OldMp3Player implements OldPlayer {@Overridepublic void playMp3(String fileName) {System.out.println("播放老式MP3:" + fileName);}
}
定义新接口:
public interface NewPlayer {void playMp4(String fileName);
}
写一个适配器(Adapter),包装老接口,适配新接口:
public class PlayerAdapter implements NewPlayer {private OldPlayer oldPlayer;public PlayerAdapter(OldPlayer oldPlayer) {this.oldPlayer = oldPlayer;}@Overridepublic void playMp4(String fileName) {System.out.println("适配器转换中...");oldPlayer.playMp3(fileName);}
}
使用适配器:
public class Main {public static void main(String[] args) {OldPlayer oldPlayer = new OldMp3Player();NewPlayer newPlayer = new PlayerAdapter(oldPlayer);newPlayer.playMp4("test.mp3");}
}
✅8. 外观模式(Facade Pattern)
外观模式的本质是:
为一组复杂子系统提供一个统一的、高层次的接口,简化客户端对子系统的使用。
简单理解:
-
内部可能很复杂(好多类、好多服务互相调用)。
-
但是对外,只暴露一个简单易用的入口。
-
客户端不需要知道内部细节,只管用外观类。
✏️ 为什么用外观模式?
-
简化调用流程,屏蔽内部复杂性
-
降低模块之间的耦合
-
更易于维护和扩展
✏️ Spring / Spring Boot中的应用示例
在实际项目中,Service层经常使用外观模式:
-
将多个Service组合在一个统一的Facade服务里。
-
Controller只调用Facade,不关心内部细节。
例子:电商系统下单逻辑
涉及多个子系统:
子系统 | 功能 |
---|---|
OrderService | 创建订单 |
PaymentService | 支付订单 |
InventoryService | 减库存 |
LogisticsService | 安排发货 |
如果Controller自己依次调用这些子系统,代码又长又乱!
于是写一个外观服务统一封装:
@Service
public class OrderFacadeService {@Autowiredprivate OrderService orderService;@Autowiredprivate PaymentService paymentService;@Autowiredprivate InventoryService inventoryService;@Autowiredprivate LogisticsService logisticsService;public void placeOrder(OrderRequest request) {orderService.createOrder(request);paymentService.payOrder(request.getOrderId());inventoryService.deductStock(request.getProductId());logisticsService.arrangeShipment(request.getOrderId());}
}
Controller调用变得超简单:
@RestController
public class OrderController {@Autowiredprivate OrderFacadeService orderFacadeService;@PostMapping("/order")public void createOrder(@RequestBody OrderRequest request) {orderFacadeService.placeOrder(request);}
}
✅ 9. 策略模式(Strategy Pattern)
策略模式的本质是:
定义一系列算法(行为),将它们封装起来,使它们可以互相替换使用。
核心思想:
-
不同策略可以动态切换。
-
行为由策略对象决定,不由调用者写死。
-
避免
if...else...
或switch...case...
代码膨胀。
✏️ 为什么要用策略模式?
-
将算法/行为从使用者中分离,更灵活更可扩展。
-
减少代码耦合,新策略只需新增类,不改动原有代码(符合开闭原则)。
-
运行时可以动态切换策略,满足不同业务场景。
✏️ Spring / Spring Boot中的应用示例
(1)Spring Security中的认证机制
Spring Security允许通过不同认证策略(用户名密码登录、OAuth2登录、JWT认证等),
每种认证方式就是一种策略,由AuthenticationProvider接口统一约定。
核心接口:
public interface AuthenticationProvider {Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
不同实现:
-
DaoAuthenticationProvider
:基于数据库用户名密码认证 -
JwtAuthenticationProvider
:基于JWT Token认证 -
OAuth2AuthenticationProvider
:基于OAuth2认证
Spring Security会根据具体情况选择不同的AuthenticationProvider策略执行认证。
(2)实际业务开发中常见使用(举例)
比如你开发一个支付系统:
-
有微信支付(WeChatPayStrategy)
-
有支付宝支付(AliPayStrategy)
-
有银联支付(UnionPayStrategy)
不同支付方式就是不同的策略!
✏️手写示例
场景模拟:支付策略系统
定义策略接口:
public interface PayStrategy {void pay(double amount);
}
定义不同策略实现:
public class WeChatPayStrategy implements PayStrategy {@Overridepublic void pay(double amount) {System.out.println("使用微信支付:" + amount + "元");}
}
微信支付:
public class AliPayStrategy implements PayStrategy {@Overridepublic void pay(double amount) {System.out.println("使用支付宝支付:" + amount + "元");}
}
支付宝支付:
public class PayContext {private PayStrategy payStrategy;public PayContext(PayStrategy payStrategy) {this.payStrategy = payStrategy;}public void executePay(double amount) {payStrategy.pay(amount);}
}
定义上下文类(Context),持有一个策略对象:
public class PayContext {private PayStrategy payStrategy;public PayContext(PayStrategy payStrategy) {this.payStrategy = payStrategy;}public void executePay(double amount) {payStrategy.pay(amount);}
}
使用策略:
public class Main {public static void main(String[] args) {PayContext context = new PayContext(new WeChatPayStrategy());context.executePay(100.0);context = new PayContext(new AliPayStrategy());context.executePay(200.0);}
}
输出结果:
使用微信支付:100.0元
使用支付宝支付:200.0元
✅ 10. 模板方法模式(Template Method Pattern)
模板方法模式的本质是:
在父类定义一个操作的算法骨架(模板),而将一些可变的步骤延迟到子类去实现。
简单说:
-
父类固定流程框架。
-
子类定义具体细节。
-
保证整体流程稳定,但细节可以自由变化。
✏️ 为什么要用模板方法模式?
-
固定流程标准,防止子类破坏流程结构。
-
让子类只关注差异化实现,复用公共逻辑。
-
满足开闭原则,新增业务只需继承,不改动父类。
✏️ Spring / Spring Boot中的应用示例
JdbcTemplate / RedisTemplate / RestTemplate
Spring提供了各种Template
类,使用了模板方法模式。
以JdbcTemplate
为例:
-
固定了获取连接 -> 执行SQL -> 释放资源的流程。
-
留出扩展点,比如定义ResultSet如何映射成对象(RowMapper接口)。
典型流程示例(JdbcTemplate简化版)
public class JdbcTemplate {public <T> List<T> query(String sql, RowMapper<T> rowMapper) {// 1. 获取数据库连接// 2. 执行SQL查询// 3. 遍历ResultSet,用RowMapper映射每行数据// 4. 释放连接资源}
}
-
固定的查询流程由JdbcTemplate控制。
-
每一行如何映射成对象由
RowMapper
子类控制。
✏️手写示例
场景模拟:制作咖啡 or 茶饮品
定义抽象父类,写好模板方法:
public abstract class BeverageTemplate {// 模板方法,定义制作饮品的流程public final void prepareBeverage() {boilWater();brew();pourInCup();addCondiments();}protected void boilWater() {System.out.println("烧水");}protected void pourInCup() {System.out.println("倒入杯中");}// 变化点,留给子类实现protected abstract void brew();protected abstract void addCondiments();
}
定义具体子类(茶):
public class Tea extends BeverageTemplate {@Overrideprotected void brew() {System.out.println("泡茶叶");}@Overrideprotected void addCondiments() {System.out.println("加柠檬");}
}
定义具体子类(咖啡):
public class Coffee extends BeverageTemplate {@Overrideprotected void brew() {System.out.println("冲泡咖啡粉");}@Overrideprotected void addCondiments() {System.out.println("加牛奶和糖");}
}
使用模板方法:
public class Main {public static void main(String[] args) {BeverageTemplate tea = new Tea();tea.prepareBeverage();System.out.println("-----------------");BeverageTemplate coffee = new Coffee();coffee.prepareBeverage();}
}
输出结果:
烧水
泡茶叶
倒入杯中
加柠檬
-----------------
烧水
冲泡咖啡粉
倒入杯中
加牛奶和糖
✅11. 责任链模式(Chain of Responsibility Pattern)
责任链模式的本质是:
将请求沿着一条链进行传递,每个节点可以决定处理还是传递给下一个节点。
简单理解:
-
把多个处理器串成一条链。
-
一个请求来了,沿着链一个一个传递。
-
每个处理器只关心自己负责的部分,
处理完了可以继续传递,也可以终止传递。
✏️ 为什么要用责任链模式?
-
发送者和处理者解耦,不需要指定由哪个具体对象处理。
-
灵活组合责任链,可以动态增删节点。
-
责任细分,每个处理器只做自己的事,符合单一职责原则。
-
增强扩展性,新增处理器不需要改动已有代码(符合开闭原则)。
✏️ Spring / Spring Boot中的应用示例
(1)Servlet Filter链(FilterChain)
在Java Web开发中:
-
一个请求经过多个Filter(日志、权限、限流、防护等)处理。
-
每个Filter处理完后调用
chain.doFilter(request, response)
,继续往下传。
每个Filter:
-
处理自己的逻辑。
-
决定是否继续调用下一个。
这是标准的责任链模式。
(2)Spring MVC拦截器链(HandlerInterceptor)
配置多个拦截器组成链
只要在你的WebMvcConfigurer
里按顺序添加多个拦截器,就自动形成一条链了:
@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new Interceptor1()).addPathPatterns("/**"); // 拦截所有路径registry.addInterceptor(new Interceptor2()).addPathPatterns("/api/**"); // 拦截/api路径registry.addInterceptor(new Interceptor3()).addPathPatterns("/admin/**"); // 拦截/admin路径}
}
注意:
-
注册顺序决定了链的执行顺序。
addPathPatterns()
可以控制每个拦截器生效的路径。 -
如果某个拦截器的
preHandle()
返回了false
,那么后续的拦截器链不会继续执行,请求也不会进入Controller了。 -
preHandle按注册顺序执行。postHandle和afterCompletion按注册顺序的逆序执行。
处理+可中断+链式传递,完全符合责任链模式!
✏️手写示例
场景模拟:请求处理责任链(日志、鉴权)
定义抽象处理器(Handler):
public abstract class Handler {protected Handler next;// 设置下一个处理器public void setNext(Handler next) {this.next = next;}// 处理请求public abstract void handle(String request);
}
定义具体处理器
日志处理器:
public class LogHandler extends Handler {@Overridepublic void handle(String request) {System.out.println("日志记录:" + request);if (next != null) {next.handle(request);}}
}
鉴权处理器:
public class AuthHandler extends Handler {@Overridepublic void handle(String request) {System.out.println("鉴权检查:" + request);if (next != null) {next.handle(request);}}
}
组装责任链并使用:
public class Main {public static void main(String[] args) {Handler logHandler = new LogHandler();Handler authHandler = new AuthHandler();logHandler.setNext(authHandler); // 先日志,再鉴权logHandler.handle("用户请求访问系统资源");}
}
✅ 12. 观察者模式(Observer Pattern)
观察者模式的本质是:
定义对象之间一对多的依赖关系,当主题对象状态变化时,所有依赖它的观察者都会收到通知并自动更新。
简单理解:
-
一个主题对象(Subject)。
-
多个观察者对象(Observer)。
-
主题变化 → 通知所有观察者。
✏️ 为什么要用观察者模式?
-
实现发布者与订阅者之间的松耦合。
-
灵活扩展:任意增加/减少观察者,无需改动主题对象。
-
实时响应:事件驱动,变化自动通知。
✏️ Spring / Spring Boot中的应用示例
Spring事件机制(ApplicationEventPublisher)
在Spring中:
-
通过
ApplicationEventPublisher
发布事件(比如用户注册、订单完成等)。 -
有多个
ApplicationListener
/@EventListener
注册监听器。 -
发布者不需要知道谁在监听,监听器只管监听自己感兴趣的事件。
发布事件
调用applicationContext.publishEvent(event)
→
AbstractApplicationContext.publishEvent()
→
getApplicationEventMulticaster().multicastEvent(event)
。分派给监听器
SimpleApplicationEventMulticaster
(默认实现)维护一个监听器列表,对每个注册的ApplicationListener
:
调用
supportsEventType()
判断是否感兴趣;若是,则同步或异步执行
listener.onApplicationEvent(event)
。监听器注册
上下文启动时,容器会扫描所有实现ApplicationListener
接口的 Bean,或带@EventListener
注解的方法,
并将它们注册到ApplicationEventMulticaster
。
定义事件:
public class OrderCreatedEvent extends ApplicationEvent {private final String orderId;public OrderCreatedEvent(Object source, String orderId) {super(source);this.orderId = orderId;}public String getOrderId() {return orderId;}
}
发布事件:
@Autowired
private ApplicationEventPublisher publisher;public void createOrder(String orderId) {publisher.publishEvent(new OrderCreatedEvent(this, orderId));
}
监听事件:
@Component
public class OrderCreatedListener {@EventListenerpublic void onOrderCreated(OrderCreatedEvent event) {System.out.println("收到订单创建事件:" + event.getOrderId());}
}
一发布,所有监听器同步或异步收到通知。
✏️手写示例
场景模拟:消息发布订阅
定义主题(Subject)接口:
public interface Subject {void addObserver(Observer observer);void removeObserver(Observer observer);void notifyObservers(String message);
}
定义观察者(Observer)接口:
public interface Observer {void update(String message);
}
主题实现类:
import java.util.ArrayList;
import java.util.List;public class ConcreteSubject implements Subject {private List<Observer> observers = new ArrayList<>();@Overridepublic void addObserver(Observer observer) {observers.add(observer);}@Overridepublic void removeObserver(Observer observer) {observers.remove(observer);}@Overridepublic void notifyObservers(String message) {for (Observer observer : observers) {observer.update(message);}}
}
观察者实现类:
public class ConcreteObserver implements Observer {private String name;public ConcreteObserver(String name) {this.name = name;}@Overridepublic void update(String message) {System.out.println(name + " 收到消息:" + message);}
}
测试使用:
public class Main {public static void main(String[] args) {ConcreteSubject subject = new ConcreteSubject();Observer observerA = new ConcreteObserver("观察者A");Observer observerB = new ConcreteObserver("观察者B");subject.addObserver(observerA);subject.addObserver(observerB);subject.notifyObservers("系统通知:有新活动上线!");}
}
相关文章:
Java 设计模式
Java后端常用设计模式总览表 模式核心思想Spring / Spring Boot应用手写实现核心单例模式 (Singleton)一个类只有一个实例,提供全局访问点Spring容器中的默认Bean都是单例管理volatile synchronized 双重检查锁定,懒加载单例工厂模式 (Factory)统一管理…...
Milvus如何实现关键词过滤和向量检索的混合检索
Milvus 可以实现关键词过滤和向量检索的混合检索,具体来说,可以结合向量搜索与其他属性字段(如关键词、类别标签等)进行联合查询。这样,在检索时不仅考虑向量的相似度,还能根据特定的关键词或标签等条件对数据进行筛选,从而提高检索的精度和灵活性。 1. 理解混合检索的…...
基于Qt5的蓝牙打印开发实战:从扫描到小票打印的全流程
文章目录 前言一、应用案例演示二、开发环境搭建2.1 硬件准备2.2 软件配置 三、蓝牙通信原理剖析3.1 实现原理3.2 通信流程3.3 流程详解3.4 关键技术点 四、Qt蓝牙核心类深度解析4.1 QBluetoothDeviceDiscoveryAgent4.2 QBluetoothDeviceInfo4.3 QBluetoothSocket 五、功能实现…...
Linux日志处理命令多管道实战应用
全文目录 1 日志处理1.1 实时日志分析1.1.1 nginx日志配置1.1.2 nginx日志示例1.1.3 日志分析示例 1.2 多文件合并分析1.3 时间范围日志提取 2 问题追查2.1 进程级问题定位2.2 网络连接排查2.3 硬件故障追踪 3 数据统计3.1 磁盘空间预警3.2 进程资源消耗排名3.3 HTTP状态码统计…...
Node.js CSRF 保护指南:示例及启用方法
解释 CSRF 跨站请求伪造 (CSRF/XSRF) 是一种利用用户权限劫持会话的攻击。这种攻击策略允许攻击者通过诱骗用户以攻击者的名义提交恶意请求,从而绕过我们的安全措施。 CSRF 攻击之所以可能发生,是因为两个原因。首先,CSRF 攻击利用了用户无法辨别看似合法的 HTML 元素是否…...
线性代数—向量与矩阵的范数(Norm)
参考链接: 范数(Norm)——定义、原理、分类、作用与应用 - 知乎 带你秒懂向量与矩阵的范数(Norm)_矩阵norm-CSDN博客 什么是范数(norm)?以及L1,L2范数的简单介绍_l1 norm-CSDN博客 范数(Norm…...
微服务基础-Ribbon
1. Ribbon简介: 客户端的负载均衡: 2....
移除生产环境所有console.log
大多数团队都会要求不能在生产环境输出业务侧的内容,但是往往业务开发人员会有疏漏,所以需要在工程化环境中,整体来管理console.log。我最近也是接到这样一个需求,整理了一下实现方案。 不同团队,不同场景,…...
数字人接大模型第二步:实时语音同步
接上例第一步,还是dh_live项目,增加了一个完整的实时对话样例,包含vad-asr-llm-tts-数字人全流程,以弥补之前的只有固定的问答的不足。 VAD(Voice Activity Detection,语音活动检测)VAD用于检测用户是否正在说话,从而触发后续的语音处理流程。 ASR(Automatic Speech R…...
Tomcat的安装与配置
Tomcat Tomcat是一个Java圈子中广泛使用的HTTP服务器. 后续学习Severlet内容,就是依赖Tomcat. Java程序员,要想写个网站出来,绕不开Tomcat. 我们这里使用Tomcat8 在bin目录下,这两个文件尤为重要,需要说明的是,Tomcat是那Java写的,所以在运行时需要jdk. bat后缀:是Window…...
Spring AI Alibaba - MCP连接 MySQL
先看效果 直接问他数据库有什么表。 大模型调用MySQL进行查询 搭建项目 添加依赖 创建项目后新添加Maven 依赖: <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId> …...
Spring Cloud Stream喂饭级教程【搜集全网资料整理】
文章较长,建议收藏关注,随时查看 Spring Cloud Stream 简介 Spring Cloud Stream 是 Spring 提供的一个框架,用于构建与共享消息系统相连接的高度可伸缩的事件驱动微服务,它建立在 Spring 已有的成熟组件和最佳实践之上ÿ…...
prometheus手动添加k8s集群外的node-exporter监控
1、部署node-exporter 1)helm方式部署 rootiZj6c72dzbei17o2cuksmeZ:~# helm repo add prometheus-community https://prometheus-community.github.io/helm-charts "prometheus-community" has been added to your repositories rootiZj6c72dzbei17o2cu…...
Linux(Centos版本)中安装Docker
文章目录 Linux(Centos版本)中安装Docker整体流程 Linux(Centos版本)中安装Docker整体流程 进入root权限进行安装: 下面开始安装Docker: 1、安装docker的yum管理工具:记得将yum仓库更改为国内的镜像源&…...
C语言-- 深入理解指针(4)
C语言-- 深入理解指针(4) 一、回调函数二、冒泡排序三、qsort函数3.1 使用qsort函数排序整型数据3.2 使用qsort函数排序double数据3.3 使用qsort来排序结构体数据 四、模仿qsort库函数实现通用的冒泡排序4.1 通用冒泡排序函数排序整型数据4.2 通用冒泡排…...
牟乃夏《ArcGIS Engine地理信息系统开发教程》学习笔记3-地图基本操作与实战案例
目录 一、开发环境与框架搭建 二、地图数据加载与文档管理 1. 加载地图文档(MXD) 2. 动态添加数据源 三、地图浏览与交互操作 1. 基础导航功能 2. 书签管理 3. 量测功能 四、要素选择与属性查询 1. 属性查询 2. 空间查询 五、视图同步与鹰眼…...
Spark Streaming实时数据处理实战:从DStream基础到自定义数据源集成
park-Streaming概述 Spark-Streaming是什么 Spark Streaming 用于流式数据的处理。Spark Streaming 支持的数据输入源很多,例如:Kafka、Flume、Twitter等,以及和简单的 TCP 套接字等等。数据输入后可以用 Spark 的高度抽象原语如:…...
微软GraphRAG的安装和在RAG中的使用体会
文章目录 0. 简介(1)**技术原理**(2)**优势**(3)**开源与演进** 1. 下载graphrag.git2.安装 poetry3.初始化项目:建立cases目录4. 修改.env5.修改settings.yaml,将两处 api_base改成中转站地址:…...
Python学习记录7——集合set()的使用指南
文章目录 引言一、集合特性二、创建方式三、元素操作1、添加元素(1)add(element)(2)update(iterables) 2、删除元素(1)remove(element)(2)discard(element)(3)…...
apkpure 谷歌插件 下载的apk包
谷歌插件市场搜索 apkpure 然后直接搜索下载就行了 想看apk包中的静态资源,直接改apk 为zip后缀解压就行了 apple的ipa包也是相同的道理...
Android四大核心组件
目录 一、为什么需要四大组件? 二、Activity:看得见的界面 核心功能 生命周期图解 代码示例 三、Service:看不见的劳动者 两大类型 生命周期对比 注意陷阱 四、BroadcastReceiver:消息传递专员 两种注册方式 广播类型 …...
WSL2里手动安装Docker 遇坑
在 WSL2 里手动安装 Docker Engine 时遇坑:systemctl 和 service 命令在默认的 WSL2 Ubuntu 中 无法使用,因为 WSL2 没有 systemd。怎么办? 自己操作让 Docker Engine(dockerd)直接跑起来,挂到 /var/run/do…...
【ROS2】ROS开发环境配置——vscode和git
古月21讲-ROS2/1.系统架构/1.5_ROS2开发环境配置/ ROS机器人开发肯定离不开代码编写,课程中会给大家提供大量示例源码,这些代码如何查看、编写、编译 安Linux中安装装git sudo apt install git下载教程源码 《ROS2入门21讲》课程源码的下载方式&#x…...
django.db.models.query_utils.DeferredAttribute object
在 Django 中,当你看到 django.db.models.query_utils.DeferredAttribute 对象时,通常是因为你在查询时使用了 only() 或 defer() 方法来延迟加载某些字段。这两个方法允许你控制数据库查询中的字段加载方式,从而优化查询性能。 only() 方法…...
Linux内核中的编译时安全防护:以网络协议栈控制块校验为例
引言:内存安全的无声守卫者 在操作系统内核开发中,内存溢出引发的错误往往具有极高的隐蔽性和破坏性。Linux内核作为承载全球数十亿设备的基石,其网络协议栈的设计尤其注重内存安全性。本文通过分析一段看似简单的内核代码,揭示Linux如何通过编译时静态检查(Compile-Time…...
第11章 安全网络架构和组件(一)
11.1 OSI 模型 协议可通过网络在计算机之间进行通信。 协议是一组规则和限制,用于定义数据如何通过网络介质(如双绞线、无线传输等)进行传输。 国际标准化组织(ISO)在20世纪70年代晚期开发了开放系统互连(OSI)参考模型。 11.1.1 OSI模型的…...
Git常用命令简明教程
本教程整合并优化了Git核心命令,涵盖初始化、配置、文件操作、分支管理、远程仓库操作及常见场景,适合快速入门和日常参考。命令按使用流程分组,简洁明了,包含注意事项和最佳实践。 1. 初始化与配置 初始化Git仓库并设置基本配置…...
在 Ubuntu 24.04 系统上安装和管理 Nginx
1、安装Nginx 在Ubuntu 24.04系统上安装Nginx,可以按照下面的步骤进行: 1.1、 更新系统软件包列表 在安装新软件之前,需要先更新系统的软件包列表,确保获取到最新的软件包信息。打开终端,执行以下命令: …...
数据结构——二叉树和堆(万字,最详细)
目录 1.树 1.1 树的概念与结构 1.2 树相关的术语 1.3 树的表示法 2.二叉树 2.1 概念与结构 2.2 特殊的二叉树 2.2.1 满二叉树 2.2.2 完全二叉树 2.3 二叉树存储结构 2.3.1 顺序结构 2.3.2 实现顺序结构二叉树 2.3.2.1 堆的概念与结构 2.3.2. 2 堆的插入与删除数据…...
IdeaVim 配置与使用指南
一、什么是 IdeaVim? IdeaVim 是 JetBrains 系列 IDE(如 IntelliJ IDEA, WebStorm, PyCharm 等)中的一个插件,让你在 IDE 里使用 Vim 的按键习惯,大大提升效率。 安装方法: 在 IDE 中打开 设置(Settings) →…...
前端浏览器窗口交互完全指南:从基础操作到高级控制
浏览器窗口交互是前端开发中构建复杂Web应用的核心能力,本文深入探讨23种关键交互技术,涵盖从传统API到最新的W3C提案,助您掌握跨窗口、跨标签页的完整控制方案。 一、基础窗口操作体系 1.1 窗口创建与控制 // 新窗口创建(现代浏…...
考研系列-计算机组成原理第五章、中央处理器
一、CPU的功能及结构 1.运算器的基本结构 2.控制器结构...
python+flask+flask-sockerio,部署后sockerio通信异常
前言 用python开发了一个flask web服务,前端用html,前后端通过socketio通信,开发环境,windowsminicondavscode,开发完成后本地运行没有问题,然后就开始部署,噩梦就开始了。 问题描述 程序是部…...
深度解析:TextRenderManager——Cocos Creator艺术字体渲染核心类
一、类概述 TextRenderManager 是 Cocos Creator 中实现动态艺术字体渲染的核心单例类。它通过整合资源加载、缓存管理、异步队列和自动布局等功能,支持普通字符模式和图集模式两种渲染方案,适用于游戏中的动态文本(如聊天内容、排行榜&…...
同样开源的自动化工作流工具n8n和Dify对比
n8n和Dify作为两大主流工具,分别专注于通用自动化和AI应用开发领域,选择哪个更“好用”需结合具体需求、团队能力及业务场景综合判断。以下是核心维度的对比分析: 一、核心定位与适用场景 维度n8nDify核心定位开源全场景自动化工具ÿ…...
设计模式每日硬核训练 Day 16:责任链模式(Chain of Responsibility Pattern)完整讲解与实战应用
🔄 回顾 Day 15:享元模式小结 在 Day 15 中,我们学习了享元模式(Flyweight Pattern): 通过共享对象,分离内部状态与外部状态,大量减少内存开销。适用于字符渲染、游戏场景、图标缓…...
基于边缘人工智能的AI无人机-更高效更安全的飞行任务执行
基于边缘人工智能的AI无人机-更高效更安全的飞行任务执行 人工智能有可能改变人们的生活和工作方式。人工智能和无人机是近年来发展迅速的两项技术。当这两种技术结合在一起时,它们会创造出许多以前不可能的应用。基于人工智能的无人机旨在独立执行任务,…...
30、不是说字符串是不可变的吗,string s=“abc“;s=“123“不就是变了吗?
一、核心概念澄清:不可变性的真实含义 1、不可变性的定义 字符串不可变性指对象内容不可修改,而非变量不可修改。 类比: 不可变字符串 装在密封信封里的信纸(内容不可更改)变量赋值 更换信封的指向(从…...
线上查询车辆出险记录:快速掌握事故情况!
在如今汽车成为人们日常不可或缺的交通工具之际,车辆出险记录成为了许多车主关注的焦点之一。为了帮助车主们快速了解车辆出险、理赔、事故记录,现在有了一种便捷的方式,那就是通过API接口在线查询。本文将介绍如何利用API接口,通…...
Python爬虫课程实验指导书
1.1Requests类库的认知 1.1.1 认识请求类库 Requests是用Python语言编写,基于,采用Apache2 Licensed开源协议的。它比urllib更加方便,可以节约我们大量的工作,完全满足HTTP测试需求。urllibHTTP库 Requests官网地址:ht…...
streamlit实现非原生的按钮触发效果 + flask实现带信息的按钮触发
目录 简介不携带信息的触发隐藏指定st.button(label, key)触发button的html代码汇总 携带信息的触发为什么需要携带信息前端JavaScript修改flask处理总代码 简介 由于streamlit可以同时在实现前后端结合,非常方便,但是这也造成了user难以方便的对页面的…...
机器学习基础——Seaborn使用
1.使用tips数据集,创建一个展示不同时间段(午餐/晚餐)账单总额分布的箱线图 import matplotlib.pyplot as plt import numpy as np import pandas as pd import seaborn as snstips pd.read_csv(./tips.csv)sns.boxplot(data tips,x time,y total_bill, )plt.show() 2.使用…...
Godot开发2D冒险游戏——第三节:游戏地图绘制
一、初步构建游戏地图 在游戏场景当中添加一个新的子节点:TileMapLayer 这一层称为瓦片地图层 根据提示,下一步显然是添加资源 为TileMapLayer节点添加一个TileSet 将地板添加进来,然后选择自动分割图集 自定义时要确保大小合适 让Godot自…...
Spark Mllib 机器学习
概述 机器学习是什么 根据百度百科的定义: 机器学习是一种通过算法和模型使计算机从数据中自动学习并进行预测或决策的技术。 定义比较抽象,根据常见的机器学习可以总结出三个关键字: 算法、经验、性能。 机器学习的过程可以抽象成一个pipel…...
在windows使用docker打包springboot项目镜像并上传到阿里云
1、前提:已有spring项目 2、在项目根目录下创建Dockerfile文件 FROM openjdk:11 WORKDIR /ruoyi-admin COPY ruoyi-admin/build/libs/lifecolor-web.jar lifecolor-web.jar CMD ["java", "-jar", "lifecolor-web.jar"] 3、选…...
前端高频面试题day3
JavaScript作用域理解 核心概念 作用域:定义变量/函数的可见范围及生命周期,分为 全局作用域、函数作用域、块级作用域。作用域链:变量查找从当前作用域逐级向上直至全局,遵循词法作用域(静态作用域)。闭…...
时空特征如何融合?LSTM+Resnet有奇效,SOTA方案预测准确率超91%
LSTM有着不错的时序信息提取能力,ResNet有着不错的空间特征信息提取能力。如果现在有时空特征融合的创新需求,我们是否能将LSTM和ResNet两者的优点融合起来呢? 随着这个思路下去,LSTM ResNet混合模型横空出世,在各个…...
蓝桥杯Java全攻略:从零到一掌握竞赛与企业开发实战
蓝桥杯Java软件开发竞赛已成为全国高校学生展示编程能力的重要舞台,本指南将带您从零开始构建完整的Java知识体系,不仅覆盖蓝桥杯高频考点,还延伸至企业级开发实战,助您在竞赛中脱颖而出并为未来职业发展奠定坚实基础。 一、Java基础语法与数据结构 竞赛解题流程图设计 蓝…...
【Nginx】负载均衡配置详解
Nginx作为高性能的HTTP服务器和反向代理服务器,提供了强大的负载均衡功能。本文将详细介绍Nginx负载均衡的配置方法和相关策略。 一、基础负载均衡配置 1.单服务示例配置 配置nginx.conf模块 在Nginx配置文件中定义upstream模块: worker_processes a…...
打造企业级AI文案助手:GPT-J+Flask全栈开发实战
一、智能文案革命的序幕:为什么需要AI文案助手? 在数字化营销时代,内容生产效率成为企业核心竞争力。据统计,营销人员平均每天需要撰写3.2篇文案,而传统人工创作存在三大痛点: 效率瓶颈:创意构…...