Spring中的设计模式

Easter79
• 阅读 669

spring在容器中使用了观察者模式:

一、spring事件:ApplicationEvent,该抽象类继承了EventObject类,jdk建议所有的事件都应该继承自EventObject。

二、spring事件监听器:ApplicationLisener,该接口继承了EventListener接口,jdk建议所有的事件监听器都应该继承EventListener。

Java代码    Spring中的设计模式

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

三、spring事件发布:ApplicationEventPublisher 。 ApplicationContext继承了该接口,在ApplicationContext的抽象类AbstractApplicationContext中做了实现。

package org.springframework.context;

public interface ApplicationEventPublisher {  

/** * Notify all <strong>matching</strong> listeners registered with this * application of an application event. Events may be framework events * (such as RequestHandledEvent) or application-specific events. * @param event the event to publish * @see org.springframework.web.context.support.RequestHandledEvent */

  void publishEvent(ApplicationEvent var1);

    void publishEvent(Object var1);
}

 Spring中的设计模式

  AbstractApplicationContext类中publishEvent方法实现: 

/**
     * Publish the given event to all listeners.
     * <p>Note: Listeners get initialized after the MessageSource, to be able
     * to access it within listener implementations. Thus, MessageSource
     * implementations cannot publish events.
     * @param event the event to publish (may be an {@link ApplicationEvent}
     * or a payload object to be turned into a {@link PayloadApplicationEvent})
     */
    @Override
    public void publishEvent(Object event) {
        publishEvent(event, null);
    }

    /**
     * Publish the given event to all listeners.
     * @param event the event to publish (may be an {@link ApplicationEvent}
     * or a payload object to be turned into a {@link PayloadApplicationEvent})
     * @param eventType the resolved event type, if known
     * @since 4.2
     */
    protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
        Assert.notNull(event, "Event must not be null");
        if (logger.isTraceEnabled()) {
            logger.trace("Publishing event in " + getDisplayName() + ": " + event);
        }

        // Decorate event as an ApplicationEvent if necessary
        ApplicationEvent applicationEvent;
        if (event instanceof ApplicationEvent) {
            applicationEvent = (ApplicationEvent) event;
        }
        else {
            applicationEvent = new PayloadApplicationEvent<>(this, event);
            if (eventType == null) {
                eventType = ((PayloadApplicationEvent) applicationEvent).getResolvableType();
            }
        }

        // Multicast right now if possible - or lazily once the multicaster is initialized
        if (this.earlyApplicationEvents != null) {
            this.earlyApplicationEvents.add(applicationEvent);
        }
        else {       //事件广播委托给ApplicationEventMulticaster来进行  
            getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
        }

        // Publish event via parent context as well...
        if (this.parent != null) {
            if (this.parent instanceof AbstractApplicationContext) {
                ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
            }
            else {
                this.parent.publishEvent(event);
            }
        }
    }

   由上代码可知,AbstractApplicationContext类并没有具体的做事件广播,而是委托给ApplicationEventMulticaster来进行,ApplicationEventMulticaster的multicastEvent()方法实现如下:

    @Override
    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            Executor executor = getTaskExecutor();
            if (executor != null) {
                executor.execute(() -> invokeListener(listener, event));
            }
            else {
                invokeListener(listener, event);
            }
        }
    }

protected void invokeListener(ApplicationListener listener, ApplicationEvent event) {
        ErrorHandler errorHandler = this.getErrorHandler();
        if (errorHandler != null) {
            try {
                listener.onApplicationEvent(event);
            } catch (Throwable var6) {
                errorHandler.handleError(var6);
            }
        } else {
            try {
                listener.onApplicationEvent(event);
            } catch (ClassCastException var5) {
                LogFactory.getLog(this.getClass()).debug("Non-matching event type for listener: " + listener, var5);
            }
        }

    }

 获得listener集合,遍历listener触发事件Executor接口有多个实现类,可以支持同步或异步广播事件。

问题:spring容器是怎么根据事件去找到事件对应的事件监听器呢?

一、入口

private ApplicationContext applicationContext=new ClassPathXmlApplicationContext("classpath:/spring/applicationContext.xml");

