朱晔和你聊Spring系列S1E3:Spring咖啡罐里的豆子

标题中的咖啡罐指的是Spring容器,容器里装的当然就是被称作Bean的豆子。本文我们会以一个最基本的例子来熟悉Spring的容器管理和扩展点。
阅读PDF版本

为什么要让容器来管理对象?

首先我们来聊聊这个问题,为什么我们要用Spring来管理对象(的生命周期和对象之间的关系)而不是自己new一个对象呢?大家可能会回答是方便,为了解耦。我个人觉得除了这两个原因之外,还有就是给予了我们更多可能性。如果我们以容器为依托来管理所有的框架、业务对象,那么不仅仅我们可以无侵入调整对象的关系,还有可能无侵入随时调整对象的属性甚至悄悄进行对象的替换。这就给了我们无限多的可能性,大大方便了框架的开发者在程序背后实现一些扩展。不仅仅Spring Core本身以及Spring Boot大量依赖Spring这套容器体系,一些外部框架也因为这个原因可以和Spring进行无缝整合。
Spring可以有三种方式来配置Bean,分别是最早期的XML方式、后来的注解方式以及现在最流行的Java代码配置方式。

Bean的回调事件

在前文parent模块(空的一个SpringBoot应用程序)的基础上,我们先来创建一个beans模块:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>me.josephzhu</groupId><artifactId>spring101-beans</artifactId><version>0.0.1-SNAPSHOT</version><packaging>jar</packaging><name>spring101-beans</name><description></description><parent><groupId>me.josephzhu</groupId><artifactId>spring101</artifactId><version>0.0.1-SNAPSHOT</version></parent><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>

然后来创建我们的豆子:

package me.josephzhu.spring101beans;import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;@Component
public class MyService implements InitializingBean, DisposableBean {public int increaseCounter() {this.counter++;return counter;}public int getCounter() {return counter;}public void setCounter(int counter) {this.counter = counter;}private int counter=0;public MyService(){counter++;System.out.println(this + "#constructor:" + counter);}public String hello(){return this + "#hello:" + counter;}@PreDestroypublic void preDestroy() {System.out.println(this + "#preDestroy:" + counter);}@Overridepublic void afterPropertiesSet() {counter++;System.out.println(this + "#afterPropertiesSet:" + counter);}@PostConstructpublic void postConstruct(){counter++;System.out.println(this + "#postConstruct:" + counter);}@Overridepublic void destroy() {System.out.println(this + "#destroy:" + counter);}
}

这里可以看到,我们的服务中有一个counter字段,默认是0。这个类我们实现了InitializingBean接口和DisposableBean接口,同时还创建了两个方法分别加上了@PostConstruct和@PreDestroy注解。这两套实现方式都可以在对象的额外初始化功能和释放功能,注解的实现不依赖Spring的接口,侵入性弱一点。
接下去,我们创建一个Main类来测试一下:

package me.josephzhu.spring101beans;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;import javax.annotation.Resource;@SpringBootApplication
public class Spring101BeansApplication implements CommandLineRunner {@Autowiredprivate ApplicationContext applicationContext;@Resourceprivate MyService helloService;@Autowiredprivate MyService service;public static void main(String[] args) {SpringApplication.run(Spring101BeansApplication.class, args);}@Overridepublic void run(String... args) throws Exception {System.out.println("====================");applicationContext.getBeansOfType(MyService.class).forEach((name, service)->{System.out.println(name + ":" + service);});System.out.println("====================");System.out.println(helloService.hello());System.out.println(service.hello());}
}

ApplicationContext直接注入即可,不一定需要用ApplicationContextAware方式来获取。执行程序后可以看到输出如下:

me.josephzhu.spring101beans.MyService@7fb4f2a9#constructor:1
me.josephzhu.spring101beans.MyService@7fb4f2a9#postConstruct:2
me.josephzhu.spring101beans.MyService@7fb4f2a9#afterPropertiesSet:3
====================
myService:me.josephzhu.spring101beans.MyService@7fb4f2a9
====================
me.josephzhu.spring101beans.MyService@7fb4f2a9#hello:3
me.josephzhu.spring101beans.MyService@7fb4f2a9#hello:3
me.josephzhu.spring101beans.MyService@7fb4f2a9#preDestroy:3
me.josephzhu.spring101beans.MyService@7fb4f2a9#destroy:3

