Spring进阶(二十)之事件处理

news2024/10/2 10:28:18

目录

为什么需要使用事件这种模式

事件模式中的几个概念

使用事件模式实现上面用户注册的业务

事件对象

事件监听器

事件广播器

事件广播默认实现

自定义用户注册成功事件类

用户注册服务

下面我们使用spring来将上面的对象组装起来

测试用例模拟用户注册 

添加注册成功发送邮件功能

小结

Spring中实现事件模式

事件相关的几个类

硬编码的方式使用spring事件3步骤 

案例

小结

面向接口的方式

面向@EventListener注解方式

监听器支持排序功能

通过接口实现监听器的情况

通过@EventListener注解实现监听器的情况

监听器异步模式

事件使用的建议


为什么需要使用事件这种模式

先来看一个业务场景:

产品经理:路人,这两天你帮我实现一个注册的功能 我:注册功能比较简单,将用户信息入库就可以了,伪代码如下:

public void registerUser(UserModel user){
    //插入用户信息到db,完成注册
    this.insertUser(user);
}

过了几天,产品经理:路人,注册成功之后,给用户发送一封注册成功的邮件 我:修改了一下上面注册代码,如下:

public void registerUser(UserModel user){
    //插入用户信息到db,完成注册
    this.insertUser(user);
    //发送邮件
    this.sendEmailToUser(user);
}

由于修改了注册接口,所以所有调用这个方法的地方都需要重新测试一遍,让测试的兄弟们帮忙跑了一 遍。 又过了几天,产品经理:路人,注册成功之后,给用户发一下优惠券 我:好的,又调整了一下代码:

public void registerUser(UserModel user){
    //插入用户信息到db,完成注册
    this.insertUser(user);
    //发送邮件
    this.sendEmailToUser(user);
    //发送优惠券
    this.sendCouponToUser(user);
}

我:测试的兄弟们,辛苦一下大家,注册接口又修改了,帮忙再过一遍。 过了一段时间,公司效益太好,产品经理:路人,注册的时候,取消给用户发送优惠券的功能。 我:又跑去调整了一下上面代码,将发送优惠券的功能干掉了,如下

public void registerUser(UserModel user){
    //插入用户信息到db,完成注册
    this.insertUser(user);
    //发送邮件
    this.sendEmailToUser(user);
}

由于调整了代码,而注册功能又属于核心业务,所以需要让测试再次帮忙过一遍,又要麻烦测试来一遍了。

突然有一天,产品经理:路人,注册接口怎么这么慢啊,并且还经常失败?你这让公司要损失多少用户 啊 我:赶紧跑去查看了一下运行日志,发现注册的时候给用户发送邮件不稳定,依赖于第三方邮件服务 器,耗时比较长,并且容易失败。 跑去给产品经理说:由于邮件服务器不稳定的原因,导致注册不稳定。 产品经理:邮件你可以不发,但是你得确保注册功能必须可以用啊。 我想了想,将上面代码改成了下面这样,发送邮件放在了子线程中执行:

public void registerUser(UserModel user){
    //插入用户信息到db,完成注册
    this.insertUser(user);
    //发送邮件,放在子线程中执行,邮件的发送结果对注册逻辑不会有干扰作用
    new Thread(()->{
        this.sendEmailToUser(user);
    }).start();
}

又过了几天,产品经理又跑来了说:路人,最近效益不好,需要刺激用户消费,注册的时候继续发送优 惠券。 我:倒,这是玩我么,反反复复让我调整注册的代码,让我改还好,让测试也反反复复来回搞,这是要 玩死我们啊。 花了点时间,好好复盘整理了一下:发现问题不在于产品经理,从业务上来看,产品提的这些需求都是 需求合理的,而结果代码反复调整、测试反复测试,以及一些次要的功能导致注册接口不稳定,这些问 题归根到底,主要还是我的设计不合理导致的,将注册功能中的一些次要的功能耦合到注册的方法中 了,并且这些功能可能会经常调整,导致了注册接口的不稳定性。 其实上面代码可以这么做:

找3个人:注册器、路人A、路人B。

注册器:负责将用户信息落库,落库成功之后,喊一声:用户XXX注册成功了。

