部落格

  • 武漢肺炎延燒 泰非政府組織籲關野生動物市場

    摘錄自2020年02月26日中央社報導

    武漢肺炎蔓延全球,野生動物被懷疑是可能的傳染源。泰國非政府組織(NGO)今(26日)發布報告,呼籲大眾停止購買野生動物,亞洲各國政府應關閉所有野生動物交易市場。

    泰國非政府組織自由地(Freeland)與卓越分析中心販運部門(Analytical Center of Excellence on Trafficking)發布「東南亞的野生動物販運:演化、軌跡和如何組織販運」報告,提出上述呼籲。

    自由地創辦人蓋爾斯特(Steven Galster)在記者會中指出,野生動物交易衍生的疾病風險問題,不只在中國,也不會只在武漢肺炎。緬甸、越南和泰國都賣野生動物,例如穿山甲、烏龜、猴子等。

    自由地執行經理馬占達(Onkuri Majumdar)指出,大家關注2019年冠狀病毒疾病(COVID-19,武漢肺炎)可能是蝙蝠或穿山甲傳染給人類。但不要忘記,嚴重急性呼吸道症候群(SARS)、禽流感、伊波拉病毒(Ebola Virus)甚至愛滋病毒都是由動物傳給人類,撲殺這些動物並不是解決的辦法。她呼籲各國政府關閉野生動物交易市場,大眾停止購買野生動物。

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業 缺乏曝光? 下一步”網站設計“幫您第一時間規劃公司的門面形象

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

    ※試算大陸海運運費!

  • 航運業減碳新招 看上阿摩尼亞

    摘錄自2020年02月20日台灣醒報報導

    航運業減碳又有新招。英國研究指出,肥料中常見的氨是取代貨輪柴油的最佳選擇,而且燃燒過程中並不會產生二氧化碳。對此,航運產業也希望能以氨取代柴油,幫助對抗氣候變遷。而最快在10年內,貨輪將可利用氨驅動。

    據《BBC》報導,英國皇家學會研究人員大衛表示,「氨是唯一能驅動貨輪航行各地零排碳的燃料。」研究也指出,製造氨雖會產生碳,但可透過新技術製造零碳的氨。首先是在製造時將二氧化碳捕捉並埋在地下,另一方法是利用再生能源製造氨。

    負責認證全球船隻的韓國船級社稍早也指出,由於氨作為燃料無須大量專門技術,因此是一種可行的潔淨能源。該組織在報告中指出,「氨的優勢在於相對容易存儲,因為與氫相比,前者具有合理能量、密度與液化溫度,與其他碳中性燃料相比,其生產與運輸成本也較低,並且已具備可穩定生產與運輸的技術。」

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

    ※專營大陸快遞台灣服務

    台灣快遞大陸的貨運公司有哪些呢?

  • 氣候風險應納入企業稽核 全球會計師公會聯盟發起倡議

    環境資訊中心綜合外電;姜唯 編譯;林大利 審校

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    ※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

    ※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

    台灣海運大陸貨務運送流程

    兩岸物流進出口一站式服務

  • 日311地震重災區 女川核電廠2號機通過重啟審查

    摘錄自2020年02月26日中央社報導

    日本女川核電廠2號機以重啟運轉為前提的申請,已通過日本原子力規制委員會審查,成為位於311大地震重災區首座合格的核電廠,也是第2座遭受海嘯侵襲重生的核電廠。

    日本放送協會(NHK)報導,東北電力公司希望位於宮城縣的女川核電廠2號機能重啟運轉提出申請,日本原子力規制委員會今(26日)已正式彙整完成表示合格的審查書。

    女川核電廠2號機是位於2011年311大地震後遭受重創的東北地方,首度獲審查合格的核電廠機組。東北電力公司在311大地震2年後的2013年,以重啟運轉為前提向原子力規制委員會提出審查申請。

    持續進行審議的原子力規制委員會,2019年11月彙整出表示事實上符合新規範標準的審查書草案,並向一般民眾徵集意見。

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包”嚨底家”

    網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    小三通海運與一般國際貿易有何不同?

    小三通快遞通關作業有哪些?

  • Spring註解@Configuration是如何被處理的?

    Spring註解@Configuration是如何被處理的?

    從SpringApplication開始

    一般情況下啟動SpringBoot都是新建一個類包含main方法,然後使用SpringApplication.run來啟動程序:

    @SpringBootApplication
    public class AutoConfigApplication {
    
        public static void main(String[] args){
            ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(AutoConfigApplication.class,args);
        }
    }
    

    SpringApplication.run接收兩個參數分別為:primarySource、運行參數(args),上面的代碼使用AutoConfigApplication.class作為primarySource。SpringApplication還有一個實例方法也叫run,SpringBoot的大部分啟動都由實例run方法來完成的,其中構造ApplicationContext由createApplicationContext方法完成:

    protected ConfigurableApplicationContext createApplicationContext() {
            Class<?> contextClass = this.applicationContextClass;
            if (contextClass == null) {
                try {
                    switch (this.webApplicationType) {
                    case SERVLET:
                        contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                        break;
                    case REACTIVE:
                        contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                        break;
                    default:
                        contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
                    }
                }
                catch (ClassNotFoundException ex) {
                    throw new IllegalStateException(
                            "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
                }
            }
            return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
        }

    createApplicationContext根據this.webApplicationType來構造ApplicationContext,不同的環境都會使用不同的實例,但本文非web環境所有構造的時候會使用AnnotationConfigApplicationContext類。創建AnnotationConfigApplicationContext的時候會調用默認構造方法

    public AnnotationConfigApplicationContext() {
        this.reader = new AnnotatedBeanDefinitionReader(this);
        this.scanner = new ClassPathBeanDefinitionScanner(this);
    }

    AnnotationConfigApplicationContext默認構造函數創建兩個對象:

    • reader(AnnotatedBeanDefinitionReader):用於手動註冊bean
    • scanner(ClassPathBeanDefinitionScanner): 用於掃描Component、Repository、Service等註解

    AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner會註冊一些註解處理器,註冊的方式都是使用AnnotationConfigUtils的registerAnnotationConfigProcessors方法

    public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
                BeanDefinitionRegistry registry, @Nullable Object source) {
    
            ...
            
            if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
                RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
                def.setSource(source);
                beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
            }
            ...
            return beanDefs;
        }

    最終AnnotationConfigApplicationContext構造方法執行完成后ApplicationContext會有以下BeanDefinition:

    構造完ApplicationContext后SpringApplicaiton緊接着會加載primarySource,上面提到 過primarySource是在運行的時候傳遞進來的(AutoConfigApplication.class),加載過程中不貼代碼了,只要知道最終ApplicaitonContext中會多一個AutoConfigApplication的BeanDefinition:

    小結

    總的來說SpringApplicaiton主要幹了這些事:

    • 創建AnnotationConfigApplicationContext
    • 加載一些處理註解的后處理器如:ConfigurationClassPostProcessor
    • primarySource加載進ApplicationContext

    最重要的一點是,現在是有一個AnnotationConfigApplicationContext裡面包含了primarySource(AutoConfigApplication)以及ConfigurationClassPostProcessor。打個斷點在ApplicaitonContext刷新之前打印下context中的bean的名稱,可以確定這樣說沒毛病!

    @Configuration啥時候被解析?

    雖說有了primarySource和ConfigurationClassPostProcessor后處理器,還是需要有個執行的入口。ConfigurationClassPostProcessor是BeanDefinitionRegistryPostProcessor的實現類,BeanDefinitionRegistryPostProcessor會在ApplicationContext的refresh操作時被處理:

    public void refresh() throws BeansException, IllegalStateException {
            synchronized (this.startupShutdownMonitor) {
                ...
                invokeBeanFactoryPostProcessors(beanFactory);
                ...
            }
    }
        
    public static void invokeBeanFactoryPostProcessors(
                ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
            
            ...
            //找出所有類型為BeanDefinitionRegistryPostProcessor的bean的名稱
            String[] postProcessorNames =
                        beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
            for (String ppName : postProcessorNames) {
                if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                    currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                    processedBeans.add(ppName);
                }
            }
            sortPostProcessors(currentRegistryProcessors, beanFactory);
            registryProcessors.addAll(currentRegistryProcessors);
            //執行BeanDefinitionRegistryPostProcessor
            invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
            ...
    }
    
    private static void invokeBeanDefinitionRegistryPostProcessors(
            Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {
    
        for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
            //調用postProcessBeanDefinitionRegistry方法
            postProcessor.postProcessBeanDefinitionRegistry(registry);
        }
    }   
    

    invokeBeanDefinitionRegistryPostProcessors會調用BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法,通過斷點調試工具確認下ConfigurationClassPostProcessor有沒有在這一步被處理:

    調試輸出postProcessors集合裏面有一個了ConfigurationClassPostProcessor元素,說明了ConfigurationClassPostProcessor的執行入口沒有問題。

    ConfigurationClassPostProcessor處理器

    ConfigurationClassPostProcessor首先會判斷在ApplicationContext中的bean是否被@Configuration註解標記,然後使用ConfigurationClassParser來解析@Configuration,ConfigurationClassPostProcessor的解析@Configuration的大致流程:

    1. 使用ConfigurationClassUtils.checkConfigurationClassCandidate檢查BeanDefinition是否@Configuration註解標記
    2. 對@Configuration進行排序
    3. 使用ConfigurationClassParser解析@Configuration註解的信息
    4. 使用ConfigurationClassBeanDefinitionReader解析BeanDefinition
    public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
            List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
            
            //獲取所有BeanDefinitio名稱
            String[] candidateNames = registry.getBeanDefinitionNames();
            
            for (String beanName : candidateNames) {
                BeanDefinition beanDef = registry.getBeanDefinition(beanName);
                //如果是full、lite則說明已經處理過的類
                if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
                        ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
                    }
                }
                //檢查BeanDefinition是否有@Configuration註解
                else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
                    configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
                }
            }
    
            //如果沒有找到@Configuration標記的類,則返回不作處理也
            if (configCandidates.isEmpty()) {
                return;
            }
    
            //對@Configuration進行排序
            configCandidates.sort((bd1, bd2) -> {
                int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
                int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
                return Integer.compare(i1, i2);
            });
            
            ...
      
            ConfigurationClassParser parser = new ConfigurationClassParser(
                    this.metadataReaderFactory, this.problemReporter, this.environment,
                    this.resourceLoader, this.componentScanBeanNameGenerator, registry);
    
            Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
            Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
            do {
                //解析@Configuration class
                parser.parse(candidates);
                parser.validate();
    
                Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
                configClasses.removeAll(alreadyParsed);
    
                //讀取BeanDefinition
                if (this.reader == null) {
                    this.reader = new ConfigurationClassBeanDefinitionReader(
                            registry, this.sourceExtractor, this.resourceLoader, this.environment,
                            this.importBeanNameGenerator, parser.getImportRegistry());
                }
                this.reader.loadBeanDefinitions(configClasses);
                alreadyParsed.addAll(configClasses);
    
                candidates.clear();
                ...
            }
            while (!candidates.isEmpty());
            ...
        }

    最後還是通過調試工具看一下示例中的的啟動類AutoConfigApplication沒有被處理:

    圖上显示configCandidates中有一個名稱為autoConfigApplication的BeanDefinition的元素,說明AutoConfigApplication會被當作配置類解析,但是AutoConfigApplication並沒有使用@Configuration註解,為什麼還會被當做配置類呢?其實@Configuration在@SpringBootApplication註解中:

    紅色背景列出來的就是@Configuration註解,它是@SpringBootConfiguration的元註解。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業 缺乏曝光? 下一步”網站設計“幫您第一時間規劃公司的門面形象

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

    ※試算大陸海運運費!

  • 談一談依賴倒置原則

    談一談依賴倒置原則

    為獲得良好的閱讀體驗,請訪問原文:

    一、概念

    依賴倒置原則(Dependence Inversion Principle,DIP)是指設計代碼結構時,高層模塊不應該依賴低層模塊,二者都應該依賴其抽象。

    抽象不應該依賴細節,細節應該依賴抽象。通過依賴倒置,可以減少類與類之間的耦合性,提高系統的穩定性,提高代碼的可讀性和可維護性,並且能夠降低修改程序所造成的風險。

    二、為什麼

    先來看一個例子

    可是依賴倒置原則是怎麼做到的呢?我們先來看一個例子:一個愛學習的「我沒有三顆心臟」同學現在正在學習「設計模式」和「Java」的課程,偽代碼如下:

    public class Wmyskxz {
    
        public void studyJavaCourse() {
            System.out.println("「我沒有三顆心臟」同學正在學習「Java」課程");
        }
    
        public void studyDesignPatternCourse() {
            System.out.println("「我沒有三顆心臟」同學正在學習「設計模式」課程");
        }
    }

    我們來模擬上層調用一下:

    public static void main(String[] args) {
        Wmyskxz wmyskxz = new Wmyskxz();
        wmyskxz.studyJavaCourse();
        wmyskxz.studyDesignPatternCourse();
    }

    原因一:有效控制影響範圍

    由於「我沒有三顆心臟」同學熱愛學習,隨着學習興趣的 “暴增”,可能會繼續學習 AI(人工智能)的課程。這個時候,因為「業務的擴展」,要從底層實現到高層調用依次地修改代碼。

    我們需要在 Wmyskxz 類中新增 studyAICourse() 方法,也需要在高層調用中增加調用,這樣一來,系統發布后,其實是非常不穩定的。顯然在這個簡單的例子中,我們還可以自信地認為,我們能 Hold 住這一次的修改帶來的影響,因為都是新增的代碼,我們回歸的時候也可以很好地 cover 住,但實際的情況和實際的軟件環境要複雜得多。

    最理想的情況就是,我們已經編寫好的代碼可以 “萬年不變”,這就意味着已經覆蓋的單元測試可以不用修改,已經存在的行為可以保證保持不變,這就意味着「穩定」。任何代碼上的修改帶來的影響都是有未知風險的,不論看上去多麼簡單。

    原因二:增強代碼可讀性和可維護性

    另外一點,你有沒有發現其實加上新增的 AI 課程的學習,他們三節課本質上行為都是一樣的,如果我們任由這樣行為近乎一樣的代碼在我們的類裏面肆意擴展的話,很快我們的類就會變得臃腫不堪,等到我們意識到不得不重構這個類以緩解這樣的情況的時候,或許成本已經變得高得可怕了。

    原因三:降低耦合

    《資本論》中有這樣一段描述:

    在商品經濟的萌芽時期,出現了物物交換。假設你要買一個 iPhone,賣 iPhone 的老闆讓你拿一頭豬跟他換,可是你並沒有養豬,你只會編程。所以你找到一位養豬戶,說給他做一個養豬的 APP 來換他一頭豬,他說換豬可以,但是得用一條金項鏈來換…

    所以這裏就出現了一連串的對象依賴,從而造成了嚴重的耦合災難。解決這個問題的最好的辦法就是,買賣雙發都依賴於抽象——也就是貨幣——來進行交換,這樣一來耦合度就大為降低了。

    三、怎麼做

    我們現在的代碼是上層直接依賴低層實現,現在我們需要定義一個抽象的 ICourse 接口,來對這種強依賴進行解耦(就像上面《資本論》中的例子那樣):

    接下來我們可以參考一下偽代碼,先定一個課程的抽象 ICourse 接口:

    public interface ICourse {
        void study();
    }

    然後編寫分別為 JavaCourseDesignPatternCourse 編寫一個類:

    public class JavaCourse implements ICourse {
    
        @Override
        public void study() {
            System.out.println("「我沒有三顆心臟」同學正在學習「Java」課程");
        }
    }
    
    public class DesignPatternCourse implements ICourse {
    
        @Override
        public void study() {
            System.out.println("「我沒有三顆心臟」同學正在學習「設計模式」課程");
        }
    }

    然後把 Wmyskxz 類改造成如下的樣子:

    public class Wmyskxz {
    
        public void study(ICourse course) {
            course.study();
        }
    }

    再來是我們的調用:

    public static void main(String[] args) {
        Wmyskxz wmyskxz = new Wmyskxz();
        wmyskxz.study(new JavaCourse());
        wmyskxz.study(new DesignPatternCourse());
    }

    這時候我們再來看代碼,無論「我沒有三顆心臟」的興趣怎麼暴漲,對於新的課程,都只需要新建一個類,通過參數傳遞的方式告訴它,而不需要修改底層的代碼。實際上這有點像大家熟悉的依賴注入的方式了。

    總之,切記:以抽象為基準比以細節為基準搭建起來的架構要穩定得多,因此在拿到需求后,要面相接口編程,先頂層設計再細節地設計代碼結構。

    參考資料

    1. – 那些年搞不懂的高深術語——依賴倒置•控制反轉•依賴注入•面向接口編程
    2. 《Spring 5 核心原理 與 30 個類手寫實戰》 – 譚勇德 著

    按照慣例黏一個尾巴:

    歡迎轉載,轉載請註明出處!
    獨立域名博客:wmyskxz.com
    簡書ID:
    github:
    歡迎關注公眾微信號:wmyskxz
    分享自己的學習 & 學習資料 & 生活
    想要交流的朋友也可以加qq群:3382693

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

    ※專營大陸快遞台灣服務

    台灣快遞大陸的貨運公司有哪些呢?

  • HNU_團隊項目_數據庫框架Mybatis_環境配置和樣例,IDEA項目搭建四——使用Mybatis實現Dao層

    HNU_團隊項目_數據庫框架Mybatis_環境配置和樣例,IDEA項目搭建四——使用Mybatis實現Dao層

    前言

    數據庫從最傳統的JDBC連接方式和數據庫池化技術Hibernate的使用,再到Mybatis的快捷輕量級操作,技術迭代的速度飛快。

    在了解了基礎的理論和方法后,學習前沿編程框架、工具,我認為是一種必然的趨勢。

    再不看看外面,可能真的要落伍了~

    之後,我借團隊項目開發的機會,學習了Mybatis框架,和大家分享一下經驗,願意和大家共同進步!

     

    正文

    從零開始

    參考: 

    1. 下載Mybatis的,並且導入

      點擊連接,選擇下載Mybatis的jar包。這裡有三個文件,基礎使用的話,下載第一個mybatis-3.5.3.zip即可,如果需要查看源碼的話,建議將Source code也下載下來,Windows使用zip,Ubuntu使用tar.gz;

      Eclipse導入只要build puth即可,IDEA的話,在File->Project Structure里導入jar;(建議在項目內建一個lib文件夾,便於遷移)

      只是數據庫部分的話,以下兩個包便足夠了。

     

     

    2. 新建Java Web Application,編寫數據庫配置文件

    文件名可以自定義,我設置的是Mybatis.xml。但是要放到src的根目錄下,這一點要注意。

    注意

    url的配置信息中 ” & ” 符號無法解析,需要變為 “ &amp; “;

    映射文件路徑以 “ / ”作為文件夾,從src作為根目錄進行訪問;

     1 <?xml version="1.0" encoding="UTF-8" ?>
     2 <!DOCTYPE configuration
     3         PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
     4         "http://mybatis.org/dtd/mybatis-3-config.dtd">
     5 <!--上面一段是非常重要的,有了這一段,xml的配置就擁有了自動補全,這也是考量Mybatis包是否導入成功的一個方面;-->
     6 <configuration>
     7 <!--    可以兼容多種數據庫,配置多個environment,default為當前配置-->
     8     <environments default="mysql">
     9         <environment id="mysql">
    10             <transactionManager type="JDBC"/>
    11 <!--            使用數據庫池化技術,詳細可以看官方文檔-->
    12             <dataSource type="POOLED">
    13                 <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
    14                 <property name="url" value="jdbc:mysql://localhost:3306/databaseName?useUnicode=true&amp;serverTimezone=GMT%2B8&amp;characterEncoding=utf-8"/>
    15                 <property name="username" value=username"/>
    16                 <property name="password" value="password"/>
    17             </dataSource>
    18         </environment>
    19     </environments>
    20 <!--    映射文件路徑,需要告訴數據庫,要執行哪些操作,以及數據庫實體類和字段的對應關係,接下來進行解釋-->
    21     <mappers>
    22         <mapper resource="main/mapper/UserMapper.xml"/>
    23     </mappers>
    24 </configuration>

     

    3. 編寫執行SQL語句的SqlSessionFactory

      可以編寫工具類或者工廠類,避免代碼重複。我這裏設置的是DBTools.java

     1 package main.dao;
     2 
     3 import org.apache.ibatis.io.Resources;
     4 import org.apache.ibatis.session.SqlSession;
     5 import org.apache.ibatis.session.SqlSessionFactory;
     6 import org.apache.ibatis.session.SqlSessionFactoryBuilder;
     7 
     8 import javax.annotation.Resource;
     9 import java.io.IOException;
    10 import java.io.InputStream;
    11 
    12 /*
    13 Comet_Fei
    14 2019/11/17
    15 */
    16 public class DBTools
    17 {
    18     private static SqlSessionFactory sqlSessionFactory;
    19 
    20     static
    21     {
    22         try
    23         {
    24             //輸入流讀取文件 Resources Mybatis的自帶加載類
    25             InputStream config = Resources.getResourceAsStream("mybatis.xml");
    26             //工廠設計模式
    27             sqlSessionFactory = new SqlSessionFactoryBuilder().build(config);
    28         }
    29         catch (IOException e)
    30         {
    31             e.printStackTrace();
    32         }
    33     }
    34 
    35     //創建可以執行映射文件sql的sqlsession
    36     public static SqlSession getSqlSession()
    37     {
    38         return sqlSessionFactory.openSession();
    39     }
    40 }

     

    4. 編寫表對應的實體類User.java

     此處可用IDEA的pojo快捷操作,生成對應實體;

    package main.pojo;
    
    
    public class User {
    
        private String userPhoneNumber;private String userName;private String userPassword;public User(String userPhoneNumber, String userName, String userPassword) {
            this.userPhoneNumber = userPhoneNumber;
            this.userName = userName;
            this.userPassword = userPassword;
        }
    
        public String getUserPhoneNumber() {
            return userPhoneNumber;
        }
    
        public void setUserPhoneNumber(String userPhoneNumber) {
            this.userPhoneNumber = userPhoneNumber;
        }public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName) {
            this.userName = userName;
        }public String getUserPassword() {
            return userPassword;
        }
    
        public void setUserPassword(String userPassword) {
            this.userPassword = userPassword;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "userPhoneNumber='" + userPhoneNumber + '\'' +
                    ", userName='" + userName + '\'' +
                    ", userPassword='" + userPassword + '\'' +
                    '}';
        }
    }

     

     

    5. 編寫實體管理類映射接口UserMapper.java

      UserMapper接口和Mybatis的映射xml文件相對應,便於操作和使用

     

     1 package main.mapper;
     2 
     3 import main.pojo.User;
     4 
     5 import java.util.List;
     6 
     7 /*
     8 Comet_Fei
     9 2019/11/17
    10 */
    11 public interface UserMapper
    12 {
    13     int addUser(User user);
    14     User getUser(String userPhoneNumber);
    15     int deleteUser(String userPhoneNumber);
    16     int setPassword(User user);
    17     List<User> listUser();
    18 
    19 }

     

    5. 編寫映射文件UserMapper.xml

      這類文件非常重要,會將數據庫表和Java代碼的pojo(bean)進行映射,極大的簡化了操作步驟,減小代碼量。

    參數說明:

    • resultMap是數據庫表和實體的Map映射設置,在使用時,只要加入 resultMap=”userResultMap” 參數即可,如select語句;
    • parameterType是傳入的參數類型,如果傳入參數較多的話,可以使用實體類傳參,但是要寫明實體類全路徑;(還沒有學習複雜語句,所以這裏只是簡單介紹)
    • resultType是返回值類型,但是不能和resultMap同時存在;
    • 傳參方式如代碼所示,用  ${ } 標識參數,和接口一致;
    • namespace是全路徑,訪問到當前對應Map的接口文件;
    • 注意傳參的數據類型,如果是字符型或者String,需要用單引號包含,如: ‘${userName}’ 
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="main.mapper.UserMapper">
        <resultMap id="userResultMap" type="main.pojo.User">
            <result property="userPhoneNumber" column="user_phone_number"/>
            <result property="headPictureId" column="head_pictureId"/>
            <result property="rolePowerId" column="role_power_id"/>
            <result property="userName" column="user_name"/>
            <result property="userPersonalAutograph" column="user_personal_autograph"/>
            <result property="userPassword" column="user_password"/>
            <result property="userSex" column="user_sex"/>
            <result property="userEmail" column="user_email"/>
            <result property="userAddress" column="user_address"/>
            <result property="userUploadNumbers" column="user_upload_numbers"/>
        </resultMap>
    
        <!--    添加用戶-->
        <insert id="addUser" parameterType="main.pojo.User">
            INSERT INTO user(user_phone_number, user_name, user_password)
            VALUES ('${userPhoneNumber}', '${userName}', '${userPassword}');
        </insert>
        <!--    刪除用戶-->
        <delete id="deleteUser" parameterType="String">
            DELETE FROM user WHERE user_phone_number = '${userPhoneNumber}';
        </delete>
    
        <!--    獲得賬號為user_phone_number的用戶-->
        <select id="getUser" resultMap="userResultMap">
            SELECT * FROM user WHERE user_phone_number = '${userPhoneNumber}';
        </select>
    
        <!--    獲得用戶列表-->
        <select id="listUser" resultType="main.pojo.User">
            SELECT * FROM user;
        </select>
    
        <!--    找回密碼-->
        <update id="setPassword" parameterType="main.pojo.User">
            UPDATE user SET user_password = ${userPassword} WHERE user_phone_number = '${userPhoneNumber}';
        </update>
    
    </mapper>

     

     

    6. 最後是操作層(DAO)UserDao.java

      因為操作層有特定的業務,這裏只是一個示意而已。java裏面的main函數可以進行局部測試,便於和前端、控制器(servlet)等對接。

     1 package main.dao;
     2 
     3 import main.mapper.UserMapper;
     4 import main.pojo.User;
     5 import org.apache.ibatis.session.SqlSession;
     6 
     7 import java.io.IOException;
     8 
     9 /*
    10 Comet_Fei
    11 2019/11/17
    12 */
    13 public class UserDao {
    14 //    定義dao內的sqlSession和映射接口mapper
    15     private SqlSession sqlSession;
    16     private UserMapper mapper;
    17 
    18     public UserDao()
    19     {
    20 //        通過工廠,初始化session;通過特有getMapper設置映射接口
    21         sqlSession = DBTools.getSqlSession();
    22         mapper = sqlSession.getMapper(UserMapper.class);
    23     }
    24 
    25     //添加用戶 成功1 失敗0
    26     public int addUser(String userPhoneNumber, String userPassword) throws IOException
    27     {
    28         //用戶的 手機號,姓名(默認手機號),密碼
    29         User user = new User(userPhoneNumber,userPhoneNumber, userPassword);
    30         int result = mapper.addUser(user);
    31         sqlSession.commit();
    32         System.out.println(result);
    33         return result;
    34     }
    35 
    36     //用戶登錄 短信 成功2;失敗0
    37     public int right(String userPhoneNumber)
    38     {
    39         int result;
    40         User user = mapper.getUser(userPhoneNumber);
    41         if(user != null)
    42         {
    43             result = 2;//登錄成功
    44         }
    45         else
    46         {
    47             result = 0;//登錄失敗
    48         }
    49         sqlSession.commit();
    50         return result;
    51     }
    52 
    53     //用戶登錄 手機 密碼 成功2;密碼錯誤1;手機號錯誤0
    54     public int right(String userPhoneNumber, String userPassword)
    55     {
    56         int result;
    57         User user = mapper.getUser(userPhoneNumber);
    58         if((user != null)&&(userPassword.equals(user.getUserPassword())))
    59         {
    60             result = 2;//登錄成功
    61         }
    62         else if(user != null)
    63         {
    64             result = 1;//密碼錯誤
    65         }
    66         else
    67         {
    68             result = 0;//此手機號未註冊
    69         }
    70         sqlSession.commit();
    71         return result;
    72     }
    73 
    74     //刪除用戶 成功是1,不成功是0
    75     public int deleteUser(String userPhoneNumber)
    76     {
    77         int result = mapper.deleteUser(userPhoneNumber);
    78         sqlSession.commit();
    79         return result;
    80     }
    81 
    82     //重設密碼
    83     public int setPassword(String userPhoneNumber, String userPassword)
    84     {
    85         User user = new User(userPhoneNumber, userPhoneNumber, userPassword);
    86         return mapper.setPassword(user);
    87     }
    88     public static void main(String[] args) throws IOException {
    89         UserDao dao = new UserDao();
    90 //        System.out.println(dao.deleteUser("111")); //刪除用戶
    91 //        dao.addUser("111","111");// 添加用戶
    92 //        System.out.println(dao.right("11"));//短信登錄;
    93 //        System.out.println(dao.right("111","111"));//短信登錄;
    94         System.out.println(dao.setPassword("111","111"));
    95     }
    96 }

     

    以上為全部內容,之後還會寫一些關於IDEA操作上的快捷鍵,如,IDEA如何連接數據庫、快速生成pojo實體類等的博客。

    如果運行異常,歡迎留言交流,共同進步!

    這裏特別感謝博客: 

     

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    ※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

    ※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

    台灣海運大陸貨務運送流程

    兩岸物流進出口一站式服務

  • 程序員需要了解的硬核知識之操作系統和應用

    程序員需要了解的硬核知識之操作系統和應用

    利用計算機運行程序大部分都是為了提高處理效率。例如,Microsoft Word 這樣的文字處理軟件,是用來提高文本文件處理效率的程序,Microsoft Excel 等表格計算軟件,是用來提高賬本處理效率的程序。這種為了提高特定處理效率的程序統稱為 應用

    程序員的工作就是編寫各種各樣的應用來提高工作效率,程序員一般不編寫操作系統,但是程序員編寫的應用離不開操作系統,此篇文章我們就針對 Windows 操作系統來說明一下操作系統和應用之間的關係。

    操作系統功能的歷史

    操作系統其實也是一種軟件,任何新事物的出現肯定都有它的歷史背景,那麼操作系統也不是憑空出現的,肯定有它的歷史背景。

    在計算機尚不存在操作系統的年代,完全沒有任何程序,人們通過各種按鈕來控制計算機,這一過程非常麻煩。於是,有人開發出了僅具有加載和運行功能的監控程序,這就是操作系統的原型。通過事先啟動監控程序,程序員可以根據需要將各種程序加載到內存中運行。雖然仍舊比較麻煩,但比起在沒有任何程序的狀態下進行開發,工作量得到了很大的緩解。

    隨着時代的發展,人們在利用監控程序編寫程序的過程中發現很多程序都有公共的部分。例如,通過鍵盤進行文字輸入,显示器進行數據展示等,如果每編寫一個新的應用程序都需要相同的處理的話,那真是太浪費時間了。因此,基本的輸入輸出部分的程序就被追加到了監控程序中。初期的操作系統就是這樣誕生了。

    類似的想法可以共用,人們又發現有更多的應用程序可以追加到監控程序中,比如硬件控製程序編程語言處理器(彙編、編譯、解析)以及各種應用程序等,結果就形成了和現在差異不大的操作系統,也就是說,其實操作系統是多個程序的集合體。

    我在 這篇文章中提到了彙編語言,這裏簡單再提一下。

    彙編語言是一種低級語言,也被稱為符號語言。彙編語言是第二代計算機語言,在彙編語言中,用助記符代替機器指令的操作碼,用地址符號或標號代替指令或操作數的地址。用一些容易理解和記憶的字母,單詞來代替一個特定的指令,比如:用ADD代表数字邏輯上的加減,MOV代表數據傳遞等等,通過這種方法,人們很容易去閱讀已經完成的程序或者理解程序正在執行的功能,對現有程序的bug修復以及運營維護都變得更加簡單方便

    可以說共用思想真是人類前進的一大步,對於解放生產力而言簡直是太重要了

    要把操作系統放在第一位

    對於程序員來說,程序員創造的不是硬件,而是各種應用程序,但是如果程序員只做應用不懂硬件層面的知識的話,是無法成為硬核程序員的。現在培訓機構培養出了一批怎麼用的人才,卻沒有培訓出為什麼這麼做的人才,畢竟為什麼不是培訓機構教的,而是學校教的,我很相信耗子叔說的話:學習沒有速成這回事。言歸正題。

    在操作系統誕生之後,程序員不需要在硬件層面考慮問題,所以程序員的數量就增加了。哪怕自稱對硬件一竅不通的人也可能製作出一個有模有樣的程序。不過,要想成為一個全面的程序員,有一點需要清楚的就是,掌握硬件的基本知識,並藉助操作系統進行抽象化,可以大大提高編程效率。

    下面就看一下操作系統是如何給開發人員帶來便利的,在 Windows 操作系統下,用 C 語言製作一個具有表示當前時間功能的應用。time() 是用來取得當前日期和時間的函數,printf() 是把結果打印到显示器上的函數,如下:

    #include <stdio.h>
    #include <time.h>
    
    void main(){
      // 保存當前日期和時間信息
      time_t tm;
      
      // 取得當前的日期和時間
      time(&tm);
      
      // 在显示器上显示日期和時間
      printf("%s\n", ctime(&tm));
    }

    讀者可以自行運行程序查看結果,我們主要關注硬件在這段代碼中做了什麼事情

    • 通過 time_t tm,為 time_t 類型的變量申請分配內存空間;
    • 通過 time(&tm) ,將當前的日期和時間數據保存到變量的內存空間中
    • 通過 printf(“%s\n”,ctime(&tm)), 把變量內存空間的內容輸出到显示器上。

    應用的可執行文件指的是,計算機的 CPU 可以直接解釋並運行的本地代碼,不過這些代碼是無法直接控制硬件的,事實上,這些代碼是通過操作系統來間接控制硬件的。變量中涉及到的內存分配情況,以及 time() 和 printf() 這些函數的運行結果,都不是面向硬件而是面向操作系統的。操作系統收到應用發出的指令后,首先會對該指令進行解釋,然後會對 時鐘IC 和显示器用的 I/O 進行控制。

    計算機中都安裝有保存日期和時間的實時時鐘(Real-time clock),上面提到的時鐘IC 就是值該實時時鐘。

    系統調用和編程語言的移植性

    操作系統控制硬件的功能,都是通過一些小的函數集合體的形式來提供的。這些函數以及調用函數的行為稱為系統調用,也就是通過應用進而調用操作系統的意思。在前面的程序中用到了 time() 以及 printf() 函數,這些函數內部也封裝了系統調用。

    C 語言等高級編程語言並不依存於特定的操作系統,這是因為人們希望不管是Windows 操作系統還是 Linux 操作系統都能夠使用相同的源代碼。因此,高級編程語言的機制就是,使用獨自的函數名,然後在編譯的時候將其轉換為系統調用的方式(也有可能是多個系統調用的組合)。也就是說,高級語言編寫的應用在編譯后,就轉換成了利用系統調用的本地代碼

    不過,在高級語言中也存在直接調用系統調用的編程語言,不過,利用這種方式做成應用,移植性並不友好。

    移植性:移植性指的是同樣的程序在不同操作系統下運行時所花費的時間,時間越少證明移植性越好。

    操作系統和高級編程語言使硬件抽象化

    通過使用操作系統提供的系統調用,程序員不必直接編寫控制硬件的程序,而且,通過使用高級編程語言,有時也無需考慮系統調用的存在,系統調用往往是自動觸發的,操作系統和高級編程語言能夠使硬件抽象化,這很了不起。

    下面讓我們看一個硬件抽象化的具體實例

    #include <stdio.h>
    
    void main(){
      
      // 打開文件
      FILE *fp = fopen("MyFile.txt","w");
      
      // 寫入文件
      fputs("你好", fp);
      
      // 關閉文件
      fclose(fp);
    }

    上述代碼使用 C 編寫的程序,fputs() 是用來往文件中寫入字符串的函數,fclose() 是用來關閉文件的函數。

    上述應用在編譯運行后,會向文件中寫入 “你好” 字符串。文件是操作系統對磁盤空間的抽象化,就如同我們在 這篇文章提到的一樣,磁盤就如同樹的年輪,磁盤的讀寫是以扇區為單位的,通過磁道來尋址,如果直接對硬件讀寫的話,那麼就會變為通過向磁盤用的 I/O 指定扇區位置來對數據進行讀寫了。

    但是,在上面代碼中,扇區壓根就沒有出現過傳遞給 fopen() 函數的參數,是文件名 MyFile.txt 和指定文件寫入的 w。傳遞給 fputs() 的參數,是往文件中寫入的字符串”你好” 和 fp,傳遞給 fclose() 的參數,也僅僅是 fp,也就是說磁盤通過打開文件這個操作,把磁盤抽象化了,打開文件這個操作就可以說是操作硬件的指令。

    下面讓我們來看一下代碼清單中 fp 的功能,變量 fp 中被賦予的是 fopen() 函數的返回值,該值被稱為文件指針。應用打開文件后,操作系統就會自動申請分配用來管理文件讀寫的內存空間。內存地址可以通過 fopen() 函數的返回值獲得。用 fopen() 打開文件后,接下來就是通過制定的文件指針進行操作,正因為如此,fputs() 和 fclose() 以及 fclose() 參數中都制定了文件指針。

    由此我們可以得出一個結論,應用程序是通過系統調用,磁盤抽象來實現對硬盤的控制的。

    Windows 操作系統的特徵

    Windows 操作系統是世界上用戶數量最龐大的群體,作為 Windows 操作系統的資深用戶,你都知道 Windows 操作系統有哪些特徵嗎?下面列舉了一些 Windows 操作系統的特性

    • Windows 操作系統有兩個版本:32位和64位
    • 通過 API 函數集成來提供系統調用
    • 提供了採用圖形用戶界面的用戶界面
    • 通過 WYSIWYG 實現打印輸出,WYSIWYG 其實就是 What You See Is What You Get ,值得是显示器上显示的圖形和文本都是可以原樣輸出到打印機打印的。
    • 提供多任務功能,即能夠同時開啟多個任務
    • 提供網絡功能和數據庫功能
    • 通過即插即用實現設備驅動的自設定

    這些是對程序員來講比較有意義的一些特徵,下面針對這些特徵來進行分別的介紹

    32位操作系統

    這裏表示的32位操作系統表示的是處理效率最高的數據大小。Windows 處理數據的基本單位是 32 位。這與最一開始在 MS-DOS 等16位操作系統不同,因為在16位操作系統中處理32位數據需要兩次,而32位操作系統只需要一次就能夠處理32位的數據,所以一般在 windows 上的應用,它們的最高能夠處理的數據都是 32 位的。

    比如,用 C 語言來處理整數數據時,有8位的 char 類型,16位的short類型,以及32位的long類型三個選項,使用位數較大的 long 類型進行處理的話,增加的只是內存以及磁盤的開銷,對性能影響不大。

    現在市面上大部分都是64位操作系統了,64位操作系統也是如此。

    通過 API 函數集來提供系統調用

    Windows 是通過名為 API 的函數集來提供系統調用的。API是聯繫應用程序和操作系統之間的接口,全稱叫做 Application Programming Interface,應用程序接口。

    當前主流的32位版 Windows API 也稱為 Win32 API,之所以這樣命名,是需要和不同的操作系統進行區分,比如最一開始的 16 位版的 Win16 API,和後來流行的 Win64 API

    API 通過多個 DLL 文件來提供,各個 API 的實體都是用 C 語言編寫的函數。所以,在 C 語言環境下,使用 API 更加容易,比如 API 所用到的 MessageBox() 函數,就被保存在了 Windows 提供的 user32.dll 這個 DLL 文件中。

    提供採用了 GUI 的用戶界面

    GUI(Graphical User Interface) 指得就是圖形用戶界面,通過點擊显示器中的窗口以及圖標等可視化的用戶界面,舉個例子:Linux 操作系統就有兩個版本,一種是簡潔版,直接通過命令行控制硬件,還有一種是可視化版,通過光標點擊圖形界面來控制硬件。

    通過 WYSIWYG 實現打印輸出

    WYSIWYG 指的是显示器上輸出的內容可以直接通過打印機打印輸出。在 Windows 中,显示器和打印機被認作同等的圖形輸出設備處理的,該功能也為 WYSIWYG 提供了條件。

    藉助 WYSIWYG 功能,程序員可以輕鬆不少。最初,為了是現在显示器中显示和在打印機中打印,就必須分別編寫各自的程序,而在 Windows 中,可以藉助 WYSIWYG 基本上在一個程序中就可以做到显示和打印這兩個功能了。

    提供多任務功能

    多任務指的就是同時能夠運行多個應用程序的功能,Windows 是通過時鐘分割技術來實現多任務功能的。時鐘分割指的是短時間間隔內,多個程序切換運行的方式。在用戶看來,就好像是多個程序在同時運行,其底層是 CPU 時間切片,這也是多線程多任務的核心。

    提供網絡功能和數據庫功能

    Windows 中,網絡功能是作為標準功能提供的。數據庫(數據庫服務器)功能有時也會在後面追加。網絡功能和數據庫功能雖然並不是操作系統不可或缺的,但因為它們和操作系統很接近,所以被統稱為中間件而不是應用。意思是處於操作系統和應用的中間層,操作系統和中間件組合在一起,稱為系統軟件。應用不僅可以利用操作系統,也可以利用中間件的功能。

    相對於操作系統一旦安裝就不能輕易更換,中間件可以根據需要進行更換,不過,對於大部分應用來說,更換中間件的話,會造成應用也隨之更換,從這個角度來說,更換中間件也不是那麼容易。

    通過即插即用實現設備驅動的自動設定

    即插即用(Plug-and-Play)指的是新的設備連接(plug) 后就可以直接使用的機制,新設備連接計算機后,計算機就會自動安裝和設定用來控制該設備的驅動程序

    設備驅動是操作系統的一部分,提供了同硬件進行基本的輸入輸出的功能。鍵盤、鼠標、显示器、磁盤裝置等,這些計算機中必備的硬件的設備驅動,一般都是隨操作系統一起安裝的。

    有時 DLL 文件也會同設備驅動文件一起安裝。這些 DLL 文件中存儲着用來利用該新追加的硬件API,通過 API ,可以製作出運行該硬件的心應用。

    文章參考:

    《程序是怎樣跑起來的》第九章

    關注公眾號後台回復 191106 即可獲得《程序是怎樣跑起來的》电子書

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
    【其他文章推薦】

    台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包”嚨底家”

    網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    小三通海運與一般國際貿易有何不同?

    小三通快遞通關作業有哪些?

  • 【原創】使用批處理腳本自動生成並上傳NuGet包

    【原創】使用批處理腳本自動生成並上傳NuGet包

      Hello 大家好,我是TANZAME,我們又見面了。

      NuGet 是什麼這裏就不再重複啰嗦,園子里一搜一大把。今天要跟大家分享的是,在日常開發過程中如何統一管理我們的包,如何通過批處理腳本生成包並自動上傳到 NuGet。在實際項目開發過程中我們要上傳自己的包,一般的步驟都是:nuget spec => nuget pack => nuget push,一個包都要至少重複三個動作,如果有 N 個包那就要重複 N*3 次,想想都不能忍,所以便有了今天的分享主題。

    • 生成目錄

      既然是統一管理,生成的包自然是放在同一個文件夾,而不是分散在各個 .proj 目錄里。這裏我們在解決方案所在目錄新建一個目錄,這樣做的目的是方便 bat 腳本找到解決方案下面的子項目。比如我這裏新建的是 .nuget 這個目錄,需要注意的是如果目錄名稱有特殊字符的話不能直接右鍵新建,需要用命令提示符,直接在解決方案所在目錄使用快捷鍵 SHIFT + 右鍵 就能直接打開命令提示符,這樣可以省去一大堆 cd 的操作。

     

    • 下載 NuGet

      到 NuGet 官網下載命令行接口(CLI)。nuget.exe提供了完整的 nuget 功能, 可用於安裝、創建、發布和管理包, 而無需對項目文件進行任何更改。

    1. 請訪問 ,並選擇 NuGet 3.3 或更高版本(2.8.6 與 Mono 不兼容)。 始終建議使用最新版。若要將包發布到 nuget.org,版本至少必須是 4.1.0。
    2. 每次下載都直接下載 nuget.exe 文件。 讓瀏覽器將文件保存到選定文件夾。 此文件不 是安裝程序;如果直接在瀏覽器中運行,就不會看到任何內容。
    3. 將文件夾添加到 nuget.exe 中放置 PATH 環境變量的位置,這樣就可以從任意位置使用 CLI 工具。這裏我們把它放在上一步新建的 .nuget 文件夾下面,並設置 PATH 環境變量。

     

    • 生成清單

      是包含包元數據的 XML 清單, 此清單同時用於生成包以及為使用者提供信息。這個清單文件我們只需要生成一次,以後都不需要再重新生成。 .net Core 和使用sdk 特性.NET Standard 項目不需要 .nuspec 文件,如果是.net Core 和使用sdk 特性.NET Standard 項目則忽略此步驟。轉到項目所在目錄,SHIFT + 右鍵 調出命令提示符,輸入 nuget spec 命令即可生成我們所需要的包元數據清單。

     

      將這個清單文件剪切到第一步新建的 .nuget 文件夾,剪切過去後項目下面就不會憑空多出一個文件,看着清爽多了。然後做一下調整填入我們自己項目的相關信息, 比如像下面這樣:

    <?xml version="1.0" encoding="utf-8"?>
    <package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
      <metadata>
        <id>TZM.XFramework</id>
        <version>$version$</version>
        <title>$title$</title>
        <authors>$author$</authors>
        <owners>$author$</owners>
        <license type="expression">Apache-2.0</license>
        <projectUrl>https://github.com/TANZAME/TZM.XFramework</projectUrl>
        <iconUrl>http://go.microsoft.com/fwlink/?LinkID=386613</iconUrl>
        <description>TZM.XFramework is a lightweight and high performance object-relational mapper for .NET use the original Entity Framework api.</description>
        <copyright>Copyright 2019</copyright>
        <tags>.NET SqlServer MSSQL Database Data O/RM ADO.NET</tags>
        <repository type="git" url="https://github.com/TANZAME/TZM.XFramework" />
        <dependencies />
        <frameworkAssemblies>
          <frameworkAssembly assemblyName="System.Data"/>
          <frameworkAssembly assemblyName="System.ComponentModel.DataAnnotations"/>
          <frameworkAssembly assemblyName="System.Net.Http"/>
        </frameworkAssemblies>
      </metadata>
    </package>

     

    • 編寫腳本

      在第一步新建的文件夾里新建一個 bat 文件,重命名為 package.bat,接下來編寫我們的自動腳本。完整 bat 腳本,直接上代碼片段。

    1. 這裏我設置 nuget pack 包屬性為Release,並且不自動生成,所以需要先在 Release 模式下編譯完成再運行腳本。加上 -Build 參數的話輸出的信息太多看得賊難受,這裏把它去掉,我們自己手動編譯。
    2. 填充api_key。去 nuget 官網 登錄自己的帳號並創建一個 key,複製粘貼到 api_key 變量。
    3. 注意 .net framework 項目(fx)和 .net core 項目使用的命令不一樣
    4. 至此我們所有的準備步驟都已完成,雙擊 package.bat 運行腳本,解放雙手。
    @echo off
    set api_key=xxxxxxlef2j57rw4q26qcrvycvznyvcurgfxbzxxxxxxxx
    set source_api_uri=https://api.nuget.org/v3/index.json
    set startup_dir=%~dp0
    cd ..\
    set startup_dir=%cd%
    cd .nuget
    
    :: 打包 TZM.XFramework -Build
    echo pack TZM.XFramework
    copy TZM.XFramework.nuspec %startup_dir%\net45\TZM.XFramework
    nuget pack %startup_dir%\net45\TZM.XFramework\TZM.XFramework.csproj -Properties Configuration=Release
    del %startup_dir%\net45\TZM.XFramework\TZM.XFramework.nuspec
    echo=
    
    :: 打包 TZM.XFrameworkCore
    echo pack TZM.XFrameworkCore
    dotnet pack --no-build --configuration Release --output %startup_dir%\.nuget\ %startup_dir%\netcore\TZM.XFrameworkCore\TZM.XFrameworkCore.csproj
    
    :: 批量推送包
    for /R %cd% %%f in (*.nupkg) do ( 
    echo=
    dotnet nuget push %%f -k %api_key% -s %source_api_uri%
    )
    
    echo=
    pause

      最後貼一張最終運行的效果圖:

    • 總結

       通過這個腳本,我們可以在一個文件夾里統一管理我們的包,做到一鍵生成、上傳同時保持項目文件的清爽,嗯簡直不要太方便 ~..~

       參考資料:

       技術交流群:816425449

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業 缺乏曝光? 下一步”網站設計“幫您第一時間規劃公司的門面形象

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

    ※試算大陸海運運費!

  • Spring Cloud gateway 七 Sentinel 註解方式使用

    Spring Cloud gateway 七 Sentinel 註解方式使用

    Sentinel 註解支持

    @SentinelResource 用於定義資源,並提供可選的異常處理和 fallback 配置項。 @SentinelResource 註解包含以下屬性:

    • value:資源名稱,必需項(不能為空)
    • entryType:entry 類型,可選項(默認為 EntryType.OUT)
    • blockHandler / blockHandlerClass: blockHandler 對應處理 BlockException 的函數名稱,可選項。blockHandler 函數訪問範圍需要是 public,返回類型需要與原方法相匹配,參數類型需要和原方法相匹配並且最後加一個額外的參數,類型為 BlockException。blockHandler 函數默認需要和原方法在同一個類中。若希望使用其他類的函數,則可以指定 blockHandlerClass 為對應的類的 Class 對象,注意對應的函數必需為 static 函數,否則無法解析。
    • fallback:fallback 函數名稱,可選項,用於在拋出異常的時候提供 fallback 處理邏輯。fallback 函數可以針對所有類型的異常(除了 – exceptionsToIgnore 裏面排除掉的異常類型)進行處理。fallback 函數簽名和位置要求:
      • 返回值類型必須與原函數返回值類型一致;
      • 方法參數列表需要和原函數一致,或者可以額外多一個 Throwable 類型的參數用於接收對應的異常。
      • fallback 函數默認需要和原方法在同一個類中。若希望使用其他類的函數,則可以指定 fallbackClass 為對應的類的 Class 對象,注意對應的函數必需為 static 函數,否則無法解析。
    • defaultFallback(since 1.6.0):默認的 fallback 函數名稱,可選項,通常用於通用的 fallback 邏輯(即可以用於很多服務或方法)。默認 fallback 函數可以針對所有類型的異常(除了 exceptionsToIgnore 裏面排除掉的異常類型)進行處理。若同時配置了 fallback 和 defaultFallback,則只有 fallback 會生效。defaultFallback 函數簽名要求:
      • 返回值類型必須與原函數返回值類型一致;
      • 方法參數列表需要為空,或者可以額外多一個 Throwable 類型的參數用於接收對應的異常。
      • defaultFallback 函數默認需要和原方法在同一個類中。若希望使用其他類的函數,則可以指定 fallbackClass 為對應的類的 Class 對象,注意對應的函數必需為 static 函數,否則無法解析。
    • exceptionsToIgnore(since 1.6.0):用於指定哪些異常被排除掉,不會計入異常統計中,也不會進入 fallback 邏輯中,而是會原樣拋出。

    注:1.6.0 之前的版本 fallback 函數只針對降級異常(DegradeException)進行處理,不能針對業務異常進行處理。

    特別地,若 blockHandler 和 fallback 都進行了配置,則被限流降級而拋出 BlockException 時只會進入 blockHandler 處理邏輯。若未配置 blockHandler、fallback 和 defaultFallback,則被限流降級時會將 BlockException 直接拋出。

    使用注意點采坑日記

    @SentinelResource 註解不單單用於controller的接口流控。同時也可以用於方法上面。如果看過實現方式代碼。可以知道他底層是基於cglib動態代理實現的。進行切面處理。注意點:

    • 不能修飾在接口上面。只能修飾在實現類的方法上
    • 不能修飾在靜態的方法上面。
    • 同一個bean方法A調用方法B,假設方法A和B都進行了註解。B方法註解失效,請參考@Transactional 失效。
      • @Transactional 加於private方法, 無效
      • @Transactional 加於未加入接口的public方法, 再通過普通接口方法調用, 無效
      • @Transactional 加於接口方法, 無論下面調用的是private或public方法, 都有效
      • @Transactional 加於接口方法后, 被本類普通接口方法直接調用, 無效
      • @Transactional 加於接口方法后, 被本類普通接口方法通過接口調用, 有效
      • @Transactional 加於接口方法后, 被它類的接口方法調用, 有效
      • @Transactional 加於接口方法后, 被它類的私有方法調用后, 有效

    blockHandler 和 blockHandlerClass 的使用

    blockHandler 是可選的。如果使用blockHandlerClass,必須搭配blockHandler使用, blockHandler指定blockHandlerClass類中對應的方法名稱。方法名稱、參數、返回值、static 必須按照上述文檔描述一樣。官方文檔沒有強調要必須要搭配使用。

    同理 fallback 和 fallbackClass也是上面講述的注意點。

    改造client 服務

            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            </dependency>

    bootstrap.yml 配置文件

    spring:
        cloud:
            sentinel:
                    filter:
                        # sentienl 默認生效,本地調試false
                        enabled: true
                    transport:
                        dashboard: localhost:8890
                        port: 8719
                    # 飢餓加載
                    eager: true
                    datasource:
                        # Sentinel基於nacos存儲獲取配置信息
                        na:
                            nacos:
                                server-addr: 47.99.209.72:8848
                                groupId: DEFAULT_GROUP
                                dataId: ${spring.application.name}-${spring.profiles.active}-sentinel
                                # 類型
        #            FLOW("flow", FlowRule.class),
        #            DEGRADE("degrade", DegradeRule.class),
        #            PARAM_FLOW("param-flow", ParamFlowRule.class),
        #            SYSTEM("system", SystemRule.class),
        #            AUTHORITY("authority", AuthorityRule.class),
        #            GW_FLOW("gw-flow", "com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule"),
        #            GW_API_GROUP("gw-api-group", "com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition");
                                rule-type: flow

    nacos 創建 cloud-discovery-client-dev-sentinel 配置文件

    [
        {
            "resource": "client:log:save",
            "limitApp": "default",
            "grade": 1,
            "count": 1,
            "strategy": 0,
            "controlBehavior": 0,
            "clusterMode": false
        },
        {
            "resource": "client:fegin:test",
            "limitApp": "default",
            "grade": 1,
            "count": 1,
            "strategy": 0,
            "controlBehavior": 0,
            "clusterMode": false
        },
         {
            "resource": "user:service:saveTx",
            "limitApp": "default",
            "grade": 1,
            "count": 1,
            "strategy": 0,
            "controlBehavior": 0,
            "clusterMode": false
        },
        {
            "resource": "user:service:save:test",
            "limitApp": "default",
            "grade": 1,
            "count": 1,
            "strategy": 0,
            "controlBehavior": 0,
            "clusterMode": false
        }
    ]

    創建 BackHandlerClass DiscoveryClientControllerBackHandler

    package com.xian.cloud.common.handler;
    
    import com.alibaba.csp.sentinel.slots.block.BlockException;
    import com.xian.cloud.entity.UserEntity;
    import lombok.extern.slf4j.Slf4j;
    
    /**
     *  對應處理 BlockException 的函數名稱 服務限流
     * @Author: xlr
     * @Date: Created in 9:08 PM 2019/11/16
     */
    @Slf4j
    public class DiscoveryClientControllerBackHandler {
    
        public static String defaultMessage(BlockException e){
            
            log.warn( "DiscoveryClientControllerBackHandler  defaultMessage BlockException : {}",e );
            return "defaultMessage 服務限流,請稍後嘗試";
        }
    
        public static String saveTx(UserEntity entity,BlockException e) {
    
            log.warn( "DiscoveryClientControllerBackHandler  saveTx BlockException : {}",e );
            return "saveTx 服務限流,請稍後嘗試";
        }
    }
    

    創建 FallBackHandlerClass

    package com.xian.cloud.common.handler;
    
    import com.xian.cloud.entity.UserEntity;
    import lombok.extern.slf4j.Slf4j;
    
    /**
     * 僅針對降級功能生效(DegradeException)
     * @Author: xlr
     * @Date: Created in 9:13 PM 2019/11/16
     */
    @Slf4j
    public class DiscoveryClientControllerFallBackHandler {
    
        public static String defaultMessage(Throwable t){
            
            log.warn( "DiscoveryClientControllerFallBackHandler defaultMessage Throwable : {}",t );
            return "defaultMessage 服務降級,請稍後嘗試";
        }
    
        public static String saveTx(UserEntity entity,Throwable t) {
    
            log.warn( "DiscoveryClientControllerFallBackHandler saveTx Throwable : {}",t );
            return "saveTx 服務降級,請稍後嘗試";
        }
    }

    對外接口DiscoveryClientController 添加接口

    
    @SentinelResource(
                value = "client:fegin:test",
                blockHandler = "defaultMessage",
                fallback = "defaultMessage",
                blockHandlerClass = DiscoveryClientControllerBackHandler.class,
                fallbackClass = DiscoveryClientControllerFallBackHandler.class
        )
        @RequestMapping(value = "fegin/test",method = RequestMethod.GET)
        public String feginTest() {
            String result = serverService.hello( "fegin" );
            return  " 返回 : " + result;
        }
            
            
     @GetMapping("/log/save")
        @SentinelResource(
                value = "client/log/save",
                blockHandler = "defaultMessage",
                fallback = "defaultMessage",
                blockHandlerClass = DiscoveryClientControllerBackHandler.class,
                fallbackClass = DiscoveryClientControllerFallBackHandler.class
        )
        public String save(){
            UserEntity entity = new UserEntity();
            entity.setUsername("tom");
            entity.setPassWord("1232131");
            entity.setEmail("222@qq.com");
            userService.saveTx(entity);
            return "success";
        }
            
        @GetMapping("user/service/save")
        public String userServiceSaveTx(){
            UserEntity entity = new UserEntity();
            String result = userService.saveTx( entity );
            return result;
        }
    

    UserServiceImpl 方法

      @Override
        @Transactional
        @SentinelResource(
                value = "user:service:saveTx",
                blockHandler = "saveTx",
                fallback = "saveTx",
                blockHandlerClass = DiscoveryClientControllerBackHandler.class,
                fallbackClass = DiscoveryClientControllerFallBackHandler.class
        )
        public String saveTx(UserEntity entity) {
    
            return "success";
        }

    以上就配置完畢。然後進行測試在頁面瘋狂刷新

    http://localhost:9011/client/user/service/save

    http://localhost:9011/client/fegin/test

    停止 server服務 再次調用 fegin、test

    服務降級和服務限流來回切換提示在前端頁面。blockHandlerClass、fallbackClass。

    如何喜歡可以關注分享本公眾號。

    版權聲明:本文為博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。轉載請附帶公眾號二維碼

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

    ※專營大陸快遞台灣服務

    台灣快遞大陸的貨運公司有哪些呢?