这里我们使用@Resource注解和@Autowired注解分别引用了两次对象,可以看到由于Bean默认配置为singleton单例,所以容器中MyService类型的对象只有一份,代码输出也可以证明这点。此外,我们也通过输出看到了构造方法以及两套Bean回调的次序是:

  1. 类自己的构造方法
  2. @PostConstruct注释的方法
  3. InitializingBean接口实现的方法
  4. @PreDestroy注释的方法
  5. DisposableBean接口实现的方法

Java 代码方式创建Bean

从刚才的输出中可以看到,在刚才的例子中,我们为Bean打上了@Component注解,容器为我们创建了名为myService的MyService类型的Bean。现在我们再来用Java代码方式来创建相同类型的Bean,创建如下的文件:

package me.josephzhu.spring101beans;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;@Configuration
public class ApplicationConfig {@Bean(initMethod = "init")public MyService helloService(){MyService myService = new MyService();myService.increaseCounter();return myService;}}

这里可以看到在定义Bean的时候我们关联了一个initMethod,因此我们需要修改Bean加上这个方法:

public void init() {counter++;System.out.println(this + "#init:" + counter);}

现在我们运行代码看看结果,得到了如下错误:

Field service in me.josephzhu.spring101beans.Spring101BeansApplication required a single bean, but 2 were found:- myService: defined in file [/Users/zyhome/IdeaProjects/spring101/spring101-beans/target/classes/me/josephzhu/spring101beans/MyService.class]- helloService: defined by method 'helloService' in class path resource [me/josephzhu/spring101beans/ApplicationConfig.class]

出现错误的原因是@Autowired了一个MyService,@Resource注解因为使用Bean的名称来查找Bean,所以并不会出错,而@Autowired因为根据Bean的类型来查抄Bean找到了两个匹配所有出错了,解决方式很简单,我们在多个Bean里选一个作为主Bean。我们修改一下MyService加上注解:

@Component
@Primary
public class MyService implements InitializingBean, DisposableBean

这样,我们的@Resource根据名字匹配到的是我们@Configuration出来的Bean,而@Autowired根据类型+Primary匹配到了@Component注解定义的Bean,重新运行代码来看看是不是这样:

me.josephzhu.spring101beans.MyService@6cd24612#constructor:1
me.josephzhu.spring101beans.MyService@6cd24612#postConstruct:3
me.josephzhu.spring101beans.MyService@6cd24612#afterPropertiesSet:4
me.josephzhu.spring101beans.MyService@6cd24612#init:5
me.josephzhu.spring101beans.MyService@7486b455#constructor:1
me.josephzhu.spring101beans.MyService@7486b455#postConstruct:2
me.josephzhu.spring101beans.MyService@7486b455#afterPropertiesSet:3
====================
myService:me.josephzhu.spring101beans.MyService@7486b455
helloService:me.josephzhu.spring101beans.MyService@6cd24612
====================
me.josephzhu.spring101beans.MyService@6cd24612#hello:5
me.josephzhu.spring101beans.MyService@7486b455#hello:3
me.josephzhu.spring101beans.MyService@7486b455#preDestroy:3
me.josephzhu.spring101beans.MyService@7486b455#destroy:3
me.josephzhu.spring101beans.MyService@6cd24612#preDestroy:5
me.josephzhu.spring101beans.MyService@6cd24612#destroy:5

从输出中我们注意到几点:

  1. 先输出的的确是helloService,说明@Resource引入的是我们Java代码配置的MyService,helloService由于在我们配置的多调用了一次increaseCounter()以及关联的initMethod,所以counter的值是5
  2. initMethod执行的顺序在@PostConstruct注释的方法和InitializingBean接口实现的方法之后
  3. 虽然我们的MySerive的两种Bean的定义都是单例,但是这不代表我们的Bean就是一套,在这里我们通过代码配置和注解方式在容器内创建了两套MyService类型的Bean,它们都经历了自己的初始化过程。通过@Resource和@Autowired引入到了是不同的Bean,当然也就是不同的对象
    你还可以试试在使用@Autowired引入MyService的时候直接指定需要的Bean:
@Autowired
@Qualifier("helloService")
private MyService service;

两个重要的扩展点

我们来继续探索Spring容器提供给我们的两个有关Bean的重要扩展点。

  • 用于修改Bean定义的BeanFactoryPostProcessor。所谓修改定义就是修改Bean的元数据,元数据有哪些呢?如下图所示,类型、名字、实例化方式、构造参数、属性、Autowire模式、懒初始化模式、初始析构方法。实现了这个接口后,我们就可以修改这些已经定义的元数据,实现真正的动态配置。这里需要注意,我们不应该在这个接口的实现中去实例化Bean,否则这相当于提前进行了实例化会破坏Bean的生命周期。

1662e9802f9ea16b?w=1982&h=962&f=png&s=161724

  • 用于修改Bean实例的BeanPostProcessor。在这个阶段其实Bean已经实例化了,我们可以进行一些额外的操作对Bean进行修改。如下图,我们可以清晰的看到Bean的生命周期如下(BeanPostProcessor缩写为BPP):
  1. Bean定义加载
  2. BeanFactoryPostProcessor来修改Bean定义
  3. Bean逐一实例化
  4. BeanPostProcessor预处理
  5. Bean初始化
  6. BeanPostProcessor后处理

1662e98559c7ec26?w=830&h=504&f=png&s=162435
好,我们现在来实现这两种类型的处理器,首先是用于修改Bean定义的处理器:

package me.josephzhu.spring101beans;import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {BeanDefinition beanDefinition = configurableListableBeanFactory.getBeanDefinition("helloService");if (beanDefinition != null) {beanDefinition.setScope("prototype");beanDefinition.getPropertyValues().add("counter", 10);}System.out.println("MyBeanFactoryPostProcessor");}
}

这里,我们首先找到了我们的helloService(Java代码配置的那个Bean),然后修改了它的属性和Scope(还记得吗,在之前的图中我们可以看到,这两项都是Bean的定义,定义相当于类描述,实例当然就是类实例了)。
然后,我们再来创建一个修改Bean实例的处理器:

package me.josephzhu.spring101beans;import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;@Component
public class MyBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {if (bean instanceof MyService) {System.out.println(bean + "#postProcessAfterInitialization:" + ((MyService)bean).increaseCounter());}return bean;}@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {if (bean instanceof MyService) {System.out.println(bean + "#postProcessBeforeInitialization:" + ((MyService)bean).increaseCounter());}return bean;}
}

实现比较简单,在这个处理器的两个接口我们都调用了一次增加计数器的操作。我们运行代码来看一下这两个处理器执行的顺序是否符合刚才那个图的预期:

MyBeanFactoryPostProcessor
me.josephzhu.spring101beans.MyService@41330d4f#constructor:1
me.josephzhu.spring101beans.MyService@41330d4f#postProcessBeforeInitialization:11
me.josephzhu.spring101beans.MyService@41330d4f#postConstruct:12
me.josephzhu.spring101beans.MyService@41330d4f#afterPropertiesSet:13
me.josephzhu.spring101beans.MyService@41330d4f#init:14
me.josephzhu.spring101beans.MyService@41330d4f#postProcessAfterInitialization:15
me.josephzhu.spring101beans.MyService@6f36c2f0#constructor:1
me.josephzhu.spring101beans.MyService@6f36c2f0#postProcessBeforeInitialization:11
me.josephzhu.spring101beans.MyService@6f36c2f0#postConstruct:12
me.josephzhu.spring101beans.MyService@6f36c2f0#afterPropertiesSet:13
me.josephzhu.spring101beans.MyService@6f36c2f0#init:14
me.josephzhu.spring101beans.MyService@6f36c2f0#postProcessAfterInitialization:15
me.josephzhu.spring101beans.MyService@3b35a229#constructor:1
me.josephzhu.spring101beans.MyService@3b35a229#postProcessBeforeInitialization:2
me.josephzhu.spring101beans.MyService@3b35a229#postConstruct:3
me.josephzhu.spring101beans.MyService@3b35a229#afterPropertiesSet:4
me.josephzhu.spring101beans.MyService@3b35a229#postProcessAfterInitialization:5
====================
me.josephzhu.spring101beans.MyService@6692b6c6#constructor:1
me.josephzhu.spring101beans.MyService@6692b6c6#postProcessBeforeInitialization:11
me.josephzhu.spring101beans.MyService@6692b6c6#postConstruct:12
me.josephzhu.spring101beans.MyService@6692b6c6#afterPropertiesSet:13
me.josephzhu.spring101beans.MyService@6692b6c6#init:14
me.josephzhu.spring101beans.MyService@6692b6c6#postProcessAfterInitialization:15
myService:me.josephzhu.spring101beans.MyService@3b35a229
helloService:me.josephzhu.spring101beans.MyService@6692b6c6
====================
me.josephzhu.spring101beans.MyService@41330d4f#hello:15
me.josephzhu.spring101beans.MyService@6f36c2f0#hello:15
me.josephzhu.spring101beans.MyService@3b35a229#preDestroy:5
me.josephzhu.spring101beans.MyService@3b35a229#destroy:5

