自己动手让springboot异步处理浏览器发送的请求(只需要使用ConcurrentLinkedQueue即可)

        自己写代码的时候遇到一个问题:浏览器想springboot的controller发送请求,假设这个请求需要服务器处理二十秒,但是我不希望让浏览器卡在这里二十秒没有任何反馈。于是我想着把需要耗时的任务放到一个Task类里,在controller里只实现把Task类放到ConcurrentLinkedQueue里,这样就耗时很少了,然后在服务启动的时候就开一个TaskThread进程,这个进程负责从Queue里取Task类,然后执行Task类里比较耗时的任务。这样当我们发送请求时,立刻就能收到反馈(因为把Task放到Queue里耗时很少),然后后台慢慢的处理Task里的运算任务。啰嗦这么多,直接上代码吧:

首先代码结构如下:

自己动手让springboot异步处理浏览器发送的请求(只需要使用ConcurrentLinkedQueue即可)_第1张图片

        Task接口代码如下:

package com.service.extract.task;

public interface Task {
	
	public void run();
	
}

         Task接口里没有啥东西,只有一个run方法,只是为了扩展方便,因为我们的Task不一定有多少种。

        接下来是一个具体的Task类ExtractWaterTask,代码如下:

package com.service.extract.task;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.lang.Thread;


@Component("extractWaterTask")
@Scope("prototype")
public class ExtractWaterTask implements Task {

	private String taskId;
	
	public void setTaskId(String taskId) {
		this.taskId = taskId;
	}
	
	public String getTaskId() {
		return taskId;
	}
	
	@Override
	public void run() {
		try {
			System.out.println("start_" + this.getTaskId());
			Thread.sleep(10000);
			System.out.println("end_" + this.getTaskId());
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		
	}
	
}

        在这个具体的Task类里,我们的耗时较长的算法写到了run里,需要耗时10秒,我们用Thread.sleep模拟耗时。

        接下来是TaskThread类,这个类里实现了我们的核心需求,包括:把Task放到queue里、从queue里获取Task并执行Task的run方法,代码如下:

package com.service.extract.task;

import java.util.concurrent.ConcurrentLinkedQueue;

import org.springframework.stereotype.Component;

@Component(value="taskThread")
public class TaskThread extends Thread {

	private ConcurrentLinkedQueue _taskQueue;
	private boolean _stop = false;
	
	public TaskThread() {
		_taskQueue = new ConcurrentLinkedQueue<>();
	}
	
	public void addTask(Task task) {

		_taskQueue.add(task);
//			_taskQueue.notify();

	}
	
	public void setStop() {
		synchronized(_taskQueue) {
			_stop = true;
			_taskQueue.notify();
		}
	}
	
	@Override
	public void run() {
		synchronized(_taskQueue) {
			while(!_stop) {
				Task task = _taskQueue.poll();
				if(task!=null) {
					try {
						task.run();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}else {
					try {
						_taskQueue.wait(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}
	
}

        接下来是控制器类ExtractController,代码如下:

package com.service.extract.controller;

import java.text.SimpleDateFormat;
import java.util.Date;

import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.service.extract.ExtractApplicationContext;
import com.service.extract.task.ExtractWaterTask;
import com.service.extract.task.TaskThread;
import java.lang.Thread;

@RestController
public class ExtractController {

	
	@Resource(name="taskThread")
	private TaskThread taskThread;
	
	@RequestMapping("/test")
	public String test() {
		ExtractWaterTask extractWaterTask = (ExtractWaterTask) ExtractApplicationContext.getBean("extractWaterTask");
		SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		String time = df.format(new Date());
		extractWaterTask.setTaskId(time);
		taskThread.addTask(extractWaterTask);
		return "helll";
	}
	
}

        这个类里我们获取一个ExtractWaterTask类,然后设置TaskId,然后调用addTask方法把Task类放到TaskThread中。注意,这里我们获取ExtractWaterTask是通过getBean方法获取的,因为如果我们用@Autowired注解直接注入一个extractWaterTask,其实还是单例的类,也就是说假设我们第一次请求时间是8:20:20(8点20分20秒),控制台上会打印start_2018-09-28 08:20:20,但是如果过了5秒我们用发送了一次请求,那么setTaskId生效,此时extractWaterTask类的taskId属性会变成08:20:25,当Thread.sleep线程休息了10秒之后,控制台上会打印出end_2018-09-28 08:20:25,我把两次请求在控制台上打印的东西都写出来就很明白了:

start_2018-09-28 08:20:20

end_2018-09-28 08:20:25

start_2018-09-28 08:20:25

end_2018-09-28 08:20:25

很明显这是不对的,但是通过getBean方法获取到的类就不再是单例的类了,我们再运行服务,发送两次请求,控制台结果如下:

start_2018-09-28 08:21:22

end_2018-09-28 08:21:22

start_2018-09-28 08:21:27

end_2018-09-28 08:21:27

很明显这才是我们需要的结果。

接下来是ExtractApplicationContext类,这个类主要为了实现getBean方法:

package com.service.extract;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class ExtractApplicationContext implements ApplicationContextAware {
	

	private static ApplicationContext applicationContext;
	
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}
	
	public ApplicationContext getApplicationContext() {
		return applicationContext;
	}
	
	public static Object getBean(String beanName) {
		return applicationContext.getBean(beanName);
	}

}

最后是ExtractApplication类:

package com.service.extract;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;


@SpringBootApplication
public class ExtractApplication {

	public static ConfigurableApplicationContext applicationContext;
	
	public static void main(String[] args) {
		applicationContext = SpringApplication.run(ExtractApplication.class, args);
		com.service.extract.task.TaskThread taskThread = (com.service.extract.task.TaskThread)applicationContext.getBean("taskThread");
        taskThread.start();
	}
}

以上就是所有的代码了,理论上你创建一个Springboot工程把代码拷进去就OK了,为了方便不想拷贝的朋友,我把工程传到CSDN上了,下载地址如下:https://download.csdn.net/download/u014627099/10693625

你可能感兴趣的:(springboot,异步,java)