如何實現(xiàn)天氣數(shù)據(jù)的同步和使用QuartzScheduler?
上篇內(nèi)容給大家講解的是如何使用Redis提升應用的并發(fā)訪問能力!本文承接上篇內(nèi)容。
實現(xiàn)天氣數(shù)據(jù)的同步
在micro-weather-redis應用的基礎上,創(chuàng)建一個名稱為micro-weather-quartz的應用,用于同步天氣數(shù)據(jù)。
開發(fā)環(huán)境
為了演示本例,需要采用如下開發(fā)環(huán)境。
. 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主要分為兩個步驟,首先是創(chuàng)建一個任務,其次是將這個任務進行配置。
1.創(chuàng)建任務
創(chuàng)建
com.waylau.spring.cloud.weather.job包,在該包下創(chuàng)建WeatherDataSyncJob類,用于定義“同步天氣數(shù)據(jù)的定時任務”。該類繼承自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;
/**
*天氣數(shù)據(jù)同步任務.
*
*@since 1.0.0 2017年10月23日
* author Way Lau
*/
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("天氣數(shù)據(jù)同步任務");
}
}在這里先不寫具體的業(yè)務邏輯,只是打印一串文本“天氣數(shù)據(jù)同步任務”,用于標識這個任務是否執(zhí)行。
2.創(chuàng)建配置類
在
com.waylau.spring.cloud.weather.config包下,創(chuàng)建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 Way Lau
*/
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:定義了何時來觸發(fā)一個特定的Job;
withIntervalInSeconds(2):意味著定時任務的執(zhí)行頻率為每2秒執(zhí)行一次。
3.測試定時任務
啟動應用,觀察控制臺的打印日志,可以看到定時任務確實是按照每2秒執(zhí)行一次進行的。
2017-10-23 23:21:36.126 INEO 8440 ---[eduler_Worker-2] c.W.s.c.weather.
job .WeatherDataSyncJob
:天氣數(shù)據(jù)同步任務
2017-10-23 23:21:38.126 INFO 8440 ---[eduler_Worker-3]C.W.s.c.weather.
:天氣數(shù)據(jù)同步任務
job.weatherDataSyncJob
2017-10-23 23:21:40.125
INEO 8440 ---[eduler_Worker-4] c.w.s.c.weather.
job.WeatherDatasyncJob
:天氣數(shù)據(jù)同步任務
2017-10-23 23:21:42.126 INFO 8440 ---[eduler_Worker-5]C.w.s.c.weather.
job.WeatherDataSyncJob:天氣數(shù)據(jù)同步任務
2017-10-23 23:21:44.129 INFO 8440 ---[eduler_Worker-6]C.w.s.c.weather.
:天氣數(shù)據(jù)同步任務
job.WeatherDataSyncJob
2017-10-23 23:21:46.122 INFO 8440 ---[eduler_Worker-7]C.w.s.c.weather.
:天氣數(shù)據(jù)同步任務
job.WeatherDatasyncJob
2017-10-23 23:21:48.125 INFO 8440 ---[eduler_Worker-8] c.w.s.c.weather.
job.WeatherDatasyncJob
:天氣數(shù)據(jù)同步任務
2017-10-23 23:21:50.124 INFO 8440 ---[eduler_Worker-9] C.W.s.c.weather.
job.WeatherDataSyncJob
:天氣數(shù)據(jù)同步任務
INFO 8440 --- [duler_Worker-10]C.w.s.c.weather.
2017-10-23 23:21:52.130
:天氣數(shù)據(jù)同步任務
job.WeatherDataSyncJob
2017-10-23 23:21:54.130 INFO 8440 ---[eduler_Worker-1] c.w.s.c.weather.
job.WeatherDataSyncJob
:天氣數(shù)據(jù)同步任務
2017-10-23 23:21:56.128 INFO 8440---[eduler_Worker-2] C.w.s.c.weather.
:天氣數(shù)據(jù)同步任務
job.WeatherDataSyncJob
INFO 8440 ---[eduler_Worker-3] C.w.s.c.weather.
2017-10-23 23:21:58.125
:天氣數(shù)據(jù)同步任務
job .WeatherDataSyncJob
2017-10-23 23:22:00.117 INEO 8440---[eduler Worker-4] C.w.s.c.weather.
job.WeatherDatasyncJob
:天氣數(shù)據(jù)同步任務定時同步天氣數(shù)據(jù)
在之前的章節(jié)中,已經(jīng)實現(xiàn)了獲取天氣的API,這個API接口只要傳入相應城市的ID,就能獲取天氣的數(shù)據(jù)。
定時任務需要更新所有城市的數(shù)據(jù),所以需要遍歷所有城市的ID。
1.需要城市的信息
詳細的城市列表信息,在網(wǎng)上也有相關的接口,比如
https:/waylau.com/data/citylist.xml接口。訪問該接口,能看到如下的信息。
//依賴關系
dependencies {
//...
//添加Spring Boot Quartz Starter依賴
compile('org.springframework.boot:spring-boot-starter-quartz')
//...
}當然,城市的數(shù)據(jù)量很大,本節(jié)不會全部列舉出來。通過觀察該數(shù)據(jù),大概能理解這個XML中每個元素的含義。
.
:這個元素的意義不大,只是為了表明它的子元素是一個集合。 .<:該元素才是真正存儲數(shù)據(jù)的,其中,d1代表城市ID;d2代表城市名稱;d3代表城市名稱的拼音;d4代表城市所在省的名稱。
由于這些城市的信息數(shù)據(jù)是不會經(jīng)常變動的,因此獲取這些信息沒有必要經(jīng)常訪問這個接口。將這些數(shù)據(jù)存儲在本地的XML文件中即可,這樣,一方面減少調(diào)用這個服務的次數(shù);另一方面,讀取本地文件相對來說不管是從性能上還是從速度上,都比調(diào)用這個接口要快很多。
在應用的resources目錄下新建一個名稱為citylist.xml的XML文件,里面存儲了所需的城市數(shù)據(jù)。為了簡化數(shù)據(jù)量,這里只選取了廣東省內(nèi)的城市信息。
<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>當然,為了節(jié)省篇幅,這里也沒有把所有的城市都列舉出來。有興趣的讀者可以自行查看項目源碼。
2.將XML解析為Java bean
現(xiàn)在XML文件有了,下面需要將其轉(zhuǎn)化為Java bean。
Java自帶了JAXB(Java Architecture for XML Binding)工具,可以方便地用來處理XML,將其解析為Java bean。
首先,在
com.waylau.spring.cloud.weather.vo包下創(chuàng)建城市的信息類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 Way Lau
*/
@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 Way Lau
*/
@XmlRootElement(name = "c")
@xmlAccessorType(xmlAccessType.FIELD)
public class CityList {
@XmlElement (name= "d")
private List cityList;
public ListgetCityList(){
return cityList;
public void setCityList(List cityList) {
this.cityList=cityList;
}
} 最后,還需要對JAXB的方法做一些小小的封裝,來方便自己使用。在
com.waylau.spring.cloud.weather.util包下,創(chuàng)建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 Way Lau
public class XmlBuilder {
/*★
*將xML字符串轉(zhuǎn)換為指定類型的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轉(zhuǎn)成對象的核心接口
Unmarshaller unmarshaller= context.createUnmarshaller();
reader=new StringReader(xmlstr);
xmlObject = unmarshaller.unmarshal (reader);
if(null != reader){
reader .close();
}
returnxmlObject;
}
}3.城市數(shù)據(jù)服務接口及其實現(xiàn)
在
com.waylau.spring.cloud.weather.service包下,創(chuàng)建城市數(shù)據(jù)服務接口CityDataService。
public interface CityDataService {
/**
*獲取城市列表.
*
*@return
* @throws Exception
*/
List listCity() throws Exception;
CityDataService的實現(xiàn)為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;
/★*
*城市數(shù)據(jù)服務.
*
*@since1.0.0 2017年10月23日
* @author "https://waylau.com">Way Lau
*/
@service
public class CityDataServicelmpl implements CityDataService{
@override
public List//讀取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轉(zhuǎn)為Java對象
CityList cityList =(CityList) XmlBuilder.xmlStrTo0bject (CityList.
class, buffer.toString());
return cityList.getCityList();
}
} 其實現(xiàn)原理是:先從放置在resources目錄下的citylist.xml文件中讀取內(nèi)容,并轉(zhuǎn)換成文本;其次,將該文本通過XmlBuilder工具類轉(zhuǎn)換為Java bean。
這樣,城市數(shù)據(jù)服務就完成了。
4.同步天氣數(shù)據(jù)的接口
在原先的天氣數(shù)據(jù)服務
com.waylau.spring.cloud.weather.service.WeatherDataService中,增加同
步天氣數(shù)據(jù)的接口。
public interface WeatherDataService {
/**
*根據(jù)城市工D同步天氣數(shù)據(jù)
*
*param cityId
*return
*/
void syncDataByCityId(String cityId);
}同時,在
com.waylau.spring.cloud.weather.service.WeatherDataServicelmpl包中,實現(xiàn)該接口。
@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.完善天氣數(shù)據(jù)同步任務
回到前面的
com.waylau.spring.cloud.weather.job.WeatherDataSyncJob任務中,此時,可以對任務的執(zhí)行方法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天氣數(shù)據(jù)同步任務");
//讀取城市列表
ListcityList=null;
try{
cityList = cityDataServiceImpl.listCity();
}catch(Exception e) {
logger.error("獲取城市信息異常!",e);
for(City city:cityList){
String cityld=city.getcityId();
logger.info("天氣數(shù)據(jù)同步任務中,cityId:" +cityId);
根據(jù)城市ID獲取天氣
weatherDataServiceImpl.syncDataByCityId(cityId);
logger.info("End天氣數(shù)據(jù)同步任務");
}
} 天氣數(shù)據(jù)同步任務邏輯非常簡單,如下所示。
。獲取列表遍歷城市ID。
。根據(jù)城市ID獲取天氣,并進行存儲。
完善配置
為了更加符合真實業(yè)務的需求,需要修改定時器的更新頻率。
鑒于天氣這種業(yè)務的特點,更新頻率設置為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服務已經(jīng)啟動。
啟動應用之后,天氣數(shù)據(jù)同步任務就會自動啟動,按照預先設定的頻率進行天氣數(shù)據(jù)的更新。
觀察控制臺,應該能看到如下的日志信息。當然,為了節(jié)省篇幅,這里省去了很多內(nèi)容。
2017-10-25 00:46:11.487 INEO 9148---[eduler Worker-l] C.w.s.c.weather.
job.WeatherDataSyncJob:Start天氣數(shù)據(jù)同步任務
2017-10-25 00:46:11.534 INEO 9148---[eduler_Worker-1] C.w.s.c.weather.
job.WeatherDataSyncJob:天氣數(shù)據(jù)同步任務中,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:天氣數(shù)據(jù)同步任務中,cityId:101280102
2017-10-25 00:46:11.846 INFO 9148---[eduler_Worker-1] C.w.s.c.weather.
job.WeatherDataSyncJob:天氣數(shù)據(jù)同步任務中,cityId:101280103
2017-10-25 00:46:11.971 INFO 9148---[eduler_Worker-1]C.w.s.c.weather.
job.WeatherDataSyncJob:天氣數(shù)據(jù)同步任務中,cityId:101280104
...
2017-10-25 00:46:28.108 INFO 9148---[eduler_Worker-1] C.w.s.c.weather.
job.WeatherDataSyncJob:天氣數(shù)據(jù)同步任務中,cityId:101282103
2017-10-25 00:46:28.245 INFO 9148---[eduler_Worker-1] C.w.s.c.weather.
job.WeatherDataSyncJob:天氣數(shù)據(jù)同步任務中,cityId:101282104
2017-10-25 00:46:28.357 INFO 9148 ---[eduler_Worker-1] C.w.s.c.weather.
job.WeatherDataSyncJob:End天氣數(shù)據(jù)同步任務那么如何才能知道數(shù)據(jù)已經(jīng)成功存入Redis了呢?當然,可以選擇通過Redis 的命令行,使用key來驗證是否存在數(shù)據(jù)。但其實還有更加直觀的方式,那就是使用Redis的GUI工具。
使用 Redis Desktop Manager
Redis Desktop Manager是一款非常出色的跨平臺的開源的Redis的管理工具,基于Qt5來構建。
用戶可以在
https://redisdesktop.com/download下載獲得最新的安裝包。
通過Redis Desktop Manager,就能方便地查看到存儲在Reids里面的數(shù)據(jù)。
打開Redis Desktop Manager后,單擊左上角的按鈕來連接到Redis服務器,如圖6-3所示。

如果是一個新的連接,則需要設置這個連接的名稱(可以是任意字符),如圖6-4所示。

成功連接后,就能通過該連接查看到Redis服務器里面的數(shù)據(jù)了,如圖6-5所示。

本篇內(nèi)容給大家介紹的是如何實現(xiàn)天氣數(shù)據(jù)的同步
下篇文章給大家進行天氣預報服務的實現(xiàn),演示如何來將 Thymeleaf 技術框架集成到Spring Boot 項目中,;
覺得文章不錯的朋友可以轉(zhuǎn)發(fā)此文關注小編;
感謝大家的支持!!
本文就是愿天堂沒有BUG給大家分享的內(nèi)容,大家有收獲的話可以分享下,想學習更多的話可以到微信公眾號里找我,我等你哦。
