由获取微信access
背景 : access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复 获取 将导致上次 获取 的access_token
背景:
access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。
1、为了保密appsecrect,第三方需要一个access_token<strong>获取</strong>和刷新的中控服务器。而其他业务逻辑服务器所使用的access_token均来自于该中控服务器,不应该各自去刷新,否则会造成access_token覆盖而影响业务; 2、目前access_token的有效期通过返回的expire_in来传达,目前是7200秒之内的值。中控服务器需要根据这个有效时间提前去刷新新access_token。在刷新过程中,中控服务器对外输出的依然是老access_token,此时公众平台后台会保证在刷新短时间内,新老access_token都可用,这保证了第三方业务的平滑过渡; 3、access_token的有效时间可能会在未来有调整,所以中控服务器不仅需要内部定时主动刷新,还需要提供被动刷新access_token的接口,这样便于业务服务器在API调用获知access_token已超时的情况下,可以触发access_token的刷新流程。<br><br>简单起见,使用一个随servlet容器一起启动的servlet来实现<strong>获取</strong>access_token的功能,具体为:因为该servlet随着web容器而启动,在该servlet的init方法中触发一个线程来获得access_token,该线程是一个无线循环的线程,每隔2个小时刷新一次access_token。相关代码如下:<br><strong>1)servlet代码</strong>:
public class InitServlet extends HttpServlet { private static final long serialVersionUID = 1L; public void init(ServletConfig config) throws ServletException { new Thread(new AccessTokenThread()).start(); } }
2)线程代码:
public class AccessTokenThread implements Runnable { public static AccessToken accessToken; @Override public void run() { while(true) { try{ AccessToken token = AccessTokenUtil.freshAccessToken(); // 从微信服务器刷新access_token if(token != null){ accessToken = token; }else{ System.out.println("get access_token failed------------------------------"); } }catch(IOException e){ e.printStackTrace(); } try{ if(null != accessToken){ Thread.sleep((accessToken.getExpire_in() - 200) * 1000); // 休眠7000秒 }else{ Thread.sleep(60 * 1000); // 如果access_token为null,60秒后再<strong>获取</strong> } }catch(InterruptedException e){ try{ Thread.sleep(60 * 1000); }catch(InterruptedException e1){ e1.printStackTrace(); } } } } }
3)AccessToken代码:
public class AccessToken { private String access_token; private long expire_in; // access_token有效时间,单位为妙 public String getAccess_token() { return access_token; } public void setAccess_token(String access_token) { this.access_token = access_token; } public long getExpire_in() { return expire_in; } public void setExpire_in(long expire_in) { this.expire_in = expire_in; } }
4)servlet在web.xml中的配置
<servlet> <servlet-name>initServlet</servlet-name> <servlet->class>com.sinaapp.wx.servlet.InitServlet</servlet-><span>class</span>> <load-on-startup>0</load-on-startup> </servlet>
因为initServlet设置了load-on-startup=0,所以保证了在所有其它servlet之前启动。
其它servlet要使用access_token的只需要调用 AccessTokenThread.accessToken即可。
引出多线程并发问题:
1)上面的实现似乎没有什么问题,但是仔细一想,AccessTokenThread类中的accessToken,它存在并发访问的问题,它仅仅由AccessTokenThread每隔2小时更新一次,但是会有很多线程来读取它,它是一个典型的读多写少的场景,而且只有一个线程写。既然存在并发的读写,那么上面的代码肯定是存在问题的。
一般想到的最简单的方法是使用synchronized来处理:
public class AccessTokenThread implements Runnable { private static AccessToken accessToken; @Override public void run() { while(true) { try{ AccessToken token = AccessTokenUtil.freshAccessToken(); // 从微信服务器刷新access_token if(token != null){ AccessTokenThread.setAccessToken(token); }else{ System.out.println("get access_token failed"); } }catch(IOException e){ e.printStackTrace(); } try{ if(null != accessToken){ Thread.sleep((accessToken.getExpire_in() - 200) * 1000); // 休眠7000秒 }else{ Thread.sleep(60 * 1000); // 如果access_token为null,60秒后再<strong>获取</strong> } }catch(InterruptedException e){ try{ Thread.sleep(60 * 1000); }catch(InterruptedException e1){ e1.printStackTrace(); } } } } public synchronized static AccessToken getAccessToken() { return accessToken; } private synchronized static void setAccessToken(AccessToken accessToken) { AccessTokenThread2.accessToken = accessToken; } }
accessToken变成了private,setAccessToken也变成了private,增加了同步synchronized访问accessToken的方法。
那么到这里是不是就完美了呢?就没有问题了呢?仔细想想,还是有问题,问题出在AccessToken类的定义上,它提供了public的set方法,那么所有的线程都在使用AccessTokenThread.getAccessToken()获得了所有线程共享的accessToken之后,任何线程都可以修改它的属性!!!!而这肯定是不对的,不应该的。
2)解决方法一:
我们让AccessTokenThread.getAccessToken()方法返回一个accessToken对象的copy,副本,这样其它的线程就无法修改AccessTokenThread类中的accessToken了。如下修改AccessTokenThread.getAccessToken()方法即可:
public synchronized static AccessToken getAccessToken() { AccessToken at = new AccessToken(); at.setAccess_token(accessToken.getAccess_token()); at.setExpire_in(accessToken.getExpire_in()); return at; }
也可以在AccessToken类中实现clone方法,原理都是一样的。当然setAccessToken也变成了private。
3)解决方法二:
既然我们不应该让AccessToken的对象被修改,那么我们为什么不将accessToken定义成一个“不可变对象”?相关修改如下:
public class AccessToken { private final String access_token; private final long expire_in; // access_token有效时间,单位为妙 public AccessToken(String access_token, long expire_in) { this.access_token = access_token; this.expire_in = expire_in; } public String getAccess_token() { return access_token; } public long getExpire_in() { return expire_in; } }
如上所示,AccessToken所有的属性都定义成了final类型了,只提供构造函数和get方法。这样的话,其他的线程在获得了AccessToken的对象之后,就无法修改了。改修改要求AccessTokenUtil.freshAccessToken()中返回的AccessToken的对象只能通过有参的构造函数来创建。同时AccessTokenThread的setAccessToken也要修改成private,getAccessToken无须返回一个副本了。
注意不可变对象必须满足下面的三个条件:
a) 对象创建之后其状态就不能修改;
b) 对象的所有域都是final类型;
c) 对象是正确创建的(即在对象的构造函数中,this引用没有发生逸出);
4)解决方法三:
还有没有其他更加好,更加完美,更加高效的方法呢?我们分析一下,在解决方法二中,AccessTokenUtil.freshAccessToken()返回的是一个不可变对象,然后调用private的AccessTokenThread.setAccessToken(AccessToken accessToken)方法来进行赋值。这个方法上的synchronized同步起到了什么作用呢?因为对象时不可变的,而且只有一个线程可以调用setAccessToken方法,那么这里的synchronized没有起到"互斥"的作用(因为只有一个线程修改),而仅仅是起到了保证“可见性”的作用,让修改对其它的线程可见,也就是让其他线程访问到的都是最新的accessToken对象。而保证“可见性”是可以使用volatile来进行的,所以这里的synchronized应该是没有必要的,我们使用volatile来替代它。相关修改代码如下:
public class AccessTokenThread implements Runnable { private static volatile AccessToken accessToken; @Override public void run() { while(true) { try{ AccessToken token = AccessTokenUtil.freshAccessToken(); // 从微信服务器刷新access_token if(token != null){ AccessTokenThread2.setAccessToken(token); }else{ System.out.println("get access_token failed"); } }catch(IOException e){ e.printStackTrace(); } try{ if(null != accessToken){ Thread.sleep((accessToken.getExpire_in() - 200) * 1000); // 休眠7000秒 }else{ Thread.sleep(60 * 1000); // 如果access_token为null,60秒后再<strong>获取</strong> } }catch(InterruptedException e){ try{ Thread.sleep(60 * 1000); }catch(InterruptedException e1){ e1.printStackTrace(); } } } } private static void setAccessToken(AccessToken accessToken) { AccessTokenThread2.accessToken = accessToken; }<br> public static AccessToken getAccessToken() {<br> return accessToken;<br> } }
也可以这样改:
public class AccessTokenThread implements Runnable { private static volatile AccessToken accessToken; @Override public void run() { while(true) { try{ AccessToken token = AccessTokenUtil.freshAccessToken(); // 从微信服务器刷新access_token if(token != null){ accessToken = token; }else{ System.out.println("get access_token failed"); } }catch(IOException e){ e.printStackTrace(); } try{ if(null != accessToken){ Thread.sleep((accessToken.getExpire_in() - 200) * 1000); // 休眠7000秒 }else{ Thread.sleep(60 * 1000); // 如果access_token为null,60秒后再<strong>获取</strong> } }catch(InterruptedException e){ try{ Thread.sleep(60 * 1000); }catch(InterruptedException e1){ e1.printStackTrace(); } } } } public static AccessToken getAccessToken() { return accessToken; } }
还可以这样改:
<span>public</span> <span>class</span> AccessTokenThread <span>implements</span><span> Runnable { </span><span>public</span> <span>static</span> <span>volatile</span><span> AccessToken accessToken; @Override </span><span>public</span> <span>void</span><span> run() { </span><span>while</span>(<span>true</span><span>) { </span><span>try</span><span>{ AccessToken token </span>= AccessTokenUtil.freshAccessToken(); <span>//</span><span> 从微信服务器刷新access_token</span> <span>if</span>(token != <span>null</span><span>){ accessToken </span>=<span> token; }</span><span>else</span><span>{ System.out.println(</span>"get access_token failed"<span>); } }</span><span>catch</span><span>(IOException e){ e.printStackTrace(); } </span><span>try</span><span>{ </span><span>if</span>(<span>null</span> !=<span> accessToken){ Thread.sleep((accessToken.getExpire_in() </span>- 200) * 1000); <span>//</span><span> 休眠7000秒</span> }<span>else</span><span>{ Thread.sleep(</span>60 * 1000); <span>//</span><span> 如果access_token为null,60秒后再<strong>获取</strong></span> <span> } }</span><span>catch</span><span>(InterruptedException e){ </span><span>try</span><span>{ Thread.sleep(</span>60 * 1000<span>); }</span><span>catch</span><span>(InterruptedException e1){ e1.printStackTrace(); } } } } }</span>
accesToken变成了public,可以直接是一个AccessTokenThread.accessToken来访问。
其实这个问题的关键是:在多线程并发访问的环境中如何正确的发布一个共享对象。

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

公司安全软件导致部分应用无法正常运行的排查与解决方法许多公司为了保障内部网络安全,会部署安全软件。...

如何在 Apache 中配置 Zend?在 Apache Web 服务器中配置 Zend Framework 的步骤如下:安装 Zend Framework 并解压到 Web 服务器目录中。创建 .htaccess 文件。创建 Zend 应用程序目录并添加 index.php 文件。配置 Zend 应用程序(application.ini)。重新启动 Apache Web 服务器。

vProcesserazrabotkiveb被固定,мнелостольностьстьс粹馏标д都LeavallySumballanceFriablanceFaumDoptoMatification,Čtookazalovnetakprosto,kakaožidal.posenesko

Apache服务器是强大的Web服务器软件,充当浏览器与网站服务器间的桥梁。1. 它处理HTTP请求,根据请求返回网页内容;2. 模块化设计允许扩展功能,例如支持SSL加密和动态网页;3. 配置文件(如虚拟主机配置)需谨慎设置,避免安全漏洞,并需优化性能参数,例如线程数和超时时间,才能构建高性能、安全的Web应用。

Nginx性能监控与故障排查主要通过以下步骤进行:1.使用nginx-V查看版本信息,并启用stub_status模块监控活跃连接数、请求数和缓存命中率;2.利用top命令监控系统资源占用,iostat和vmstat分别监控磁盘I/O和内存使用情况;3.使用tcpdump抓包分析网络流量,排查网络连接问题;4.合理配置worker进程数,避免并发处理能力不足或进程上下文切换开销过大;5.正确配置Nginx缓存,避免缓存大小设置不当;6.通过分析Nginx日志,例如使用awk和grep命令或ELK

Nginx 限流问题可通过以下方法解决:使用 ngx_http_limit_req_module 限制请求次数;使用 ngx_http_limit_conn_module 限制连接数;使用第三方模块(ngx_http_limit_connections_module、ngx_http_limit_rate_module、ngx_http_access_module)实现更多限流策略;使用云服务(Cloudflare、Google Cloud Rate Limiting、AWS WAF)进行 DD

本文介绍了如何分析Nginx日志以提升网站性能和用户体验。1.理解Nginx日志格式,例如时间戳、IP地址、状态码等;2.使用awk等工具解析日志,统计访问量、错误率等指标;3.根据需求编写更复杂的脚本或使用更高级工具,例如goaccess,分析不同维度的数据;4.对于海量日志,考虑使用Hadoop或Spark等分布式框架。通过分析日志,可以识别网站访问模式、改进内容策略,并最终优化网站性能和用户体验。

摘要描述:在处理复杂数据类型时,常常会遇到如何统一表示和操作的问题。使用phrity/o库可以通过Composer轻松解决这一难题。它提供了对各种数据类型的封装类和trait,使得数据处理更加一致和高效。
