百味皆苦 java后端开发攻城狮

spring注解驱动开发(声明式事务和异步)

2019-05-15
百味皆苦

事务

环境搭建

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.12.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>4.3.12.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.3.12.RELEASE</version>
        </dependency>

数据源

/**
     声明式事务:

    环境搭建:
        1.导入相关依赖:数据源、数据库驱动、Spring-jdbc模块
        2.配置数据源、JdbcTemplate(Spring提供简化数据库操作的工具)操作数据
        3.给方法上标注@Transactional注解表示当前的方法是一个事务方法;
        4.@EnableTransactionManagement 开启基于注解的事务管理功能;
        5.配置事务管理器来控制事务
 */
@ComponentScan({"com.ldc.tx"})
@PropertySource({"classpath:dbconfig.properties"})
@Configuration
@EnableTransactionManagement
public class TxConfig {

    //数据源
    @Bean
    public DataSource dataSource() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("12358");
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate() throws PropertyVetoException {
        //Spring会的@Configuration类会做特殊的处理:给容器中添加组件,多次调用都是从容器中找组件
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
        return jdbcTemplate;
    }

    //注册事务管理器在容器中
    @Bean
    public PlatformTransactionManager transactionManager() throws PropertyVetoException {
        return new DataSourceTransactionManager(dataSource());
    }

}

业务层

@Repository
public class UserDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void insert() {
        String sql = "INSERT INTO `tbl_user` (username,age)VALUES(?,?)";
        String username = UUID.randomUUID().toString().substring(0, 5);
        jdbcTemplate.update(sql, username, 19);
    }

}

@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    @Transactional
    public void insertUser() {
        userDao.insert();
        System.out.println("插入完成...");
        int i = 10 / 0;
    }

}

    @Test
    public void test01() {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(TxConfig.class);
        UserService userService = applicationContext.getBean(UserService.class);
        userService.insertUser();
    }

  • 这个时候,我们再来运行测试方法,数据就没有插入成功了,事务就已经生效了;

总结

     声明式事务

    环境搭建
        1.导入相关依赖数据源数据库驱动Spring-jdbc模块
        2.配置数据源JdbcTemplateSpring提供简化数据库操作的工具操作数据
        3.给方法上标注@Transactional注解表示当前的方法是一个事务方法
        4.@EnableTransactionManagement 开启基于注解的事务管理功能
        5.配置事务管理器来控制事务必须要有这一步
         //注册事务管理器在容器中
         @Bean
         public PlatformTransactionManager transactionManager() throws PropertyVetoException {
         return new DataSourceTransactionManager(dataSource());
         }

源码分析

  声明式事务:
 
  环境搭建:
  1、导入相关依赖
  		数据源、数据库驱动、Spring-jdbc模块
  2、配置数据源、JdbcTemplate(Spring提供的简化数据库操作的工具)操作数据
  3、给方法上标注 @Transactional 表示当前方法是一个事务方法;
  4、 @EnableTransactionManagement 开启基于注解的事务管理功能;
  		@EnableXXX
  5、配置事务管理器来控制事务;
  		@Bean
  		public PlatformTransactionManager transactionManager()
 
 
  原理:
  1)、@EnableTransactionManagement
  			利用TransactionManagementConfigurationSelector给容器中会导入组件
  			导入两个组件
  			AutoProxyRegistrar
  			ProxyTransactionManagementConfiguration
  2)、AutoProxyRegistrar:
  			给容器中注册一个 InfrastructureAdvisorAutoProxyCreator 组件;
  			InfrastructureAdvisorAutoProxyCreator:?
  			利用后置处理器机制在对象创建以后,包装对象,返回一个代理对象(增强器),代理对象执行方法利用拦截器链进行调用;
 
  3)、ProxyTransactionManagementConfiguration 做了什么?
  			1、给容器中注册事务增强器;
  				1)、事务增强器要用事务注解的信息,AnnotationTransactionAttributeSource解析事务注解
  				2)、事务拦截器:
  					TransactionInterceptor;保存了事务属性信息,事务管理器;
  					他是一个 MethodInterceptor;
  					在目标方法执行的时候;
  						执行拦截器链;
  						事务拦截器:
  							1)、先获取事务相关的属性
  							2)、再获取PlatformTransactionManager,如果事先没有添加指定任何transactionmanger
  								最终会从容器中按照类型获取一个PlatformTransactionManager;
  							3)、执行目标方法
  								如果异常,获取到事务管理器,利用事务管理回滚操作;
  								如果正常,利用事务管理器,提交事务