这个输出结果有点长,第一行就输出了MyBeanFactoryPostProcessor这是预料之中,Bean定义的修改肯定是最先发生的。我们看下输出的规律,1、11、12、13、14、15出现了三次,之所以从1跳到了11是因为我们的BeanFactoryPostProcessor修改了其中的counter属性的值为10。这说明了,我们的helloService的初始化进行了三次:

  • 第一套指针地址是5a7fe64f,对应输出第一个hello(),这是我们@Resource引入的

1662e9889dc0535f?w=830&h=616&f=png&s=321049

  • 第二套指针地址是69ee81fc,对应输出第二个hello(),这是我们@Autowird+@Qualifier引入的(刚才一节最后我们指定了helloService)

1662e98998f48d60?w=830&h=616&f=png&s=338496

  • 第三套指针地址是29f7cefd,这是我们getBeansOfType的时候创建的,对应下面Key-Value的输出:

1662e98aafeb37de?w=830&h=504&f=png&s=257113
这里的输出说明了几点:

  • 我们的BeanFactoryPostProcessor生效了,不但修改了helloService的Scope为prototype而且修改了它的counter属性
  • 对于Scope=ptototype的Bean,显然在每次使用Bean的时候都会新建一个实例
  • BeanPostProcessor两个方法的顺序结合一开始说的Bean事件回调的顺序整体如下:
  1. 类自己的构造方法
  2. BeanFactoryPostProcessor接口实现的postProcessBeforeInitialization()方法
  3. @PostConstruct注释的方法
  4. InitializingBean接口实现的afterPropertiesSet()方法
  5. Init-method定义的方法
  6. BeanFactoryPostProcessor接口实现的postProcessAfterInitialization()方法
  7. @PreDestroy注释的方法
  8. DisposableBean接口实现的destroy()方法

最后,我们可以修改BeanFactoryPostProcessor中的代码把prototype修改为singleton看看是否我们的helloService这个Bean恢复为了单例:

MyBeanFactoryPostProcessor
me.josephzhu.spring101beans.MyService@51891008#constructor:1
me.josephzhu.spring101beans.MyService@51891008#postProcessBeforeInitialization:11
me.josephzhu.spring101beans.MyService@51891008#postConstruct:12
me.josephzhu.spring101beans.MyService@51891008#afterPropertiesSet:13
me.josephzhu.spring101beans.MyService@51891008#init:14
me.josephzhu.spring101beans.MyService@51891008#postProcessAfterInitialization:15
me.josephzhu.spring101beans.MyService@49c90a9c#constructor:1
me.josephzhu.spring101beans.MyService@49c90a9c#postProcessBeforeInitialization:2
me.josephzhu.spring101beans.MyService@49c90a9c#postConstruct:3
me.josephzhu.spring101beans.MyService@49c90a9c#afterPropertiesSet:4
me.josephzhu.spring101beans.MyService@49c90a9c#postProcessAfterInitialization:5
====================
myService:me.josephzhu.spring101beans.MyService@49c90a9c
helloService:me.josephzhu.spring101beans.MyService@51891008
====================
me.josephzhu.spring101beans.MyService@51891008#hello:15
me.josephzhu.spring101beans.MyService@51891008#hello:15
me.josephzhu.spring101beans.MyService@49c90a9c#preDestroy:5
me.josephzhu.spring101beans.MyService@49c90a9c#destroy:5
me.josephzhu.spring101beans.MyService@51891008#preDestroy:15
me.josephzhu.spring101beans.MyService@51891008#destroy:15

