常用注解总结

本文介绍Spring框架中异步任务的实现方式,包括@Async注解的基本概念、线程池配置、异步监听事件的使用,以及@ConditionalOnProperty和@ConfigurationProperties注解的作用和应用场景。
摘要由CSDN通过智能技术生成

1.Spring

1.1 @Async

1.1.1 同步和异步的基本概念

什么是异步任务?

异步任务属于多线程编程的一个环节:主线程在继续当前任务的同时,创建一个或多个新的非阻塞线程去执行其他任务。

异步任务和同步任务的区别

  • 同步是阻塞模式,异步是非阻塞模式。
  • 同步就是指程序在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会—直等待下去,直到收到返回信息才继续执行下去。
  • 异步就是程序调用一个耗时较长的功能(方法)时,它并不会阻塞程序的执行流程,程序会继续往下执行。当功能执行完毕时,程序能够获得执行完毕的消息或能够访问到执行的结果(如果有返回值或需要返回值时)。

1.1.2 @Async注解的基本概念

含义

  1. 在方法上使用@Async注解,表明该方法是一个异步任务。
  2. 在类上面使用@Async注解,表明该类中的所有方法都是异步任务。
  3. 使用此注解的方法的类对象,必须是spring管理下的bean对象(如使用@Component注解将类注册到spring中)。
  4. 使用异步任务,需要在主启动类上使用@EnableAsync注解开启异步配置。
  5. @Async注解在使用时,如果不指定线程池的名称,则使用Spring默认的线程池,Spring默认的线程池为SimpleAsyncTaskExecutor。
  6. 方法上一旦标记了@Async注解,当其它线程调用这个方法时,就会开启一个新的子线程去异步处理该业务逻辑。

1.1.3 线程池的使用

使用默认线程池的弊端:并发情况下,会无限创建线程

# spring默认线程池配置如下
默认核心线程数:8
最大线程数:Integet.MAX_VALUE
队列使用LinkedBlockingQueue
容量是:Integet.MAX_VALUE
空闲线程保留时间:60s
线程池拒绝策略:AbortPolicy

1.1.4 SpringBoot中的使用案例

主启动类上使用@EnableAsync注解开启异步配置

@SpringBootApplication
@EnableAsync//该注解也可以加到配置类上
public class MainApplication {
    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class,args);
    }
}

异步方法

@Component
public class AscTest {
    @Async
    public void ascVoidMethod(){
        //业务逻辑
        System.out.println(Thread.currentThread().getName()+":我是一个异步任务");
    }
}

以上就是一个异步方法的简单使用,但此时该异步方法使用的是默认线程池,上面提到使用默认线程池有一些弊端,我们可以根据我们的需求来设置合适的线程池。

自定义线程池

@Configuration
@Data
//@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    /**
     * 核心线程
     * 当前运行线程小于核心线程数就创建线程
     * 当前运行线程大于或等于核心线程数,就把任务加入(阻塞)队列
     */
    private int corePoolSize = 10;
    /**
     * 队列容量
     * 当前运行线程大于队列容量,就创建新的线程来执行该任务
     */
    private int queueCapacity = 30;
    /**
     * 最大线程
     * 当前运行线程大于最大线程,就通过拒绝策略来拒绝任务
     */
    private int maxPoolSize = 50;
    /**
     * 线程最大空闲时间, 单位:秒
     * 当前运行线程大于核心线程或设置allowCoreThreadTimeOut时,
     * 线程会根据该属性值进行活性检查,一旦超时就销毁线程
     */
    private int keepAliveSeconds = 60;
    /**
     * 核心线程数是否允许超时
     */
    private boolean allowCoreThreadTimeOut = true;
    /**
     * 线程名称前缀
     */
    private String preFix = "async-thread-";
    /**
     * 拒绝策略
     */
    private int rejectedExecutionHandler = 0;

    /**
     * 配置线程池
     */
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(keepAliveSeconds);
        executor.setThreadNamePrefix(preFix);
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        //初始化
        executor.initialize();
        return executor;
    }
    /**
     * 异步任务中处理异常
     */
    @Override
    public  AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler(){
        return new SimpleAsyncUncaughtExceptionHandler();
    }
}