避坑指南

防止事务不生效

  • 因为配置不正确,导致方法上的事务没生效。我们务必确认调用 @Transactional 注 解标记的方法是 public 的,并且是通过 Spring 注入的 Bean 进行调用的。

  • 首先定义一个具有 ID 和姓名属性的 UserEntity

  @Entity
  @Data
  public class UserEntity {
    @Id
    @GeneratedValue(strategy = AUTO)
    private Long id;
    private String name;
    public UserEntity() { }
    public UserEntity(String name) {
    	this.name = name;
    }
  }
  • 使用 Spring JPA 做数据库访问,实现这样一个 Repository,新增一个根据用户名查询所有数据的方法
  @Repository
  public interface UserRepository extends JpaRepository<UserEntity, Long> {
  	List<UserEntity> findByName(String name);
  }
  • 定义一个 UserService 类,负责业务逻辑处理,用户创建操作失败,期望事务可以回滚
  @Service
  @Slf4j
  public class UserService {
      @Autowired
      private UserRepository userRepository;
      @Autowired
      private UserService self;

      @PostConstruct
      public void init() {
          log.info("this {} self {}", this.getClass().getName(), self.getClass().getName());
      }

      //一个公共方法供Controller调用,内部调用事务性的私有方法
      public int createUserWrong1(String name) {
          try {
              this.createUserPrivate(new UserEntity(name));
          } catch (Exception ex) {
              log.error("create user failed because {}", ex.getMessage());
          }
          return userRepository.findByName(name).size();
      }



      //标记了@Transactional的private方法
      @Transactional
      private void createUserPrivate(UserEntity entity) {
          userRepository.save(entity);
          if (entity.getName().contains("test"))
              throw new RuntimeException("invalid username!");
      }



      //重新注入自己
      public int createUserRight(String name) {
          try {
              self.createUserPublic(new UserEntity(name));
          } catch (Exception ex) {
              log.error("create user failed because {}", ex.getMessage());
          }
          return userRepository.findByName(name).size();
      }


      //不出异常
      @Transactional
      public int createUserWrong3(String name) {
          try {
              this.createUserPublic(new UserEntity(name));
          } catch (Exception ex) {
              log.error("create user failed because {}", ex.getMessage());
          }
          return userRepository.findByName(name).size();
      }
    
      //根据用户名查询用户数
      public int getUserCount(String name) {
          return userRepository.findByName(name).size();
      }
  }
  • 下面是 Controller 的实现,只是调用一下刚才定义的 UserService 中的入口方法createUserWrong1
  @RestController
  @RequestMapping("transactionproxyfailed")
  @Slf4j
  public class TransactionProxyFailedController {

      @Autowired
      private UserService userService;

      @GetMapping("wrong1")
      public int wrong1(@RequestParam("name") String name) {
          return userService.createUserWrong1(name);
      }

      @GetMapping("wrong2")
      public int wrong2(@RequestParam("name") String name) {
          return userService.createUserWrong2(name);
      }

      @GetMapping("wrong3")
      public int wrong3(@RequestParam("name") String name) {
          return userService.createUserWrong3(name);
      }

      @GetMapping("right1")
      public int right1(@RequestParam("name") String name) {
          return userService.createUserRight(name);
      }



  }
  • 调用接口后发现,即便用户名不合法,用户也能创建成功。

  • @Transactional 生效原则 1,除非特殊配置(比如使用 AspectJ 静态织入实现AOP),否则只有定义在 public 方法上的 @Transactional 才能生效。原因是,Spring默认通过动态代理的方式实现 AOP,对目标方法进行增强,private 方法无法代理到,Spring 自然也无法动态增强事务处理逻辑。

  • 把标记了事务注解的 createUserPrivate 方法改为 public即可。在 UserService 中再建一个入口方法 createUserWrong2,来调用这个 public 方法再次尝试

  //自调用
      public int createUserWrong2(String name) {
          try {
              this.createUserPublic(new UserEntity(name));
          } catch (Exception ex) {
              log.error("create user failed because {}", ex.getMessage());
          }
          return userRepository.findByName(name).size();
      }

      //可以传播出异常
      @Transactional
      public void createUserPublic(UserEntity entity) {
          userRepository.save(entity);
          if (entity.getName().contains("test"))
              throw new RuntimeException("invalid username!");
      }
  • 测试发现,调用新的 createUserWrong2 方法事务同样不生效。

  • @Transactional 生效原则 2,必须通过代理过的类从外部调用目标方法才能生效。

  • 让 Controller 直接调用之前定义的 UserService 的 createUserPublic方法

  @GetMapping("right2")
  public int right2(@RequestParam("name") String name) {
    try {
      userService.createUserPublic(new UserEntity(name));
    } catch (Exception ex) {
      log.error("create user failed because {}", ex.getMessage());
    }
    return userService.getUserCount(name);
  }
  • this 自调用、通过 self 调用,以及在 Controller 中调用UserService 三种实现的区别

