一、创建线程
@Test
public void test0() throws Exception {
System.out.println("main函数开始执行");
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
System.out.println("===task start===");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("===task finish===");
}
});
thread.start();
System.out.println("main函数执行结束");
}
二、Future
jdk8之前的实现方式,在JUC下增加了Future,从字面意思理解就是未来的意思,但使用起来却着实有点鸡肋,并不能实现真正意义上的异步,获取结果时需要阻塞线程,或者不断轮询。
@Test
public void test1() throws Exception {
System.out.println("main函数开始执行");
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<Integer> future = executor.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("===task start===");
Thread.sleep(5000);
System.out.println("===task finish===");
return 3;
}
});
//这里需要返回值时会阻塞主线程,如果不需要返回值使用是OK的。倒也还能接收
//Integer result=future.get();
System.out.println("main函数执行结束");
System.in.read();
}
三、CompletableFuture
使用原生的CompletableFuture实现异步操作,加上对lambda的支持,可以说实现异步任务已经发挥到了极致。
@Test
public void test2() throws Exception {
System.out.println("main函数开始执行");
ExecutorService executor = Executors.newFixedThreadPool(2);
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(new Supplier<Integer>() {
@Override
public Integer get() {
System.out.println("===task start===");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("===task finish===");
return 3;
}
}, executor);
future.thenAccept(e -> System.out.println(e));
System.out.println("main函数执行结束");
}
四、Spring的Async注解
使用spring实现异步需要开启注解,可以使用xml方式或者java config的方式。
xml方式: <task:annotation-driven />
<task:annotation-driven executor="executor" />
<task:executor id="executor"
pool-size="2" 线程池的大小
queue-capacity="100" 排队队列长度
keep-alive="120" 线程保活时间(单位秒)
rejection-policy="CALLER_RUNS" 对拒绝的任务处理策略 />
java方式:
@EnableAsync
public class MyConfig {
@Bean
public TaskExecutor executor(){
ThreadPoolTaskExecutor executor=new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10); //核心线程数
executor.setMaxPoolSize(20); //最大线程数
executor.setQueueCapacity(1000); //队列大小
executor.setKeepAliveSeconds(300); //线程最大空闲时间
executor.setThreadNamePrefix("fsx-Executor-"); //指定用于新创建的线程名称的前缀。
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}
(1)@Async
@Test
public void test3() throws Exception {
System.out.println("main函数开始执行");
myService.longtime();
System.out.println("main函数执行结束");
}
@Async
public void longtime() {
System.out.println("我在执行一项耗时任务");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("完成");
}
(2)AsyncResult
如果需要返回值,耗时方法返回值用AsyncResult包装。
@Test
public void test4() throws Exception {
System.out.println("main函数开始执行");
Future<Integer> future=myService.longtime2();
System.out.println("main函数执行结束");
System.out.println("异步执行结果:"+future.get());
}
@Async
public Future<Integer> longtime2() {
System.out.println("我在执行一项耗时任务");
try {
Thread.sleep(8000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("完成");
return new AsyncResult<>(3);
}
举例子:
UserBehaviorAspect
package com.backend.aspect;
import com.backend.po.UserBehaviorPO;
import com.backend.service.IUserBehaviorService;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.concurrent.Future;
/**
* 用户行为记录切面
*
* @Package: com.backend.aspect
* @ClassName: UserBehaviorAspect
* @author:
* @since: 2020/4/26 16:26
* @version: 1.0
* @Copyright: 2020 . All rights reserved.
*/
@Aspect
@Component
@Slf4j
public class UserBehaviorAspect {
@Autowired
IUserBehaviorService service;
/**
* 切面:切所有controller层的接口方法
*
* @param
* @throws
* @return: void
* @author:
* @Date: 2020/4/28 17:05
* @version: 1.0
* @Copyright: . All rights reserved.
*/
@Pointcut("execution(public * com.backend.controller.*.*(..)) && !execution(public * com.backend.controller.UserBehaviorController.*(..))")
public void userBehavior() {
}
/**
* 用户行为记录
*
* @param proceedingJoinPoint
* @throws
* @return: java.lang.Object
* @author:
* @Date: 2020/4/28 11:13
* @version: 1.0
* @Copyright: zq. All rights reserved.
*/
@Around(value = "userBehavior()")
public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
/**
* 1.获取请求
*/
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
/**
* 2.组装参数
*/
UserBehaviorPO userBehaviorPO = prepareParameter(proceedingJoinPoint, request);
/**
* 3.异步:调用异步插入方法
*/
Future<UserBehaviorPO> future = service.insertAll(userBehaviorPO);
/**
* 4.执行接口方法
*/
Object obj = null;
try {
obj = proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
/**
* 5.异步:异常捕捉,执行将异常更新至数据库表的方法
*/
service.updateUserBehavior(future, throwable);
}
/**
* 6.数据返回
*/
return obj;
}
/**
* 组装参数
*
* @param proceedingJoinPoint
* @param request
* @throws
* @return: com.backend.po.UserBehaviorPO
* @author:
* @Date: 2020/4/28 11:16
* @version: 1.0
* @Copyright: All rights reserved.
*/
public UserBehaviorPO prepareParameter(ProceedingJoinPoint proceedingJoinPoint, HttpServletRequest request) {
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
Method targetMethod = signature.getMethod();
String value = targetMethod.isAnnotationPresent(ApiOperation.class) ? targetMethod.getAnnotation(ApiOperation.class).value() : "";
UserBehaviorPO userBehaviorPO = UserBehaviorPO.builder()
.reqApiContent(value)
.userName("用户名XXX")
.accessTime(LocalDateTime.now())
.reqUrl(request.getRequestURL().toString())
.reqHttpMethod(request.getMethod())
.reqPath(request.getServletPath())
.reqIp(request.getRemoteAddr())
.reqClassMethod(proceedingJoinPoint.getSignature().getDeclaringTypeName() + "." + proceedingJoinPoint.getSignature().getName())
.reqArgs(Arrays.toString(proceedingJoinPoint.getArgs()))
.build();
return userBehaviorPO;
}
}
IUserBehaviorService
package com.mpdo.backend.service;
import com.mpdo.backend.params.UserBehaviorParam;
import com.mpdo.backend.po.UserBehaviorPO;
import com.github.pagehelper.Page;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.concurrent.Future;
/**
* <p>
* 用户行为对象模型 服务类
* </p>
*
* @author zq
* @since 2020-04-27
*/
public interface IUserBehaviorService extends BasePageSearchService<UserBehaviorPO, UserBehaviorParam> {
/**
* <p>
* 用户行为对象模型 根据条件查询符合条件记录条目数
* </p>
*/
@Override
Integer findCountSelective(UserBehaviorParam param);
/**
* <p>
* 用户行为对象模型 根据条件分页查询符合条件记录条目内容
* </p>
*/
@Override
Page<UserBehaviorPO> findSelective(@Param("pageNum") int pageNum,
@Param("pageSize") int pageSize,
UserBehaviorParam param);
/**
* <p>
* 用户行为对象模型 根据条件查询所有符合条件记录条目内容,不分页,可能产生oom
* </p>
*/
List<UserBehaviorPO> findAll(UserBehaviorParam param);
/**
* <p>
* 用户行为对象模型 新增记录条目,所有属性均不为空(约定,非强制)
* </p>
*/
Future<UserBehaviorPO> insertAll(UserBehaviorPO param);
/**
* <p>
* 用户行为对象模型 新增记录条目数,允许部分属性为空(为支持自增主键的情况)
* </p>
*/
Integer insertSelective(UserBehaviorPO param);
/**
* <p>
* 用户行为对象模型 根据主键更新其他字段,属性为null则不更新,
* 如果希望将值更新为null,该方法不适用
* </p>
*/
Integer updateUserBehavior(Future<UserBehaviorPO> future, Throwable throwable);
}
UserBehaviorServiceImpl
package com.backend.service.impl;
import com.backend.dao.UserBehaviorMapper;
import com.backend.params.UserBehaviorParam;
import com.backend.po.UserBehaviorPO;
import com.backend.service.BaseService;
import com.backend.service.IUserBehaviorService;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.concurrent.Future;
/**
* <p>
* 用户行为对象模型 服务实现类
* </p>
*
* @author zq
* @since 2020-04-27
*/
@Slf4j
@Service
public class UserBehaviorServiceImpl implements IUserBehaviorService {
@Autowired
UserBehaviorMapper mapper;
@Override
public Integer findCountSelective(UserBehaviorParam param) {
return mapper.findCountSelective(param);
}
@Override
public Page<UserBehaviorPO> findSelective(@Param("pageNum") int pageNum,
@Param("pageSize") int pageSize,
UserBehaviorParam param) {
PageHelper.startPage(pageNum, pageSize);
return mapper.findSelective(param);
}
@Override
public List<UserBehaviorPO> findAll(UserBehaviorParam param) {
return mapper.findAll(param);
}
/**
* 异步方法:将用户行为记录插入数据库表
*
* @param param
* @throws
* @return: java.util.concurrent.Future<com.backend.po.UserBehaviorPO>
* @author: zq
* @Date: 2020/4/28 17:13
* @version: 1.0
* @Copyright: zq. All rights reserved.
*/
@Async("asyncTaskExecutor")
@Override
public Future<UserBehaviorPO> insertAll(UserBehaviorPO param) {
/**
* 1.入参日志
*/
String logPre = "[异步方法:将用户行为记录插入数据库表]";
log.info(logPre + " 入参: param={}", param);
/**
* 2.执行数据入库,并记录返回值
*/
Integer res = -1;
try {
res = mapper.insertAll(param);
} catch (Exception e) {
log.error("用户行为插入数据失败,异常如下:", e);
}
/**
* 3.出参日志
*/
log.info(logPre + " 出参: res={}", res);
/**
* 4.出参校验
*/
if (1 == res) {
log.info(logPre + " 操作成功!");
} else {
log.error(logPre + " 操作失败!请查询后台日志解决问题!");
}
/**
* 5.数据返回
*/
return new AsyncResult<>(param);
}
@Override
public Integer insertSelective(UserBehaviorPO param) {
return mapper.insertSelective(param);
}
/**
* 异步方法:将异常信息更新到数据库表记录
*
* @param future
* @param throwable
* @throws
* @return: java.lang.Integer
* @author: zq
* @Date: 2020/4/28 15:18
* @version: 1.0
* @Copyright: zq. All rights reserved.
*/
@Async("asyncTaskExecutor")
@Override
public Integer updateUserBehavior(Future<UserBehaviorPO> future, Throwable throwable) {
/**
* 1.入参日志
*/
String logPre = "[异步方法:将异常信息更新到数据库表记录]";
log.info(logPre + "入参: throwable:", throwable);
/**
* 2.返回值准备
*/
int res = -1;
/**
* 3.参数准备
*/
UserBehaviorPO po;
try {
//get方法会阻塞当前方法直到future对应的方法执行完:默认会等插入方法做完才可以做更新
po = future.get();
} catch (Exception e) {
log.error(logPre + "获取UserBehaviorPO发生异常![将异常信息更新到数据库表记录]方法未执行!", e);
return res;
}
po.setExceptionMsg(throwable.toString());
/**
* 4.整理后参数日志
*/
log.info(logPre + "整理后参数: po={}", po);
/**
* 5.根据数据去执行对应的service操作[有就更新,没有就提交]:
* ①如果此时null != userBehaviorPO.getId(),说明插入成功,此时只需要将异常信息更新进表即可
* ②如果此时userBehaviorPO.getId()==null,说明此时插入数据失败,则此时直接插入本条数据(带异常信息)(这种情况一般不会出现)
*/
if (null != po.getId()) {
res = mapper.updateByPrimaryKey(po);
log.info(logPre + "更新数据,结果:{}", res);
} else {
res = mapper.insertAll(po);
log.info(logPre + "插入数据,结果:{}", res);
}
/**
* 6.数据返回
*/
return res;
}
}
BackendConfiguration
package com.backend.config;
import com.backend.config.convert.ConverterProperties;
import com.backend.config.convert.DataCollectionProperties;
import com.backend.config.convert.StringToEnumIgnoringCaseConverterFactory;
import com.backend.config.log.LogstashProperties;
import com.backend.config.security.Authentication401EntryPoint;
import com.backend.config.security.AuthenticationFiltersConfigurer;
import com.backend.config.security.SecurityProperties;
import com.backend.feign.CbspRemoteFeign;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@EnableFeignClients(basePackageClasses = {CbspRemoteFeign.class})
@EnableConfigurationProperties({DataCollectionProperties.class, ConverterProperties.class, SecurityProperties.class, LogstashProperties.class})
public class BackendConfiguration {
public @Bean
ConverterFactory<String, Enum<?>> stringToEnumIgnoringCaseConverterFactory(Optional<List<GenericConversionService>> conversions) {
ConverterFactory<String, Enum<?>> factory = new StringToEnumIgnoringCaseConverterFactory();
conversions.ifPresent(services -> {
services.forEach(service -> service.addConverterFactory(factory));
});
return factory;
}
@Configuration
@EnableSwagger2
public static class SwaggerConfiguration {
public @Bean
Docket docket() {
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.groupName("mpdo-backend")
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.ant("/**"))
.build();
return docket;
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("mpdo-backend-api")
.contact(new Contact("backend", "portal.com", "backend@com"))
.build();
}
}
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final String[] NONE_FILTER_URLS = {"/swagger-ui*.html,/swagger-resources/**,/v2/api-docs"};
private static final String[] NONE_LOGIN_URLS = {"/login", "/logout"};
private final AuthenticationFiltersConfigurer authFiltersConfigurer;
private final Authentication401EntryPoint authenticationEntryPoint;
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(NONE_FILTER_URLS);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().sameOrigin().and()
.authorizeRequests()
.antMatchers(NONE_LOGIN_URLS).permitAll()
.anyRequest().permitAll().and()
.httpBasic().disable()
.csrf().disable()
.cors().disable()
.apply(this.authFiltersConfigurer).and()
.formLogin().disable()
.exceptionHandling().authenticationEntryPoint(this.authenticationEntryPoint)
;
}
}
@Bean("asyncTaskExecutor")
public AsyncTaskExecutor executor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(1000);
executor.setKeepAliveSeconds(300);
executor.setThreadNamePrefix("async-executor-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
BackendApplication
package com.backend;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
@EnableAsync
@Slf4j
@RestController
@SpringBootApplication
@ComponentScan({ "com" })
public class BackendApplication {
public static void main(String[] args) {
SpringApplication.run(BackendApplication.class, args);
}
@GetMapping("git")
public ResponseEntity<String> git(HttpServletRequest request) {
if (MapUtils.isNotEmpty(request.getParameterMap())) {
request.getParameterMap().forEach((key, values) -> {
log.info(key + ": " + Arrays.deepToString(values));
});
}
return ResponseEntity.ok("OK");
}
}