写一个接口来测试一下异步方法

@RestController
@RequestMapping("/async")
public class AsyncController {
    @Autowired
    AscTest ascTest;
    @GetMapping
    public void  testAsyncMethod (){
        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    System.out.println(Thread.currentThread().getName()+":hello" + i);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }}).start();
        ascTest.ascVoidMethod();

    }
}
//打印结果
Thread-17:hello0
async-thread-1:我是一个异步任务
Thread-17:hello1
Thread-17:hello2
Thread-17:hello3
Thread-17:hello4

从打印结果我们可以看到当程序阻塞的时候,异步方法不会被阻塞,会继续执行。

1.1.5 异步监听事件

  • @Async相关配置见上一个案例
  • 该案例通过AOP,自定义注解和@Async实现根据不同的性别调用不同的事件监听实现类
  • 该案例的应用:在本例中我们是通过controller的接口去调用进行测试的,我们还可以在定时任务,mq任务中使用异步监听任务,只要在AOP中通过参数和注解设置调用规则即可。

定义一个事件监听接口

public interface EventListener {
    void onEvent(MybatisPlusLearn mybatisPlusLearn);
}

定义一异步任务调用监听事件

@Component
public class AscTest {
    @Autowired
    private List<EventListener> listListener;
    
    @Async
    public void asyncListener(MybatisPlusLearn mybatisPlusLearn){
        listListener.forEach(item->
           item.onEvent(mybatisPlusLearn)
        );
    }
}

定义一个自定义注解用于接收性别参数

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SexAnnotation {
    boolean sex();
}

定义一个AOP来管理注解接收的参数

@Component
@Aspect
public class AnnotationAop {

    /**
     * 切入点:拦截注解
     */
    @Pointcut("@annotation(com.dong.asc.event.annotation.SexAnnotation)")
    private void annotationPointCut() {
    }

    @Around("annotationPointCut()")
    public Object annotationAround(ProceedingJoinPoint jp) throws Throwable {
        //获取方法
        Method method = ((MethodSignature) jp.getSignature()).getMethod();
        // 获取AspectAnnotation注解
        SexAnnotation annotation = method.getAnnotation(SexAnnotation.class);

        Object[] args = jp.getArgs();
        if (args.length==0 || !args[0].getClass().isAssignableFrom(MybatisPlusLearn.class)){
            System.out.println("参数不合法");
            return null;
        }
        MybatisPlusLearn mybatisPlusLearn = (MybatisPlusLearn) args[0];
        /**
         * annotation.sex()可以拿到注解的值
         * 本例中该值为boolean,只有两个值true和false
         * 如果注解中的字段为string,我们就可以拿到这个string进行判断过滤,如果不符合返回null就不会执行监听事件
         */
        boolean flag = annotation.sex();
        if (mybatisPlusLearn.getSex()==flag){
            //匹配就可以调用方法
            return jp.proceed();
        }
        return null;
    }
}

定义两个事件监听接口的实现类

通过上面的AOP我们可以拿到方法的参数和注解的值,通过比较这两个值,我们可以指定当参数性别和注解性别相等才调用接口

@Component
public class ManEventListenerImpl implements EventListener {
	//男:true,女:false
    @SexAnnotation(sex = true)
    @Override
    public void onEvent(MybatisPlusLearn mybatisPlusLearn) {
        System.out.println("我是"+mybatisPlusLearn.getName()+",我是靓仔");
    }
}
@Component
public class WoManListenerImpl implements EventListener {
    @SexAnnotation(sex = false)
    @Override
    public void onEvent(MybatisPlusLearn mybatisPlusLearn) {
        System.out.println("我是"+mybatisPlusLearn.getName()+",我是小姐姐");
    }
}

定义一个controller调用异步监听方法进行测试

