spring 之 Listener的异步调用

本文介绍如何在Spring框架中实现事件监听的异步处理,包括自定义事件、监听器及事件发布者的创建过程,并详细展示了如何配置Spring以实现真正的异步调用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于listener的原理我这里就不过多叙述了,大家百度一下,一大堆。

我写一个实际案例:(代码非常简单,很多主要是业务的,我就没有删除,不要觉得麻烦,很简单)

情景描述:我监听到一个事件后,想让监听器里面延时10秒执行一些操作,但是controller继续执行自己的代码,返回视图层。

本人先是用拦截器。(只贴出来相关的配置)

先来spring核心容器的配置

        <!-- 拦截器配置,拦截顺序:先执行后定义的,排在第一位的最后执行。-->
	<mvc:interceptors>
		<mvc:interceptor>
			<mvc:mapping path="${adminPath}/**" />
			<mvc:exclude-mapping path="${adminPath}/"/>
			<mvc:exclude-mapping path="${adminPath}/login"/>
			<mvc:exclude-mapping path="${adminPath}/sys/menu/tree"/>
			<mvc:exclude-mapping path="${adminPath}/sys/menu/treeData"/>
			<mvc:exclude-mapping path="${adminPath}/oa/oaNotify/self/count"/>
			<bean class="com.thinkgem.jeesite.modules.sys.interceptor.LogInterceptor" />
		</mvc:interceptor>
		<!-- finebi拦截器 拦截user和role里面的save方法 -->
		<mvc:interceptor>
			<mvc:mapping path="${adminPath}/sys/user/save" />
			<mvc:mapping path="${adminPath}/sys/role/save" />
			<bean class="com.thinkgem.jeesite.modules.sys.interceptor.FineBIInterceptor" />
		</mvc:interceptor>
	</mvc:interceptors>
下面是拦截器的内容

package com.thinkgem.jeesite.modules.sys.interceptor;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import com.thinkgem.finebi.entity.Curep;
import com.thinkgem.finebi.service.CurepService;
import com.thinkgem.finebi.service.CustomRoleService;
import com.thinkgem.finebi.utils.FineBIUtils;
import com.thinkgem.jeesite.common.service.BaseService;
import com.thinkgem.jeesite.modules.sys.dao.MenuDao;
import com.thinkgem.jeesite.modules.sys.dao.RoleDao;
import com.thinkgem.jeesite.modules.sys.entity.Menu;
import com.thinkgem.jeesite.modules.sys.entity.Role;

/**
 * @description 描述:FineBI拦截器,拦截user和role controller里面的save方法
 * @author 作者:siqiangming
 * @date 创建时间:2017年8月9日 下午2:32:31
 * @edit 修改时间:
 */
public class FineBIInterceptor extends BaseService implements HandlerInterceptor {
	
	@Autowired
	private RoleDao roleDao;
	@Autowired
	private MenuDao menuDao;
	@Resource
	private CurepService curepService;//FineBI自定义角色权限
	@Resource
	private CustomRoleService customRoleService;//FineBI自定义角色
	
	@Override
	public boolean preHandle(HttpServletRequest request,
			HttpServletResponse response, Object handler) throws Exception {
		return true;
	}

	@Override
	public void postHandle(HttpServletRequest request,
			HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
		
	}

	@Override
	public void afterCompletion(HttpServletRequest request,HttpServletResponse response, 
			Object handler, Exception ex) throws Exception {
		HandlerMethod  HandlerMethod = (HandlerMethod)handler;
		MethodParameter[] methodParameters = HandlerMethod.getMethodParameters();//获取请求参数数组
		List<String> list = new ArrayList<String>();
		for (MethodParameter methodParameter : methodParameters) {
			list.add(methodParameter.getParameterName());
		}
		System.out.println("本次拦截请求参数============="+list.toString());
		//因为FineBI每隔10秒同步一次
                Thread.sleep(10000);
		//如果是拦截的user的save方法 调用给角色赋予权限的方法
		if (list.indexOf("user") > -1) {
	    	String[] roleIdArray = request.getParameterValues("roleIdList");
	    	List<String> roleIdList = Arrays.asList(roleIdArray);
	        System.out.println(roleIdList+"------------");
	        Iterator<String> it = roleIdList.iterator();
			while (it.hasNext()) {
				Role role = roleDao.get(it.next());
				insertFineBIRoleMenu(role.getName(), role.getMenuList());
			}
		}
		//如果是拦截的role的save方法 调用给角色赋予权限的方法
		if (list.indexOf("role") > -1) {
			String id = request.getParameter("id");
			Role role = roleDao.get(id);
			if (role != null) {
				insertFineBIRoleMenu(role.getName(), role.getMenuList());
			}
		}
		FineBIUtils.refreshCache();//调用FineBI接口刷新缓存
	}
	
