Spring Aop之Advisor解析

2019獨角獸企業重金招聘Python工程師標準>>> hot3.png

???????在上文Spring Aop之Target Source詳解中,我們講解了Spring是如何通過封裝Target Source來達到對最終獲取的目標bean進行封裝的目的。其中我們講解到,Spring Aop對目標bean進行代理是通過AnnotationAwareAspectJAutoProxyCreator.postProcessAfterInitialization()進行的,Spring Aop的代理主要分為三個步驟:獲取所有的Advisor,過濾可應用到當前bean的Adivsor和使用Advisor為當前bean生成代理對象。本文主要對這三步中的第一步獲取所有的Advisor進行講解。

1. 骨架方法

???????首先我們看看postProcessAfterInitialization()方法的實現:

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException {if (bean != null) {// 獲取當前bean的key:如果beanName不為空,則以beanName為key,如果為FactoryBean類型,// 前面還會添加&符號,如果beanName為空,則以當前bean對應的class為keyObject cacheKey = getCacheKey(bean.getClass(), beanName);// 判斷當前bean是否正在被代理,如果正在被代理則不進行封裝if (!this.earlyProxyReferences.contains(cacheKey)) {// 對當前bean進行封裝return wrapIfNecessary(bean, beanName, cacheKey);}}return bean;
}

???????從上述代碼可以看出,對目標bean的封裝是主要是通過wrapIfNecessary()方法進行的,該方法就是Spring對目標bean進行代理的骨架方法。如下是該方法的實現:

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {// 判斷當前bean是否在TargetSource緩存中存在,如果存在,則直接返回當前bean。這里進行如此判斷的// 原因是在上文中,我們講解了如何通過自己聲明的TargetSource進行目標bean的封裝,在封裝之后其實// 就已經對封裝之后的bean進行了代理,并且添加到了targetSourcedBeans緩存中。因而這里判斷得到// 當前緩存中已經存在當前bean,則說明該bean已經被代理過,這樣就可以直接返回當前bean。if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {return bean;}// 這里advisedBeans緩存了已經進行了代理的bean,如果緩存中存在,則可以直接返回if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;}// 這里isInfrastructureClass()用于判斷當前bean是否為Spring系統自帶的bean,自帶的bean是// 不用進行代理的;shouldSkip()則用于判斷當前bean是否應該被略過if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {// 對當前bean進行緩存this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}// 獲取當前bean的Advices和AdvisorsObject[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);if (specificInterceptors != DO_NOT_PROXY) {// 對當前bean的代理狀態進行緩存this.advisedBeans.put(cacheKey, Boolean.TRUE);// 根據獲取到的Advices和Advisors為當前bean生成代理對象Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));// 緩存生成的代理bean的類型,并且返回生成的代理beanthis.proxyTypes.put(cacheKey, proxy.getClass());return proxy;}this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;
}

???????在上述骨架方法中,Spring主要進行了三件事:

  • 判斷當前bean是否已經生成過代理對象,或者是否是應該被略過的對象,是則直接返回,否則進行下一步;
  • 獲取當前bean的Advisors和Advices,如果當前bean不需要代理,則返回DO_NOT_PROXY;
  • 通過生成的Advisors和Advices為目標bean生成代理對象。

???????關于上述骨架方法,這里需要說明兩個點:

  • shouldSkip()方法中對當前bean判斷是否應該略過時,其主要做了兩件事:a. 為當前bean生成需要代理的Advisors;b. 判斷生成的Advisor是否為AspectJPointcutAdvisor類型。因而實際上判斷略過的過程就是判斷是否為AspectJPointcutAdvisor,判斷這個類的原因在于Spring Aop的切面和切點的生成也可以通過在xml文件中使用<aop:config/>標簽進行。這個標簽最終解析得到的Adivsor類型就是``AspectJPointcutAdvisor類型的,因為其在解析aop:config/的時候就已經生成了Advisor,因而這里需要對這種類型的Advisor進行略過。這里aop:config/`也是一種自定義標簽,關于其解析過程,讀者可以參照本人前面的博文自行閱讀器源碼;
  • getAdvicesAndAdvisorsForBean()方法就其名稱而言是獲取Advisors和Advices,但實際上其返回值是一個Advisor的數組。Spring Aop在為目標bean獲取需要進行代理的切面邏輯的時候最終得到的是Advisor,這里Advice表示的是每個切面邏輯中使用@Before@After@Around等需要織入的代理方法。因為每個代理方法都表示一個Advice,并且每個代理方法最終都會生成一個Advisor,因而Advice和Advisor就本質而言其實沒有太大的區別。Advice表示需要織入的切面邏輯,而Advisor則表示將切面邏輯進行封裝之后的織入者。

2. 切面的生成

???????雖然在shouldSkip()方法中會為當前bean生成Advisor,但是在getAdvicesAndAdvisorsForBean()中也還是會獲取一次,只不過在第一次生成的時候會將得到的Advisor都進行緩存,因而第二次獲取時可以直接從緩存中獲取。我們這里還是以getAdvicesAndAdvisorsForBean()方法為準來進行講解。如下是該方法的源碼:

protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {// 為目標bean生成AdvisorList<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);if (advisors.isEmpty()) {return DO_NOT_PROXY;}return advisors.toArray();
}

???????我們繼續看findEligibleAdvisors()方法:

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {// 將當前系統中所有的切面類的切面邏輯進行封裝,從而得到目標AdvisorList<Advisor> candidateAdvisors = findCandidateAdvisors();// 對獲取到的所有Advisor進行判斷,看其切面定義是否可以應用到當前bean,從而得到最終需要應用的AdvisorList<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);// 提供的hook方法,用于對目標Advisor進行擴展extendAdvisors(eligibleAdvisors);if (!eligibleAdvisors.isEmpty()) {// 對需要代理的Advisor按照一定的規則進行排序eligibleAdvisors = sortAdvisors(eligibleAdvisors);}return eligibleAdvisors;
}

???????在上述方法中,Spring Aop首先獲取到了系統中所有的切面邏輯,并將其封裝為了Advisor對象,然后通過遍歷Advisor判斷哪些Advisor是可以應用到當前bean的,最后將需要織入的Advisor返回。這里我們看看findCandidateAdvisors()的源碼:

protected List<Advisor> findCandidateAdvisors() {// 找到系統中實現了Advisor接口的beanList<Advisor> advisors = super.findCandidateAdvisors();if (this.aspectJAdvisorsBuilder != null) {// 找到系統中使用@Aspect標注的bean,并且找到該bean中使用@Before,@After等標注的方法,// 將這些方法封裝為一個個Advisoradvisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());}return advisors;
}

???????可以看到,findCandidateAdvisors()主要是通過兩種方式獲取切面邏輯,一種是在系統中找到實現了Advisor接口的所有類,另一種是在找到系統中使用@Aspect標注的類,并將其切面邏輯封裝為Advisor,這兩種Advisor都有可能是我們需要進行織入的切面邏輯。這里super.findCandidateAdvisors()方法最終調用的是BeanFactoryAdvisorRetrievalHelper.findAdvisorBeans()方法,我們首先看看該方法的實現:

public List<Advisor> findAdvisorBeans() {String[] advisorNames = null;synchronized (this) {advisorNames = this.cachedAdvisorBeanNames;if (advisorNames == null) {// 獲取當前BeanFactory中所有實現了Advisor接口的bean的名稱advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Advisor.class, true, false);this.cachedAdvisorBeanNames = advisorNames;}}if (advisorNames.length == 0) {return new LinkedList<>();}// 對獲取到的實現Advisor接口的bean的名稱進行遍歷List<Advisor> advisors = new LinkedList<>();for (String name : advisorNames) {// isEligibleBean()是提供的一個hook方法,用于子類對Advisor進行過濾,這里默認返回值都是trueif (isEligibleBean(name)) {// 如果當前bean還在創建過程中,則略過,其創建完成之后會為其判斷是否需要織入切面邏輯if (this.beanFactory.isCurrentlyInCreation(name)) {if (logger.isDebugEnabled()) {logger.debug("Skipping currently created advisor '" + name + "'");}} else {try {// 將當前bean添加到結果中advisors.add(this.beanFactory.getBean(name, Advisor.class));} catch (BeanCreationException ex) {// 對獲取過程中產生的異常進行封裝Throwable rootCause = ex.getMostSpecificCause();if (rootCause instanceof BeanCurrentlyInCreationException) {BeanCreationException bce = (BeanCreationException) rootCause;String bceBeanName = bce.getBeanName();if (bceBeanName != null && this.beanFactory.isCurrentlyInCreation(bceBeanName)) {if (logger.isDebugEnabled()) {logger.debug("Skipping advisor '" + name + "' with dependency on currently created bean: " + ex.getMessage());}continue;}}throw ex;}}}}return advisors;
}

???????這里findAdvisorBeans()方法邏輯其實非常簡單,其主要是在BeanFactory中找打實現了Advisor接口的類,然后通過hook方法判斷子類是否需要對Advisor進行過濾,最后將過濾之后的Advisor返回。

???????接下來我們看看BeanFactoryAspectJAdvisorsBuilder.buildAspectJAdvisors()的實現:

public List<Advisor> buildAspectJAdvisors() {List<String> aspectNames = this.aspectBeanNames;if (aspectNames == null) {synchronized (this) {aspectNames = this.aspectBeanNames;if (aspectNames == null) {List<Advisor> advisors = new LinkedList<>();aspectNames = new LinkedList<>();// 獲取當前BeanFactory中所有的beanString[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false);// 對獲取到的所有bean進行循環遍歷for (String beanName : beanNames) {// 判斷當前bean是否為子類定制的需要過濾的beanif (!isEligibleBean(beanName)) {continue;}// 獲取當前遍歷的bean的Class類型Class<?> beanType = this.beanFactory.getType(beanName);if (beanType == null) {continue;}// 判斷當前bean是否使用了@Aspect注解進行標注if (this.advisorFactory.isAspect(beanType)) {aspectNames.add(beanName);// 對于使用了@Aspect注解標注的bean,將其封裝為一個AspectMetadata類型。// 這里在封裝的過程中會解析@Aspect注解上的參數指定的切面類型,如perthis// 和pertarget等。這些被解析的注解都會被封裝到其perClausePointcut屬性中AspectMetadata amd = new AspectMetadata(beanType, beanName);// 判斷@Aspect注解中標注的是否為singleton類型,默認的切面類都是singleton// 類型if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {// 將BeanFactory和當前bean封裝為MetadataAwareAspect-// InstanceFactory對象,這里會再次將@Aspect注解中的參數都封裝// 為一個AspectMetadata,并且保存在該factory中MetadataAwareAspectInstanceFactory factory =new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);// 通過封裝的bean獲取其Advice,如@Before,@After等等,并且將這些// Advice都解析并且封裝為一個個的AdvisorList<Advisor> classAdvisors this.advisorFactory.getAdvisors(factory);// 如果切面類是singleton類型,則將解析得到的Advisor進行緩存,// 否則將當前的factory進行緩存,以便再次獲取時可以通過factory直接獲取if (this.beanFactory.isSingleton(beanName)) {this.advisorsCache.put(beanName, classAdvisors);} else {this.aspectFactoryCache.put(beanName, factory);}advisors.addAll(classAdvisors);} else {// 如果@Aspect注解標注的是perthis和pertarget類型,說明當前切面// 不可能是單例的,因而這里判斷其如果是單例的則拋出異常if (this.beanFactory.isSingleton(beanName)) {throw new IllegalArgumentException("Bean with name '" + beanName + "' is a singleton, but aspect "+ "instantiation model is not singleton");}// 將當前BeanFactory和切面bean封裝為一個多例類型的FactoryMetadataAwareAspectInstanceFactory factory =new PrototypeAspectInstanceFactory(this.beanFactory, beanName);// 對當前bean和factory進行緩存this.aspectFactoryCache.put(beanName, factory);advisors.addAll(this.advisorFactory.getAdvisors(factory));}}}this.aspectBeanNames = aspectNames;return advisors;}}}if (aspectNames.isEmpty()) {return Collections.emptyList();}// 通過所有的aspectNames在緩存中獲取切面對應的Advisor,這里如果是單例的,則直接從advisorsCache// 獲取,如果是多例類型的,則通過MetadataAwareAspectInstanceFactory立即生成一個List<Advisor> advisors = new LinkedList<>();for (String aspectName : aspectNames) {List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);// 如果是單例的Advisor bean,則直接添加到返回值列表中if (cachedAdvisors != null) {advisors.addAll(cachedAdvisors);} else {// 如果是多例的Advisor bean,則通過MetadataAwareAspectInstanceFactory生成MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);advisors.addAll(this.advisorFactory.getAdvisors(factory));}}return advisors;
}

???????對于通過@Aspect注解獲取切面邏輯的方法,這里的邏輯也比較簡單,Spring首先會過濾得到BeanFactory中所有標注有@Aspect的類,然后對該注解參數進行解析,判斷其環繞的目標bean是單例的還是多例的。如果是單例的,則直接緩存到advisorsCache中;如果是多例的,則將生成Advisor的factory進行緩存,以便每次獲取時都通過factory獲取一個新的Advisor。上述方法中主要是對@Aspect注解進行了解析,我們前面講過,Spring Aop的Advisor對應的是Advice,而每個Advice都是對應的一個@Before或者@After等標注方法的切面邏輯,這里對這些切面邏輯的解析過程就在上述的advisorFactory.getAdvisors(factory)方法調用中。這里我們看看該方法的實現:

public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {// 獲取當前切面類的Class類型Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();// 獲取當前切面bean的名稱String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();// 對當前切面bean進行校驗,主要是判斷其切點是否為perflow或者是percflowbelow,Spring暫時不支持// 這兩種類型的切點validate(aspectClass);// 將當前aspectInstanceFactory進行封裝,這里LazySingletonAspectInstanceFactoryDecorator// 使用裝飾器模式,主要是對獲取到的切面實例進行了緩存,保證每次獲取到的都是同一個切面實例MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);List<Advisor> advisors = new LinkedList<>();// 這里getAdvisorMethods()會獲取所有的沒有使用@Pointcut注解標注的方法,然后對其進行遍歷for (Method method : getAdvisorMethods(aspectClass)) {// 判斷當前方法是否標注有@Before,@After或@Around等注解,如果標注了,則將其封裝為一個AdvisorAdvisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);if (advisor != null) {advisors.add(advisor);}}// 這里的isLazilyInstantiated()方法判斷的是當前bean是否應該被延遲初始化,其主要是判斷當前// 切面類是否為perthis,pertarget或pertypewithiin等聲明的切面。因為這些類型所環繞的目標bean// 都是多例的,因而需要在運行時動態判斷目標bean是否需要環繞當前的切面邏輯if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {// 如果Advisor不為空,并且是需要延遲初始化的bean,則在第0位位置添加一個同步增強器,// 該同步增強器實際上就是一個BeforeAspect的AdvisorAdvisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);advisors.add(0, instantiationAdvisor);}// 判斷屬性上是否包含有@DeclareParents注解標注的需要新添加的屬性,如果有,則將其封裝為一個Advisorfor (Field field : aspectClass.getDeclaredFields()) {Advisor advisor = getDeclareParentsAdvisor(field);if (advisor != null) {advisors.add(advisor);}}return advisors;
}

???????在上述getAdvisors()方法中,Spring會遍歷當前切面類所有的方法,包括父類和父接口的方法,找到其中沒有使用@Pointcut注解標注的方法,然后對找到的方法進行遍歷,將其封裝為一個Advisor。這里我們繼續看封裝為Advisor的方法:

public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrderInAspect, String aspectName) {// 校驗當前切面類是否使用了perflow或者percflowbelow標識的切點,Spring暫不支持這兩種切點validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());// 獲取當前方法中@Before,@After或者@Around等標注的注解,并且獲取該注解的值,將其// 封裝為一個AspectJExpressionPointcut對象AspectJExpressionPointcut expressionPointcut = getPointcut(candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());if (expressionPointcut == null) {return null;}// 將獲取到的切點,切點方法等信息封裝為一個Advisor對象,也就是說當前Advisor包含有所有// 當前切面進行環繞所需要的信息return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod, this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}

???????到這里Spring才將@Before,@After或@Around標注的方法封裝為了一個Advisor對象。需要說明的是,這里封裝成的Advisor對象只是一個半成品。所謂的半成品指的是此時其并沒有對切點表達式進行解析,其還只是使用一個字符串保存在AspectJExpressionPointcut對象中,只有在真正使用當前Advice邏輯進行目標bean的環繞的時候才會對其進行解析。

3. 小結

???????本文主要講解了Spring是如何獲取所有的Advisor的,即首先獲取BeanFactory中所有實現了Advisor接口的bean,然后獲取BeanFactory中所有標注了@Aspect注解的bean,解析該bean中的所有的切面邏輯,并且封裝為一個個Advisor,這兩種方式得到的Advisor都有可能是最終會應用到目標bean上的切面邏輯。需要注意的是,這里獲取到的Advisor并沒有對切點表達式進行解析,實際的解析過程是在判斷當前bean是否可以應用到目標bean時進行的。這也是一個小小的優化,因為解析切點表達式的過程是一個比較復雜的過程。

轉載于:https://my.oschina.net/zhangxufeng/blog/1929863

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/389901.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/389901.shtml
英文地址,請注明出處:http://en.pswp.cn/news/389901.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

react事件處理函數中綁定this的bind()函數

問題引入 import React, { Component } from react; import {Text,View } from react-native;export default class App extends Component<Props> {constructor(props){super(props)this.state{times:0}this.timePlusthis.timePlus.bind(this);}timePlus(){let timethis…

301. 刪除無效的括號

301. 刪除無效的括號 給你一個由若干括號和字母組成的字符串 s &#xff0c;刪除最小數量的無效括號&#xff0c;使得輸入的字符串有效。 返回所有可能的結果。答案可以按 任意順序 返回。 示例 1&#xff1a; 輸入&#xff1a;s “()())()” 輸出&#xff1a;["(())…

為什么隨機性是信息

用位思考 (Thinking in terms of Bits) Imagine you want to send outcomes of 3 coin flips to your friends house. Your friend knows that you want to send him those messages but all he can do is get the answer of Yes/No questions arranged by him. Lets assume th…

Chrome無法播放m3u8格式的直播視頻流的問題解決

出國&#xff0c;然后安裝這個插件即可&#xff1a;Native HLS Playback https://chrome.google.com/webstore/detail/native-hls-playback/emnphkkblegpebimobpbekeedfgemhof?hlzh-CN轉載于:https://www.cnblogs.com/EasonJim/p/8737001.html

大數據相關從業_如何在組織中以數據從業者的身份閃耀

大數據相關從業Build bridges, keep the maths under your hat and focus on serving.架起橋梁&#xff0c;將數學放在腦海中&#xff0c;并專注于服務。 通過協作而不是通過孤立的孤島來交付出色的數據工作。 (Deliver great data work through collaboration not through co…

暑假周總結六

本周開始了做網站的商品展示和商品查詢的功能&#xff0c;基本功能已完成了。平均每天花4到5個小時進行學習和編碼 這周學習了lucene分詞器&#xff0c;但是雖然學了一些這些方面的東西&#xff0c;但是查詢的時候效果還是不行&#xff0c;還是繼續學習 一些更好處理關鍵字的方…

Django進階之中間件

中間件簡介 在http請求 到達視圖函數之前 和視圖函數return之后&#xff0c;django會根據自己的規則在合適的時機執行中間件中相應的方法。 中間件的執行流程 1、執行完所有的request方法 到達視圖函數。 2、執行中間件的其他方法 2、經過所有response方法 返回客戶端。 注意…

漢諾塔遞歸算法進階_進階python 1遞歸

漢諾塔遞歸算法進階When something is specified in terms of itself, it is called recursion. The recursion gives us a new idea of how to solve a kind of problem and this gives us insights into the nature of computation. Basically, many of computational artifa…

500. 鍵盤行

500. 鍵盤行 給你一個字符串數組 words &#xff0c;只返回可以使用在 美式鍵盤 同一行的字母打印出來的單詞。鍵盤如下圖所示。 美式鍵盤 中&#xff1a; 第一行由字符 “qwertyuiop” 組成。 第二行由字符 “asdfghjkl” 組成。 第三行由字符 “zxcvbnm” 組成。 示例 1&a…

windows 停止nginx

1、查找進程 tasklist | findstr nginx2、殺死進程 taskkill /pid 6508 /F3、一次殺死多個進程taskkill /pid 6508 /pid 16048 /f轉載于:https://blog.51cto.com/dressame/2161759

SpringBoot返回json和xml

有些情況接口需要返回的是xml數據&#xff0c;在springboot中并不需要每次都轉換一下數據格式&#xff0c;只需做一些微調整即可。 新建一個springboot項目&#xff0c;加入依賴jackson-dataformat-xml&#xff0c;pom文件代碼如下&#xff1a; <?xml version"1.0&quo…

575. 分糖果

575. 分糖果 給定一個偶數長度的數組&#xff0c;其中不同的數字代表著不同種類的糖果&#xff0c;每一個數字代表一個糖果。你需要把這些糖果平均分給一個弟弟和一個妹妹。返回妹妹可以獲得的最大糖果的種類數。 示例 1:輸入: candies [1,1,2,2,3,3] 輸出: 3 解析: 一共有三…

如何開啟并配置CITRIX Xenserver的SNMP服務

以下博文轉載至虛擬人生Citrix Xenserver使用標準的NET-SNMP協議&#xff0c;關于NET-SNMP請參考www.net-snmp.org. Xenserver并沒有自己的MIB庫.Xenserver默認是禁止SNMP服務且并沒有開啟SNMP服務使用的端口,通過以下方式開啟并配置SNMP服務&#xff1a;1.編輯Xenserver的/etc…

orange 數據分析_使用Orange GUI的放置結果數據分析

orange 數據分析Objective : Analysing of several factors influencing the recruitment of students and extracting information through plots.目的&#xff1a;分析影響學生招生和通過情節提取信息的幾個因素。 Description : The following analysis presents the diffe…

C++(1)引用

引用 引用 為對象起另外一個名字&#xff0c;通過將聲明符寫成 &d&#xff0c;其中d是聲明的變量名。一旦初始化完成&#xff0c;引用將和起初始值綁定在一起&#xff0c;無法再綁定到另一個對象&#xff0c;因此引用必須初始化。 引用就是別名&#xff0c;初始化以后&am…

普里姆從不同頂點出發_來自三個不同聚類分析的三個不同教訓數據科學的頂點...

普里姆從不同頂點出發繪制大流行時期社區的風險群圖&#xff1a;以布宜諾斯艾利斯為例 (Map Risk Clusters of Neighbourhoods in the time of Pandemic: a case of Buenos Aires) 介紹 (Introduction) Every year is unique and particular. But, 2020 brought the world the …

一步一步圖文介紹SpriteKit使用TexturePacker導出的紋理集Altas

1、為什么要使用紋理集&#xff1f; 游戲是一種很耗費資源的應用&#xff0c;特別是在移動設備中的游戲&#xff0c;性能優化是非常重要的 紋理集是將多張小圖合成一張大圖&#xff0c;使用紋理集有以下優點&#xff1a; 1、減少內存占用&#xff0c;減少磁盤占用&#xff1b; …

BZOJ.1007.[HNOI2008]水平可見直線(凸殼 單調棧)

題目鏈接 可以看出我們是要維護一個下凸殼。 先對斜率從小到大排序。斜率最大、最小的直線是一定會保留的&#xff0c;因為這是凸殼最邊上的兩段。 維護一個單調棧&#xff0c;棧中為當前可見直線(按照斜率排序)。 當加入一條直線l時&#xff0c;可以發現 如果l與棧頂直線l的交…

荷蘭牛欄 荷蘭售價_荷蘭的公路貨運是如何發展的

荷蘭牛欄 荷蘭售價I spent hours daily driving on one of the busiest motorways in the Netherlands when commuting was still a norm. When I first came across with the goods vehicle data on CBS website, it immediately attracted my attention: it could answer tho…

Vim 行號的顯示與隱藏

2019獨角獸企業重金招聘Python工程師標準>>> Vim 行號的顯示與隱藏 一、當前文檔的顯示與隱藏 1 打開一個文檔 [rootpcname ~]# vim demo.txt This is the main Apache HTTP server configuration file. It contains the configuration directives that give the s…