Java实现异步调用

一、创建线程

 @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");
    }
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值