上篇内容给大家讲解的是如何使用Redis提升应用的并发访问能力!本文承接上篇内容。
实现天气数据的同步
在micro-weather-redis应用的基础上,创建一个名称为micro-weather-quartz的应用,用于同步天气数据。
开发环境
为了演示本例,需要采用如下开发环境。
- . JDK8。
- . Gradle 4.0。
- .Spring Boot Web Starter 2.0.0.M4。
- Apache HttpClient 4.5.3。
- Spring Boot Data Redis Starter 2.0.0.M4。
- .Redis 3.2.100。
- Spring Boot Quartz Starter 2.0.0.M4。
- .Quartz Scheduler 2.3.0.
项目配置
Spring Boot Quartz Starter提供了Spring Boot对Quartz Scheduler的开箱即用功能。在原有的依赖的基础上,添加Spring Boot Quartz Starter的依赖。
//依赖关系
dependencies {
//...
//添加Spring Boot Quartz Starter依赖
compile('org.springframework.boot:spring-boot-starter-quartz')
//...
}
如何使用Quartz Scheduler
使用Quartz Scheduler主要分为两个步骤,首先是创建一个任务,其次是将这个任务进行配置。
1.创建任务
创建 com.waylau.spring.cloud.weather.job包,在该包下创建WeatherDataSyncJob类,用于定义“同步天气数据的定时任务”。该类继承自rg.springframework.scheduling.quartz.QuartzJobBean,并重写了executeInternal方法,详见如下。
package com.waylau.spring.cloud.weather.job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.quartz.QuartzJobBean;
/**
*天气数据同步任务.
*
*@since 1.0.0 2017年10月23日
* author <a href="https://waylau.com">Way Lau</a>
*/
public class WeatherDataSyncJob extends QuartzJobBean{
private final static Logger logger =LoggerFactory.getLogger(Weath-
erDatasyncJob.class);
/*(non-Javadoc)
* see org.springframework.scheduling. quartz.QuartzJobBean#execu-
teInternal(org.quartz.JobExecutionContext)
*/
coverride
protected void executeInternal(JobExecutionContext context) throws
JobExecutionException{
logger.info("天气数据同步任务");
}
}
在这里先不写具体的业务逻辑,只是打印一串文本“天气数据同步任务”,用于标识这个任务是否执行。
2.创建配置类
在 com.waylau.spring.cloud.weather.config包下,创建QuartzConfiguration配置类。该类详情如下。
package com.waylau.spring.cloud.weather.config;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org. quartz.SimpleScheduleBuilder;
import org.quartz .Trigger;
import org.quartz.TriggerBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.waylau.spring.cloud.weather.job.WeatherDataSyncJob;
/**
Quartz配置类.
*
*since 1.0.0 2017年10月23日
*@author <a href="https://waylau.com">Way Lau</a>
*/
configuration
public class QuartzConfiguration{
Bean
public JobDetail weatherDataSyncJobJobDetail(){
return JobBuilder.newJob(WeatherDataSyncJob.class).withIdentity
( "weatherDatasyncJob")
-storeDurably(.build(;
}
@Bean
public Trigger sampleJobTrigger() {
SimplescheduleBuilder scheduleBuilder = SimpleScheduleBuilder.
simpleschedule()
.withIntervalInSeconds(2).repeatForever(;
return TriggerBuilder.newTrigger().forJob(weatherDataSyncJob-
JobDetail())
.withIdentity("weatherDataSyncTrigger").withSchedule
(scheduleBuilder).build();
}
}
其中:
- JobDetail:定义了一个特定的Job。JobDetail实例可以使用JobBuilder API轻松构建;
- Trigger:定义了何时来触发一个特定的Job;
- withIntervalInSeconds(2):意味着定时任务的执行频率为每2秒执行一次。
3.测试定时任务
启动应用,观察控制台的打印日志,可以看到定时任务确实是按照每2秒执行一次进行的。
2017-10-23 23:21:36.126 INEO 8440 ---[eduler_Worker-2] c.W.s.c.weather.
job .WeatherDataSyncJob
:天气数据同步任务
2017-10-23 23:21:38.126 INFO 8440 ---[eduler_Worker-3]C.W.s.c.weather.
:天气数据同步任务
job.weatherDataSyncJob
2017-10-23 23:21:40.125
INEO 8440 ---[eduler_Worker-4] c.w.s.c.weather.
job.WeatherDatasyncJob
:天气数据同步任务
2017-10-23 23:21:42.126 INFO 8440 ---[eduler_Worker-5]C.w.s.c.weather.
job.WeatherDataSyncJob:天气数据同步任务
2017-10-23 23:21:44.129 INFO 8440 ---[eduler_Worker-6]C.w.s.c.weather.
:天气数据同步任务
job.WeatherDataSyncJob
2017-10-23 23:21:46.122 INFO 8440 ---[eduler_Worker-7]C.w.s.c.weather.
:天气数据同步任务
job.WeatherDatasyncJob
2017-10-23 23:21:48.125 INFO 8440 ---[eduler_Worker-8] c.w.s.c.weather.
job.WeatherDatasyncJob
:天气数据同步任务
2017-10-23 23:21:50.124 INFO 8440 ---[eduler_Worker-9] C.W.s.c.weather.
job.WeatherDataSyncJob
:天气数据同步任务
INFO 8440 --- [duler_Worker-10]C.w.s.c.weather.
2017-10-23 23:21:52.130
:天气数据同步任务
job.WeatherDataSyncJob
2017-10-23 23:21:54.130 INFO 8440 ---[eduler_Worker-1] c.w.s.c.weather.
job.WeatherDataSyncJob
:天气数据同步任务
2017-10-23 23:21:56.128 INFO 8440---[eduler_Worker-2] C.w.s.c.weather.
:天气数据同步任务
job.WeatherDataSyncJob
INFO 8440 ---[eduler_Worker-3] C.w.s.c.weather.
2017-10-23 23:21:58.125
:天气数据同步任务
job .WeatherDataSyncJob
2017-10-23 23:22:00.117 INEO 8440---[eduler Worker-4] C.w.s.c.weather.
job.WeatherDatasyncJob
:天气数据同步任务
定时同步天气数据
在之前的章节中,已经实现了获取天气的API,这个API接口只要传入相应城市的ID,就能获取天气的数据。
定时任务需要更新所有城市的数据,所以需要遍历所有城市的ID。
1.需要城市的信息
详细的城市列表信息,在网上也有相关的接口,比如 https:/waylau.com/data/citylist.xml接口。访问该接口,能看到如下的信息。
//依赖关系
dependencies {
//...
//添加Spring Boot Quartz Starter依赖
compile('org.springframework.boot:spring-boot-starter-quartz')
//...
}
当然,城市的数据量很大,本节不会全部列举出来。通过观察该数据,大概能理解这个XML中每个元素的含义。
- .<xml><c c1="0">:这个元素的意义不大,只是为了表明它的子元素是一个集合。
- .<:该元素才是真正存储数据的,其中,d1代表城市ID;d2代表城市名称;d3代表城市名称的拼音;d4代表城市所在省的名称。
由于这些城市的信息数据是不会经常变动的,因此获取这些信息没有必要经常访问这个接口。将这些数据存储在本地的XML文件中即可,这样,一方面减少调用这个服务的次数;另一方面,读取本地文件相对来说不管是从性能上还是从速度上,都比调用这个接口要快很多。
在应用的resources目录下新建一个名称为citylist.xml的XML文件,里面存储了所需的城市数据。为了简化数据量,这里只选取了广东省内的城市信息。
<?xml version="1.0"encoding="UTF-8"?>
<c c1="O">
<d d1="101280101" d2="广州" d3="guangzhou" d4="广东"/><d d1="101280102"
d2="番禺d3="panyu" d4="广东"/>
<d d1="101280103" d2="从化"d3="conghua" d4="广东"/><d d1="101280104"
d2="增城d3="zengcheng"d4="广东"/>
<d dl="101280105" d2="花都”d3="huadu" d4="广东"/><d d1="101280201" d2=
"韶关”d3="shaoguan" d4="广东"/>
<d dl="101280202" d2="乳源"d3="ruyuan" d4="广东"/><d d1="101280203" d2=
"始兴"d3="shixing" d4="广东"/>
<d d1="101280204" d2="翁源"d3="wengyuan" d4="广东"/><d dl="101280205"
d2="乐昌"d3="lechang" d4="广东"/>
<d d1="101280206" d2="仁化" d3="renhua" d4="广东"/><d d1="101280207" d2=
"南雄"d3="nanxiong"d4="广东"/>
<d d1="101280208" d2="新丰"d3="xinfeng" d4="广东"/><d d1="101280209"
d2="曲江”d3="qujiang" d4="广东"/>
<d d1="101280210" d2="演江"d3="chengjiang" d4="广东" /><d d1="101280211"
d2="武江"d3="wujiang" d4="广东"/>
<d dl="101280301" d2="惠州"d3="huizhou" d4="广东"/><d d1="101280302"
d2="博罗”d3="boluo" d4="广东"/>
<d dl="101280303" d2="惠阳"d3="huiyang" d4="广东"/><d d1="101280304"
d2="惠东”d3="huidong" d4="广东"/>
<d d1="101280305" d2="龙门"d3="longmen" d4="广东"/><d dl="101280401"
d2="梅州"d3="meizhou" d4="广东"/>
<d dl="101280402" d2="兴宁” d3="xingning" d4="广东"/><d dl="101280403"
d2="蕉岭”d3="jiaoling" d4="广东"/>
<d d1="101280404" d2="大埔"d3="dabu" d4="广东"/><d d1="101280406" d2=
”丰顺" d3="fengshun" d4="广东"/>
<d d1="101280407" d2="平远" d3="pingyuan" d4="广东"/><d dl="101280408"
d2="五华” d3="wuhua" d4="广东"/>
<d d1="101280409" d2="梅县"d3="meixian" d4="广东"/><d d1="101280501"
d2="油头”d3="shantou" d4="广东"/>
<d d1="101280502" d2="潮阳"d3="chaoyang" d4="广东" /><d d1="101280503"
d2="澄海”d3="chenghai"d4="广东"/>
<d dl="101280504" d2="南澳d3="nanao" d4="广东"/><d dl="101280601" d2=
"深圳"d3="shenzhen" d4="广东"/>
<d d1="101280701" d2="珠海"d3="zhuhai" d4="广东" /><d d1="101280702" d2=
"斗门" d3="doumen" d4="广东"/>
<d dl-"101280703"
d2="金湾"d3="jinwan" d4="广东"/><d dl="101280800" d2=
"佛山" d3="foshan" d4="广东"/>
<d d1="101280801" d2="顺德”d3="shunde" d4="广东"/><d d1="101280802" d2=
"三水"d3="sanshui"d4="广东"/>
..
</C>
当然,为了节省篇幅,这里也没有把所有的城市都列举出来。有兴趣的读者可以自行查看项目源码。
2.将XML解析为Java bean
现在XML文件有了,下面需要将其转化为Java bean。
Java自带了JAXB(Java Architecture for XML Binding)工具,可以方便地用来处理XML,将其解析为Java bean。
首先,在 com.waylau.spring.cloud.weather.vo包下创建城市的信息类Cityo
package com.waylau.spring.cloud.weather.vo;
import javax.xml.bind.annotation. XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation. XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
/**
*城市.
*
*@since 1.0.02017年10月23日
* author <a href="https://waylau.com">Way Lau</a>
*/
@XmlRo○tElement (name ="d")
@xmlAccessorType(xmlAccessType.FIELD)
public class City{
@xmlAttribute(name = "d1")
private String cityId;
@XmlAttribute(name ="d2")
private string cityName;
@xmlAttribute(name = "d3")
private string cityCode;
@XmlAttribute(name ="d4")
private String province;
//省略getter/setter方法
}
其中,@XmlAttribute所定义的name正是映射为XML中的元素属性。
同时,还需要一个CityList来表示城市信息的集合。
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation. XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
/*★
*城市列表.
*
*@since 1.0.0 2017年10月23日
*@author <a href="https://waylau.com">Way Lau</a>
*/
@XmlRootElement(name = "c")
@xmlAccessorType(xmlAccessType.FIELD)
public class CityList {
@XmlElement (name= "d")
private List<City> cityList;
public List<City>getCityList(){
return cityList;
public void setCityList(List<City> cityList) {
this.cityList=cityList;
}
}
最后,还需要对JAXB的方法做一些小小的封装,来方便自己使用。在 com.waylau.spring.cloud.weather.util包下,创建XmlBuilder工具类。
import java.io.Reader;
import java.io.StringReader;
import javax.xml.bind. JAXBContext;
import javax.xml.bind.Unmarshaller;
/**
*XML工具.
*
*@since 1.0.o 2017年10月24日
@author <a href="https://waylau.com">Way Lau</a>
public class XmlBuilder {
/*★
*将xML字符串转换为指定类型的POJO
*
*@param clazz
*@param xmlStr
@return
*@throws Exception
*/
public static Object xmlStrTo0bject (Class<?> clazz,String xmlStr)
throws Exception {
object xmlobject = null;
Reader reader = null;
JAXBContext context = JAXBContext.newInstance (clazz);
//将xml转成对象的核心接口
Unmarshaller unmarshaller= context.createUnmarshaller();
reader=new StringReader(xmlstr);
xmlObject = unmarshaller.unmarshal (reader);
if(null != reader){
reader .close();
}
returnxmlObject;
}
}
3.城市数据服务接口及其实现
在 com.waylau.spring.cloud.weather.service包下,创建城市数据服务接口CityDataService。
public interface CityDataService {
/**
*获取城市列表.
*
*@return
* @throws Exception
*/
List<City> listCity() throws Exception;
CityDataService的实现为CityDataServicelmpl。
package com.waylau.spring.cloud.weather.service;
import java.io.BufferedReader;
import java.io.InputstreamReader;
import java.util.List;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import com.waylau.spring.cloud.weather.util.xmlBuilder;
import com.waylau.spring.cloud.weather.vo.City;
import com.waylau.spring.cloud.weather.vo.CityList;
/★*
*城市数据服务.
*
*@since1.0.0 2017年10月23日
* @author <a href="https://waylau.com">Way Lau</a>
*/
@service
public class CityDataServicelmpl implements CityDataService{
@override
public List<City listCity() throws Exception {
//读取XML文件
Resource resource =new ClassPathResource ("citylist.xml");
BufferedReader br = new BufferedReader(new InputStreamReader(re-
source.getInputStream(),"utf-8"));
StringBuffer buffer = new StringBuffer();
String line = "";
while((line = br.readLine() != null){
buffer-append(line) ;
br.close();
//XML转为Java对象
CityList cityList =(CityList) XmlBuilder.xmlStrTo0bject (CityList.
class, buffer.toString());
return cityList.getCityList();
}
}
其实现原理是:先从放置在resources目录下的citylist.xml文件中读取内容,并转换成文本;其次,将该文本通过XmlBuilder工具类转换为Java bean。
这样,城市数据服务就完成了。
4.同步天气数据的接口
在原先的天气数据服务 com.waylau.spring.cloud.weather.service.WeatherDataService中,增加同
步天气数据的接口。
public interface WeatherDataService {
/**
*根据城市工D同步天气数据
*
*param cityId
*return
*/
void syncDataByCityId(String cityId);
}
同时,在 com.waylau.spring.cloud.weather.service.WeatherDataServicelmpl包中,实现该接口。
@Service
public class WeatherDataServicelmpl implements WeatherDataService
@Autowired
private RestTemplate restTemplate;
Autowired
private StringRedisTemplate stringRedisTemplate;
private final string WEATHER_API = "http://wthrcdn.etouch.cn/weather_
mini";
@override
public void syncDataByCityId(String cityId){
String uri = WEATHER_API + "?citykey=" +cityId;
this.saveWeatherData(uri);
private void saveWeatherData(String uri){
ValueOperations<String,String ops = this.stringRedisTemplate.
opsForValue();
String key - uri;
String strBody =null;
ResponseEntity<String response= restTemplate.getForEntity (uri,
String.class);
if(response.getStatusCodeValue() ==200){
strBody-response.getBody);
}
ops.set(key, strBody,TIME_OUT,TimeUnit.SECONDS);
}
}
syncDataByCityId方法就是为了将天气信息存储于Redis中。
5.完善天气数据同步任务
回到前面的 com.waylau.spring.cloud.weather.job.WeatherDataSyncJob任务中,此时,可以对任务的执行方法executeInternal进行完善。
public class WeatherDataSyncJob extends QuartzJobBean{
private final static Logger logger= LoggerFactory.getLogger(Weather
DataSyncJob.class);
@Autowired
private CityDataService cityDataServiceImpl;
@Autowired
private WeatherDataService weatherDataServiceImpl;
/*(non-Javadoc)
* see org.springframework.scheduling.quartz.QuartzJobBeantexecute
Internal(org.quartz.JobExecutionContext)
*/
@override
protected void executeInternal (JobExecutionContext context) throws
JobExecutionException {
logger.info("Start天气数据同步任务");
//读取城市列表
List<City>cityList=null;
try{
cityList = cityDataServiceImpl.listCity();
}catch(Exception e) {
logger.error("获取城市信息异常!",e);
for(City city:cityList){
String cityld=city.getcityId();
logger.info("天气数据同步任务中,cityId:" +cityId);
根据城市ID获取天气
weatherDataServiceImpl.syncDataByCityId(cityId);
logger.info("End天气数据同步任务");
}
}
天气数据同步任务逻辑非常简单,如下所示。
。获取列表遍历城市ID。
。根据城市ID获取天气,并进行存储。
完善配置
为了更加符合真实业务的需求,需要修改定时器的更新频率。
鉴于天气这种业务的特点,更新频率设置为30分钟是比较合理的。代码如下。
@configuration
public class QuartzConfiguration {
private final int TIME= 1800;//更新频率
@Bean
public JobDetail weatherDataSyncJobJobDetail() {
return JobBuilder.newJob(WeatherDataSyncJob.class).withIdentity
("weatherDataSyncJob")
.storeDurably().build();
@Bean
public Trigger sampleJobTrigger() {
SimpleScheduleBuilder scheduleBuilder= SimpleScheduleBuilder.
simpleschedule()
.withIntervalInSeconds (TIME).repeatForever();
return TriggerBuilder.newTrigger().forJob(weatherDataSyncJob-
JobDetail())
.withIdentity("weatherDataSyncTrigger").withSchedule
(scheduleBuilder) .build();
}
}
测试应用
在启动应用之前,需要保证Redis服务已经启动。
启动应用之后,天气数据同步任务就会自动启动,按照预先设定的频率进行天气数据的更新。
观察控制台,应该能看到如下的日志信息。当然,为了节省篇幅,这里省去了很多内容。
2017-10-25 00:46:11.487 INEO 9148---[eduler Worker-l] C.w.s.c.weather.
job.WeatherDataSyncJob:Start天气数据同步任务
2017-10-25 00:46:11.534 INEO 9148---[eduler_Worker-1] C.w.s.c.weather.
job.WeatherDataSyncJob:天气数据同步任务中,cityId:101280101
2017-10-25 00:46:11.534 INFO 9148 ---[
main] o.s.b.w.embedded.
tomcat.TomcatWebServer: Tomcat started on port(s):8080 (http)
2017-10-25 00:46:11.534 INFO 9148 ---[
main] c.w.spring.
cloud.weather.Application:Started Application in 3.185 seconds
(JVM running for 3.534)
2017-10-25 00:46:11.706 INFO 9148---[eduler_Worker-1] C.w.s.c.weather.
job.WeatherDataSyncJob:天气数据同步任务中,cityId:101280102
2017-10-25 00:46:11.846 INFO 9148---[eduler_Worker-1] C.w.s.c.weather.
job.WeatherDataSyncJob:天气数据同步任务中,cityId:101280103
2017-10-25 00:46:11.971 INFO 9148---[eduler_Worker-1]C.w.s.c.weather.
job.WeatherDataSyncJob:天气数据同步任务中,cityId:101280104
...
2017-10-25 00:46:28.108 INFO 9148---[eduler_Worker-1] C.w.s.c.weather.
job.WeatherDataSyncJob:天气数据同步任务中,cityId:101282103
2017-10-25 00:46:28.245 INFO 9148---[eduler_Worker-1] C.w.s.c.weather.
job.WeatherDataSyncJob:天气数据同步任务中,cityId:101282104
2017-10-25 00:46:28.357 INFO 9148 ---[eduler_Worker-1] C.w.s.c.weather.
job.WeatherDataSyncJob:End天气数据同步任务
那么如何才能知道数据已经成功存入Redis了呢?当然,可以选择通过Redis 的命令行,使用key来验证是否存在数据。但其实还有更加直观的方式,那就是使用Redis的GUI工具。
使用 Redis Desktop Manager
Redis Desktop Manager是一款非常出色的跨平台的开源的Redis的管理工具,基于Qt5来构建。
用户可以在 https://redisdesktop.com/download下载获得最新的安装包。
通过Redis Desktop Manager,就能方便地查看到存储在Reids里面的数据。
打开Redis Desktop Manager后,单击左上角的按钮来连接到Redis服务器,如图6-3所示。
如果是一个新的连接,则需要设置这个连接的名称(可以是任意字符),如图6-4所示。
成功连接后,就能通过该连接查看到Redis服务器里面的数据了,如图6-5所示。
本篇内容给大家介绍的是如何实现天气数据的同步
- 下篇文章给大家进行天气预报服务的实现,演示如何来将 Thymeleaf 技术框架集成到Spring Boot 项目中,;
- 觉得文章不错的朋友可以转发此文关注小编;
- 感谢大家的支持!!
|