	/**
	 * @description 描述:同时将角色的权限添加到finebi数据库
	 * @author 作者:siqiangming
	 * @date 创建时间:2017年8月8日 下午7:34:54
	 * @edit 修改时间:
	 * @parameter 参数:角色id,菜单list
	 */
	public void insertFineBIRoleMenu(String name,List<Menu> menuList){
		Integer roleId = customRoleService.getRoleIdByName(name);
		if(roleId != null){
			deleteFineBIRoleMenu(name);
			if (menuList.size() > 0){
				//遍历角色的权限菜单
				Iterator<Menu> it = menuList.iterator();
				while(it.hasNext()){
					Menu menu = menuDao.get(it.next().getId());
					//查询FineBI中菜单对应的id
					Integer entryId = curepService.getEntryIdByName(menu.getName());
					if(entryId == null){
						continue;
					}
					Curep curep = new Curep(roleId, 7, entryId, 1, 1, 1);
					//检查是否存在这条记录
					if(curepService.checkIfExistCurep(curep)){
						continue;
					}
					curepService.save(curep);
				}
			}
		}
		
	}
	/**
	 * @description 描述:删除FineBI中的角色权限
	 * @author 作者:siqiangming
	 * @date 创建时间:2017年8月8日 下午7:35:37
	 * @edit 修改时间:
	 * @parameter 参数:角色id
	 */
	public void deleteFineBIRoleMenu(String name){
		Integer roleId = customRoleService.getRoleIdByName(name);
		if(roleId != null){
			curepService.deleteCurep(roleId);
			System.out.println(name+":角色的finebi的角色权限已经删除");
		}
	}

}
结果发现页面操作也要等10秒钟,这样的话,太影响了。

百度无数,没有找到解决办法。自己想的是异步调用,没有找到类似的案例。

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

果断抛弃使用监听器。

监听器简单说一下:

1、事件    继成一个ApplicationEvent类

2、事件监听者      实现一个ApplicationListener<RoleEvent>接口,接口监听事件,或者集成他的实现类。

3、事件发布者     也就是事件的触发者,监听者怎么知道事件产生呢,就是通过他。


好了,废话不多说了,贴出代码

事件

package com.thinkgem.jeesite.modules.sys.listener;

import java.util.List;

import org.springframework.context.ApplicationEvent;

import com.thinkgem.jeesite.modules.sys.entity.Role;

/**
 * @description 描述:事件
 * @author 作者:siqiangming
 * @date 创建时间:2017年8月11日 下午3:05:44
 * @edit 修改时间:
 */

public class RoleEvent extends ApplicationEvent {
	private static final long serialVersionUID = 1L;
	
	private List<Role> roleList;
	private Role role;
	
	public RoleEvent(Object source, List<Role> roleList) {
		super(source);
		this.roleList = roleList;
		System.out.println("我是保存用户的时候。。。");
	}
	
	public RoleEvent(Object source, Role role) {
		super(source);
		this.role = role;
		System.out.println("我是保存角色的时候。。。");
	}

	public List<Role> getRoleList() {
		return roleList;
	}

	public void setRoleList(List<Role> roleList) {
		this.roleList = roleList;
	}

	public Role getRole() {
		return role;
	}

	public void setRole(Role role) {
		this.role = role;
	}

}

监听者

package com.thinkgem.jeesite.modules.sys.listener;

import java.util.Iterator;
import java.util.List;

import javax.annotation.Resource;

import org.apache.log4j.Logger;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

import com.thinkgem.finebi.entity.Curep;
import com.thinkgem.finebi.service.CurepService;
import com.thinkgem.finebi.service.CustomRoleService;
import com.thinkgem.finebi.utils.FineBIUtils;
import com.thinkgem.jeesite.modules.sys.entity.Menu;
import com.thinkgem.jeesite.modules.sys.entity.Role;
import com.thinkgem.jeesite.modules.sys.service.SystemService;

/**
 * @description 描述:事件监听者
 * @author 作者:siqiangming
 * @date 创建时间:2017年8月11日 下午3:14:21
 * @edit 修改时间:
 */
@Component
public class RoleListener implements ApplicationListener<RoleEvent> {

	private Logger logger = Logger.getLogger(RoleListener.class);
	@Resource
	private SystemService systemService;
	@Resource
	private CurepService curepService;//FineBI自定义角色权限
	@Resource
	private CustomRoleService customRoleService;//FineBI自定义角色
	