通过 this 自调用,没有机会走到 Spring 的代理类

后两种改进方案调用的是 Spring 注入的 UserService,通过代理调用才有机会对 createUserPublic 方法进行动态增强。

  • 强烈建议你在开发时打开相关的 Debug 日志,以方便了解Spring 事务实现的细节,并及时判断事务的执行情况

事务即便生效也不一定回滚

  • 因为异常处理不正确,导致事务虽然生效但出现异常时没回滚。Spring 默认只会对 标记 @Transactional 注解的方法出现了 RuntimeException 和 Error 的时候回滚,如果 我们的方法捕获了异常,那么需要通过手动编码处理事务回滚。如果希望 Spring 针对其他 异常也可以回滚,那么可以相应配置 @Transactional 注解的 rollbackFor 和 noRollbackFor 属性来覆盖其默认设置。

  • 通过 AOP 实现事务处理可以理解为,使用 try…catch…来包裹标记了 @Transactional 注 解的方法,当方法出现了异常并且满足一定条件的时候,在 catch 里面我们可以设置事务 回滚,没有异常则直接提交事务。
  • 第一,只有异常传播出了标记了 @Transactional 注解的方法,事务才能回滚。
  • 第二,默认情况下,出现 RuntimeException(非受检异常)或 Error 的时候,Spring才会回滚事务。受检异常一般是业务异常,或者说是类似另一种方法的返回值,出现这样的异常可能业务还能完成,所以不会主动回滚;而Error 或 RuntimeException 代表了非预期的结果,应该回滚
  • 在 createUserWrong1 方法中会抛出一个 RuntimeException,但由于方法内 catch 了 所有异常,异常无法从方法传播出去,事务自然无法回滚。
  • 在 createUserWrong2 方法中,注册用户的同时会有一次 otherTask 文件读取操作, 如果文件读取失败,我们希望用户注册的数据库操作回滚。虽然这里没有捕获异常,但 因为 otherTask 方法抛出的是受检异常,createUserWrong2 传播出去的也是受检异 常,事务同样不会回滚。
public class UserService {
    @Autowired
    private UserRepository userRepository;

    //异常无法传播出方法,导致事务无法回滚
    @Transactional
    public void createUserWrong1(String name) {
        try {
            userRepository.save(new UserEntity(name));
            throw new RuntimeException("error");
        } catch (Exception ex) {
            log.error("create user failed", ex);
        }
    }

    //即使出了受检异常也无法让事务回滚
    @Transactional
    public void createUserWrong2(String name) throws IOException {
        userRepository.save(new UserEntity(name));
        otherTask();
    }

