Spring 5 offers asynchronous service layer that can be called by any asynchronous controllers. Let us build these service layer using the following steps:
- Before we start this recipe, the use of @Async requires a thorough and appropriate configuration of any TaskExecutor type in SpringAsynchConfig including some proxy-related configurations on @EnableAsync annotation.
- Create a package org.packt.web.reactor.service and add EmployeeService with some template methods:
public interface EmployeeService { public CompletableFuture<List<Employee>> readEmployees(); public Callable<List<Employee>> readEmployeesCall(); public Future<Employee> readEmployee(Integer empId); public void addEmployee(EmployeeForm emp); public void updateEmployee(EmployeeForm emp, int id) ; public void delEmployee(Integer empId); }
- Any Spring 5 service can be converted to the asynchronous type just by having it return a Callable<T> task. Usually, it is mandatory for synchronous services to return Callable<T>, even when if it is created to be void by default. Create EmployeeServiceImpl by implementing readEmployeesCall() which retrieves a list of employees and wraps it with a Callable task:
@Service public class EmployeeServiceImpl implements EmployeeService { @Autowired private EmployeeDao employeeDaoImpl; @Override public Callable<List<Employee>> readEmployeesCall() { Callable< List<Employee> > task = new Callable< List<Employee> >() { @Override public List<Employee> call () throws Exception { System.out.println("controller:readEmployeesCall task executor: " + Thread.currentThread().getName()); Thread.sleep(6000); List<Employee> empList = employeeDaoImpl.getEmployees(); return empList; } }; return task; } }
- Another option for creating non-blocking services is to apply the @EnableAsync feature in the Spring platform. With this annotation, the @Async can now be attached to service methods with or without a return value, in order to run its transaction asynchronously:
@Async @Override public CompletableFuture<List<Employee>> readEmployees() { Supplier<List<Employee>> supplyListEmp = ()->{ System.out.println("service:readEmployees task executor: " + Thread.currentThread().getName()); System.out.println("processing for 5000 ms"); try { Thread.sleep(6000); } catch (InterruptedException e) { e.printStackTrace(); } return employeeDaoImpl.getEmployees(); }; return CompletableFuture.supplyAsync(supplyListEmp); } @Async @Override public void addEmployee(EmployeeForm empForm) {
Employee emp = new Employee(); emp.setDeptId(empForm.getEmpId()); emp.setFirstName(empForm.getFirstName()); // refer to sources try { System.out.println("service:addEmployee task executor: " + Thread.currentThread().getName()); System.out.println("processing for 1000 ms"); Thread.sleep(1000); } catch (InterruptedException e) { } employeeDaoImpl.addEmployeeBySJI(emp); } @Async public Future<Employee> readEmployee(Integer empId) { try { System.out.println("service:readEmployee(empid) task executor: " + Thread.currentThread().getName()); System.out.println("processing for 2000 ms"); Thread.sleep(2000); } catch (InterruptedException e) { } return new AsyncResult<>(employeeDaoImpl.getEmployee(empId)); } @Async @Override public void updateEmployee(EmployeeForm empForm, int id) { Employee emp = new Employee(); emp.setDeptId(empForm.getEmpId()); emp.setFirstName(empForm.getFirstName()); // refer to sources try { System.out.println("service:updateEmployee task executor: " + thread.currentThread().getName()); System.out.println("processing for 1000 ms"); Thread.sleep(1000); } catch (InterruptedException e) { } employeeDaoImpl.updateEmployee(emp); } @Async @Override public void delEmployee(Integer empId) { try { System.out.println("service:delEmployee task executor: " + Thread.currentThread().getName()); System.out.println("processing for 1000 ms");
Thread.sleep(1000); } catch (InterruptedException e) { } employeeDaoImpl.delEmployee(empId); }
Combining @Async with Callable<T> will not work for Spring 5 service implementation due to some proxy-related issues.
- Now, create a full-blown form controller that will perform the adding of new employees and retrieving the list of employees from the data source using the recently implemented non-blocking methods:
@Controller @RequestMapping(value="/react/empform.html") public class EmployeeController { @Autowired private EmployeeService employeeServiceImpl; @Autowired private DepartmentService departmentServiceImpl; @InitBinder("employeeForm") public void initBinder(WebDataBinder binder){ binder.registerCustomEditor(Integer.class, "age", new AgeEditor()); binder.registerCustomEditor(Date.class, new DateEditor()); } @RequestMapping(method=RequestMethod.GET) public String employeeForm(Model model){ EmployeeForm employeeForm = new EmployeeForm(); model.addAttribute("employeeForm", employeeForm); references(model); return "emp-form"; } @RequestMapping(method=RequestMethod.POST) public String employeeList(Model model, @Validated @ModelAttribute("employeeForm") EmployeeForm employeeForm, BindingResult result){ try { employeeServiceImpl.addEmployee(employeeForm); List<Employee> empList = employeeServiceImpl .readEmployees().get(5000, TimeUnit.SECONDS); model.addAttribute("empList", empList); } catch (InterruptedException e) { } catch (ExecutionException e) { } catch (TimeoutException e) { } return "emp-list"; } private void references(Model model){ List<Integer> deptIds = new ArrayList<>(); List<Department> depts = departmentServiceImpl.readDepartments(); Iterator<Department> iterate = depts.iterator(); while(iterate.hasNext()){ deptIds.add(iterate.next().getId()); } model.addAttribute("deptIds", deptIds); } }
- For creating reports and updating and deleting records of employees, we have this ReportController below that will showcase how to invoke @Async methods with Thread.sleep(n). The following request handler accesses the CompletableFuture<T> result from an asynchronous readDepartments() of DepartmentService through a risky get() method:
@Controller public class ReportController { @Autowired private DepartmentService departmentServiceImpl; @Autowired private EmployeeService employeeServiceImpl; @RequestMapping(value="/react/viewdepts.html", method=RequestMethod.GET) public String viewDepts(Model model){ try { model.addAttribute("departments", departmentServiceImpl .readDepartments().get(5000, TimeUnit.MILLISECONDS)); } catch (InterruptedException e) { } catch (ExecutionException e) { } catch (TimeoutException e) { } return "dept-list"; }
- Another way of retrieving result from CompletableFuture<T> task is through its non-risky join() method, which does not throw InterruptedException when something wrong happens during the asynchronous process:
@RequestMapping(value="/react/viewemps.html", method=RequestMethod.GET) public String viewEmps(Model model){ List<Employee> empList = employeeServiceImpl.readEmployees().join(); model.addAttribute("empList", empList); return "emp-list"; }
- There are @Async methods that perform asynchronous with the help of Thread.sleep() just to delay the process and to avoid thread-related Exception like the following delEmployee() of EmployeeService:
@RequestMapping(value={"/react/delemp.html/{empId}"}) public String deleteRecord(Model model, @PathVariable("empId") Integer empId){ try { employeeServiceImpl.delEmployee(empId); Thread.sleep(1000); List<Employee> empList = employeeServiceImpl
.readEmployees().get(5000, TimeUnit.SECONDS); model.addAttribute("empList", empList); } catch (InterruptedException e) { } catch (ExecutionException e) { } catch (TimeoutException e) { } return "emp-list"; }
- One way of executing asynchronous services is to use the supplyAsync() static method of CompletableFuture<T> that requires a Supplier<T> functional interface, as shown by the following request handler:
@RequestMapping(value={"/react/updateemp.html/{id}"},
method=RequestMethod.POST) public String updateRecordSubmit(Model model,@PathVariable("id") Integer id, @Validated @ModelAttribute("employeeForm") EmployeeForm employeeForm, BindingResult result){ Consumer<List<Employee>> processResult = (empList) ->{ model.addAttribute("empList", empList); }; Supplier<List<Employee>> asyncSupplier = () ->{ try { employeeServiceImpl.updateEmployee(employeeForm, id); Thread.sleep(1000); return employeeServiceImpl.readEmployees().get(5000, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); } return null; }; CompletableFuture.supplyAsync(asyncSupplier)
.thenAccept(processResult); return "emp-list"; } private void references(Model model){ List<Integer> deptIds = new ArrayList<>(); List<Department> depts = departmentServiceImpl .readDepartments().getNow(new ArrayList<>()); // refer to sources model.addAttribute("deptIds", deptIds); }
- Use the EmployeeDao implementation from Chapter 3, Implementing MVC Design Pattern, on JDBC concepts.
- Import the AgeEditor and DateEditor of the previous chapters.
- Utilize the message bundles, view mappings and view pages from Chapter 3, Implementing MVC Design Pattern.
- Save all files. Then clean, build, and run transactions. Run each request handler several times and observe /logs/tomcat9-stdout.xxxx-xx-xx.log:
service:readEmployees task executor: pool-3-thread-4 processing for 5000 ms service:addEmployee task executor: pool-3-thread-3 processing for 1000 ms Thread pool-3-thread-3 has a processing time: 1012 Thread pool-3-thread-4 has a processing time: 5003 service:delEmployee task executor: pool-3-thread-5 processing for 1000 ms service:readEmployees task executor: pool-3-thread-6 processing for 5000 ms Thread pool-3-thread-5 has a processing time: 1072 Thread pool-3-thread-6 has a processing time: 5002