AI摘要
我以为模板方法模式只是个教科书上的概念,直到我在Spring的Bean创建过程中踩了一个坑——自定义的初始化逻辑总是莫名其妙地执行了两次,才意识到这个看似简单的模式背后藏着Spring框架的设计精髓。
那是在我们重构一个老项目时遇到的问题。我们需要在Bean初始化时加载一些外部配置,于是很自然地在@PostConstruct方法里写初始化逻辑。但测试时发现这个方法被执行了两次。排查了半天,才发现是因为我们同时实现了InitializingBean接口,而Spring的模板方法模式确保了初始化逻辑的固定执行顺序。这次经历让我决心深入研究Spring源码中的模板方法模式。
一、从一个初始化方法执行两次的问题说起
1.1 问题代码重现
先看看当时有问题的代码:
@Component
@Slf4j
public class ConfigLoader implements InitializingBean {
private Map<String, String> configMap;
@PostConstruct
public void init() {
log.info("@PostConstruct方法执行:开始加载配置");
loadConfig();
}
@Override
public void afterPropertiesSet() throws Exception {
log.info("InitializingBean.afterPropertiesSet()方法执行:开始加载配置");
loadConfig(); // 这里又被执行了一次!
}
private void loadConfig() {
// 从数据库加载配置
configMap = configRepository.loadAllConfigs();
log.info("配置加载完成,共{}条配置", configMap.size());
}
@Bean
public SomeBean someBean() {
return new SomeBean(configMap);
}
}运行结果出乎意料:
2022-05-15 14:32:03 INFO ConfigLoader - @PostConstruct方法执行:开始加载配置
2022-05-15 14:32:03 INFO ConfigLoader - 配置加载完成,共50条配置
2022-05-15 14:32:03 INFO ConfigLoader - InitializingBean.afterPropertiesSet()方法执行:开始加载配置
2022-05-15 14:32:03 INFO ConfigLoader - 配置加载完成,共50条配置配置被加载了两次! 这不仅浪费资源,还可能导致并发问题。
1.2 问题的根本原因
问题的根源在于对Spring Bean初始化流程理解不足。Spring使用模板方法模式定义了Bean的创建流程,这个流程中的每个步骤都有固定的执行顺序。让我带你看一下Spring源码中的关键部分:
// Spring Bean创建的核心流程
public abstract class AbstractAutowireCapableBeanFactory {
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {
// 1. 实例化Bean
BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
// 2. 属性注入
populateBean(beanName, mbd, instanceWrapper);
// 3. 初始化Bean - 这里就是模板方法模式的核心
Object exposedObject = initializeBean(beanName, exposedObject, mbd);
return exposedObject;
}
protected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) {
// 3.1 Aware接口回调
invokeAwareMethods(beanName, bean);
// 3.2 BeanPostProcessor前置处理
Object wrappedBean = applyBeanPostProcessorsBeforeInitialization(bean, beanName);
// 3.3 调用初始化方法 - 模板方法
invokeInitMethods(beanName, wrappedBean, mbd);
// 3.4 BeanPostProcessor后置处理
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
return wrappedBean;
}
}二、模板方法模式深度解析
2.1 模板方法模式的定义
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
// 模板方法模式的通用结构
public abstract class AbstractTemplate {
// 模板方法 - 定义算法骨架(通常用final修饰,防止子类修改)
public final void templateMethod() {
step1(); // 固定步骤
step2(); // 固定步骤
hookMethod(); // 钩子方法(可选步骤)
if (hook()) { // 钩子方法控制流程
step3(); // 条件执行步骤
}
primitiveOperation1(); // 抽象方法,由子类实现
primitiveOperation2(); // 抽象方法,由子类实现
}
// 固定实现的方法
private void step1() {
System.out.println("执行步骤1");
}
private void step2() {
System.out.println("执行步骤2");
}
// 钩子方法 - 空实现,子类可以覆盖
protected void hookMethod() {
// 空实现,子类可以选择性覆盖
}
// 钩子方法 - 返回布尔值控制流程
protected boolean hook() {
return true; // 默认返回true
}
// 抽象方法 - 必须由子类实现
protected abstract void primitiveOperation1();
protected abstract void primitiveOperation2();
}2.2 Spring中的模板方法模式应用场景
Spring框架中大量使用了模板方法模式,主要应用在:
- Bean生命周期管理 -
AbstractAutowireCapableBeanFactory - 事务管理 -
PlatformTransactionManager - 数据访问 -
JdbcTemplate - 缓存抽象 -
CacheManager - 安全框架 -
AbstractSecurityInterceptor
三、深入Spring源码:Bean创建流程中的模板方法
3.1 核心模板方法:doCreateBean()
让我们深入Spring源码,看看模板方法模式的具体实现:
public abstract class AbstractAutowireCapableBeanFactory
extends AbstractBeanFactory implements AutowireCapableBeanFactory {
/**
* 创建Bean的核心模板方法
* 这个方法定义了Bean创建的完整算法骨架
*/
@Override
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {
// 声明BeanWrapper,用于包装创建的Bean实例
BeanWrapper instanceWrapper = null;
// 步骤1:实例化Bean - 可能是工厂方法、构造器或通过Supplier
if (mbd.isSingleton()) {
// 从缓存中移除正在创建的Bean
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
// 调用模板方法:创建Bean实例
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
// 步骤2:应用MergedBeanDefinitionPostProcessor
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
// 调用模板方法:后处理合并的Bean定义
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
} catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Post-processing of merged bean definition failed", ex);
}
mbd.postProcessed = true;
}
}
// 步骤3:解决循环依赖 - 早期引用暴露
boolean earlySingletonExposure = (mbd.isSingleton() &&
this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
// 将早期Bean引用添加到SingletonFactory中
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
Object exposedObject = bean;
try {
// 步骤4:属性注入 - 调用模板方法
populateBean(beanName, mbd, instanceWrapper);
// 步骤5:初始化Bean - 调用模板方法
exposedObject = initializeBean(beanName, exposedObject, mbd);
} catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
} else {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Initialization of bean failed", ex);
}
}
// 步骤6:处理循环依赖的早期引用
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
} else if (!this.allowRawInjectionDespiteWrapping &&
hasDependentBean(beanName)) {
// 处理依赖关系...
}
}
}
// 步骤7:注册可销毁的Bean
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
} catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Invalid destruction signature", ex);
}
return exposedObject;
}
}3.2 初始化阶段的模板方法:invokeInitMethods()
这是初始化阶段的核心,也是我们问题发生的地方:
public abstract class AbstractAutowireCapableBeanFactory {
/**
* 初始化方法的调用 - 模板方法
*/
protected void invokeInitMethods(String beanName, Object bean, RootBeanDefinition mbd)
throws Throwable {
// 判断Bean是否实现了InitializingBean接口
boolean isInitializingBean = (bean instanceof InitializingBean);
// 如果实现了InitializingBean接口且不是由外部管理的初始化方法
if (isInitializingBean &&
!(mbd != null && mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
if (logger.isTraceEnabled()) {
logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
}
// 调用afterPropertiesSet()方法
((InitializingBean) bean).afterPropertiesSet();
}
// 处理自定义的init-method
if (mbd != null && bean.getClass() != NullBean.class) {
String initMethodName = mbd.getInitMethodName();
if (StringUtils.hasLength(initMethodName) &&
!(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
!mbd.isExternallyManagedInitMethod(initMethodName)) {
// 通过反射调用init-method
invokeCustomInitMethod(beanName, bean, mbd);
}
}
}
/**
* 调用自定义的初始化方法
*/
protected void invokeCustomInitMethod(String beanName, Object bean, RootBeanDefinition mbd)
throws Throwable {
// 获取初始化方法
Method initMethod = mbd.getResolvedInitMethod();
if (initMethod == null) {
initMethod = ClassUtils.getMethodIfAvailable(
bean.getClass(), mbd.getInitMethodName());
if (initMethod == null) {
throw new NoSuchMethodException("No init method found");
}
mbd.resolvedInitMethod = initMethod;
}
// 执行初始化方法
ReflectionUtils.makeAccessible(initMethod);
initMethod.invoke(bean);
}
}3.3 @PostConstruct的处理机制
那么@PostConstruct是在哪里处理的呢?这涉及到另一个重要的接口BeanPostProcessor:
public class InitDestroyAnnotationBeanPostProcessor
implements DestructionAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor {
/**
* 在初始化之前执行 - 处理@PostConstruct
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
// 查找Bean中所有@PostConstruct注解的方法
LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
try {
// 执行@PostConstruct方法
metadata.invokeInitMethods(bean, beanName);
} catch (InvocationTargetException ex) {
throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException());
} catch (Throwable ex) {
throw new BeanCreationException(beanName, "Failed to invoke init method", ex);
}
return bean;
}
/**
* 查找生命周期元数据
*/
private LifecycleMetadata findLifecycleMetadata(Class<?> clazz) {
if (this.lifecycleMetadataCache == null) {
return buildLifecycleMetadata(clazz);
}
LifecycleMetadata metadata = this.lifecycleMetadataCache.get(clazz);
if (metadata == null) {
synchronized (this.lifecycleMetadataCache) {
metadata = this.lifecycleMetadataCache.get(clazz);
if (metadata == null) {
metadata = buildLifecycleMetadata(clazz);
this.lifecycleMetadataCache.put(clazz, metadata);
}
}
}
return metadata;
}
/**
* 构建生命周期元数据
*/
private LifecycleMetadata buildLifecycleMetadata(Class<?> clazz) {
List<LifecycleElement> initMethods = new ArrayList<>();
// 递归查找父类中的@PostConstruct方法
Class<?> targetClass = clazz;
do {
List<LifecycleElement> currInitMethods = new ArrayList<>();
// 查找所有@PostConstruct注解的方法
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
if (method.isAnnotationPresent(PostConstruct.class)) {
LifecycleElement element = new LifecycleElement(method);
currInitMethods.add(element);
}
});
initMethods.addAll(0, currInitMethods);
targetClass = targetClass.getSuperclass();
} while (targetClass != null && targetClass != Object.class);
return new LifecycleMetadata(clazz, initMethods, Collections.emptyList());
}
}四、解决初始化方法执行两次的问题
理解了Spring的初始化流程后,我们可以正确重写代码:
@Component
@Slf4j
public class ConfigLoader implements InitializingBean {
private Map<String, String> configMap;
// 方案1:只使用@PostConstruct
@PostConstruct
public void init() {
log.info("初始化配置");
loadConfig();
}
// 方案2:只实现InitializingBean接口
// @Override
// public void afterPropertiesSet() throws Exception {
// log.info("初始化配置");
// loadConfig();
// }
// 方案3:明确区分不同的初始化阶段(推荐)
@PostConstruct
public void postConstruct() {
log.info("@PostConstruct阶段:执行前置初始化");
preLoadConfig();
}
@Override
public void afterPropertiesSet() throws Exception {
log.info("afterPropertiesSet阶段:执行主初始化");
loadConfig();
}
private void preLoadConfig() {
// 加载基本配置
log.info("加载基本配置");
}
private void loadConfig() {
// 加载完整配置
configMap = configRepository.loadAllConfigs();
log.info("配置加载完成,共{}条配置", configMap.size());
}
}执行顺序:
@PostConstruct注解的方法(通过BeanPostProcessor)InitializingBean.afterPropertiesSet()方法- 自定义的
init-method
五、实战:基于模板方法模式实现自定义框架
理解了Spring的模板方法模式后,我在实际项目中应用了这个模式。我们公司有个需求:统一处理各种外部API的调用,但不同的API有不同的认证、参数组装和错误处理逻辑。
5.1 设计API调用模板
/**
* API调用模板 - 抽象类定义算法骨架
*/
public abstract class AbstractApiTemplate<T, R> {
/**
* 模板方法 - 定义API调用的完整流程(final防止子类修改)
*/
public final ApiResponse<R> execute(ApiRequest<T> request) {
// 1. 参数验证
validateRequest(request);
// 2. 构建请求
HttpRequest httpRequest = buildRequest(request);
// 3. 执行请求
HttpResponse httpResponse = executeRequest(httpRequest);
// 4. 处理响应
ApiResponse<R> response = handleResponse(httpResponse);
// 5. 后置处理
postProcess(request, response);
return response;
}
/**
* 钩子方法:参数验证(子类可以覆盖)
*/
protected void validateRequest(ApiRequest<T> request) {
if (request == null) {
throw new IllegalArgumentException("请求参数不能为空");
}
if (request.getData() == null) {
throw new IllegalArgumentException("请求数据不能为空");
}
// 调用具体实现的验证
doValidate(request);
}
/**
* 抽象方法:具体验证逻辑(子类必须实现)
*/
protected abstract void doValidate(ApiRequest<T> request);
/**
* 钩子方法:构建请求
*/
protected HttpRequest buildRequest(ApiRequest<T> request) {
// 构建基础请求
HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(URI.create(getApiUrl()))
.timeout(Duration.ofSeconds(getTimeout()));
// 添加公共头部
addCommonHeaders(builder);
// 添加认证信息
addAuthentication(builder);
// 构建请求体(抽象方法,子类实现)
HttpRequest httpRequest = buildRequestBody(builder, request);
return httpRequest;
}
/**
* 抽象方法:构建请求体(子类必须实现)
*/
protected abstract HttpRequest buildRequestBody(
HttpRequest.Builder builder, ApiRequest<T> request);
/**
* 具体方法:执行HTTP请求
*/
protected HttpResponse executeRequest(HttpRequest httpRequest) {
try {
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(getConnectTimeout()))
.build();
return client.send(httpRequest, HttpResponse.BodyHandlers.ofString());
} catch (Exception e) {
throw new ApiException("API请求失败", e);
}
}
/**
* 抽象方法:处理响应(子类必须实现)
*/
protected abstract ApiResponse<R> handleResponse(HttpResponse httpResponse);
/**
* 钩子方法:后置处理(子类可以覆盖)
*/
protected void postProcess(ApiRequest<T> request, ApiResponse<R> response) {
// 默认实现:记录日志
logRequestAndResponse(request, response);
// 调用具体实现的后置处理
doPostProcess(request, response);
}
/**
* 钩子方法:具体后置处理逻辑(子类可以覆盖)
*/
protected void doPostProcess(ApiRequest<T> request, ApiResponse<R> response) {
// 默认空实现
}
// ========== 具体方法的默认实现 ==========
/**
* 获取API地址(抽象方法,子类必须实现)
*/
protected abstract String getApiUrl();
/**
* 获取超时时间(钩子方法,子类可以覆盖)
*/
protected int getTimeout() {
return 30; // 默认30秒
}
/**
* 获取连接超时时间(钩子方法,子类可以覆盖)
*/
protected int getConnectTimeout() {
return 10; // 默认10秒
}
/**
* 添加公共头部(钩子方法,子类可以覆盖)
*/
protected void addCommonHeaders(HttpRequest.Builder builder) {
builder.header("Content-Type", "application/json")
.header("User-Agent", "MyApiClient/1.0");
}
/**
* 添加认证信息(钩子方法,子类可以覆盖)
*/
protected void addAuthentication(HttpRequest.Builder builder) {
// 默认不添加认证
}
/**
* 记录请求和响应日志
*/
private void logRequestAndResponse(ApiRequest<T> request, ApiResponse<R> response) {
// 实际项目中可以使用SLF4J
System.out.printf("[API调用] 请求: %s, 响应状态: %d%n",
request.getData().getClass().getSimpleName(),
response.getCode());
}
}5.2 具体API实现:微信支付API
/**
* 微信支付API实现
*/
@Component
@Slf4j
public class WechatPayApiTemplate extends AbstractApiTemplate<WechatPayRequest, WechatPayResponse> {
@Value("${wechat.pay.api.url}")
private String apiUrl;
@Value("${wechat.pay.appid}")
private String appId;
@Value("${wechat.pay.mchid}")
private String mchId;
@Value("${wechat.pay.apikey}")
private String apiKey;
@Autowired
private WechatPaySignService signService;
@Override
protected String getApiUrl() {
return apiUrl;
}
@Override
protected int getTimeout() {
return 60; // 微信支付需要更长超时时间
}
@Override
protected void doValidate(ApiRequest<WechatPayRequest> request) {
WechatPayRequest wechatRequest = request.getData();
if (StringUtils.isEmpty(wechatRequest.getOutTradeNo())) {
throw new IllegalArgumentException("商户订单号不能为空");
}
if (wechatRequest.getTotalAmount() == null ||
wechatRequest.getTotalAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("订单金额必须大于0");
}
// 微信支付特定验证
if (StringUtils.isEmpty(wechatRequest.getOpenid()) &&
StringUtils.isEmpty(wechatRequest.getProductId())) {
throw new IllegalArgumentException("必须提供openid或product_id");
}
}
@Override
protected void addAuthentication(HttpRequest.Builder builder) {
// 添加微信支付特定的认证头部
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
String nonceStr = UUID.randomUUID().toString().replace("-", "");
builder.header("Wechat-AppId", appId)
.header("Wechat-MchId", mchId)
.header("Wechat-Timestamp", timestamp)
.header("Wechat-NonceStr", nonceStr);
}
@Override
protected HttpRequest buildRequestBody(
HttpRequest.Builder builder, ApiRequest<WechatPayRequest> request) {
WechatPayRequest wechatRequest = request.getData();
// 构建微信支付请求体
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("appid", appId);
requestBody.put("mch_id", mchId);
requestBody.put("nonce_str", UUID.randomUUID().toString().replace("-", ""));
requestBody.put("body", wechatRequest.getBody());
requestBody.put("out_trade_no", wechatRequest.getOutTradeNo());
requestBody.put("total_fee", wechatRequest.getTotalAmount()
.multiply(BigDecimal.valueOf(100)).intValue()); // 转换为分
requestBody.put("spbill_create_ip", wechatRequest.getClientIp());
requestBody.put("notify_url", wechatRequest.getNotifyUrl());
requestBody.put("trade_type", wechatRequest.getTradeType());
if (StringUtils.isNotEmpty(wechatRequest.getOpenid())) {
requestBody.put("openid", wechatRequest.getOpenid());
}
// 生成签名
String sign = signService.generateSign(requestBody, apiKey);
requestBody.put("sign", sign);
// 转换为JSON
String jsonBody = JSON.toJSONString(requestBody);
// 构建POST请求
return builder.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
.build();
}
@Override
protected ApiResponse<WechatPayResponse> handleResponse(HttpResponse httpResponse) {
int statusCode = httpResponse.statusCode();
String responseBody = (String) httpResponse.body();
WechatPayResponse wechatResponse = JSON.parseObject(responseBody, WechatPayResponse.class);
ApiResponse<WechatPayResponse> response = new ApiResponse<>();
response.setData(wechatResponse);
response.setSuccess(statusCode == 200);
response.setCode(statusCode);
response.setMessage(wechatResponse.getReturnMsg());
// 验证微信支付返回的签名
if (response.isSuccess()) {
boolean signValid = signService.verifySign(wechatResponse, apiKey);
if (!signValid) {
response.setSuccess(false);
response.setMessage("签名验证失败");
log.warn("微信支付返回签名验证失败: {}", responseBody);
}
}
return response;
}
@Override
protected void doPostProcess(ApiRequest<WechatPayRequest> request,
ApiResponse<WechatPayResponse> response) {
// 微信支付特定的后置处理
if (response.isSuccess()) {
// 记录支付成功
log.info("微信支付成功,订单号: {}, 微信支付订单号: {}",
request.getData().getOutTradeNo(),
response.getData().getTransactionId());
// 发送支付成功事件
eventPublisher.publishEvent(new WechatPaySuccessEvent(
request.getData().getOutTradeNo(),
response.getData().getTransactionId()
));
} else {
// 记录支付失败
log.error("微信支付失败,订单号: {}, 错误信息: {}",
request.getData().getOutTradeNo(),
response.getMessage());
// 发送支付失败事件
eventPublisher.publishEvent(new WechatPayFailedEvent(
request.getData().getOutTradeNo(),
response.getMessage()
));
}
}
}5.3 支付宝支付API实现
/**
* 支付宝支付API实现
*/
@Component
@Slf4j
public class AlipayApiTemplate extends AbstractApiTemplate<AlipayRequest, AlipayResponse> {
@Value("${alipay.api.url}")
private String apiUrl;
@Value("${alipay.appid}")
private String appId;
@Value("${alipay.private.key}")
private String privateKey;
@Value("${alipay.public.key}")
private String publicKey;
@Autowired
private AlipaySignService signService;
@Override
protected String getApiUrl() {
return apiUrl;
}
@Override
protected int getTimeout() {
return 45; // 支付宝支付超时时间
}
@Override
protected void doValidate(ApiRequest<AlipayRequest> request) {
AlipayRequest alipayRequest = request.getData();
if (StringUtils.isEmpty(alipayRequest.getOutTradeNo())) {
throw new IllegalArgumentException("商户订单号不能为空");
}
if (alipayRequest.getTotalAmount() == null ||
alipayRequest.getTotalAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("订单金额必须大于0");
}
// 支付宝特定验证
if (StringUtils.isEmpty(alipayRequest.getSubject())) {
throw new IllegalArgumentException("订单标题不能为空");
}
}
@Override
protected void addCommonHeaders(HttpRequest.Builder builder) {
// 支付宝需要特定的Content-Type
builder.header("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
}
@Override
protected HttpRequest buildRequestBody(
HttpRequest.Builder builder, ApiRequest<AlipayRequest> request) {
AlipayRequest alipayRequest = request.getData();
// 构建支付宝请求参数
Map<String, String> params = new HashMap<>();
params.put("app_id", appId);
params.put("method", "alipay.trade.app.pay");
params.put("charset", "utf-8");
params.put("sign_type", "RSA2");
params.put("timestamp", LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
params.put("version", "1.0");
params.put("notify_url", alipayRequest.getNotifyUrl());
// 业务参数
Map<String, Object> bizContent = new HashMap<>();
bizContent.put("out_trade_no", alipayRequest.getOutTradeNo());
bizContent.put("total_amount", alipayRequest.getTotalAmount().toString());
bizContent.put("subject", alipayRequest.getSubject());
bizContent.put("product_code", "QUICK_MSECURITY_PAY");
params.put("biz_content", JSON.toJSONString(bizContent));
// 生成签名
String sign = signService.generateSign(params, privateKey);
params.put("sign", sign);
// 构建表单数据
String formData = params.entrySet().stream()
.map(entry -> entry.getKey() + "=" + URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8))
.collect(Collectors.joining("&"));
// 构建POST请求
return builder.POST(HttpRequest.BodyPublishers.ofString(formData))
.build();
}
@Override
protected ApiResponse<AlipayResponse> handleResponse(HttpResponse httpResponse) {
int statusCode = httpResponse.statusCode();
String responseBody = (String) httpResponse.body();
ApiResponse<AlipayResponse> response = new ApiResponse<>();
if (statusCode == 200) {
// 解析支付宝响应
Map<String, String> responseParams = parseResponse(responseBody);
AlipayResponse alipayResponse = new AlipayResponse();
alipayResponse.setTradeNo(responseParams.get("trade_no"));
alipayResponse.setOutTradeNo(responseParams.get("out_trade_no"));
alipayResponse.setTotalAmount(new BigDecimal(responseParams.get("total_amount")));
// 验证签名
boolean signValid = signService.verifySign(responseParams, publicKey);
response.setData(alipayResponse);
response.setSuccess("10000".equals(responseParams.get("code")) && signValid);
response.setCode(statusCode);
response.setMessage(responseParams.get("msg"));
if (!signValid) {
log.warn("支付宝返回签名验证失败: {}", responseBody);
}
} else {
response.setSuccess(false);
response.setCode(statusCode);
response.setMessage("支付宝接口调用失败");
}
return response;
}
@Override
protected void doPostProcess(ApiRequest<AlipayRequest> request,
ApiResponse<AlipayResponse> response) {
// 支付宝支付特定的后置处理
if (response.isSuccess()) {
// 生成支付参数(用于客户端调起支付宝)
String payParams = generatePayParams(response.getData());
response.getData().setPayParams(payParams);
log.info("支付宝支付成功,订单号: {}, 支付宝交易号: {}",
request.getData().getOutTradeNo(),
response.getData().getTradeNo());
} else {
log.error("支付宝支付失败,订单号: {}, 错误信息: {}",
request.getData().getOutTradeNo(),
response.getMessage());
}
}
private Map<String, String> parseResponse(String responseBody) {
// 解析支付宝返回的URL编码字符串
return Arrays.stream(responseBody.split("&"))
.map(param -> param.split("="))
.filter(pair -> pair.length == 2)
.collect(Collectors.toMap(
pair -> pair[0],
pair -> URLDecoder.decode(pair[1], StandardCharsets.UTF_8)
));
}
private String generatePayParams(AlipayResponse response) {
// 生成调起支付宝的支付参数
Map<String, String> params = new HashMap<>();
params.put("app_id", appId);
params.put("method", "alipay.trade.app.pay");
params.put("charset", "utf-8");
params.put("sign_type", "RSA2");
params.put("timestamp", LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
params.put("version", "1.0");
Map<String, Object> bizContent = new HashMap<>();
bizContent.put("out_trade_no", response.getOutTradeNo());
bizContent.put("total_amount", response.getTotalAmount().toString());
bizContent.put("subject", "订单支付");
bizContent.put("product_code", "QUICK_MSECURITY_PAY");
params.put("biz_content", JSON.toJSONString(bizContent));
// 生成签名
String sign = signService.generateSign(params, privateKey);
params.put("sign", sign);
// 转换为URL参数
return params.entrySet().stream()
.map(entry -> entry.getKey() + "=" + entry.getValue())
.collect(Collectors.joining("&"));
}
}5.4 使用示例
@Service
@Slf4j
public class PaymentService {
@Autowired
private WechatPayApiTemplate wechatPayApiTemplate;
@Autowired
private AlipayApiTemplate alipayApiTemplate;
/**
* 微信支付
*/
public WechatPayResponse wechatPay(WechatPayRequest request) {
log.info("发起微信支付,订单号: {}", request.getOutTradeNo());
ApiRequest<WechatPayRequest> apiRequest = new ApiRequest<>();
apiRequest.setData(request);
ApiResponse<WechatPayResponse> response = wechatPayApiTemplate.execute(apiRequest);
if (!response.isSuccess()) {
throw new PaymentException("微信支付失败: " + response.getMessage());
}
return response.getData();
}
/**
* 支付宝支付
*/
public AlipayResponse alipay(AlipayRequest request) {
log.info("发起支付宝支付,订单号: {}", request.getOutTradeNo());
ApiRequest<AlipayRequest> apiRequest = new ApiRequest<>();
apiRequest.setData(request);
ApiResponse<AlipayResponse> response = alipayApiTemplate.execute(apiRequest);
if (!response.isSuccess()) {
throw new PaymentException("支付宝支付失败: " + response.getMessage());
}
return response.getData();
}
/**
* 统一支付接口 - 使用策略模式选择支付方式
*/
public PaymentResponse pay(PaymentRequest paymentRequest) {
AbstractApiTemplate<?, ?> apiTemplate;
ApiRequest<?> apiRequest;
switch (paymentRequest.getPayType()) {
case WECHAT:
WechatPayRequest wechatRequest = convertToWechatPayRequest(paymentRequest);
apiTemplate = wechatPayApiTemplate;
apiRequest = new ApiRequest<WechatPayRequest>().setData(wechatRequest);
break;
case ALIPAY:
AlipayRequest alipayRequest = convertToAlipayRequest(paymentRequest);
apiTemplate = alipayApiTemplate;
apiRequest = new ApiRequest<AlipayRequest>().setData(alipayRequest);
break;
default:
throw new IllegalArgumentException("不支持的支付类型: " + paymentRequest.getPayType());
}
// 使用模板方法执行支付
ApiResponse<?> response = apiTemplate.execute(apiRequest);
if (!response.isSuccess()) {
throw new PaymentException("支付失败: " + response.getMessage());
}
return convertToPaymentResponse(response);
}
}六、模板方法模式的进阶应用
6.1 使用回调接口增强扩展性
在有些场景下,我们可能需要更灵活的扩展点。这时可以使用回调接口:
/**
* 支持回调接口的模板方法抽象类
*/
public abstract class AbstractCallbackTemplate<T, R> {
/**
* 模板方法 - 支持回调
*/
public final R execute(T request, TemplateCallback<T, R> callback) {
// 1. 前置处理
preProcess(request);
// 2. 执行具体逻辑(通过回调接口)
R result = callback.doInTemplate(request);
// 3. 后置处理
postProcess(request, result);
return result;
}
/**
* 钩子方法:前置处理
*/
protected void preProcess(T request) {
// 默认实现:记录日志
log.info("开始处理请求: {}", request);
}
/**
* 钩子方法:后置处理
*/
protected void postProcess(T request, R result) {
// 默认实现:记录日志
log.info("请求处理完成: {}, 结果: {}", request, result);
}
/**
* 回调接口
*/
@FunctionalInterface
public interface TemplateCallback<T, R> {
R doInTemplate(T request) throws Exception;
}
}
/**
* 使用回调接口的示例
*/
@Service
public class TransactionService {
@Autowired
private AbstractCallbackTemplate<String, Boolean> transactionTemplate;
@Transactional
public void processWithTransaction(String businessId) {
transactionTemplate.execute(businessId, request -> {
// 在这里编写事务性的业务逻辑
return doBusinessLogic(request);
});
}
private Boolean doBusinessLogic(String businessId) {
// 复杂的业务逻辑
// 这个方法中的代码都在事务中执行
return true;
}
}6.2 模板方法模式在Spring源码中的其他应用
除了Bean生命周期,模板方法模式在Spring的很多地方都有应用:
// 1. JdbcTemplate - 数据库操作模板
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
@Override
public <T> T execute(ConnectionCallback<T> action) throws DataAccessException {
// 获取连接
Connection con = DataSourceUtils.getConnection(obtainDataSource());
try {
Connection conToUse = con;
// 应用连接定制器
if (this.nativeJdbcExtractor != null) {
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}
// 执行用户回调
return action.doInConnection(conToUse);
} catch (SQLException ex) {
// 转换异常
throw translateException("ConnectionCallback", ex);
} finally {
// 释放连接
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
@Override
public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
// 调用模板方法
return execute(new ConnectionCallback<T>() {
@Override
public T doInConnection(Connection con) throws SQLException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = con.prepareStatement(sql);
rs = ps.executeQuery();
return rse.extractData(rs);
} finally {
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(ps);
}
}
});
}
}
// 2. RestTemplate - HTTP请求模板
public class RestTemplate extends InterceptingHttpAccessor implements RestOperations {
@Override
public <T> T execute(String url, HttpMethod method,
RequestCallback requestCallback,
ResponseExtractor<T> responseExtractor,
Object... uriVariables) throws RestClientException {
// 构建请求
ClientHttpRequest request = createRequest(url, method);
// 应用请求回调
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
// 执行请求
ClientHttpResponse response = request.execute();
// 处理响应
return responseExtractor.extractData(response);
}
}
// 3. Cache抽象 - 缓存操作模板
public abstract class AbstractCacheManager implements CacheManager, InitializingBean {
@Override
public Cache getCache(String name) {
// 模板方法:获取缓存
Cache cache = this.cacheMap.get(name);
if (cache == null) {
synchronized (this.cacheMap) {
cache = this.cacheMap.get(name);
if (cache == null) {
// 延迟创建缓存
cache = createCache(name);
this.cacheMap.put(name, cache);
}
}
}
return cache;
}
/**
* 抽象方法:由子类实现具体的缓存创建逻辑
*/
protected abstract Cache createCache(String name);
}七、模板方法模式的最佳实践和陷阱
7.1 最佳实践
- 合理设计抽象类:抽象类应该定义算法的骨架,具体步骤由子类实现
- 使用final保护模板方法:防止子类修改算法结构
- 提供合适的钩子方法:让子类能够适度扩展算法
- 保持方法单一职责:每个方法只做一件事
7.2 常见陷阱
// 陷阱1:模板方法过于复杂
public abstract class ComplexTemplate {
// 模板方法做了太多事情,违反了单一职责原则
public final void complexTemplateMethod() {
step1();
step2();
step3();
step4();
step5();
step6();
step7();
step8();
// ... 太多步骤
}
}
// 陷阱2:钩子方法过多
public abstract class TooManyHooksTemplate {
public final void templateMethod() {
step1();
hook1(); // 钩子方法太多,难以理解和维护
hook2();
hook3();
if (hook4()) {
step2();
}
hook5();
// ...
}
protected void hook1() {}
protected void hook2() {}
protected void hook3() {}
protected boolean hook4() { return true; }
protected void hook5() {}
// ...
}
// 陷阱3:子类破坏算法结构
public class EvilSubclass extends GoodTemplate {
// 错误:试图绕过final限制
public void templateMethod() { // 编译错误,不能覆盖final方法
// 直接调用父类步骤,破坏算法结构
super.step3();
super.step1();
super.step2();
}
}7.3 设计原则
基于我在Spring源码中学到的经验,我总结了模板方法模式的设计原则:
- 好莱坞原则:"不要调用我们,我们会调用你"。父类控制流程,子类提供实现。
- 开闭原则:对扩展开放,对修改关闭。通过增加新的子类来扩展功能。
- 里氏替换原则:子类必须能够替换它们的父类。
- 依赖倒置原则:依赖于抽象,而不是具体实现。
总结
通过深入分析Spring源码中的模板方法模式,我获得了以下重要认知:
- 模板方法模式是框架设计的基石:Spring通过这个模式固定了Bean生命周期、事务管理、数据访问等核心流程。
- 钩子方法是灵活性的关键:Spring提供了大量的钩子方法(如
BeanPostProcessor),让开发者可以在不修改框架源码的情况下扩展功能。 - 执行顺序很重要:在Spring的Bean初始化中,
@PostConstruct、InitializingBean.afterPropertiesSet()、init-method的执行顺序是固定的,理解这个顺序可以避免很多坑。 - 模式组合使用更强大:Spring中模板方法模式常与其他模式(如工厂方法、策略模式)结合使用,形成更灵活的设计。
最重要的收获:设计模式不是孤立的语法规则,而是解决特定问题的经验总结。通过阅读Spring这样的优秀框架源码,我们可以学习到这些模式在真实项目中的最佳实践,从而在自己的项目中更好地应用它们。
这次对Spring源码的深入研究,不仅解决了初始化方法执行两次的问题,更重要的是让我掌握了通过源码学习设计模式的方法。现在,每当我遇到复杂的设计问题时,我都会想:Spring是怎么解决类似问题的?这种思考方式大大提升了我的设计能力。