    //因为文件不存在,一定会抛出一个IOException
    private void otherTask() throws IOException {
        Files.readAllLines(Paths.get("file-that-not-exist"));
    }
}
  • Controller 中的实现,仅仅是调用 UserService 的 createUserWrong1 和createUserWrong2 方法,这 2 个方法的实现和调用,虽然完全避开了事务不生效的坑,但因为异常处理不当,导致程序没有如我们期望的文件操作出现异常时回滚事务。
  • 第一,如果你希望自己捕获异常进行处理的话,也没关系,可以手动设置让当前事务处于回滚状态:
    @Transactional
    public void createUserRight1(String name) {
        try {
            userRepository.save(new UserEntity(name));
            throw new RuntimeException("error");
        } catch (Exception ex) {
            log.error("create user failed", ex);
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        log.info("result {} ", userRepository.findByName(name).size());//为什么这里是1你能想明白吗?
    }
  • 第二,在注解中声明,期望遇到所有的 Exception 都回滚事务(来突破默认不回滚受检异 常的限制):
    //DefaultTransactionAttribute
    @Transactional(rollbackFor = Exception.class)
    public void createUserRight2(String name) throws IOException {
        userRepository.save(new UserEntity(name));
        otherTask();
    }

确认事务传播配置是否符合自己的业务逻辑

  • 如果方法涉及多次数据库操作,并希望将它们作为独立的事务进行提交或回滚,那么 我们需要考虑进一步细化配置事务传播方式,也就是 @Transactional 注解的 Propagation 属性。

  • 有这么一个场景:一个用户注册的操作,会插入一个主用户到用户表,还会注册一个关联的 子用户。我们希望将子用户注册的数据库操作作为一个独立事务来处理,即使失败也不会影 响主流程,即不影响主用户的注册。
  • 模拟一个实现类似业务逻辑的 UserService:
@Service
@Slf4j
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private SubUserService subUserService;

    @Transactional
    public void createUserWrong(UserEntity entity) {
        createMainUser(entity);
        subUserService.createSubUserWithExceptionWrong(entity);
    }

    public int getUserCount(String name) {
        return userRepository.findByName(name).size();
    }

    private void createMainUser(UserEntity entity) {
        userRepository.save(entity);
        log.info("createMainUser finish");
    }
}
  • SubUserService 的 createSubUserWithExceptionWrong 实现正如其名,因为最后我们抛出了一个运行时异常,错误原因是用户状态无效,所以子用户的注册肯定是失败的。我们期望子用户的注册作为一个事务单独回滚,不影响主用户的注册,这样的逻辑可以实现吗?
@Service
@Slf4j
public class SubUserService {

    @Autowired
    private UserRepository userRepository;

    @Transactional
    public void createSubUserWithExceptionWrong(UserEntity entity) {
        log.info("createSubUserWithExceptionWrong start");
        userRepository.save(entity);
        throw new RuntimeException("invalid status");
    }

}
  • 我们在 Controller 里实现一段测试代码,调用 UserService
@RestController
@RequestMapping("transactionpropagation")
@Slf4j
public class TransactionPropagationController {

    @Autowired
    private UserService userService;

    @GetMapping("wrong")
    public int wrong(@RequestParam("name") String name) {
        try {
            userService.createUserWrong(new UserEntity(name));
        } catch (Exception ex) {
            log.error("createUserWrong failed, reason:{}", ex.getMessage());
        }
        return userService.getUserCount(name);
    }
}
  • 调用后很明显事务回滚了,因为运行时异常逃出了 @Transactional 注解标记的 createUserWrong 方法,Spring 当然会回滚事务了。如果我们希望主方法不回滚,应该 把子方法抛出的异常捕获了。也就是这么改,把 subUserService.createSubUserWithExceptionWrong 包裹上catch,这样外层主方法就不会出现异常了
    @Transactional
    public void createUserWrong2(UserEntity entity) {
        createMainUser(entity);
        try {
            subUserService.createSubUserWithExceptionWrong(entity);
        } catch (Exception ex) {
            // 虽然捕获了异常,但是因为没有开启新事务,而当前事务因为异常已经被标记为rollback了,所以最终还是会回滚。
            log.error("create sub user error:{}", ex.getMessage());
        }
    }
  • 我们之前说,出了异常事务不一定回滚,这里说的却是不出异常,事务也不 一定可以提交。原因是,主方法注册主用户的逻辑和子方法注册子用户的逻辑是同一个事 ,子逻辑标记了事务需要回滚,主逻辑自然也不能提交了。
  • 修复方式就很明确了,想办法让子逻辑在独立事务中运行,也就是改一下 SubUserService 注册子用户的方法,为注解加上 propagation = Propagation.REQUIRES_NEW 来设置 REQUIRES_NEW 方式的事务传播策略,也就是执 行到这个方法时需要开启新的事务,并挂起当前事务
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void createSubUserWithExceptionRight(UserEntity entity) {
        log.info("createSubUserWithExceptionRight start");
        userRepository.save(entity);
        throw new RuntimeException("invalid status");
    }
  • 主方法没什么变化,同样需要捕获异常,防止异常漏出去导致主事务回滚,重新命名为 createUserRight
    @Transactional
    public void createUserRight(UserEntity entity) {
        createMainUser(entity);
        try {
            subUserService.createSubUserWithExceptionRight(entity);
        } catch (Exception ex) {
            // 捕获异常,防止主方法回滚
            log.error("create sub user error:{}", ex.getMessage());
        }
    }

  • 改造后,重新运行程序可以看到如下的关键日志:

    第 1 行日志提示我们针对 createUserRight 方法开启了主方法的事务; 第 2 行日志提示创建主用户完成; 第 3 行日志可以看到主事务挂起了,开启了一个新的事务,针对 createSubUserWithExceptionRight 方案,也就是我们的创建子用户的逻辑; 第 4 行日志提示子方法事务回滚; 第 5 行日志提示子方法事务完成,继续主方法之前挂起的事务; 第 6 行日志提示主方法捕获到了子方法的异常; 第 8 行日志提示主方法的事务提交了,随后我们在 Controller 里没看到静默回滚的异 常。

  • 运行测试程序看到如下结果,getUserCount 得到的用户数量为 1,代表只有一个用户也就 是主用户注册完成了,符合预期

