朱曄和你聊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.pswp.cn/news/389573.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/389573.shtml
英文地址,請注明出處:http://en.pswp.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; 卷積的原理與濾波類似。但是卷積卻有著細小的差別。 卷積操作也是卷積核與圖像對應位置的乘積和。但是卷積操作在做乘…