由微信公众号推消息引发的spring装配单例问题

  • 背景需求

再用户执行成功某种操作之后,给关注公众号的用户推送一条消息。比如用户登录成功之后,微信推送一条登录成功的提醒。

公众号推消息需要一个access_token(类似于接入凭证),access_token获取一次的生效时间是两个小时,超时需要重新获取。

本文主要讲述spring单例,对微信推送相关不做太多赘述,知道上面的前提即可。

  • 原始实现

微信推送消息有一个固定的地址,每次只需要替换正确的access_token,然组装参数发送请求即可。原始代码如下:

@Service
public class WechatPushService {
     private String userinfoUrl = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN";

	 public void pushWechatMessageTemplate(String openId,Map<String,String> templateMap,String templateId) {
		 //获取token
		 RedisStockManager redisStockManager = RedisStockManager.getInstance();
		 String access_token =redisStockManager.get(RedisKey.WEIXIN_ACCESS_TOKEN);
	
		 userinfoUrl = userinfoUrl.replace("ACCESS_TOKEN", access_token);
            
                /*配置参数发送请求等后续操作*/ 
	 }
}

调用代码采用@autowirde注解自动装配WechatPushService类,然后调用其pushWechatMessageTemplate方法。

从pushWechatMessageTemplate方法中可以看出,每次推送消息都会先从redis中获取及时生效的token,然后替换掉userinfoUrl变量中的ACCESS_TOKEN并重新赋值给userinfoUrl。

设想一下,

服务器刚启动时,userinfoUrl=“https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN”

当推送第一条消息时,假设此时redis中的token为1,那么userinfoUrl=“https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=1“

当推送第二条消息时,因为spring默认装配是单例的,第一次推送消息已经改变了userinfoUrl 的值,所以此时的replace方法已经无法匹配到ACCESS_TOKEN这个字符串了,那么此时userinfoUrl的值为“https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=1“

假设过了两个小时,access_token=1已经失效了,此时access_token=2,那么此时在推送消息是,userinfoUrl依旧是“https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=1“,access_token无法被正常替换,这将导致access_token验证失败无法正常推送。

 

  • 解决方案

替换access_token的时候采用中间变量形式,不修改原始变量的值,即:

String url = userinfoUrl.replace("ACCESS_TOKEN", access_token);

这样就可以保证每次替换都会成功,消息也可以正常推送。

 

  • 后记

为了证明spring默认装配是单例模式,写了如下的的测试代码。

package singletonbean;

import org.springframework.stereotype.Component;

@Component
public class Student {
	public String str = "my name is {name}";

	public void method1(String name){
		str = str.replace("{name}", name);
		System.out.println(str);
	}
}
package singletonbean.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages="singletonbean")
public class Config1 {

}
package singletonbean.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages="singletonbean")
public class Config1 {

}

 

package singletonbean;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import singletonbean.config.Config1;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Config1.class)
public class SingletonBeanTest {

	@Autowired
	Student s;
	
	@Test
	public void test1(){
		System.out.println("原始字符串:"+s.str);
		s.method1("张三");
		System.out.println("运行一次之后的字符串:"+s.str);
		s.method1("李四");
		System.out.println("运行第二次之后的字符串:"+s.str);
	}
}

 

测试结果如下:

原始字符串:my name is {name}

my name is 张三

运行一次之后的字符串:my name is 张三

my name is 张三

运行第二次之后的字符串:my name is 张三

 

由上述结果可以证明,Spring默认的装配模式是单例,如果不是单例,每次的原始字符串都应该为my name is {name}。

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值