当前位置: 首页 > news >正文

设计模式-模板方法模式详解

模板方法模式详解

目录

  • 1. 模板方法模式简介
  • 2. 核心流程
  • 3. 重难点分析
  • 4. Spring中的源码分析
  • 5. 面试高频点

1. 模板方法模式简介

1.1 定义

模板方法模式(Template Method Pattern)是一种行为型设计模式,它定义了一个算法的骨架,将一些步骤延迟到子类中实现。模板方法使得子类可以不改变算法的结构,就可以重定义算法的某些特定步骤。

1.2 核心思想

  • 算法骨架固定:定义算法的主要流程,不允许子类改变
  • 步骤可定制:将算法中的某些步骤抽象出来,由子类实现
  • 代码复用:避免重复代码,提高代码复用性
  • 扩展性:通过子类扩展新的实现方式

1.3 适用场景

  • 多个类有相同的算法结构,但具体实现不同
  • 需要控制子类扩展的范围
  • 需要提取公共行为到父类中
  • 框架设计中的钩子方法(Hook Method)

1.4 模板方法模式结构

类图结构
«abstract»
AbstractClass
+templateMethod()
+primitiveOperation1()
+primitiveOperation2()
+concreteOperation()
+hook()
ConcreteClassA
+primitiveOperation1()
+primitiveOperation2()
+hook()
ConcreteClassB
+primitiveOperation1()
+primitiveOperation2()
+hook()
核心组件
  1. AbstractClass(抽象类):定义模板方法和抽象方法
  2. ConcreteClass(具体类):实现抽象方法
  3. Template Method(模板方法):定义算法骨架
  4. Primitive Operations(原语操作):抽象方法,由子类实现
  5. Hook Methods(钩子方法):可选方法,子类可以重写

1.5 模板方法模式分类

基本模板方法模式
// 抽象类定义模板方法
public abstract class AbstractClass {// 模板方法,定义算法骨架public final void templateMethod() {primitiveOperation1();primitiveOperation2();concreteOperation();if (hook()) {hookOperation();}}// 抽象方法,由子类实现protected abstract void primitiveOperation1();protected abstract void primitiveOperation2();// 具体方法,在抽象类中实现protected void concreteOperation() {System.out.println("具体操作");}// 钩子方法,子类可以重写protected boolean hook() {return true;}protected void hookOperation() {System.out.println("钩子操作");}
}
带钩子的模板方法模式
// 带钩子方法的模板方法
public abstract class AbstractClassWithHooks {public final void templateMethod() {beforeOperation();doOperation();afterOperation();if (needAdditionalOperation()) {additionalOperation();}}protected abstract void doOperation();// 钩子方法protected void beforeOperation() {// 默认空实现}protected void afterOperation() {// 默认空实现}protected boolean needAdditionalOperation() {return false;}protected void additionalOperation() {// 默认空实现}
}

2. 核心流程

2.1 模板方法执行流程

客户端抽象类具体类调用templateMethod()执行primitiveOperation1()调用子类实现返回结果执行primitiveOperation2()调用子类实现返回结果执行concreteOperation()检查hook()条件执行hookOperation()alt[hook()返回tru-e]返回最终结果客户端抽象类具体类

2.2 模板方法设计流程