路人A和路人B,竖起耳朵,当听到有人喊:XXX注册成功 的声音之后,立即行动做出下面反应: 路人A:负责给XXX发送一封注册邮件 路人B:负责给XXX发送优惠券

我们来看一下: 注册器只负责将用户信息落库,及广播一条用户注册成功的消息。 A和B相当于一个监听者,只负责监听用户注册成功的消息,当听到有这个消息产生的时候,A和B就去做 自己的事情。

这里面注册器是感知不到A/B存在的,A和B也不用感知注册器的存在,A/B只用关注是否有人广播: XXX 注册成功了 的消息,当AB听到有人广播注册成功的消息,他们才做出反应,其他时间闲着休息。

这种方式就非常好: 当不想给用户发送优惠券的时候,只需要将B去掉就行了,此时基本上也不用测试,注册一下B的代码就行了。 若注册成功之后需要更多业务,比如还需要给用户增加积分,只需新增一个监听者C,监听到注册成功 消息后,负责给用户添加积分,此时根本不用去调整注册的代码,开发者和测试人员只需要确保监听者 C中的正确性就可以了。

上面这种模式就是事件模式。

事件模式中的几个概念

事件源:事件的触发者,比如上面的注册器就是事件源。

事件:描述发生了什么事情的对象,比如上面的:xxx注册成功的事件

事件监听器:监听到事件发生的时候,做一些处理,比如上面的:路人A、路人B

使用事件模式实现上面用户注册的业务

事件对象

表示所有事件的父类,内部有个source字段,表示事件源;我们自定义的事件需要继承这个类。

public abstract class AbstractEvent {

    private Object source;

    public AbstractEvent(Object source) {
        this.source = source;
    }

    public Object getSource() {
        return source;
    }

    public void setSource(Object source) {
        this.source = source;
    }
}

事件监听器

我们使用一个接口来表示事件监听器,是个泛型接口,后面的类型 E 表示当前监听器需要监听的 事件类型,此接口中只有一个方法,用来实现处理事件的业务;其定义的监听器需要实现这个接 口。

public interface EventListener<E extends AbstractEvent> {

    void onEvent(E e);
}

事件广播器

负责事件监听器的管理(注册监听器&移除监听器,将事件和监听器关联起来)

负责事件的广播(将事件广播给所有的监听器,对该事件感兴趣的监听器会处理该事件)

public interface EventMulticaster {

    /**
     * 广播事件给所有的监听器,对该事件感兴趣的监听器会处理该事件
     *
     * @param event
     */
    void multicastEvent(AbstractEvent event);

    /**
     * 添加一个事件监听器(监听器中包含了监听器中能够处理的事件)
     *
     * @param listener 需要添加监听器
     */
    void addEventListener(EventListener<?> listener);

    /**
    * 将事件监听器移除
    *
    * @param listener 需要移除的监听器
    */

    void removeEventListener(EventListener<?> listener);
}

事件广播默认实现

/**
 * 事件广播器简单实现
 */
public class SimpleEventMulticaster implements EventMulticaster {

    private Map<Class<?>, List<EventListener>> eventObjectEventListenerMap = new HashMap<>();


    @Override
    public void multicastEvent(AbstractEvent event) {

        List<EventListener> eventListeners = this.eventObjectEventListenerMap.get(event.getClass());
        if (eventListeners != null) {
            for (EventListener eventListener : eventListeners) {
                eventListener.onEvent(event);
            }
        }
    }


    @Override
    public void addEventListener(EventListener<?> listener) {

        Class<?> eventType = this.getEventType(listener);
        List<EventListener> eventListeners = this.eventObjectEventListenerMap.get(eventType);
        if (eventListeners == null) {
            eventListeners = new ArrayList<>();
            this.eventObjectEventListenerMap.put(eventType, eventListeners);
        }
        eventListeners.add(listener);
    }


    @Override
    public void removeEventListener(EventListener<?> listener) {
        Class<?> eventType = this.getEventType(listener);
        List<EventListener> eventListeners =
                this.eventObjectEventListenerMap.get(eventType);
        if (eventListeners != null) {
            eventListeners.remove(listener);
        }
    }

    /**
    * 获取事件监听器需要监听的事件类型
    *
    * @param listener
    * @return
    */
    protected Class<?> getEventType(EventListener listener) {

        ParameterizedType parameterizedType = (ParameterizedType) listener.getClass().getGenericInterfaces()[0];
        Type eventType = parameterizedType.getActualTypeArguments()[0];

        return (Class<?>) eventType;
    }
}

