如何实现天气数据的同步和使用QuartzScheduler?
上篇内容给大家讲解的是如何使用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 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("天气数据同步任务");
}
}
在这里先不写具体的业务逻辑,只是打印一串文本“天气数据同步任务”,用于标识这个任务是否执行。
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 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:定义了何时来触发一个特定的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中每个元素的含义。
.
:这个元素的意义不大,只是为了表明它的子元素是一个集合。 .<:该元素才是真正存储数据的,其中,d1代表城市ID;d2代表城市名称;d3代表城市名称的拼音;d4代表城市所在省的名称。
由于这些城市的信息数据是不会经常变动的,因此获取这些信息没有必要经常访问这个接口。将这些数据存储在本地的XML文件中即可,这样,一方面减少调用这个服务的次数;另一方面,读取本地文件相对来说不管是从性能上还是从速度上,都比调用这个接口要快很多。
在应用的resources目录下新建一个名称为citylist.xml的XML文件,里面存储了所需的城市数据。为了简化数据量,这里只选取了广东省内的城市信息。
<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 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包下,创建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字符串转换为指定类型的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 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 "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转为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天气数据同步任务");
//读取城市列表
ListcityList=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 项目中,;
觉得文章不错的朋友可以转发此文关注小编;
感谢大家的支持!!
本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。