二、生成Spring上下文ApplicationContext

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {
        super(parent);
        this.setConfigLocations(configLocations);
        if (refresh) {
            this.refresh();
        }

    }

三、调用spring容器初始化方法

public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                initMessageSource();
    
            
                                //初始化一个事件注册表
                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // 初始化事件监听器
                // Check for listener beans and register them.
                registerListeners();
                // 实例化所有单例对象,其中包括默认注册表
                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);
                // 发布事件
                // Last step: publish corresponding event.
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }

            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
            }
        }
    }                                                      

3.1 initApplicationEventMulticaster()方法代码

protected void initApplicationEventMulticaster() {
        ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
     //先查找BeanFactory配置文件中是否有ApplicationEventMulticaster  
        if (beanFactory.containsLocalBean("applicationEventMulticaster")) {
            this.applicationEventMulticaster = (ApplicationEventMulticaster)beanFactory.getBean("applicationEventMulticaster", ApplicationEventMulticaster.class);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
            }
        } else {// 如果beanFactory中没有,则创建一个SimpleApplicationEventMulticaster
            this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
            beanFactory.registerSingleton("applicationEventMulticaster", this.applicationEventMulticaster);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Unable to locate ApplicationEventMulticaster with name 'applicationEventMulticaster': using default [" + this.applicationEventMulticaster + "]");
            }
        }

    }

  spring先从beanFactory中获取ApplicationEventMulticaster,如果没有自定义,则创建一个SimpleApplicationEventMulticaster。 

ApplicationEventMulticaster包含以下属性:defaultRetriever即为注册表,注册监听事件的相关消息; retrieverCache用来做defaultRetriever的缓存。 

public abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {
    private final AbstractApplicationEventMulticaster.ListenerRetriever defaultRetriever = new AbstractApplicationEventMulticaster.ListenerRetriever(false);
    final Map<AbstractApplicationEventMulticaster.ListenerCacheKey, AbstractApplicationEventMulticaster.ListenerRetriever> retrieverCache = new ConcurrentHashMap(64);
    private ClassLoader beanClassLoader;
    private BeanFactory beanFactory;
    private Object retrievalMutex;
}

 ListenerRetriever的数据结构如下:applicationListeners用来存放监听事件, applicationListenerBeans为存放监听事件的类名称。

private class ListenerRetriever {
        public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet();
        public final Set<String> applicationListenerBeans = new LinkedHashSet();
        private final boolean preFiltered;

 ListenerCacheKey的数据结构如下:eventType是事件类型,sourceType是事件的源类型,即为事件的构造函数的参数类型。

private static class ListenerCacheKey {
        private final ResolvableType eventType;
        private final Class<?> sourceType;

3.2 registerListeners()方法代码

初始化注册表以后,则把事件注册到注册表中,registerListeners()

protected void registerListeners() {
     //获取所有的listener的迭代器
        Iterator var1 = this.getApplicationListeners().iterator();

        while(var1.hasNext()) {
            ApplicationListener<?> listener = (ApplicationListener)var1.next();
            //把获取所有的listener, 把事件的bean放到ApplicationEventMulticaster中的ApplicationListener
        this.getApplicationEventMulticaster().addApplicationListener(listener);
        }

        String[] listenerBeanNames = this.getBeanNamesForType(ApplicationListener.class, true, false);
        String[] var7 = listenerBeanNames;
        int var3 = listenerBeanNames.length;

        for(int var4 = 0; var4 < var3; ++var4) {
            String listenerBeanName = var7[var4];
          //把事件的名称放到ApplicationListenerBean里去          this.getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
        }

        Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
        this.earlyApplicationEvents = null;
        if (earlyEventsToProcess != null) {
            Iterator var9 = earlyEventsToProcess.iterator();

            while(var9.hasNext()) {
                ApplicationEvent earlyEvent = (ApplicationEvent)var9.next();
                this.getApplicationEventMulticaster().multicastEvent(earlyEvent);
            }
        }

    }

3.3 finishBeanFactoryInitialization(beanFactory) 具体会执行到下面的方法,会把AbstractApplicationEventMulticaster的defaultRetriever属性赋值。   执行PostProcessorRegistrationDelegate类的postProcessAfterInitialization()方法:

public Object postProcessAfterInitialization(Object bean, String beanName) {
            if (this.applicationContext != null && bean instanceof ApplicationListener) {
                Boolean flag = (Boolean)this.singletonNames.get(beanName);
                if (Boolean.TRUE.equals(flag)) {
                    this.applicationContext.addApplicationListener((ApplicationListener)bean);
                } else if (flag == null) {
                    if (logger.isWarnEnabled() && !this.applicationContext.containsBean(beanName)) {
                        logger.warn("Inner bean '" + beanName + "' implements ApplicationListener interface " + "but is not reachable for event multicasting by its containing ApplicationContext " + "because it does not have singleton scope. Only top-level listener beans are allowed " + "to be of non-singleton scope.");
                    }

                    this.singletonNames.put(beanName, Boolean.FALSE);
                }
            }

            return bean;
        }