上面3个类支撑了整个事件模型,下面我们使用上面三个类来实现注册的功能,目标是:高内聚低耦合, 让注册逻辑方便扩展。

自定义用户注册成功事件类

/**
 * 用户注册成功事件
 */
public class UserRegisterSuccessEvent extends AbstractEvent {
    //用户名
    private String userName;

    /**
     * 创建用户注册成功事件对象
     *
     * @param source   事件源
     * @param userName 当前注册的用户名
     */
    public UserRegisterSuccessEvent(Object source, String userName) {
        super(source);
        this.userName = userName;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
}

用户注册服务


/**
 * 用户注册服务
 */
public class UserRegisterService {
    //事件发布者
    private EventMulticaster eventMulticaster; //@0

    /**
     * 注册用户
     *
     * @param userName 用户名
     */
    public void registerUser(String userName) { //@1
        //用户注册(将用户信息入库等操作)
        System.out.println(String.format("用户【%s】注册成功", userName)); //@2
        //广播事件
        this.eventMulticaster.multicastEvent(new UserRegisterSuccessEvent(this, userName)); //@3
    }

    public EventMulticaster getEventMulticaster() {
        return eventMulticaster;
    }

    public void setEventMulticaster(EventMulticaster eventMulticaster) {
        this.eventMulticaster = eventMulticaster;
    }
}

@0:事件发布者

@1:registerUser这个方法负责用户注册,内部主要做了2个事情

@2:模拟将用户信息落库

@3:使用事件发布者eventPublisher发布用户注册成功的消息:

下面我们使用spring来将上面的对象组装起来

@Configuration
@ComponentScan
public class MainConfig {
    /**
     * 注册一个bean:事件发布者
     *
     * @param eventListeners
     * @return
     */
    @Bean
    @Autowired(required = false)
    public EventMulticaster eventMulticaster(List<EventListener> eventListeners) { //@1

        EventMulticaster eventPublisher = new SimpleEventMulticaster();
        if (eventListeners != null) {
            eventListeners.forEach(eventPublisher::addEventListener);
        }
        return eventPublisher;
    }

    /**
     * 注册一个bean:用户注册服务
     *
     * @param eventMulticaster
     * @return
     */
    @Bean
    public UserRegisterService userRegisterService(EventMulticaster eventMulticaster) { //@2

        UserRegisterService userRegisterService = new UserRegisterService();
        userRegisterService.setEventMulticaster(eventMulticaster);
        return userRegisterService;
    }

}

上面有2个方法,负责向spring容器中注册2个bean。

@1:向spring容器中注册了一个bean: 事件发布者 ,方法传入了 EventListener 类型的List,这 个地方会将容器中所有的事件监听器注入进来,丢到 EventMulticaster 中。

@2:向spring容器中注册了一个bean: 用户注册服务

测试用例模拟用户注册 

public class EventTest {

    @Test
    public void test0() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);

        //获取用户注册服务
        UserRegisterService userRegisterService = context.getBean(UserRegisterService.class);

        //模拟用户注册
        userRegisterService.registerUser("测试用户1");
    }
}

运行输出:

用户【测试用户1】注册成功

添加注册成功发送邮件功能

下面添加一个注册成功发送邮件的功能,只需要自定义一个监听用户注册成功事件的监听器就可以了, 其他代码不需要任何改动,如下

@Component
public class SendEmailOnUserRegisterSuccessListener implements EventListener<UserRegisterSuccessEvent> {
    @Override
    public void onEvent(UserRegisterSuccessEvent event) {
        System.out.println(String.format("给用户【%s】发送注册成功邮件!", event.getUserName()));
    }
}

运行输出:

用户【测试用户1】注册成功
给用户【测试用户1】发送注册成功邮件!

小结

上面将注册的主要逻辑(用户信息落库)和次要的业务逻辑(发送邮件)通过事件的方式解耦了。次要的业务做成了可插拔的方式,比如不想发送邮件了,只需要将邮件监听器上面的 @Component 注释就可 以了,非常方便扩展。 上面用到的和事件相关的几个类,都是我们自己实现的,其实这些功能在spring中已经帮我们实现好 了,用起来更容易一些,下面带大家来体验一下。