	@Override
	public void onApplicationEvent(RoleEvent event) {
		logger.debug("我监听到----------事件发生");
		try {
			Thread.sleep(10000);
			logger.debug("我睡眠了----------ok");
		} catch (InterruptedException e) {
			e.printStackTrace();
			logger.debug("我睡眠了----------报错");
		}
		List<Role> roleList = event.getRoleList();
		if (roleList != null && roleList.size() >0) {
			logger.debug("我监听到----------"+roleList.toString());
			Iterator<Role> it = roleList.iterator();
			while (it.hasNext()) {
				Role role = systemService.getRole(it.next().getId());
				insertFineBIRoleMenu(role.getName(), role.getMenuList());
			}
		}
		Role role = event.getRole();
		if (role != null) {
			logger.debug("我监听到----------"+role.toString());
			insertFineBIRoleMenu(role.getName(), role.getMenuList());
		}
		FineBIUtils.refreshCache();//调用FineBI接口刷新缓存
	}

	/**
	 * @description 描述:同时将角色的权限添加到finebi数据库
	 * @author 作者:siqiangming
	 * @date 创建时间:2017年8月8日 下午7:34:54
	 * @edit 修改时间:
	 * @parameter 参数:角色id,菜单list
	 */
	public void insertFineBIRoleMenu(String name,List<Menu> menuList){
		Integer roleId = customRoleService.getRoleIdByName(name);
		if(roleId != null){
			deleteFineBIRoleMenu(name);
			if (menuList.size() > 0){
				//遍历角色的权限菜单
				Iterator<Menu> it = menuList.iterator();
				while(it.hasNext()){
					Menu menu = systemService.getMenu(it.next().getId());
					//查询FineBI中菜单对应的id
					Integer entryId = curepService.getEntryIdByName(menu.getName());
					if(entryId == null){
						continue;
					}
					Curep curep = new Curep(roleId, 7, entryId, 1, 1, 1);
					//检查是否存在这条记录
					if(curepService.checkIfExistCurep(curep)){
						continue;
					}
					curepService.save(curep);
				}
			}
		}
		
	}
	/**
	 * @description 描述:删除FineBI中的角色权限
	 * @author 作者:siqiangming
	 * @date 创建时间:2017年8月8日 下午7:35:37
	 * @edit 修改时间:
	 * @parameter 参数:角色id
	 */
	public void deleteFineBIRoleMenu(String name){
		Integer roleId = customRoleService.getRoleIdByName(name);
		if(roleId != null){
			curepService.deleteCurep(roleId);
			System.out.println(name+":角色的finebi的角色权限已经删除");
		}
	}
}
发布者

package com.thinkgem.jeesite.modules.sys.listener;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import com.thinkgem.jeesite.modules.sys.entity.Role;

/**
 * @description 描述:事件发布者
 * @author 作者:siqiangming
 * @date 创建时间:2017年8月11日 下午3:16:52
 * @edit 修改时间:
 */
@Component
public class RoleEventpPublisher {

	@Autowired
	private ApplicationContext applicationContext;
	
	public void publishEventByRole(Role role){
		applicationContext.publishEvent(new RoleEvent(this, role));
	}
	
	public void publishEventByUser(List<Role> roleList){
		applicationContext.publishEvent(new RoleEvent(this, roleList));
	}
}
到这里,是否可以了呢,当然不行了,你得有人去发布,或者说有地方调用他。

因为代码太多我只拿出来了一部分,先注入发布者

	@Autowired
	private RoleEventpPublisher publisher;//事件发布者
		if (StringUtils.isNotBlank(user.getId())){
			// 更新用户与角色关联
			userDao.deleteUserRole(user);
			if (user.getRoleList() != null && user.getRoleList().size() > 0){
				userDao.insertUserRole(user);
				//TODO 发布事件
				publisher.publishEventByUser(user.getRoleList());
			}else{
				throw new ServiceException(user.getLoginName() + "没有设置角色!");
			}
			// 将当前用户同步到Activiti
			saveActivitiUser(user);
			// 清除用户缓存
			UserUtils.clearCache(user);
//			// 清除权限缓存
//			systemRealm.clearAllCachedAuthorizationInfo();
		}

OK,到这里完事,但是这是同步的,结果和拦截器是一样的。

干货来了,看下嘛的重点,在spring核心容器配置下面代码,其他不用更改。

   <!-- 异步事件处理  20170811-->
	<bean id="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster">
		<!-- 注入任务执行器 这样就实现了异步调用 (缺点是全局的,要么全异步,要么全同步,删除这个属性既是同步) -->
		<property name="taskExecutor" ref="coreTaskExecutor" />
	</bean>
	<!-- 20170811 -->
	<bean id="coreTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
		<property name="corePoolSize" value="10"/><!-- 核心线程数 -->  
		<property name="maxPoolSize" value="20"/><!-- 最大线程数 --> 
		<property name="queueCapacity" value="25"/><!-- 队列最大长度 -->
		<property name="threadNamePrefixSet" value="weiqiao-taskExecutor"/><!-- 线程名前缀集主要用来打印日志区分方便  -->
	</bean>