mybatis配合事务

  • userData
@Data
public class UserData {
    private Long id;
    private String name;
    private String source;
}
  • userDataMapper
@Mapper
public interface UserDataMapper {
    @Insert("insert into userdata(name,source)values(#{name},#{source})")
    void insert(@Param("name") String name, @Param("source") String source);

    @Select("select count(*) from userdata where name=#{name}")
    int count(@Param("name") String name);
}
  • userService
@Service
@Slf4j
public class UserService {

    @Autowired
    private UserDataMapper userDataMapper;

    @Autowired
    private SubUserService subUserService;


    @Transactional
    public void createUser(String name) {
        createMainUser(name);
        try {
            subUserService.createSubUser(name);
        } catch (Exception ex) {
            log.error("create sub user error:{}", ex.getMessage());
        }
        //如果createSubUser是NESTED模式,这里抛出异常会导致嵌套事务无法『提交』
        throw new RuntimeException("create main user error");
    }

    private void createMainUser(String name) {
        userDataMapper.insert(name, "main");
    }


    public int getUserCount(String name) {
        return userDataMapper.count(name);
    }
}
  • subUserService
@Service
@Slf4j
public class SubUserService {

    @Autowired
    private UserDataMapper userDataMapper;

    //比较切换为REQUIRES_NEW,这里的createSubUser可以插入数据成功
    @Transactional(propagation = Propagation.NESTED)
    public void createSubUser(String name) {
        userDataMapper.insert(name, "sub");
    }
}
  • controller
@Slf4j
@RestController
@RequestMapping("nested")
public class NestedController {
    @Autowired
    private UserService userService;

    @GetMapping("test")
    public int right() {
        String name = UUID.randomUUID().toString();
        log.info("create user {}", name);
        try {
            userService.createUser(name);
        } catch (Exception ex) {
            log.error("create user error:{}", ex.getMessage());
        }
        return userService.getUserCount(name);
    }
}

异步

Spring异步线程池的接口类,其实质是java.util.concurrent.Executor

Spring 已经实现的异常线程池:

  1. SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。
  2. SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方
  3. ConcurrentTaskExecutor:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类
  4. SimpleThreadPoolTaskExecutor:是Quartz的SimpleThreadPool的类。线程池同时被quartz和非quartz使用,才需要使用此类
  5. ThreadPoolTaskExecutor :最常使用,推荐。 其实质是对java.util.concurrent.ThreadPoolExecutor的包装

@Async注解

spring对过@Async定义异步任务

异步的方法有3种 1:最简单的异步调用,返回值为void 2:带参数的异步调用 异步方法可以传入参数 3:异常调用返回Future

@Component
public class AsyncExceptionDemo {
    private static final Logger log = LoggerFactory.getLogger(AsyncExceptionDemo.class);

