最近做了一个java的项目,部门领导给了一套代码让我尽快掌握,说心里话本人真心不喜欢java的这种项目方式,各种配置各种xml文件简直头都大了,下面就将我遇到的其中一个我认为是坑的地方整理出来,希望能帮助到后面像我一样的兄弟
功能需求说明:
使用Jsoup编写了一套爬虫程序,用来自动录入网站的数据,之前测试都是写在页面中,手动的访问页面触发爬虫(后续一些问题就是因为这样产生的),还有就是项目需要实现自动触发也就是定时器
开发过程:
既然确定是定时器,操刀子就上直接百度java定时器,发现很多quartz、spring、spring-task、Timer ,发现Timer 这东西应该是最简单粗暴的,于是在网上找到下面代码
package tasklListener; import java.util.TimerTask; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import paypay.web.service.timers.ChannelDataSync; /** * 上下文监听器,需要在web.xml中进行配置,请参见结点 * * @author Administrator * */ public class MyContextListener implements ServletContextListener { private java.util.Timer timer = null; private ServletContext context = null; public void contextInitialized(ServletContextEvent event) { this.context = event.getServletContext(); timer = new java.util.Timer(true); event.getServletContext().log("定时器已启动"); // 设定MyTask中任务每5秒执行一次,0表示马上执行,可以改为2000,则表示2秒以后开始执行 // 以后都按后面指定的每5秒执行一次 timer.schedule(new MyTask(this.context), 60000, 60 * 60 * 2000); event.getServletContext().log("已经添加任务调度表"); } public void contextDestroyed(ServletContextEvent event) { timer.cancel(); this.context.log("定时器销毁"); this.context = null; } private static class MyTask extends TimerTask { private static boolean isRunning = false; private ServletContext context = null; public MyTask(ServletContext context) { this.context = context; } // 下面的方法会按之前设定的每5秒执行一次,所以,此处不需要循环 public void run() { if (!isRunning) { isRunning = true; context.log("开始执行指定任务"); // TODO 添加自定义的详细任务,以下只是示例 // 这里完成从数据库取数据,然后存放到MySQL数据库中 ChannelDataSync Dateuser=new ChannelDataSync(); java.text.DateFormat df = new java.text.SimpleDateFormat("yyyyMMdd") ; java.util.Date date = new java.util.Date() ; String datestr = df.format(new java.util.Date()); Dateuser.selectTSuserdata(datestr); isRunning = false; context.log("指定任务执行结束"); } else { context.log("上一次任务执行还未结束"); } } } }
建好了类,不对啊怎么触发啊仔细看了一下注释
/** * 上下文监听器,需要在web.xml中进行配置,请参见结点 * * @author Administrator * */
于是到web.xml中找到
org.springframework.web.context.ContextLoaderListener
那我就直接复制listener-class节点,然后写入自己的包名+类名
org.springframework.web.context.ContextLoaderListener com.shopping.zy.MyContextListener
编译后报错(大神别鄙视我,我是真的java新手,对于配置什么xm文件完全不会啊 )
看了一下报错信息:cvc-complex-type.2.4.d: Invalid content was found starting with element 'listener-class'. No child element is expected at this point.
本人的英文很烂,但也大概看出来说是子元素有问题,既然是子元素不对,那就重新写一个对象不就行了 ,于是配置文件改成:
org.springframework.web.context.ContextLoaderListener com.shopping.zy.MyContextListener
测试通过!
再接下来就是在MyContextListener类中做操作了 首先是在定时器的初始化方法contextInitialized中:
timer = new java.util.Timer(true); event.getServletContext().log("定时器已启动"); //1000*60*60 1秒 *60=1分钟 *2 等于开启后60分钟执行采集任务 //(1000*60*60)*12 (1000*60*60)=1个小时 *12表示没12小时执行一次 int tempint=(1000*60*60)*(Integer.parseInt(sysconfig.getAotoupdate_step())); timer.schedule(new MyTask(this.context), 1000*60*60,(1000*60*60)*(Integer.parseInt(sysconfig.getAotoupdate_step())));
具体的可以参看代码,schedule函数的参数是分别是,要调用的函数,延迟执行时间,执行间隔时间 我提供的代码中最后的的执行间隔时间是动态在数据库中取出的所以是动态的,你也可以直接写成固定的
到这了你们肯定觉得没什么坑啊,那么我就先来介绍一下第一个坑:
首先在MyContextListener是不可以直接使用srping的对象的,即使你声明了对应的对象,但由于MyContextListener的启动线程和spring不一致(我自己理解的,如果谁知道可以给我解释一下),在MyContextListener类中使用spring对象是不会有实例化相关的注入对象的,这就坑爹了,总不能自己把所有的数据库操作类重新做一遍吧,于是乎又是神器出山,继续百度,找了半天发现一个帖子中的解决办法:
ISysConfigService sysConfigService; WebApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(event.getServletContext()); sysConfigService = (ISysConfigService) context.getBean("sysConfigService"); SysConfig sysconfig= sysConfigService.getSysConfig();
这里里面有一点是需要注意的:context.getBean("sysConfigService")中的sysConfigService为实体类是我项目中的操作类,但是通过context.getBean()方法返回的对象类型必须是其对应的ISysConfigService接口,而不是其对象类型,
虽然不知道是为什么但我觉得这肯定跟spring的设计模式有关系,这是不是就是工厂类的设计模式啊,在此处坑了我很久也是今天我想写这篇博文的一个诱因吧。这样得到的对象是包括其本身的注入对象的。
接下来就是要实现调用我写好的爬虫函数了 ,这简单啊 ,直接实例化类对象 然后调用方法就行了 ,由于类对象中没有注入对象不需要使用上面的方法,说干就干
ManageHT mht=new ManageHT(); mht.zy_sr_cj(context);
mht.cj_start(context); mht.zy_collect_news();
三个方法分别是我对应的三个采集的函数,之前接收的是HttpServletRequest ,但是在MyContextListener中我不知道如何获取,于是我就讲context传入了,如果你知道怎么获取可以给我留言,这不是重点因为后续我使用了其他的办法,
使用上面的方式访问对应的函数会遇到和之前一样的问题,对应的注入对象无法加载,弄了半天最后决定坚持我的简单粗暴的原则,既然直接网页访问可以, MyContextListener不可以,那我就模拟一个网页请求不就行了 还研究什么注入不注入对象的
说干就干:
public static String doGet(String url, String queryString, String charset, boolean pretty) { StringBuffer response = new StringBuffer(); HttpClient client = new HttpClient(); HttpMethod method = new GetMethod(url); try { if (StringUtils.isNotBlank(queryString)) //对get请求参数做了http请求默认编码,好像没有任何问题,汉字编码后,就成为%式样的字符串 method.setQueryString(URIUtil.encodeQuery(queryString)); client.executeMethod(method); if (method.getStatusCode() == HttpStatus.SC_OK) { BufferedReader reader = new BufferedReader(new InputStreamReader(method.getResponseBodyAsStream(), charset)); String line; while ((line = reader.readLine()) != null) { if (pretty) response.append(line).append(System.getProperty("line.separator")); else response.append(line); } reader.close(); } } catch (URIException e) { } catch (IOException e) { } finally { method.releaseConnection(); } return response.toString(); }
模拟get请求的操作,对于我的需求来说get/post没什么区别,直接百度一段代码,
最后的调用部分代码变成:
String sr = doGet("http://localhost:8080/shopping/admin/testsr.htm", null, "UTF-8", true); Thread thread = Thread.currentThread(); thread.sleep(1000*60*10);//暂停10分钟后程序继续执行 String mr = doGet("http://localhost:8080/shopping/admin/zy_collect_mr.htm", null, "UTF-8", true); Thread threadsecond = Thread.currentThread(); threadsecond.sleep(1000*60*10);//暂停10分钟后程序继续执行 String news = doGet("http://localhost:8080/shopping/admin/zy_collect_news.htm", null, "UTF-8", true);
运行项目:执行定时器-执行任务-调用我的爬虫页面,看着数据一条一条进入到数据库中,今天的努力没白费,作为一个新手java程序员我想两说,虽然我对java了解不深,但是java的一些机制确实导致了项目开发进度的缓慢(就是大牛你也得承认,java开发效率就是比.net、python、php)等慢多了 ,这只是我接触java项目以后遇到的比较简单的,之前焦头烂额的也没记录下来,希望以后能把我遇到的问题总结出来,如果再有.net转java的兄弟,希望能让你少走一些弯路。