本次输出结果的hello()方法明显是同一个bean,结果中也没出现三次1、11、12、13、14、15。

总结

本文以探索的形式讨论了下面的一些知识点:

  1. 容器管理对象的意义是什么
  2. Bean的生命周期回调事件
  3. Spring提供的Bean的两个重要扩展点
  4. @Resource和@Autowired的区别
  5. 注解方式和代码方式配置Bean
  6. @Primary和@Qualifier注解的作用
  7. Bean的不同类型的Scope

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

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

相关文章

ab实验置信度_为什么您的Ab测试需要置信区间

ab实验置信度by Alos Bissuel, Vincent Grosbois and Benjamin HeymannAlosBissuel&#xff0c;Vincent Grosbois和Benjamin Heymann撰写 The recent media debate on COVID-19 drugs is a unique occasion to discuss why decision making in an uncertain environment is a …

基于Pytorch的NLP入门任务思想及代码实现:判断文本中是否出现指定字

今天学了第一个基于Pytorch框架的NLP任务&#xff1a; 判断文本中是否出现指定字 思路&#xff1a;&#xff08;注意&#xff1a;这是基于字的算法&#xff09; 任务&#xff1a;判断文本中是否出现“xyz”&#xff0c;出现其中之一即可 训练部分&#xff1a; 一&#xff…

erlang下lists模块sort(排序)方法源码解析(二)

上接erlang下lists模块sort&#xff08;排序&#xff09;方法源码解析(一)&#xff0c;到目前为止&#xff0c;list列表已经被分割成N个列表&#xff0c;而且每个列表的元素是有序的&#xff08;从大到小&#xff09; 下面我们重点来看看mergel和rmergel模块&#xff0c;因为我…

洛谷P4841 城市规划(多项式求逆)

传送门 这题太珂怕了……如果是我的话完全想不出来…… 题解 1 //minamoto2 #include<iostream>3 #include<cstdio>4 #include<algorithm>5 #define ll long long6 #define swap(x,y) (x^y,y^x,x^y)7 #define mul(x,y) (1ll*(x)*(y)%P)8 #define add(x,y) (x…

支撑阻力指标_使用k表示聚类以创建支撑和阻力

支撑阻力指标Note from Towards Data Science’s editors: While we allow independent authors to publish articles in accordance with our rules and guidelines, we do not endorse each author’s contribution. You should not rely on an author’s works without seek…

高版本(3.9版本)python在anaconda安装opencv库及skimage库(scikit_image库)诸多问题解决办法

今天开始CV方向的学习&#xff0c;然而刚拿到基础代码的时候发现 from skimage.color import rgb2gray 和 import cv2标红&#xff08;这里是因为我已经配置成功了&#xff0c;所以没有红标&#xff09;&#xff0c;我以为是单纯两个库没有下载&#xff0c;去pycharm中下载ski…

python 实现斐波那契数列

# coding:utf8 __author__ blueslidef fun(arg1,arg2,stop):if arg10:print(arg1,arg2)arg3 arg1arg2print(arg3)if arg3<stop:arg3 fun(arg2,arg3,stop)fun(0,1,100)转载于:https://www.cnblogs.com/bluesl/p/9079705.html

单机安装ZooKeeper

2019独角兽企业重金招聘Python工程师标准>>> zookeeper下载、安装以及配置环境变量 本节介绍单机的zookeeper安装&#xff0c;官方下载地址如下&#xff1a; https://archive.apache.org/dist/zookeeper/ 我这里使用的是3.4.11版本&#xff0c;所以找到相应的版本点…

均线交易策略的回测 r_使用r创建交易策略并进行回测