    /**
     * 最简单的异步调用,返回值为void
     */
    @Async
    public void asyncInvokeSimplest() {
        log.info("asyncSimplest");
    }

    /**
     * 带参数的异步调用 异步方法可以传入参数
     *  对于返回值是void,异常会被AsyncUncaughtExceptionHandler处理掉
     * @param s
     */
    @Async
    public void asyncInvokeWithException(String s) {
        log.info("asyncInvokeWithParameter, parementer={}", s);
        throw new IllegalArgumentException(s);
    }

    /**
     * 异常调用返回Future
     *  对于返回值是Future,不会被AsyncUncaughtExceptionHandler处理,需要我们在方法中捕获异常并处理
     *  或者在调用方在调用Futrue.get时捕获异常进行处理
     * 
     * @param i
     * @return
     */
    @Async
    public Future<String> asyncInvokeReturnFuture(int i) {
        log.info("asyncInvokeReturnFuture, parementer={}", i);
        Future<String> future;
        try {
            Thread.sleep(1000 * 1);
            future = new AsyncResult<String>("success:" + i);
            throw new IllegalArgumentException("a");
        } catch (InterruptedException e) {
            future = new AsyncResult<String>("error");
        } catch(IllegalArgumentException e){
            future = new AsyncResult<String>("error-IllegalArgumentException");
        }
        return future;
    }

}

在调用方法时,可能出现方法中抛出异常的情况。在异步中主要有有两种异常处理方法:

对于方法返回值是Futrue的异步方法:

a) 一种是在调用future的get时捕获异常;

b) 在异常方法中直接捕获异常

对于返回值是void的异步方法:通过AsyncUncaughtExceptionHandler处理异常

实现AsyncConfigurer接口对异常线程池更加细粒度的控制 a) 创建线程自己的线程池 b) 对void方法抛出的异常处理的类AsyncUncaughtExceptionHandler

/**
 * 通过实现AsyncConfigurer自定义异常线程池,包含异常处理
 * 
 * @author hry
 *
 */
@Service
public class MyAsyncConfigurer implements AsyncConfigurer{
    private static final Logger log = LoggerFactory.getLogger(MyAsyncConfigurer.class);

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();  
        threadPool.setCorePoolSize(1);  
        threadPool.setMaxPoolSize(1);  
        threadPool.setWaitForTasksToCompleteOnShutdown(true);  
        threadPool.setAwaitTerminationSeconds(60 * 15);  
        threadPool.setThreadNamePrefix("MyAsync-");
        threadPool.initialize();
        return threadPool;  
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
         return new MyAsyncExceptionHandler();  
    }

    /**
     * 自定义异常处理类
     * @author hry
     *
     */
    class MyAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {  

        @Override  
        public void handleUncaughtException(Throwable throwable, Method method, Object... obj) {  
            log.info("Exception message - " + throwable.getMessage());  
            log.info("Method name - " + method.getName());  
            for (Object param : obj) {  
                log.info("Parameter value - " + param);  
            }  
        }  

    } 

}

启动异步配置

@SpringBootApplication
@EnableAsync // 启动异步调用
public class AsyncApplicationWithAsyncConfigurer {
    private static final Logger log = LoggerFactory.getLogger(AsyncApplicationWithAsyncConfigurer.class);

    public static void main(String[] args) {
        log.info("Start AsyncApplication.. ");
        SpringApplication.run(AsyncApplicationWithAsyncConfigurer.class, args);
    }


}

测试,因为@Async是在单元测试执行之后才开始执行的,这时候单元测试框架和spring认为该单元测试已经成功了,于是将连接关闭了,这时候就连接不到数据库了!!!

@RunWith(SpringRunner.class)
@SpringBootTest(classes=AsyncApplicationWithAsyncConfigurer.class)
public class AsyncApplicationWithAsyncConfigurerTests {
    @Autowired
    private AsyncExceptionDemo asyncDemo;

    @Test
    public void contextLoads() throws InterruptedException, ExecutionException {
        asyncDemo.asyncInvokeSimplest();
        asyncDemo.asyncInvokeWithException("test");
        Future<String> future = asyncDemo.asyncInvokeReturnFuture(100);
        System.out.println(future.get());
    }
}

Similar Posts

Comments