Spring中实现事件模式

事件相关的几个类

Spring中事件相关的几个类需要先了解一下,下面来个表格,将spring中事件相关的类和我们上面自定 义的类做个对比,方便大家理解

硬编码的方式使用spring事件3步骤 

步骤1:定义事件

自定义事件,需要继承 ApplicationEvent 类

步骤2:定义监听器

自定义事件监听器,需要实现 ApplicationListener 接口,这个接口有个方法 onApplicationEvent 需要实现,用来处理感兴趣的事件。

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends
    EventListener {
    /**
    * Handle an application event.
    * @param event the event to respond to
    */
    void onApplicationEvent(E event);
}

步骤3:创建事件广播器

创建事件广播器 ApplicationEventMulticaster ,这是个接口,你可以自己实现这个接口,也可以直 接使用系统给我们提供的 SimpleApplicationEventMulticaster ,如下:

ApplicationEventMulticaster applicationEventMulticaster = new SimpleApplicationEventMulticaster();

步骤4:向广播器中注册事件监听器

将事件监听器注册到广播器 ApplicationEventMulticaster 中,如:

ApplicationEventMulticaster applicationEventMulticaster = new SimpleApplicationEventMulticaster();

applicationEventMulticaster.addApplicationListener(new SendEmailOnOrderCreateListener());

步骤5:通过广播器发布事件

广播事件,调用 ApplicationEventMulticaster#multicastEvent方法,此时广播器中对这 个事件感兴趣的监听器会处理这个事件。

applicationEventMulticaster.multicastEvent(new OrderCreateEvent(applicationEventMulticaster, 1L));

案例

实现功能:电商中订单创建成功之后,给下单人发送一封邮件,发送邮件的功能放在监听器中实现。

事件类:订单创建成功事件


/**
 * 订单创建事件
 */
public class OrderCreateEvent extends ApplicationEvent {
    //订单id
    private Long orderId;

    /**
     * @param source  事件源
     * @param orderId 订单id
     */
    public OrderCreateEvent(Object source, Long orderId) {
        super(source);
        this.orderId = orderId;
    }

    public Long getOrderId() {
        return orderId;
    }

    public void setOrderId(Long orderId) {
        this.orderId = orderId;
    }
}

监听器:负责监听订单成功事件,发送邮件

/**
 * 订单创建成功给用户发送邮件
 */
@Component
public class SendEmailOnOrderCreateListener implements ApplicationListener<OrderCreateEvent> {
    @Override
    public void onApplicationEvent(OrderCreateEvent event) {
        System.out.println(String.format("订单【%d】创建成功,给下单人发送邮件通知!", event.getOrderId()));
    }
}

测试

@Test
    public void test2() throws InterruptedException {
        //创建事件广播器
        ApplicationEventMulticaster applicationEventMulticaster = new SimpleApplicationEventMulticaster();

        //注册事件监听器
        applicationEventMulticaster.addApplicationListener(new SendEmailOnOrderCreateListener());

        //广播事件订单创建事件
        applicationEventMulticaster.multicastEvent(new OrderCreateEvent(applicationEventMulticaster, 1L));
    }

运行输出:

订单【1】创建成功,给下单人发送邮件通知!

小结

用上面spring集成好的事件处理的方式,简化了不少。但是,我们还可以进一步简化,另外两种方式来实现,面向接口的方式和面向@EventListener注解的方式

面向接口的方式

来一个案例:实现用户注册成功后发布事件,然后在监听器中发送邮件的功能,更好的展现怎么使用。

用户注册事件

需要继承 ApplicationEvent

/**
 * 用户注册事件
 */
public class UserRegisterEvent extends ApplicationEvent {
    //用户名
    private String userName;

    public UserRegisterEvent(Object source, String userName) {
        super(source);
        this.userName = userName;
    }

    public String getUserName() {
        return userName;
    }
}

发送邮件监听器

需实现 ApplicationListener 接口

/**
 * 用户注册成功发送邮件
 */
@Component
public class SendEmailListener implements ApplicationListener<UserRegisterEvent> {
    @Override
    public void onApplicationEvent(UserRegisterEvent event) {
        System.out.println(String.format("给用户【%s】发送注册成功邮件!", event.getUserName()));
    }
}

