SpringBoot集成Quartz定时框架

前言

本系列文章将简单的学习SpringCloud微服务相关知识,其实也是因为时间的原因,一直拖到现在,遂打算趁着假期,决定记录下来。

从天气预报微服务系统的单体架构——>分布式架构的演变过程中,一步一步,由浅及深的学习SpringCloud微服务的思想与其实现的组件。

本系列文章分为以下几个章节:

  • SpringBoot搭建天气预报微服务系统(单体架构)
  • SpringBoot集成Redis缓存
  • SpringBoot集成Quartz定时框架
  • SpringBoot结合Thymeleaf模板与Bootstrap快速搭建界面
  • 单体架构到微服务架构的拆分
  • SpringCloud微服务组件—Eureka服务注册与发现
  • SpringCloud微服务组件—Feign负载均衡与高可用
  • SpringCloud微服务组件—Zuul实现API网关分配
  • SpringCloud微服务组件—Config集中实现配置管理
  • SpringCloud微服务组件—Hystrix服务熔断机制

项目源码已上传至Github.

开发环境

  • JDK 1.8
  • IDEA 2017.3
  • Gradle 4
  • HttpClient 4.5.3
  • Redis 3.2
  • SpringBoot 2.0.0.RELEASE
//依赖关系
dependencies {
    //该依赖用于编译阶段
    compile('org.springframework.boot:spring-boot-starter-web')
    //HttpClient
    compile('org.apache.httpcomponents:httpclient:4.5.3')
    //Redis
    compile('org.springframework.boot:spring-boot-starter-data-redis')
    //Quartz
    compile('org.springframework.boot:spring-boot-starter-quartz')
    //该依赖用于测试阶段
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

为什么是Quartz

Quartz,是任务定时框架。早些时候,我有简单的介绍过它。SpringBoot也对Quartz进行了良好的集成,让我们可以很方便的使用它。
在天气预报系统中,我们为什么要用到定时任务呢?其实上一篇在介绍Redis的时候,就已经介绍过了。Quartz结合Redis,定时请求天气数据,存入缓存。
其实在本章节中,还有重要的一步,便是读取XML文件中的城市列表,将其转换为实体类,用Quartz和Redis一起来同步所有城市的数据。

如何使用Quartz与项目结合

目录结构
SpringBoot集成Quartz定时框架_第1张图片

通过第三方API,创建城市实体类

http://mobile.weather.com.cn/js/citylist.xml

很遗憾,现在已经打不开这个链接了。
但是,依然找到了部分的城市列表XML(广东省)。

  • cityList.xml
    位于项目资源目录下,之后会使用到。

<c c1="0">
<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 d1="101280105" d2="花都" d3="huadu" d4="广东"/>
<d d1="101280201" d2="韶关" d3="shaoguan" d4="广东"/>
<d d1="101280202" d2="乳源" d3="ruyuan" d4="广东"/>
<d d1="101280203" d2="始兴" d3="sJhixing" d4="广东"/>
<d d1="101280204" d2="翁源" d3="wengyuan" d4="广东"/>
<d d1="101280205" d2="乐昌" d3="lechang" d4="广东"/>
<d d1="101280206" d2="仁化" d3="renhua" d4="广东"/>
<d d1="101280207" d2="南雄" d3="nanxiong" d4="广东"/>
.....
c>
我们可以从中创建两个实体类,这个涉及到Xml字符串与Java对象之间的转换(可以用[JAXB实现](https://blog.csdn.net/qq_33764491/article/details/79582880))。
  • City
/**
 * @Author: cfx
 * @Description: 将Xml解析成对应字段@XmlAccessorType(XmlAccessType.FIELD)通过字段访问
 * @Date: Created in 2018/4/5 22:14
 */
@XmlRootElement(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;
    ...
  • CityList
/**
 * @Author: cfx
 * @Description: 城市列表
 * @Date: Created in 2018/4/5 22:22
 */
@XmlRootElement(name = "c")
@XmlAccessorType(XmlAccessType.FIELD)
public class CityList {

    @XmlElement(name = "d")
    private List cityList;
    ...

获取城市列表

  • CityDataService
public interface CityDataService {
    /**
     * 获取City列表
     * @return
     * @throws Exception
     */
    List listCity() throws Exception;
}
  • CityDataServiceImpl
public class CityDataServiceImpl implements CityDataService {

    private static final Logger logger = LoggerFactory.getLogger(CityDataServiceImpl.class);

    @Override
    public List listCity() throws Exception {
        //读取xml文件
        Resource resource = new ClassPathResource("citylist.xml");
        BufferedReader br = new BufferedReader(new InputStreamReader
                (resource.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.xmlStrToObject(CityList.class,buffer.toString());
        return cityList.getCityList();
    }
}
  • XmlBuilder(XML转换JAVA对象的工具类)
public class XmlBuilder {
    /**
     * 将XML转换为指定的POJO
     * @param clazz
     * @param xmlStr
     * @return
     * @throws Exception
     */
    public static Object xmlStrToObject(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();
        }
        return xmlObject;
    }
}

使用Quartz

如果你了解Quartz,就会知道它的三大要素:Job、Trigger、Schedule。

定义一个Job是用来说明具体要定时的任务是什么。

在天气预报系统中,我们的定时任务是定时获取所有城市的天气任务。
所以需要在WeatherDataService接口中定义根据城市ID同步天气的方法。
* WeatherDataService

public interface WeatherDataService {
    /**
     * 根据城市Id查询天气
     * @param cityId
     * @return
     */
    WeatherResponse getDataByCityId(String cityId);

    /**
     * 根据城市名称查询天气
     * @param cityName
     * @return
     */
    WeatherResponse getDataByCityName(String cityName);

    /**
     * 根据城市Id来同步天气
     * @param cityId
     */
    void syncDataByCityId(String cityId);
}
  • WeatherDataServiceImpl
@Service
public class WeatherDataServiceImpl implements WeatherDataService {

    private static final Logger logger = LoggerFactory.getLogger(WeatherDataServiceImpl.class);

    private static final String WEATHER_URI = "http://wthrcdn.etouch.cn/weather_mini?";

    private static final long TIME_OUT = 1800L;//1800s

    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public WeatherResponse getDataByCityId(String cityId) {
        String uri = WEATHER_URI + "citykey=" + cityId;
        return this.doGetWeather(uri);
    }

    @Override
    public WeatherResponse getDataByCityName(String cityName) {
        String uri = WEATHER_URI + "city=" + cityName;
        return this.doGetWeather(uri);
    }

    @Override
    public void syncDataByCityId(String cityId) {
        String uri = WEATHER_URI + "citykey=" + cityId;
        this.saveWeatherData(uri);
    }

    /**
     * 把天气数据放入缓存
     * 相当于更新缓存
     * @param uri
     */
    private void saveWeatherData(String uri) {
        String key = uri;
        String strBody = null;
        ValueOperations ops = stringRedisTemplate.opsForValue();
        //调用服务接口来获取
        ResponseEntity respString = restTemplate.getForEntity(uri,String.class);

        //将接口返回的Json字符串转换成对象
        if (respString.getStatusCodeValue() == 200) {
            strBody = respString.getBody();
        }

        //数据写入缓存
        ops.set(uri,strBody,TIME_OUT, TimeUnit.SECONDS);
    }

    /**
     * 重构代码
     * @param uri
     * @return
     */
    private WeatherResponse doGetWeather(String uri) {
        String key = uri;
        String strBody = null;
        ObjectMapper mapper = new ObjectMapper();
        WeatherResponse resp = null;
        ValueOperations ops = stringRedisTemplate.opsForValue();
        //先查缓存,如果缓存中有天气信息就在缓存中取
        if (stringRedisTemplate.hasKey(key)) {
            logger.info("Redis has data");
            strBody = ops.get(key);
        } else {
            logger.info("Redis don't has data");
            //如果缓存没有,再去调用服务接口来获取
            ResponseEntity respString = restTemplate.getForEntity(uri,String.class);

            //将接口返回的Json字符串转换成对象
            if (respString.getStatusCodeValue() == 200) {
                strBody = respString.getBody();
            }

            //数据写入缓存
            ops.set(uri,strBody,TIME_OUT, TimeUnit.SECONDS);
        }

        try {
            resp = mapper.readValue(strBody,WeatherResponse.class);
        } catch (IOException e) {
            logger.info("Error!!",e);
        }

        return resp;
    }

}
  • WeatherDataSyncJob(具体的天气数据同步Job类)
public class WeatherDataSyncJob extends QuartzJobBean{

    private static final Logger logger = LoggerFactory.getLogger(WeatherDataSyncJob.class);

    @Autowired
    private CityDataService cityDataService;

    @Autowired
    private WeatherDataService weatherDataService;

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        logger.info("天气数据同步Job,Start!");
        //获取城市ID列表
        List cityList = null;

        try {
            cityList = cityDataService.listCity();
        } catch (Exception e) {
            logger.error("Exception!!",e);
        }
        //遍历城市ID获取天气
        for (City city : cityList) {
            String cityId = city.getCityId();
            logger.info("天气数据同步Job,cityId:" + cityId);

            weatherDataService.syncDataByCityId(cityId);
        }

        logger.info("天气数据同步Job,End!");
    }
}
  • Quartz配置类(声明和绑定Job、Trigger、Schedule)
@Configuration
public class QuartzConfiguration {

    private static final int TIME = 1800;//更新频率

    //JobDetail
    @Bean
    public JobDetail weatherDataSyncJobDetail() {
        return JobBuilder.newJob(WeatherDataSyncJob.class).withIdentity("weatherDataSyncJob")
        .storeDurably().build();
    }
    //Trigger触发器
    //还可以使用CronScheduleBuilder来自定义cron表达式,更加灵活
    @Bean
    public Trigger weatherDataSyncTrigger() {
        SimpleScheduleBuilder schedBuilder = SimpleScheduleBuilder.simpleSchedule()
                .withIntervalInSeconds(TIME).repeatForever();

        return TriggerBuilder.newTrigger().forJob(weatherDataSyncJobDetail())
                .withIdentity("weatherDataSyncTrigger").withSchedule(schedBuilder).build();
    }

}

运行

启动项目运行,观察控制台。发现已经开始同步所有城市的数据到缓存中。
SpringBoot集成Quartz定时框架_第2张图片

SpringBoot集成Quartz定时框架_第3张图片

你可能感兴趣的:(SpringBoot,Quartz,天气预报,SpringBoot,Quartz)