上篇内容给大家讲解的是如何使用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)*/coverrideprotected void executeInternal(JobExecutionContext context) throwsJobExecutionException{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*/configurationpublic class QuartzConfiguration{Beanpublic JobDetail weatherDataSyncJobJobDetail(){return JobBuilder.newJob(WeatherDataSyncJob.class).withIdentity( "weatherDatasyncJob")-storeDurably(.build(;}@Beanpublic 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.weatherDataSyncJob2017-10-23 23:21:40.125INEO 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.WeatherDataSyncJob2017-10-23 23:21:46.122 INFO 8440 ---[eduler_Worker-7]C.w.s.c.weather.:天气数据同步任务job.WeatherDatasyncJob2017-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.WeatherDataSyncJob2017-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.WeatherDataSyncJobINFO 8440 ---[eduler_Worker-3] C.w.s.c.weather.2017-10-23 23:21:58.125:天气数据同步任务job .WeatherDataSyncJob2017-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文件中即可,这样,一方面减少调用这个服务的次数;另一方面,读取本地文件相对来说不管是从性能上还是从速度上,都比调用这个接口要快很多。
在应用的resources目录下新建一个名称为citylist.xml的XML文件,里面存储了所需的城市数据。为了简化数据量,这里只选取了广东省内的城市信息。
<?xml version="1.0"encoding="UTF-8"?>..
当然,为了节省篇幅,这里也没有把所有的城市都列举出来。有兴趣的读者可以自行查看项目源码。
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 Laupublic 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 Way Lau*/@servicepublic class CityDataServicelmpl implements CityDataService{@overridepublic List
其实现原理是:先从放置在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包中,实现该接口。
@Servicepublic class WeatherDataServicelmpl implements WeatherDataService@Autowiredprivate RestTemplate restTemplate;Autowiredprivate StringRedisTemplate stringRedisTemplate;private final string WEATHER_API = "http://wthrcdn.etouch.cn/weather_mini";@overridepublic void syncDataByCityId(String cityId){String uri = WEATHER_API + "?citykey=" +cityId;this.saveWeatherData(uri);private void saveWeatherData(String uri){ValueOperations
syncDataByCityId方法就是为了将天气信息存储于Redis中。
5.完善天气数据同步任务
回到前面的com.waylau.spring.cloud.weather.job.WeatherDataSyncJob任务中,此时,可以对任务的执行方法executeInternal进行完善。
public class WeatherDataSyncJob extends QuartzJobBean{private final static Logger logger= LoggerFactory.getLogger(WeatherDataSyncJob.class);@Autowiredprivate CityDataService cityDataServiceImpl;@Autowiredprivate WeatherDataService weatherDataServiceImpl;/*(non-Javadoc)* see org.springframework.scheduling.quartz.QuartzJobBeantexecuteInternal(org.quartz.JobExecutionContext)*/@overrideprotected void executeInternal (JobExecutionContext context) throwsJobExecutionException {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分钟是比较合理的。代码如下。
@configurationpublic class QuartzConfiguration {private final int TIME= 1800;//更新频率@Beanpublic JobDetail weatherDataSyncJobJobDetail() {return JobBuilder.newJob(WeatherDataSyncJob.class).withIdentity("weatherDataSyncJob").storeDurably().build();@Beanpublic 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:1012801012017-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:1012801022017-10-25 00:46:11.846 INFO 9148---[eduler_Worker-1] C.w.s.c.weather.job.WeatherDataSyncJob:天气数据同步任务中,cityId:1012801032017-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:1012821032017-10-25 00:46:28.245 INFO 9148---[eduler_Worker-1] C.w.s.c.weather.job.WeatherDataSyncJob:天气数据同步任务中,cityId:1012821042017-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 项目中,;
- 觉得文章不错的朋友可以转发此文关注小编;
- 感谢大家的支持!!