步骤1:识别算法结构
// 识别需要模板化的算法
public class AlgorithmExample {public void processData() {// 步骤1:初始化initialize();// 步骤2:验证数据validateData();// 步骤3:处理数据processData();// 步骤4:保存结果saveResult();// 步骤5:清理资源cleanup();}private void initialize() { /* 具体实现 */ }private void validateData() { /* 具体实现 */ }private void processData() { /* 具体实现 */ }private void saveResult() { /* 具体实现 */ }private void cleanup() { /* 具体实现 */ }
}
步骤2:提取抽象类
// 提取抽象类,定义模板方法
public abstract class DataProcessor {// 模板方法,定义算法骨架public final void processData() {initialize();validateData();doProcessData();saveResult();cleanup();}// 具体方法,所有子类共享protected void initialize() {System.out.println("初始化数据处理器");}protected void validateData() {System.out.println("验证数据格式");}protected void saveResult() {System.out.println("保存处理结果");}protected void cleanup() {System.out.println("清理资源");}// 抽象方法,由子类实现protected abstract void doProcessData();
}
步骤3:实现具体类
// 实现具体的数据处理器
public class XmlDataProcessor extends DataProcessor {@Overrideprotected void doProcessData() {System.out.println("处理XML数据");}
}public class JsonDataProcessor extends DataProcessor {@Overrideprotected void doProcessData() {System.out.println("处理JSON数据");}
}public class CsvDataProcessor extends DataProcessor {@Overrideprotected void doProcessData() {System.out.println("处理CSV数据");}
}
步骤4:客户端调用
// 客户端使用模板方法
public class Client {public static void main(String[] args) {DataProcessor xmlProcessor = new XmlDataProcessor();xmlProcessor.processData();DataProcessor jsonProcessor = new JsonDataProcessor();jsonProcessor.processData();DataProcessor csvProcessor = new CsvDataProcessor();csvProcessor.processData();}
}

2.3 钩子方法使用流程

返回true
返回false
开始模板方法
执行固定步骤1
执行固定步骤2
检查钩子方法
执行钩子操作
跳过钩子操作
执行固定步骤3
结束
钩子方法示例
// 带钩子方法的抽象类
public abstract class AbstractProcessor {public final void process() {beforeProcess();doProcess();afterProcess();// 钩子方法:是否需要额外处理if (needAdditionalProcess()) {additionalProcess();}// 钩子方法:是否需要日志记录if (needLogging()) {logProcess();}}protected abstract void doProcess();// 钩子方法,子类可以重写protected void beforeProcess() {System.out.println("默认前置处理");}protected void afterProcess() {System.out.println("默认后置处理");}protected boolean needAdditionalProcess() {return false;}protected void additionalProcess() {System.out.println("默认额外处理");}protected boolean needLogging() {return true;}protected void logProcess() {System.out.println("记录处理日志");}
}

3. 重难点分析

3.1 重难点一:模板方法的设计原则

难点分析
  • 算法稳定性:模板方法定义的算法骨架不能被子类改变
  • 扩展性平衡:既要保证算法稳定,又要允许子类扩展
  • 方法粒度:如何合理划分抽象方法和具体方法
解决方案
// 良好的模板方法设计
public abstract class AbstractTemplate {// 模板方法使用final,防止子类重写public final void templateMethod() {// 1. 固定的前置处理beforeProcess();// 2. 核心业务逻辑,由子类实现doProcess();// 3. 固定的后置处理afterProcess();// 4. 可选的钩子方法if (needAdditionalProcess()) {additionalProcess();}}// 抽象方法:核心业务逻辑protected abstract void doProcess();// 具体方法:固定逻辑,子类不能改变private void beforeProcess() {System.out.println("固定前置处理");}private void afterProcess() {System.out.println("固定后置处理");}// 钩子方法:可选扩展点protected boolean needAdditionalProcess() {return false;}protected void additionalProcess() {// 默认空实现}
}

3.2 重难点二:钩子方法的设计和使用

难点分析
  • 钩子方法命名:如何命名钩子方法,使其意图清晰
  • 钩子方法粒度:钩子方法应该提供多大的扩展能力
  • 钩子方法顺序:多个钩子方法的执行顺序如何设计
解决方案
// 钩子方法设计示例
public abstract class AbstractProcessor {public final void process() {// 主流程initialize();validate();processData();save();cleanup();// 钩子方法:可选的扩展点if (needAudit()) {audit();}if (needNotification()) {notify();}if (needMetrics()) {recordMetrics();}}protected abstract void processData();// 钩子方法:审计protected boolean needAudit() {return false;}protected void audit() {System.out.println("执行审计");}// 钩子方法:通知protected boolean needNotification() {return true; // 默认需要通知}protected void notify() {System.out.println("发送通知");}// 钩子方法:指标记录protected boolean needMetrics() {return false;}protected void recordMetrics() {System.out.println("记录指标");}
}

3.3 重难点三:模板方法模式的扩展性设计

难点分析
  • 扩展点设计:如何设计合理的扩展点
  • 版本兼容性:如何保证模板方法的向后兼容
  • 性能考虑:钩子方法对性能的影响
解决方案
// 可扩展的模板方法设计
public abstract class AbstractProcessor {// 模板方法,支持版本控制public final void process() {process(ProcessVersion.V1);}// 支持版本控制的模板方法public final void process(ProcessVersion version) {beforeProcess(version);switch (version) {case V1:processV1();break;case V2:processV2();break;default:processV1();}afterProcess(version);}// 版本1的处理流程private void processV1() {doProcess();if (needAdditionalProcess()) {additionalProcess();}}// 版本2的处理流程private void processV2() {doProcess();if (needAdditionalProcess()) {additionalProcess();}if (needAdvancedProcess()) {advancedProcess();}}protected abstract void doProcess();// 钩子方法:版本控制protected void beforeProcess(ProcessVersion version) {// 默认实现}protected void afterProcess(ProcessVersion version) {// 默认实现}protected boolean needAdditionalProcess() {return false;}protected void additionalProcess() {// 默认实现}protected boolean needAdvancedProcess() {return false;}protected void advancedProcess() {// 默认实现}
}// 版本枚举
public enum ProcessVersion {V1, V2
}

3.4 重难点四:模板方法模式的测试

难点分析
  • 抽象类测试:如何测试抽象类
  • 模板方法测试:如何测试模板方法的执行流程
  • 钩子方法测试:如何测试钩子方法的各种情况
解决方案
// 测试抽象类的模板方法
public class AbstractProcessorTest {@Testpublic void testTemplateMethod() {// 创建测试用的具体实现AbstractProcessor processor = new AbstractProcessor() {@Overrideprotected void doProcess() {System.out.println("测试处理");}@Overrideprotected boolean needAdditionalProcess() {return true;}@Overrideprotected void additionalProcess() {System.out.println("测试额外处理");}};// 测试模板方法processor.process();// 验证执行结果// 可以使用Mock对象验证方法调用}@Testpublic void testHookMethods() {// 测试钩子方法的不同情况AbstractProcessor processor1 = new AbstractProcessor() {@Overrideprotected void doProcess() {// 实现}@Overrideprotected boolean needAdditionalProcess() {return false; // 不执行额外处理}};AbstractProcessor processor2 = new AbstractProcessor() {@Overrideprotected void doProcess() {// 实现}@Overrideprotected boolean needAdditionalProcess() {return true; // 执行额外处理}};// 分别测试两种情况processor1.process();processor2.process();}
}

4. Spring中的源码分析

4.1 Spring中的模板方法模式应用

4.1.1 JdbcTemplate中的模板方法
// JdbcTemplate的核心模板方法
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {// 查询模板方法public <T> T query(String sql, ResultSetExtractor<T> rse) throws DataAccessException {return query(sql, rse, (Object[]) null);}public <T> T query(String sql, ResultSetExtractor<T> rse, Object... args) throws DataAccessException {return query(sql, rse, newArgPreparedStatementSetter(args));}public <T> T query(String sql, ResultSetExtractor<T> rse, PreparedStatementSetter pss) throws DataAccessException {return query(new SimplePreparedStatementCreator(sql), pss, rse);}// 核心模板方法实现public <T> T query(PreparedStatementCreator psc, PreparedStatementSetter pss, ResultSetExtractor<T> rse) throws DataAccessException {return execute(psc, new PreparedStatementCallback<T>() {@Overridepublic T doInPreparedStatement(PreparedStatement ps) throws SQLException {ResultSet rs = null;try {if (pss != null) {pss.setValues(ps);}rs = ps.executeQuery();return rse.extractData(rs);} finally {JdbcUtils.closeResultSet(rs);}}});}// 执行模板方法public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action) throws DataAccessException {return execute(psc, action, true);}public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action, boolean closeResources) throws DataAccessException {if (logger.isDebugEnabled()) {String sql = getSql(psc);logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : ""));}Connection con = null;PreparedStatement ps = null;try {// 获取连接con = getConnection();// 创建PreparedStatementps = psc.createPreparedStatement(con);// 应用语句设置器applyStatementSettings(ps);// 执行回调T result = action.doInPreparedStatement(ps);// 处理警告handleWarnings(ps);return result;} catch (SQLException ex) {// 释放资源if (closeResources) {JdbcUtils.closeStatement(ps);ps = null;}// 转换异常throw translateException("PreparedStatementCallback", sql, ex);} finally {if (closeResources) {JdbcUtils.closeStatement(ps);JdbcUtils.closeConnection(con);}}}
}
4.1.2 AbstractApplicationContext中的模板方法
// AbstractApplicationContext的模板方法
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {// 刷新容器的模板方法@Overridepublic void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// 准备刷新prepareRefresh();// 获取BeanFactoryConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// 准备BeanFactoryprepareBeanFactory(beanFactory);try {// 允许BeanFactory的后处理postProcessBeanFactory(beanFactory);// 调用BeanFactoryPostProcessorsinvokeBeanFactoryPostProcessors(beanFactory);// 注册BeanPostProcessorsregisterBeanPostProcessors(beanFactory);// 初始化MessageSourceinitMessageSource();// 初始化ApplicationEventMulticasterinitApplicationEventMulticaster();// 刷新特定上下文子类onRefresh();// 检查监听器Bean并注册registerListeners();// 实例化所有非懒加载的单例BeanfinishBeanFactoryInitialization(beanFactory);// 完成刷新finishRefresh();} catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}// 销毁已创建的BeandestroyBeans();// 重置'active'标志cancelRefresh(ex);throw ex;} finally {// 重置公共内省缓存resetCommonCaches();}}}// 钩子方法:刷新特定上下文子类protected void onRefresh() throws BeansException {// 默认空实现,子类可以重写}// 钩子方法:准备刷新protected void prepareRefresh() {this.startupDate = System.currentTimeMillis();this.closed.set(false);this.active.set(true);if (logger.isInfoEnabled()) {logger.info("Refreshing " + this);}// 初始化属性源initPropertySources();// 验证必需属性getEnvironment().validateRequiredProperties();}
}
4.1.3 AbstractBeanFactory中的模板方法
// AbstractBeanFactory的模板方法
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {// 获取Bean的模板方法@Overridepublic Object getBean(String name) throws BeansException {return doGetBean(name, null, null, false);}// 核心模板方法protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {final String beanName = transformedBeanName(name);Object bean;// 尝试从缓存中获取单例BeanObject sharedInstance = getSingleton(beanName);if (sharedInstance != null && args == null) {bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);} else {// 检查Bean定义是否存在if (isPrototypeCurrentlyInCreation(beanName)) {throw new BeanCurrentlyInCreationException(beanName);}// 检查父BeanFactoryBeanFactory parentBeanFactory = getParentBeanFactory();if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {String nameToLookup = originalBeanName(name);if (parentBeanFactory instanceof AbstractBeanFactory) {return ((AbstractBeanFactory) parentBeanFactory).doGetBean(nameToLookup, requiredType, args, typeCheckOnly);} else if (args != null) {return (T) parentBeanFactory.getBean(nameToLookup, args);} else {return parentBeanFactory.getBean(nameToLookup, requiredType);}}if (!typeCheckOnly) {markBeanAsCreated(beanName);}try {final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);checkMergedBeanDefinition(mbd, beanName, args);// 确保依赖的Bean被初始化String[] dependsOn = mbd.getDependsOn();if (dependsOn != null) {for (String dep : dependsOn) {if (isDependent(beanName, dep)) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");}registerDependentBean(dep, beanName);try {getBean(dep);} catch (NoSuchBeanDefinitionException ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"'" + beanName + "' depends on missing bean '" + dep + "'", ex);}}}// 创建Bean实例if (mbd.isSingleton()) {sharedInstance = getSingleton(beanName, () -> {try {return createBean(beanName, mbd, args);} catch (BeansException ex) {destroySingleton(beanName);throw ex;}});bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);} else if (mbd.isPrototype()) {Object prototypeInstance = null;try {beforePrototypeCreation(beanName);prototypeInstance = createBean(beanName, mbd, args);} finally {afterPrototypeCreation(beanName);}bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);} else {String scopeName = mbd.getScope();final Scope scope = this.scopes.get(scopeName);if (scope == null) {throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");}try {Object scopedInstance = scope.get(beanName, () -> {beforePrototypeCreation(beanName);try {return createBean(beanName, mbd, args);} finally {afterPrototypeCreation(beanName);}});bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);} catch (IllegalStateException ex) {throw new BeanCreationException(beanName, "Scope '" + scopeName + "' is not active for the current thread; consider " +"defining a scoped proxy for this bean if you intend to refer to it from a singleton", ex);}}} catch (BeansException ex) {if (containsSingleton(beanName)) {throw ex;}throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid bean definition with name '" + beanName + "'", ex);}}return adaptBeanInstance(name, bean, requiredType);}// 抽象方法:创建Bean实例protected abstract Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)throws BeanCreationException;// 钩子方法:原型Bean创建前protected void beforePrototypeCreation(String beanName) {// 默认空实现}// 钩子方法:原型Bean创建后protected void afterPrototypeCreation(String beanName) {// 默认空实现}
}

4.2 Spring模板方法模式的特点

4.2.1 模板方法设计特点
  1. 算法骨架固定:核心流程不允许子类改变
  2. 扩展点丰富:提供多个钩子方法供子类扩展
  3. 异常处理完善:统一的异常处理机制
  4. 资源管理规范:统一的资源获取和释放
4.2.2 钩子方法使用特点
// Spring中钩子方法的典型使用
public abstract class AbstractApplicationContext {// 钩子方法:刷新特定上下文子类protected void onRefresh() throws BeansException {// 默认空实现,子类可以重写}// 钩子方法:准备刷新protected void prepareRefresh() {// 默认实现,子类可以重写}// 钩子方法:完成刷新protected void finishRefresh() {// 默认实现,子类可以重写}
}// 具体实现类
public class GenericApplicationContext extends AbstractApplicationContext {@Overrideprotected void onRefresh() throws BeansException {// 特定上下文的刷新逻辑super.onRefresh();}@Overrideprotected void prepareRefresh() {// 特定上下文的准备逻辑super.prepareRefresh();}
}

5. 面试高频点

5.1 基础概念类问题

Q1: 什么是模板方法模式?有什么特点?

答案要点:

  • 定义:定义算法骨架,将步骤延迟到子类实现
  • 特点:算法结构固定,步骤可定制,代码复用
  • 核心:模板方法 + 抽象方法 + 钩子方法
Q2: 模板方法模式与策略模式的区别?

答案要点:

  • 模板方法模式:算法结构固定,步骤可定制
  • 策略模式:算法完全可替换
  • 使用场景:模板方法用于算法结构相同,策略模式用于算法完全不同

5.2 设计原理类问题

Q3: 模板方法模式的核心组件有哪些?

答案要点:

1. AbstractClass(抽象类)- templateMethod():模板方法- primitiveOperation():抽象方法- concreteOperation():具体方法- hook():钩子方法2. ConcreteClass(具体类)- 实现抽象方法- 可选重写钩子方法
Q4: 钩子方法的作用是什么?

答案要点:

  • 扩展点:提供可选的扩展能力
  • 控制流程:控制某些步骤是否执行
  • 定制行为:允许子类定制特定行为
  • 向后兼容:保证模板方法的向后兼容性

5.3 实现细节类问题

Q5: 如何设计一个好的模板方法?

答案要点:

// 设计原则
1. 模板方法使用final,防止子类重写
2. 抽象方法定义核心业务逻辑
3. 具体方法实现固定逻辑
4. 钩子方法提供扩展点
5. 合理的方法粒度划分
Q6: 模板方法模式如何保证算法稳定性?

答案要点:

  • final关键字:模板方法使用final防止重写
  • 访问控制:合理使用访问修饰符
  • 方法设计:将可变部分抽象为方法
  • 文档规范:明确说明哪些方法可以重写

5.4 Spring应用类问题

Q7: Spring中哪些地方使用了模板方法模式?

答案要点:

1. JdbcTemplate:数据库操作模板
2. AbstractApplicationContext:应用上下文刷新
3. AbstractBeanFactoryBean创建和管理
4. AbstractTransactionalDataSource:事务管理
5. AbstractControllerWeb控制器基类
Q8: Spring的JdbcTemplate是如何使用模板方法模式的?

答案要点:

// 核心流程
1. 获取数据库连接
2. 创建PreparedStatement
3. 设置参数
4. 执行SQL(抽象方法)
5. 处理结果集
6. 释放资源

5.5 实际应用类问题

Q9: 在项目中如何应用模板方法模式?

答案要点:

// 应用场景
1. 数据处理流程:数据验证、处理、保存
2. 文件处理流程:读取、解析、转换、输出
3. 业务流程:审批、执行、记录、通知
4. 框架设计:提供扩展点的框架
Q10: 模板方法模式的优缺点是什么?

答案要点:
优点:

  • 代码复用,避免重复代码
  • 算法结构稳定,易于维护
  • 扩展性好,支持子类扩展
  • 符合开闭原则

缺点:

  • 类数量增加,系统复杂度提高
  • 继承关系,子类与父类耦合
  • 模板方法修改影响所有子类
  • 调试困难,流程分散在多个类中

5.6 源码分析类问题

Q11: Spring的AbstractApplicationContext.refresh()方法是如何设计的?

答案要点:

// 设计特点
1. 使用synchronized保证线程安全
2. 定义完整的容器刷新流程
3. 提供多个钩子方法供子类扩展
4. 统一的异常处理和资源管理
5. 支持不同应用上下文的定制
Q12: 如何测试模板方法模式?

答案要点:

// 测试策略
1. 测试抽象类:创建测试用的具体实现
2. 测试模板方法:验证执行流程
3. 测试钩子方法:测试不同条件分支
4. 使用Mock对象:验证方法调用
5. 集成测试:测试完整流程

5.7 设计模式对比类问题

Q13: 模板方法模式与其他模式的区别?

答案要点:

模式关系区别
策略模式组合算法完全可替换 vs 算法结构固定
工厂方法继承创建对象 vs 定义算法
命令模式组合请求封装 vs 算法定义
责任链组合处理链 vs 算法步骤
Q14: 什么时候使用模板方法模式?

答案要点:

// 使用场景判断
if (多个类有相同算法结构) {if (算法结构稳定) {if (需要子类定制步骤) {使用模板方法模式;}}
}// 具体场景
1. 框架设计:提供扩展点
2. 业务流程:标准化流程
3. 数据处理:统一处理流程
4. 算法实现:算法结构相同

总结

模板方法模式是Spring框架中广泛使用的设计模式,它通过定义算法骨架和提供扩展点,实现了代码复用和灵活扩展的平衡。

关键要点