用户注册服务

内部提供用户注册的功能,并发布用户注册事件

/**
 * 用户注册服务
 */
@Component
public class UserRegisterService implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher applicationEventPublisher;

    /**
     * 负责用户注册及发布事件的功能
     *
     * @param userName 用户名
     */
    public void registerUser(String userName) {
        //用户注册(将用户信息入库等操作)
        System.out.println(String.format("用户【%s】注册成功", userName));
        //发布注册成功事件
        this.applicationEventPublisher.publishEvent(new UserRegisterEvent(this, userName));
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { //@1
        this.applicationEventPublisher = applicationEventPublisher;
    }

}

测试

    @Test
    public void test3() throws InterruptedException {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(MainConfig1.class);
        context.refresh();

        //获取用户注册服务
        com.example.Way1.UserRegisterService userRegisterService = context.getBean(com.example.Way1.UserRegisterService.class);
        //模拟用户注册
        userRegisterService.registerUser("test");
    }

运行输出

用户【test】注册成功
给用户【test】发送注册成功邮件!

面向@EventListener注解方式

其实和上面的面向接口的差不多,唯一的区别就是监听器不用实现ApplicationListener接口了,而是用@EventListener注解来代替,其它部分都一样。


/**
 * 用户注册事件
 */
public class UserRegisterEvent extends ApplicationEvent {
    //用户名
    private String userName;

    public UserRegisterEvent(Object source, String userName) {
        super(source);
        this.userName = userName;
    }

    public String getUserName() {
        return userName;
    }
}

/**
 * 用户注册监听器
 */
@Component
public class UserRegisterListener {

    @EventListener
    public void sendMail(UserRegisterEvent event) {
        System.out.println(String.format("给用户【%s】发送注册成功邮件!", event.getUserName()));
    }
    @EventListener
    public void sendCompon(UserRegisterEvent event) {
        System.out.println(String.format("给用户【%s】发送优惠券!", event.getUserName()));
    }
}


/**
 * 用户注册服务
 */
@Component
public class UserRegisterService implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher applicationEventPublisher;

    /**
     * 负责用户注册及发布事件的功能
     *
     * @param userName 用户名
     */
    public void registerUser(String userName) {
        //用户注册(将用户信息入库等操作)
        System.out.println(String.format("用户【%s】注册成功", userName));
        //发布注册成功事件
        this.applicationEventPublisher.publishEvent(new UserRegisterEvent(this, userName));
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { //@1
        this.applicationEventPublisher = applicationEventPublisher;
    }

}

测试:

    @Test
    public void test4() throws InterruptedException {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(MainConfig2.class);
        context.refresh();

        //获取用户注册服务
        com.example.Way2.UserRegisterService userRegisterService = context.getBean(com.example.Way2.UserRegisterService.class);
        //模拟用户注册
        userRegisterService.registerUser("test");
    }

用户【test】注册成功
给用户【test】发送注册成功邮件!
给用户【test】发送优惠券!

监听器支持排序功能

如果某个事件有多个监听器,默认情况下,监听器执行顺序是无序的,不过我们可以为监听器指定顺序。

通过接口实现监听器的情况

指定监听器的顺序有三种方式

方式一:

实现org.springframework.core.Ordered接口,需要实现一个getOrder方法,返回顺序值,值越小,顺序越高。

方式二:

实现org.springframework.core.PriorityOrdered接口

PriorityOrdered接口继承了方式一中的Ordered接口,所以如果你实现PriorityOrdered接口,也需要实 现getOrder方法。

方式三:

类上使用@org.springframework.core.annotation.Order注解

通过@EventListener注解实现监听器的情况

在标注 @EventListener 的方法上面使用 @Order(顺序值) 注解来标注顺序

监听器异步模式

具体实现如下:
 

@ComponentScan
@Configuration
public class MainConfig5 {
    @Bean
    public ApplicationEventMulticaster applicationEventMulticaster() { //@1
        //创建一个事件广播器
        SimpleApplicationEventMulticaster result = new SimpleApplicationEventMulticaster();

        //给广播器提供一个线程池,通过这个线程池来调用事件监听器
        Executor executor = this.applicationEventMulticasterThreadPool().getObject();

        //设置异步执行器
        result.setTaskExecutor(executor);//@1
        return result;
    }