   执行AbstractApplicationContext类的addApplicationListener()方法:

  public void addApplicationListener(ApplicationListener<?> listener) {
        if (this.applicationEventMulticaster != null) {
            this.applicationEventMulticaster.addApplicationListener(listener);
        } else {
            this.applicationListeners.add(listener);
        }
    }

执行AbstractApplicationEventMulticaster类的addApplicationListener()方法

public void addApplicationListener(ApplicationListener<?> listener) {
        Object var2 = this.retrievalMutex;
        synchronized(this.retrievalMutex) {
            this.defaultRetriever.applicationListeners.add(listener);
            this.retrieverCache.clear();
        }
   }

 【spring根据反射机制,通过方法getBeansOfType()获取所有继承了ApplicationListener接口的监听器,然后把监听器全放到注册表里,所以我们可以在spring配置文件中配置自定义的监听器,在spring初始化的时候会把监听器自动注册到注册表中。】

3.4 finishRefresh()里面执行发布事件。

protected void finishRefresh() {
        this.initLifecycleProcessor();
        this.getLifecycleProcessor().onRefresh();
        this.publishEvent((ApplicationEvent)(new ContextRefreshedEvent(this)));
        LiveBeansView.registerApplicationContext(this);
    }

在applicationContext发布事件的时候。 

public void publishEvent(ApplicationEvent event) {
        this.publishEvent(event, (ResolvableType)null);
    }

    public void publishEvent(Object event) {
        this.publishEvent(event, (ResolvableType)null);
    }

    protected void publishEvent(Object event, ResolvableType eventType) {
        Assert.notNull(event, "Event must not be null");
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("Publishing event in " + this.getDisplayName() + ": " + event);
        }

        Object applicationEvent;
        if (event instanceof ApplicationEvent) {
            applicationEvent = (ApplicationEvent)event;
        } else {
            applicationEvent = new PayloadApplicationEvent(this, event);
            if (eventType == null) {
                eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType();
            }
        }

        if (this.earlyApplicationEvents != null) {
            this.earlyApplicationEvents.add(applicationEvent);
        } else {
      // 调用ApplicationEventMulticaster的multicastEvent()方法              this.getApplicationEventMulticaster().multicastEvent((ApplicationEvent)applicationEvent, eventType);
        }

        if (this.parent != null) {
            if (this.parent instanceof AbstractApplicationContext) {
                ((AbstractApplicationContext)this.parent).publishEvent(event, eventType);
            } else {
                this.parent.publishEvent(event);
            }
        }

    }

AbstractApplicationContext类并没有具体的做事件广播,而是委托给ApplicationEventMulticaster来进行。

 ApplicationEventMulticaster的方法multicastEvent()为: 

public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
        ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
        Iterator var4 = this.getApplicationListeners(event, type).iterator();

        while(var4.hasNext()) {
            final ApplicationListener<?> listener = (ApplicationListener)var4.next();
            Executor executor = this.getTaskExecutor();
            if (executor != null) {
                executor.execute(new Runnable() {
                    public void run() {
                        SimpleApplicationEventMulticaster.this.invokeListener(listener, event);
                    }
                });
            } else {
                this.invokeListener(listener, event);
            }
        }

    }