  1. 理解原理:掌握模板方法模式的核心思想和设计原则
  2. 掌握实现:学会设计模板方法和钩子方法
  3. Spring应用:理解Spring中模板方法模式的应用
  4. 实际应用:能够在项目中合理使用模板方法模式
  5. 测试方法:掌握模板方法模式的测试策略

通过深入学习模板方法模式,可以更好地理解Spring框架的设计思想,提升代码的可维护性和扩展性。

http://www.dtcms.com/a/389260.html

相关文章:

  • Red Hat 8.5.0-18 部署ceph文件系统
  • 将ceph文件存储挂载给k8s使用
  • ENVI系列教程(七)——自定义 RPC 文件图像正射校正
  • 「Java EE开发指南」如何用MyEclipse开发Java EE企业应用程序?(二)
  • Linux -- 传输层协议UDP
  • 使用Android Studio中自带的手机投屏功能
  • LeetCode:19.螺旋矩阵
  • Windows 命令行:在 cd 命令中使用绝对路径与相对路径
  • 图片修改尺寸
  • 《嵌入式硬件(十五):基于IMX6ULL的统一异步收发器(UART)的操作》
  • Python爬虫实战:研究Pandas,构建苏宁易购月饼销售数据采集与智能推荐系统
  • 导购app佣金模式的分布式计算架构:实时分账与财务对账
  • Linux Bash脚本自动创建keystore和生成公钥
  • 数据库管理员偏爱哪些MySQL数据库连接工具?
  • 大数据毕业设计选题推荐-基于大数据的农产品交易数据分析与可视化系统-Spark-Hadoop-Bigdata
  • MySQL C API 的“连接孵化器”-`mysql_init()`
  • oracle 数据库导入dmp文件
  • 第二部分:VTK核心类详解(第28章 vtkMatrix4x4矩阵类)
  • JDK、JRE、JVM 是什么?有什么关系?【Java】
  • Visual Studio 2022创建CPP项目
  • Nginx反向代理+负载均衡
  • React Suspense底层原理揭秘
  • 关于pycharm高版本导入torch的问题
  • 【硬件研讨】【笔记本电脑】给老ThinkPad升级内存
  • 论文Review 3DGS SuGaR | CVPR 2024 | 3DGS 转 Mesh 开源方案!!
  • Makefile学习(一)- 基础规则
  • 动态代理 设计模式
  • APP小程序被攻击了该如何应对
  • 零基础从头教学Linux(Day 37)
  • ADB 在嵌入式 Linux 系统调试中的应用