均线交易策略的回测 rR Programming language is an open-source software developed by statisticians and it is widely used among Data Miners for developing Data Analysis. R can be best programmed and developed in RStudio which is an IDE (Integrated Development…

opencv入门课程:彩色图像灰度化和二值化(采用skimage库和opencv库两种方法)

用最简单的办法实现彩色图像灰度化和二值化&#xff1a; 首先采用skimage库&#xff08;skimage库现在在scikit_image库中&#xff09;实现&#xff1a; from skimage.color import rgb2gray import numpy as np import matplotlib.pyplot as plt""" skimage库…

SVN中Revert changes from this revision 跟Revert to this revision

譬如有个文件&#xff0c;有十个版本&#xff0c;假定版本号是1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5&#xff0c;6&#xff0c;7&#xff0c;8&#xff0c;9&#xff0c;10。Revert to this revision&#xff1a; 如果是在版本6这里点击“Revert to this rev…

归 [拾叶集]

归 心归故乡 想象行走在 乡间恬静小路上 让那些疲惫的梦 都随风飞散吧&#xff01; 不去想那些世俗 人来人往 熙熙攘攘 秋日午后 阳光下 细数落叶 来日方长 世上的路 有诗人、浪子 歌咏吟唱 世上的人 在欲望、信仰中 彷徨 彷徨又迷茫 亲爱的人儿 快结束那 无休止的独自流浪 莫要…

instagram分析以预测与安的限量版运动鞋转售价格

Being a sneakerhead is a culture on its own and has its own industry. Every month Biggest brands introduce few select Limited Edition Sneakers which are sold in the markets according to Lottery System called ‘Raffle’. Which have created a new market of i…

opencv:用最邻近插值和双线性插值法实现上采样(放大图像)与下采样(缩小图像)

上采样与下采样 概念&#xff1a; 上采样&#xff1a; 放大图像&#xff08;或称为上采样&#xff08;upsampling&#xff09;或图像插值&#xff08;interpolating&#xff09;&#xff09;的主要目的 是放大原图像,从而可以显示在更高分辨率的显示设备上。 下采样&#xff…

CSS魔法堂:那个被我们忽略的outline

前言 在CSS魔法堂&#xff1a;改变单选框颜色就这么吹毛求疵&#xff01;中我们要模拟原生单选框通过Tab键获得焦点的效果&#xff0c;这里涉及到一个常常被忽略的属性——outline&#xff0c;由于之前对其印象确实有些模糊&#xff0c;于是本文打算对其进行稍微深入的研究^_^ …

初创公司怎么做销售数据分析_初创公司与Faang公司的数据科学

初创公司怎么做销售数据分析介绍 (Introduction) In an increasingly technological world, data scientist and analyst roles have emerged, with responsibilities ranging from optimizing Yelp ratings to filtering Amazon recommendations and designing Facebook featu…

opencv:灰色和彩色图像的像素直方图及直方图均值化的实现与展示

直方图及直方图均值化的理论&#xff0c;实现及展示 直方图&#xff1a; 首先&#xff0c;我们来看看什么是直方图&#xff1a; 理论概念&#xff1a; 在图像处理中&#xff0c;经常用到直方图&#xff0c;如颜色直方图、灰度直方图等。 图像的灰度直方图就描述了图像中灰度分…

mysql.sock问题

Cant connect to local MySQL server through socket /tmp/mysql.sock 上述提示可能在启动mysql时遇到&#xff0c;即在/tmp/mysql.sock位置找不到所需要的mysql.sock文件&#xff0c;主要是由于my.cnf文件里对mysql.sock的位置设定导致。 mysql.sock默认的是在/var/lib/mysql,…

交换机的基本原理配置(一)

1、配置主机名 在全局模式下输入hostname 名字 然后回车即可立马生效&#xff08;在生产环境交换机必须有自己唯一的名字&#xff09; Switch(config)#hostname jsh-sw1jsh-sw1(config)#2、显示系统OS名称及版本信息 特权模式下&#xff0c;输入命令 show version Switch#show …

opencv:卷积涉及的基础概念,Sobel边缘检测代码实现及Same(相同)填充与Vaild(有效)填充

滤波 线性滤波可以说是图像处理最基本的方法&#xff0c;它可以允许我们对图像进行处理&#xff0c;产生很多不同的效果。 卷积 卷积的概念&#xff1a; 卷积的原理与滤波类似。但是卷积却有着细小的差别。 卷积操作也是卷积核与图像对应位置的乘积和。但是卷积操作在做乘…