    @Bean
    public ThreadPoolExecutorFactoryBean applicationEventMulticasterThreadPool(){

        ThreadPoolExecutorFactoryBean result = new ThreadPoolExecutorFactoryBean();
        result.setThreadNamePrefix("applicationEventMulticasterThreadPool-");
        result.setCorePoolSize(5);
        return result;
    }

}

@1:定义了一个名称为 applicationEventMulticaster 的事件广播器,内部设置了一个线程池 用来异步调用监听器

事件使用的建议

  1. spring中事件是使用接口的方式还是使用注解的方式,具体使用哪种方式都可以,不过在公司内部 最好大家都统一使用一种方式
  2. 异步事件的模式,通常将一些非主要的业务放在监听器中执行,因为监听器中存在失败的风险,所 以使用的时候需要注意。如果只是为了解耦,但是被解耦的次要业务也是必须要成功的,可以使用消息中间件的方式来解决这些问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/54427.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第四十篇 Vue封装swiper组件(v-swiper指令) 3.0

在前面讲到 Vue组件的封装不知道还记不记得&#xff0c;这里就不在过多的赘述&#xff0c;这里附上链接跳转可以进行回顾翻阅&#xff0c;上一篇内容​​​​​​​讲到这个自定义的指令&#xff0c;也就是为这篇封装swiper组件使用指令做铺垫的&#xff0c;那么也一同附在这里…

电子电气架构设计之三电系统设计

文中缩略词参考 SSTS&#xff1a;Sub System Technical Specification&#xff0c;子系统功能规范 CTS&#xff1a;Component Technical Specification&#xff0c;部件功能规范 DCDC&#xff1a;Direct Current Direct Current Converter&#xff0c;直流转直流变换器 BMS&…

Postgresql源码(92)深入分析HOT更新

0 概述与总结 hot更新已经有几篇分析了&#xff0c;这里是最后一篇&#xff08;总结性的&#xff0c;前面的可以忽略&#xff09;。前面在看update代码时&#xff0c;大部分集中在heap_update上&#xff0c;没有涉及寻找HOT链的逻辑。本篇重点看HOT链是如何使用的。 &#xf…

[附源码]计算机毕业设计JAVA鑫地酒店酒水库存管理系统论文

[附源码]计算机毕业设计JAVA鑫地酒店酒水库存管理系统论文 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; …

搜索技术——群智能

如果有兴趣了解更多相关内容&#xff0c;欢迎来我的个人网站看看&#xff1a;瞳孔空间 一&#xff1a;初识群智能 1.1&#xff1a;粒子群算法 粒子群算法&#xff0c;也称粒子群优化算法或鸟群觅食算法&#xff08;Particle Swarm Optimization&#xff09;&#xff0c;缩写…

语音特征:spectrogram、Fbank(fiterbank)、MFCC

1.各种语音特征 语音特征用于语音识别和语音合成等。 语音特征有声谱图spectrogram、Fbank(fiterbank)、MFCC(Mel-frequency cepstral coefficients)等。 Fbank 特征提取方法就是相当 于 MFCC 去掉最后一步的离散余弦变换&#xff08;有损变换&#xff09;. 在深度学习之前…

git学习笔记

1、安装及配置git 1、到官网下载git安装包&#xff1a;https://git-scm.com/download/win 2、安装完成后&#xff0c;菜单栏有如下工具 3、配置账户和邮件信息 $ git config --global user.name "xxx"$ git config --global user.email "xxxmegvii.com"4…

十大排序算法(C++)

十大排序算法Sorting algorithm(C) 百度百科&#xff1a; 所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。排序算法&#xff0c;就是如何使得记录按照要求排列的方法。排序算法在很多领域得到相当地…

高通平台开发系列讲解(AI篇)如何让yolov5运行在SNPE

文章目录 一、模型下载二、模型转换三、模型量化四、后处理加速沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇章主要介绍高通平台SNPE SDK运行yolov5。 一、模型下载 首先去git上下载yolov5s的代码和模型https://github.com/ultralytics/yolov5 二、模型转换 采…

Python爬虫实战,requests+xlwings模块,Python实现制作天气预报表!