根据事件和类型获取所有的监听器方法: getApplicationListeners()

protected Collection<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event, ResolvableType eventType) {
        Object source = event.getSource();
        Class<?> sourceType = source != null ? source.getClass() : null;
        AbstractApplicationEventMulticaster.ListenerCacheKey cacheKey = new AbstractApplicationEventMulticaster.ListenerCacheKey(eventType, sourceType);
        AbstractApplicationEventMulticaster.ListenerRetriever retriever = (AbstractApplicationEventMulticaster.ListenerRetriever)this.retrieverCache.get(cacheKey);        //从缓存里查找ListenerRetriever    
        if (retriever != null) {
            return retriever.getApplicationListeners();
        } else if (this.beanClassLoader == null || ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) && (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader))) {
            Object var7 = this.retrievalMutex;
            synchronized(this.retrievalMutex) {
                retriever = (AbstractApplicationEventMulticaster.ListenerRetriever)this.retrieverCache.get(cacheKey);
                if (retriever != null) {
                    return retriever.getApplicationListeners();
                } else {            //如果缓存里不存在,则去获得 
                    retriever = new AbstractApplicationEventMulticaster.ListenerRetriever(true);
                    Collection<ApplicationListener<?>> listeners = this.retrieveApplicationListeners(eventType, sourceType, retriever);
                    this.retrieverCache.put(cacheKey, retriever);
                    return listeners;
                }
            }
        } else {
            return this.retrieveApplicationListeners(eventType, sourceType, (AbstractApplicationEventMulticaster.ListenerRetriever)null);
        }
    }

private Collection<ApplicationListener<?>> retrieveApplicationListeners(ResolvableType eventType, Class<?> sourceType, AbstractApplicationEventMulticaster.ListenerRetriever retriever) {
        LinkedList<ApplicationListener<?>> allListeners = new LinkedList();
        Object var7 = this.retrievalMutex;
        LinkedHashSet listeners;
        LinkedHashSet listenerBeans;
        synchronized(this.retrievalMutex) {         //获取注册表里所有的listener, defaultRetriever在前面已被赋值 
            listeners = new LinkedHashSet(this.defaultRetriever.applicationListeners);
            listenerBeans = new LinkedHashSet(this.defaultRetriever.applicationListenerBeans);
        }

        Iterator var14 = listeners.iterator();

        while(var14.hasNext()) {
            ApplicationListener<?> listener = (ApplicationListener)var14.next();       //根据事件类型,事件源类型,获取所需要的监听事件  
            if (this.supportsEvent(listener, eventType, sourceType)) {
                if (retriever != null) {
                    retriever.applicationListeners.add(listener);
                }

                allListeners.add(listener);
            }
        }

        if (!listenerBeans.isEmpty()) {
            BeanFactory beanFactory = this.getBeanFactory();
            Iterator var16 = listenerBeans.iterator();

            while(var16.hasNext()) {
                String listenerBeanName = (String)var16.next();

                try {
                    Class<?> listenerType = beanFactory.getType(listenerBeanName);
                    if (listenerType == null || this.supportsEvent(listenerType, eventType)) {
                        ApplicationListener<?> listener = (ApplicationListener)beanFactory.getBean(listenerBeanName, ApplicationListener.class);
                        if (!allListeners.contains(listener) && this.supportsEvent(listener, eventType, sourceType)) {
                            if (retriever != null) {
                                retriever.applicationListenerBeans.add(listenerBeanName);
                            }

                            allListeners.add(listener);
                        }
                    }
                } catch (NoSuchBeanDefinitionException var13) {
                    ;
                }
            }
        }

        AnnotationAwareOrderComparator.sort(allListeners);
        return allListeners;
    }

 配合上面的注解,即可理解,根据事件和事件类型找到对应的监听器,那么如何根据事件类型找到对应的监听器呢?

 上面方法中的supportsEvent(listener, eventType, sourceType)方法实现了根据事件类型查找对应的监听器,代码具体实现为:

protected boolean supportsEvent(ApplicationListener<?> listener, ResolvableType eventType, Class<?> sourceType) {
        GenericApplicationListener smartListener = listener instanceof GenericApplicationListener ? (GenericApplicationListener)listener : new GenericApplicationListenerAdapter(listener);
        return ((GenericApplicationListener)smartListener).supportsEventType(eventType) && ((GenericApplicationListener)smartListener).supportsSourceType(sourceType);
    }

 如上可知:上面方法的返回结果跟方法smartListener.supportsEventType(eventType)和smartListener.supportsSourceType(sourceType)有关。

smartListener.supportsEventType(eventType)方法实现为:

public boolean supportsEventType(ResolvableType eventType) {
        if (this.delegate instanceof SmartApplicationListener) {
            Class<? extends ApplicationEvent> eventClass = eventType.getRawClass();
            return ((SmartApplicationListener)this.delegate).supportsEventType(eventClass);
        } else {
            return this.declaredEventType == null || this.declaredEventType.isAssignableFrom(eventType);
        }
    }

  该方法主要的逻辑就是根据事件类型判断是否和监听器参数泛型的类型是否一致。 

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

 在定义自己的监听器要明确指定参数泛型,表明该监听器支持的事件,如果不指明具体的泛型,则没有监听器监听事件。

smartListener.supportsSourceType(sourceType)方法的实现为:

public boolean supportsSourceType(Class<?> sourceType) {
        return this.delegate instanceof SmartApplicationListener ? ((SmartApplicationListener)this.delegate).supportsSourceType(sourceType) : true;
    }

  以上是spring的事件体系。

四、自定义事件和监听器

我们可以使用spring的事件广播体系,自定义自己的事件:

自定义事件,继承ApplicationEvent:

public class DIYEvent extends ApplicationEvent {
    private static final long serialVersionUID = 7099057708183571977L;

    public DIYEvent(String source) {
        super(source);
    }
}

自定义listener,继承ApplicationListener

@Component
public class DIYListener implements ApplicationListener<DIYEvent> {
    @Override
    public void onApplicationEvent(DIYEvent diyEvent) {
        System.out.println("自定义监听器执行");
        System.out.println(diyEvent.getSource());
    }
}

测试触发事件:

public class DIYTest{
    private ApplicationContext applicationContext=new ClassPathXmlApplicationContext("classpath:/spring/applicationContext.xml");

    @Test
    public void diyTest(){
        applicationContext.publishEvent(new DIYEvent("测试数据"));
    }
}

  获取ApplicationContext,发布事件。

调试结果:

自定义监听器执行

测试数据
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Easter79 Easter79
3年前
spring中策略模式使用
策略模式工作中经常使用到策略模式工厂模式,实现一个接口多种实现的灵活调用与后续代码的扩展性。在spring中使用策略模式更为简单,所有的bean均为spring容器管理,只需获取该接口的所有实现类即可。下面以事件处理功能为例,接收到事件之后,根据事件类型调用不同的实现接口去处理。如需新增事件,只需扩展实现类即可,无需改动之前的代码。这样即
Easter79 Easter79
3年前
SpringBoot的事件监听
事件监听的流程分为三步:1、自定义事件,一般是继承ApplicationEvent抽象类。2、定义事件监听器,一般是实现ApplicationListener接口。3、a、启动的时候,需要将监听器加入到Spring容器中。b、或者将监听器加入到容器中。@Componentc、使用@EventLis
Easter79 Easter79
3年前
Spring中ApplicationContext的事件机制
   ApplicationContext事件机制是观察者设计模式的实现,通过ApplicationEvent类和ApplicationListener接口,可以实现ApplicationContext事件处理。如果容器中有一个ApplicationListenerBean,每当ApplicationContext发布ApplicationEvent时,
Easter79 Easter79
3年前
SpringRequestContext源码阅读
Spring源码关于RequestContext相关信息获取事件监听器的相关代码实现publicclassRequestContextListenerimplementsServletRequestListener{
Stella981 Stella981
3年前
SpringBoot的事件监听
事件监听的流程分为三步:1、自定义事件,一般是继承ApplicationEvent抽象类。2、定义事件监听器,一般是实现ApplicationListener接口。3、a、启动的时候,需要将监听器加入到Spring容器中。b、或者将监听器加入到容器中。@Componentc、使用@EventLis
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k