- 背景需求
再用户执行成功某种操作之后,给关注公众号的用户推送一条消息。比如用户登录成功之后,微信推送一条登录成功的提醒。
公众号推消息需要一个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}。