@RestController
@RequestMapping("/async")
public class AsyncController {
    @Autowired
    AscTest ascTest;
    @Autowired
    MybatisPlusLearnService mybatisPlusLearnService;

    @GetMapping("/listener")
    public void asyncListener(){
        List<MybatisPlusLearn> list=mybatisPlusLearnService.list();
        list.forEach(item-> ascTest.asyncListener(item));
    }
}

//测试结果如下
我是小明,我是靓仔
我是小刚,我是靓仔
我是小红,我是小姐姐
我是小王,我是靓仔

1.2 @ConditionalOnProperty

1.2.1 作用

在spring boot中有时候需要控制配置类是否生效,可以使用@ConditionalOnProperty注解来控制@Configuration是否生效

1.2.2 示例

配置文件

city:
  company:
    logo: dong

配置类

/**
 * prefix为配置文件中的前缀,
 * name为配置的名字
 * havingValue是与配置的值对比值,当两个值相同返回true,配置类生效.
 */
@Configuration
@ConditionalOnProperty(prefix = "city.company",name = "logo",havingValue = "dong")
public class TestConditionalOnProperty {
    @Value("${city.company.logo}")
    private String logo;

    public void test(){
        System.out.println(logo);
    }
}

测试

@RestController
@RequestMapping("/annotation")
public class AnnotationController {
    @Autowired
    TestConditionalOnProperty conditionalOnProperty;

    @GetMapping("/conditionalOnProperty")
    public void testConditionalOnProperty(){
        conditionalOnProperty.test();
    }
}

当配置文件中city.company.logo的值和配置类中注解havingValue 的值相等,控制台就输出值“dong”,不相等的时候启动程序会直接报错,报错信息如下

Field conditionalOnProperty in com.dong.controller.AnnotationController required a bean of type 'com.dong.annotation.TestConditionalOnProperty' that could not be found.

这是因为当不相等的时候配置类没有生效,就导致@Autowired失败

1.2.3 使用场景

  1. MQ的消费者,当配置满足相关配置后才能进行消费
@Component
@ConditionalOnProperty(prefix = "city.company", name="logo" , havingValue = "dong")
@RocketMQMessageListener(topic = "one_topic" ,consumerGroup ="one_group",consumeThreadMax = 4)
public class ConsumerOne implements RocketMQListener<MessageExt> {
    @Override
    public void onMessage(MessageExt messageExt) {
        String msg = new String(messageExt.getBody());
        System.out.println(msg);
    }
}

1.3 @ConfigurationProperties

1.3.1 作用

  1. 在spring开发过程中我们常使用到@ConfigurationProperties注解,通常是用来将properties和yml配置文件属性转化为bean对象使用。
  2. 该注解通过反射为配置类注入值,需要对配置类接收的配置文件属性提供get和set方法。

1.3.2 示例1:将属性转换成bean对象

配置文件

collect:
  myMap:
    sex:name: 张三

配置类

@Configuration
@ConfigurationProperties(prefix = "collect")
@Data
public class TestConfigurationProperties {
    private Map<String,String> myMap =new HashMap<>();
}

可以在配置类中加上@EnableConfigurationProperties(TestConfigurationProperties .class)来使ConfigurationProperties注解生效,@Component和@Configuration有一样的效果

测试类

@RestController
@RequestMapping("/annotation")
public class AnnotationController {
    @Autowired
    TestConfigurationProperties configurationProperties;
    
    @GetMapping("/ConfigurationProperties")
    public void testConfigurationProperties(){
        Map<String,String> map= configurationProperties.getMyMap();
        System.out.println(map);
        //{name=张三, sex=男}
    }
}

1.3.3 使用场景

  1. 像示例1那样初始化集合数据。
  2. 可以通过prefix 指明前缀,不同字段类型来接收前缀下不同属性的属性值
collect:
    sex:name: 张三
@Configuration
@ConfigurationProperties(prefix = "collect")
@Data
public class TestConfigurationProperties {
    private String sex;
    private String name;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值