到这里才是真正的异步调用,将结果发图上来。




<think>好的,我现在需要回答用户关于@EventListener对应方法异步调用的问题。首先,我得回想一下Spring框架中的事件处理机制。记得@EventListener是用来处理应用中的事件的,通常这些方法是同步执行的。但用户想知道如何让它们异步调用,所以需要解释如何实现这一点。 首先,我应该确认异步调用的基本概念,比如需要@Async注解和启用异步支持。然后,步骤可能包括添加@EnableAsync,配置线程池,以及在事件监听方法上使用@Async。不过要注意,如果直接同时使用@EventListener和@Async,可能会有问题,需要正确的方法。 可能还需要提到在Spring Boot中自动配置的情况,以及自定义线程池的方法,比如通过实现AsyncConfigurer接口或者定义TaskExecutor bean。另外,要提醒用户注意事务上下文和异常处理的差异,因为异步执行可能不在同一个线程中,事务可能不会传播,异常处理也需要特别处理,比如配置AsyncUncaughtExceptionHandler。 还需要检查是否有遗漏的细节,比如确保@Async和@EventListener在同一个类中,或者是否需要使用@TransactionalEventListener来结合事务事件。同时,示例代码可能会有帮助,但用户可能不需要具体的代码,而是解释清楚步骤和注意事项。 可能还需要区分同步和异步事件处理的区别,比如异步情况下,发布事件后不会阻塞,监听方法在另一个线程执行。此外,错误处理的不同,比如在异步情况下异常不会直接传播到发布者,需要额外的处理机制。 总结一下,回答的结构应该是先介绍@EventListener的默认同步行为,然后分步骤说明如何实现异步,包括启用异步支持、配置线程池、添加@Async注解,最后提到注意事项和常见问题。这样用户能逐步了解如何实现并注意到潜在的问题。</think>关于Spring框架中`@EventListener`注解对应方法的异步调用实现,这里为您分步骤说明: 1. **默认同步机制** - `@EventListener`标注的方法默认同步执行,调用流程为: $$ 事件发布者 \rightarrow 阻塞等待 \rightarrow 监听器执行完成 $$ - 当调用`ApplicationEventPublisher.publishEvent()`时,当前线程会等待所有监听器执行完毕 2. **启用异步处理** 需要三个关键步骤: (1) 添加`@EnableAsync`注解 ```java @Configuration @EnableAsync public class AsyncConfig { ... } ``` (2) 配置线程池(Spring Boot自动配置默认使用`SimpleAsyncTaskExecutor`,建议自定义): ```java @Bean public TaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(25); return executor; } ``` (3) 为监听方法添加`@Async`注解: ```java @Async @EventListener public void handleCustomEvent(CustomEvent event) { // 异步执行的业务逻辑 } ``` 3. **异步执行特征** - 非阻塞流程: $$ 事件发布者 \rightarrow 立即返回 \rightarrow 任务提交线程池 \rightarrow 独立线程执行 $$ - 执行线程名称变化:从`http-nio-8080-exec-1`变为`task-1` - 事务上下文分离:异步方法与原线程处于不同事务上下文 4. **注意事项** - **事务边界**:异步方法内需自行管理`@Transactional` - **异常处理**:需实现`AsyncUncaughtExceptionHandler`处理未捕获异常 - **执行顺序**:`@Order`注解在异步场景下不保证精确顺序 - **返回值处理**:支持返回`ListenableFuture`或`CompletableFuture`进行链式处理 5. **性能调优建议** - 根据事件处理类型设置不同线程池: $$ IO密集型任务 \rightarrow 大线程池 $$ $$ CPU密集型任务 \rightarrow 小线程池+队列控制 $$ - 监控线程池指标:使用`ThreadPoolTaskExecutor`的`getThreadPoolExecutor()`获取队列大小、活跃线程数等 6. **特殊场景处理** - 需要事务提交后执行时,改用`@TransactionalEventListener`: ```java @Async @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void handleAfterCommit(OrderCreatedEvent event) { ... } ``` - 需要事件处理结果时: ```java @Async @EventListener public CompletableFuture<String> handleWithResult(Event event) { return CompletableFuture.completedFuture("处理完成"); } ``` 实际应用中需根据具体业务场景权衡同步/异步处理的优缺点,异步虽能提升响应速度,但会增加系统复杂度和调试难度。建议对核心业务日志添加`MDC`跟踪标识,便于跨线程日志追踪。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值