事务
环境搭建
<!-- 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.配置数据源、JdbcTemplate(Spring提供简化数据库操作的工具)操作数据
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 已经实现的异常线程池:
- SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。
- SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方
- ConcurrentTaskExecutor:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类
- SimpleThreadPoolTaskExecutor:是Quartz的SimpleThreadPool的类。线程池同时被quartz和非quartz使用,才需要使用此类
- 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());
}
}