前言 今天为大家介绍PythonExcel的实战项目&#xff0c;非常有趣&#xff0c;废话不多说。 Let’s start happily 开发工具 Python版本&#xff1a; 3.6.4 相关模块&#xff1a; xlwings模块 requests模块 pathlib模块 xlwings模块 json模块 环境搭建 安装Python并…

RAR压缩包,去除密码?

压缩包设置了加密&#xff0c;需要输入压缩包密码才能够顺利解压文件出来。但是有些时候&#xff0c;一些文件只需要一段时间内要加密&#xff0c;之后文件不需要加密了&#xff0c;每次解压文件的时候还是需要输入压缩包密码才行&#xff0c;就很麻烦&#xff0c;那么RAR压缩包…

SAP 接口主动推送企业微信异常消息

"推送企业微信格式lv_json { "msgtype": "markdown", "markdown": &&{ "content": "### 异常JOB通知\n >JOB名称&#xff1a; && gt_alv-jobname && \n 程序名称&#xff1a; && gt_…

戟星安全实验室|五分钟教你挖掘小程序漏洞

戟星安全实验室 忆享科技旗下高端的网络安全攻防服务团队.安服内容包括渗透测试、代码审计、应急响应、漏洞研究、威胁情报、安全运维、攻防演练等。 本文约1252字&#xff0c;阅读约需5分钟。 前言 现在大多小程序反编译教程所使用的都是node.js&#xff0c;操作过程较为麻烦…

第一周练习——认识复杂度和简单排序算法

前言&#xff1a; &#x1f44f;作者简介&#xff1a;我是笑霸final&#xff0c;一名热爱技术的在校学生。 &#x1f4dd;个人主页&#xff1a;个人主页1 || 笑霸final的主页2 &#x1f4d5;系列专栏&#xff1a;《数据结构与算法》 &#x1f4e7;如果文章知识点有错误的地方&a…

Mediapipe学习记录

学习文档 1、Google MediaPipe&#xff1a;设备端机器学习【完整解决方案】背后的技术实现 - 极术社区 - 连接开发者与智能计算生态 2、【转载】Google MediaPipe&#xff1a;设备端机器学习【完整解决方案】背后的技术实现 3、MediaPipe框架结构 - 走看看 Handtracking封装…

qt creator 设置 项目依赖关系

qt creator中有两种设置项目依赖关系的方式。 1、对于有依赖的项目&#xff0c;如果工程比较简单&#xff0c;可以将所有项目放到一个空的项目下&#xff0c;然后显示地指定从属关系&#xff0c;参考&#xff1a;qmake TEMPLATE subdirs_丘上人的博客-CSDN博客 2、通过qt cre…

leecode#Excel表列序号#组合两个表

题目描述&#xff1a; 给你一个字符串 columnTitle &#xff0c;表示 Excel 表格中的列名称。返回 该列名称对应的列序号 。 分析&#xff1a; 法1&#xff0c;进制转换 这道题要求将Excel 表中的列名称转换成相对应的列序号。由于Excel 表的列名称由大写字母组成&#xff…

vue学习53~60(Vue组件化编程)

2 Vue组件化编程 2.1 模块与组件、模块化与组件化 2.1.1 模块 理解:向外提供特定功能的js程序,一般就是一 个js文件为什么: js 文件很多很复杂作用:复用js,简化js的编写,提高js运行效率 2.1.2 组件 理解:用来实现局部(特定)功能效果的代码集合(html/css/js/image…)为什么…

正大国际期货:外盘期货交易中常见的五类技术分析方法

外盘期货交易中&#xff0c;技术分析是必不可少的&#xff0c;技术分析是指以市场行为为研究对象&#xff0c;以判断市场趋势并跟随趋势的周期性变化来进行一切金融衍生物交易决策的方法的总和。常用的技术方法大体上可以分五大类&#xff1a;指标类、切线类、形态类、波浪类、…

安卓程序逆向与防护

安卓程序逆向与防护实际用处代码打包生成apk的步骤程序基础逆向步骤程序基础防御代码混淆针对逆向工具的防御手段ProguardSO文件防止二次打包程序进阶逆向攻击实战练习逆向简易安卓程序实际用处 1.当想要获得一款前后端分离的安卓程序的数据时&#xff0c;一般会考虑抓包后端传…