整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          Java開發面試過程中的問題整理

          、Java基礎

          1、Java中兩種數據類型(為后面進一步提問做鋪墊)

          (1)基本數據類型,分為boolean、byte、int、char、long、short、double、float;
          (2)引用數據類型 ,分為數組、類、接口。

          擴展:Java中引入了基本數據類型,但是為了能夠將這些基本數據類型當成對象操作,Java為每一個基本數據類型都引入了對應的包裝類型(wrapper class),int的包裝類就是Integer,從Java 5開始引入了自動裝箱/拆箱機制,使得二者可以相互轉換。

          基本數據類型: boolean,char,byte,short,int,long,float,double

          封裝類類型: Boolean,Character,Byte,Short,Integer,Long,Float,Double

          2、java中==和eqauls()的區別

          ==分兩類分析,既可以比較基本類型也可以比較引用類型,對于基本類型來說是比較的數值。對于引用類型來說比較的內存地址值;equals是屬于java.lang.Object類里面的方法,Object里的equals里默認的是雙等于==。分兩種情況討論:一種是自定義類,看自定義類有沒有重寫equals方法,通常情況下,如果重寫了equals則比較的是類中相應屬性是否相等。如果沒有重寫equals方法,則仍然使用==比較的是地址。

          總結、:

          == :
            基本類型:比較值是否相等
            引用類型:比較的就是內存地址是否相同
          equals :
            引用類型:默認情況下,比較的是地址值??梢赃M行重寫,使其比較對象的值是否相等。

          3、說說int和Integer有何區別

          (1)Integer是int的包裝類;int是基本數據類型;

          (2)Integer變量必須實例化后才能使用;int變量不需要;

          (3)Integer實際是對象的引用,指向此new的Integer對象;int是直接存儲數據值 ;

          (4)Integer的默認值是null;int的默認值是0。

          4、switch中能否使用string類型的參數作為變量(涉及到jdk版本的理解)

          在JDK1.7之前,switch只能支持byte、short、char、int、float、double或者其對應的封裝類以及Enum類型。JDK1.7開始支持String。當字符串不會頻繁改變時可以用枚舉來代替String。

          5、說明ArrayList和LinkedList的區別和優缺點,在哪些場景會使用?

          區別:

          (1)ArrayList是實現了基于動態數組的數據結構,LinkedList是基于鏈表結構;

          (2)對于隨機訪問的get和set方法,ArrayList要優于LinkedList,因為LinkedList要移動指針;

          (3)對于新增和刪除操作add和remove,LinkedList比較占優勢,因為ArrayList要移動數據;

          各自優缺點:

          (1)對ArrayList和LinkedList而言,在列表末尾增加一個元素所花的開銷都是固定的。對ArrayList而言,主要是在內部數組中增加一項,指向所添加的元素,偶爾可能會導致對數組重新進行分配;而對LinkedList而言,這個開銷是 統一的,分配一個內部Entry對象。

          (2)在ArrayList集合中添加或者刪除一個元素時,當前的列表移動元素后面所有的元素都會被移動。而LinkedList集合中添加或者刪除一個元素的開銷是固定的。

          (3)LinkedList集合不支持 高效的隨機隨機訪問(RandomAccess),因為可能產生二次項的行為。

          (4)ArrayList的空間浪費主要體現在在list列表的結尾預留一定的容量空間,而LinkedList的空間花費則體現在它的每一個元素都需要消耗相當的空間。

          應用場景:

          ArrayList使用在查詢比較多,但是插入和刪除比較少的情況,而LinkedList用在查詢比較少而插入刪除比較多的情況

          6、實現多線程的幾種方式,多線程的應用場景有哪些?

          (1)、繼承Thread類,重寫run方法

          (2)、實現Runnable接口,重寫run方法?!究梢员苊庥捎贘ava的單繼承特性而帶來的局限。適合多個線程去處理同一資源的情況】

          (3)、實現Callable接口,重寫call方法?!居蟹祷刂?,允許拋出異常】

          (4)、使用線程池【減少創建新線程的時間,重復利用線程池中線程,降低資源消耗,可有返回值】

          7、${}和#{}的區別

          使用#{}:

          (1)、傳入參數,sql在解析的時候會加上" ",當成字符串來解析,如 id = "id";

          (2)、#{}能夠很大程度上防止sql注入;

          使用${}:

          (1)、傳入數據直接顯示在生成的sql中,sql在解析的時候值為id = id。

          (2)、${}方式無法防止sql注入

          最后:能用#{}時盡量用#{},但有些場合需要使用$。

          注意MyBatis排序時使用order by 動態參數時需要注意,用$而不是#(#會自動拼接符號)

          8、描述一下JVM加載class文件的原理機制和特點。

          Java中的所有類,都需要由類加載器裝載到JVM中才能運行。JVM中類的加載是由類加載器(ClassLoader)和它的子類來實現的。在我們使用一個類之前,JVM需要先將該類的字節碼文件(.class文件)從磁盤、網絡或其他來源加載到內存中(加載Class文件到JVM),并對字節碼進行解析生成對應的Class對象,這就是類加載器的功能。我們可以利用類加載器,實現類的動態加載。在寫程序的時候,我們幾乎不需要關心類的加載,因為這些都是隱式裝載的,除非我們有特殊的用法,像是反射,就需要顯式的加載所需要的類。

          Java類的加載是動態的,它并不會一次性將所有類全部加載后再運行,而是保證程序運行的基礎類(像是基類)完全加載到jvm中,至于其他類,則在需要的時候才加載。這當然就是為了節省內存開銷。

          進一步提問:類加載的方式有幾種?區別是什么?

          類裝載方式有兩類:隱式裝載和顯示裝載,其中顯示裝載又分2種方式。

          (1)、隱式裝載,程序在運行過程中當碰到通過new 等方式生成對象時,隱式調用類裝載器加載對應的類到jvm中,比如

          Dog dog = new Dog();【第一種方式】

          (2)、顯式裝載,

          【第二種方式】使用Class.forName()通過反射加載類型,并創建對象實例,比如

          Class clazz = Class.forName(“Dog”);
          Object dog =clazz.newInstance();

          如果無法找到Dog,則拋出ClassNotFoundException。

          【第三種方式】使用某個ClassLoader實例的loadClass()方法

          Class clazz = classLoader.loadClass("Dog");

          Object dog=clazz.newInstance();

          如果無法找到Dog,則拋出ClassNotFoundException。

          區別:

          方式1和2使用的類加載器是相同的,都是當前類加載器(即:this.getClass.getClassLoader)。
          方式3由用戶指定類加載器。如果需要在當前類路徑以外尋找類,則只能采用方式3。即第3種方式加載的類與當前類分屬不同的命名空間。

          方式1是靜態加載,方式2和3是動態加載。

          進一步提問:java內置的類加載器(ClassLoader)有哪些,簡述一下類加載器工作原理

          Java的類加載器有三個:

          第一種是Bootstrap Loader(引導類加載器)。它的實現依賴于底層操作系統,由C編寫而成,沒有繼承于ClassLoader類。根類加載器從系統屬性sun.boot.class.path所指定的目錄中加載類庫。默認為jre目錄下的lib目錄下的class文件,該加載器沒有父加載器。負責加載虛擬機的核心類庫,如java.lang.*。Object類就是由根類加載器加載的。

          第二種是Extended Loader(標準擴展類加載器)。它的父加載器為根類加載器。由java編寫而成,是ClassLoader的子類。它從java.ext.dirs中加載類庫,或者從JDK安裝目錄jre\lib\ext子目錄下加載類庫。如果把用戶創建的jar文件放在該目錄下,也會自動由擴展類加載器加載。

          第三種是AppClass Loader(應用程序類路徑類加載器)。它的父加載器為擴展類加載器。由java編寫而成,是ClassLoader的子類,它從環境變量classpath或者系統屬性java.class.path所指定的目錄中加載類,是用戶自定義的類加載器的默認父加載器。

          加載類時,會以Bootstrap Loader→Extended Loader→AppClass Loader的順序來尋找類,如果找不到,就會丟出NoClassDefFoundError。

          9、在一個類中,聲明了若干個static方法和非static方法,聲明的static方法能否直接訪問聲明的非static方法?

          static方法不能直接訪問非static方法,因為static方法是屬于這個類本身的一個方法,在編譯期間就已經確定了;而非static方法是屬于這個類的對象的方法,需要在實例化之后才能訪問到。即:static方法調用時不需要創建對象,可以直接調用,非static方法是要與對象關聯在一起的,必須創建一個對象后,才可以在該對象上進行方法調用,若在static方法中訪問非static方法,非static方法不知道關聯到哪個對象上,將不能通過編譯。

          進一步提問:說一下靜態方法和非靜態方法都是在什么時候被裝載到內存中的?

          靜態方法(Static Method)與靜態成員變量一樣,屬于類本身,在類裝載的時候被裝載到內存(Memory),不自動進行銷毀,會一直存在于內存中,直到JVM關閉。
          非靜態方法(Non-Static Method)又叫實例化方法,屬于實例對象,實例化后才會分配內存,必須通過類的實例來引用。不會常駐內存,當實例對象被JVM 回收之后,也跟著消失。

          進一步提問:靜態方法怎樣訪問非靜態方法?

          靜態方法不能直接使用本類的非靜態方法
          解決方式有三種
          (1)、兩個方法都改成非靜態
          (2)、兩個方法都改成靜態
          (3)、先創建類的實例,然后靜態方法再調用這個實例的非靜態方法

          10、在Java中,對象什么時候可以被垃圾回收?

          Java垃圾回收不是實時的,垃圾回收器的作用是查找和回收(清理)無用的對象。以便讓JVM更有效地使用內存。垃圾回收器的運行時間是不確定的,由JVM決定,在運行時是間歇執行的。也可以通過System.gc()來強制回收垃圾,但是這個命令下達后JVM不一定會立即響應執行,但間隔一小段時間基本都會執行。

          11、一個漢字占幾個字節

          中文在不同編碼下占不定長的 2~4個字節。注意在utf-16中占用兩個字節,在java 運行時用UTF-16編碼在轉碼的時候會在前面加上表示字節順序的字符,這個字符稱為”零寬度非換行空格”(ZERO WIDTH NO-BREAK SPACE),用FEFF表示。FEFF占用兩個字節。

          12、項目中使用Maven進行構建,有哪些優勢?你使用過哪些maven命令?說一下maven中本地倉庫和遠程倉庫的區別和聯系。

          優點:

          (1)創建項目,自動關聯和下載依賴的jar包,統一維護jar包

          (2)升級框架版本方便

          常用 Maven 命令:

          (1)、安裝項目到本地倉庫:mvn install

          (2)、創建maven項目:mvn archetype:generate

          (3)、驗證項目是否正確:mvn validate

          (4)、maven 打包:mvn package

          (5)、只打jar包:mvn jar:jar

          (6)、生成源碼jar包:mvn source:jar

          (7)、產生應用需要的任何額外的源代碼:mvn generate-sources

          (8)、編譯源代碼: mvn compile

          (9)、編譯測試代碼:mvn test-compile

          (10)、運行測試:mvn test

          (11)、運行檢查:mvn verify

          (12)、清理maven項目:mvn clean

          Maven倉庫關系:

          maven的倉庫只有兩大類:1.本地倉庫 2.遠程倉庫,在遠程倉庫中又分成了3種:2.1 中央倉庫 2.2 私服 2.3 其它公共庫

          運行Maven的時候,Maven所需要的任何構件都是直接從本地倉庫獲取的。如果本地倉庫沒有,它會首先嘗試從遠程倉庫下載構件至本地倉庫,然后再使用本地倉庫的構件。

          13、Thread.sleep(0)有沒有實際作用

          觸發操作系統立刻重新進行一次CPU競爭,操作系統重新計算線程的優先級(包括當前線程)。競爭的結果也許是當前線程仍然獲得CPU控制權,也許會換成別的線程獲得CPU控制權。

          二、前端框架

          1、在項目中使用過哪些前端框架?

          Vue(用于構建用戶界面的 漸進式框架,特點:輕量級、雙向數據綁定、組件化)、

          React(構建用戶界面的 JAVASCRIPT 庫,特點:只負責顯示、聲明式框架、數據驅動DOM)、

          Angular(前端JS框架,核心:MVVM、模塊化、自動化雙向數據綁定、語義化標簽、依賴注入等)、

          QucikUI(企業級web前端開發解決方案)、

          Layui(前端 UI 框架)、

          Avalon(前端MVVM框架)、

          還有Dojo、Ember、Aurelia等

          2、js中如何查看某變量的數據類型?可以查看的數據類型有哪些?

          使用typeof查看變量的數據類型。

          javascript共有6種數據類型:

          基本類型5種:number、string、boolean、null、undefined。引用類型1種:object

          typeof檢測返回6種:undefined、boolean、string、number、object、function

          3、前端進行文件下載時,能不能用ajax向后端發起請求?

          Ajax不能實現文件下載功能

          原因:ajax的返回值是json,text,html,xml類型,或者可以說ajax的接收類型只能是String字符串,不是流類型,所以無法實現文件下載。但用aja仍然可以獲得文件的內容(可以讀取到返回的response,但只是讀取而已),該文件將被保留在內存中,無法將文件保存到磁盤,這是因為javascript無法和磁盤進行交互,否則這會是一個嚴重的安全問題,js無法調用瀏覽器的下載處理機制和程序,會被瀏覽器阻塞。

          4、如何解決前后端交互過程中特殊字符的傳參(比如中文、特殊符號等)?

          (這個問題屬于送分題,有一定項目經驗的人,都會注意到在前后端進行交互時,需要進行decoder編碼-Encoder解碼的過程,防止亂碼)

          進一步提問:前后端數組傳參如何處理?

          前端:數據使用JSON.stringify(str)處理

          后端:數據轉換:List<Object> objectList = JSONObject.parseArray(str, Object.class)

          5、FreeMarker、jsp、html 三者的區別

          先說說freemarker和jsp的不同,運行機制就不大一樣,jsp是編譯成繼承自servlet的class文件,運行jsp就是運行一個servlet(Java文件編譯后會產生一個class文件,最終執行的就是這個class文件,JSP也一樣,它也要編譯成class文件。JSP不止要編譯,它還得要轉譯,首先把JSP轉譯成一個Servlet文件,然后再編譯成class文件。當用戶訪問JSP時就執行了class文件)。

          而freemarker就是套模板,通過模板+內容直接生成HTML然后輸出。

          HTML(Hypertext Markup Language)文本標記語言,它是靜態頁面,和JavaScript一樣是解釋性語言。

          JSP(Java Server Page)Java服務端的頁面,它是動態頁面,它是需要經過JDK編譯后把內容發給客戶端去顯示。

          6、vue實例內部和外部分別怎么調用vue中的方法?

          內部調用: this.operate();

          外部調用:vm.operate(); (vm是vue實例名)

          三、后端技術

          1、Spring:

          (1)Spring中IOC和AOP的應用場景。

          AOP:面向切面編程??梢赃\用在日志,事務和異常處理等。如果不使用aop,那么就必須在每個類和方法中去實現它們。代碼糾纏在一起。每個類和方法中都包含日志、事務或者異常處理甚至是業務邏輯。在一個這樣的方法中,很難分清代碼中實際做的是什么處理。AOP 所做的就是將所有散落各處的事務代碼集中到一個事務切面中。

          AOP日志處理:使用Aop在接口方法上插入一行自定義的切面注解類,在切面處理類中可以記錄接口名稱、請求參數、請求ip、請求url、請求時間、響應參數、響應狀態、調用時長等;

          AOP事務處理:Spring在方法訪問數據庫之前,自動開啟事務,當訪問數據庫結束之后,自動提交/回滾事務;

          AOP異常處理:自定義開啟環繞通知,一旦運行接口報錯,環繞通知捕獲異常跳轉異常處理頁面。

          IOC就是Inversion of Control,即控制反轉,又稱依賴注入。它不是什么技術,而是一種設計思想。在Java開發中,傳統的創建對象的方法是直接通過 new 關鍵字(之前我們通過 "類名 對象名 = new 類名( )"的方式進行對象的創建,也就是說我們的程序負責對象的創建,控制了它是否被創建這件事情,這就叫做控制),而 spring 則是通過 IOC 容器來創建對象,也就是說我們將創建對象的控制權交給了 IOC 容器。這稱為控制反轉。概括的說就是:

          IOC 讓程序員不再關注怎么去創建對象,而是關注于對象創建之后的操作,把對象的創建、初始化、銷毀等工作交給spring容器來做。

          舉個例子:梳理這個問題在各種社會形態里如何解決:一個人(Java實例,調用者)需要一把斧子(Java實例,被調用者)

          (1) 原始社會里,幾乎沒有社會分工。需要斧子的人(調用者)只能自己去磨一把斧子(被調用者)。對應的情形為:Java程序里的調用者自己創建被調用者。
          (2)進入工業社會,工廠出現。斧子不再由普通人完成,而在工廠里被生產出來,此時需要斧子的人(調用者)找到工廠,購買斧子,無須關心斧子的制造過程。對應Java程序的簡單工廠的設計模式。
          (3)進入“按需分配”社會,需要斧子的人不需要找到工廠,坐在家里發出一個簡單指令:需要斧子。斧子就自然出現在他面前。對應Spring的依賴注入。

          第一種情況下,Java實例的調用者創建被調用的Java實例,必然要求被調用的Java類出現在調用者的代碼里。無法實現二者之間的松耦合。
          第二種情況下,調用者無須關心被調用者具體實現過程,只需要找到符合某種標準(接口)的實例,即可使用。此時調用的代碼面向接口編程,可以讓調用者和被調用者解耦,這也是工廠模式大量使用的原因。但調用者需要自己定位工廠,調用者與特定工廠耦合在一起。
          第三種情況下,調用者無須自己定位工廠,程序運行到需要被調用者時,系統自動提供被調用者實例。事實上,調用者和被調用者都處于Spring的管理下,二者之間的依賴關系由Spring提供。

          生活中這種例子比比皆是,支付寶在整個淘寶體系里就是龐大的ioc容器,交易雙方之外的第三方資金管理中心。

          (2)Spring依賴注入的方式有哪些?

          【set設值注入、構造函數注入、spring注解注入】

          Spring IOC既可以通過XML的形式進行bean與依賴注入配置,也可以通過注解的方式。(在springmvc中,我們一般使用xml進行裝配,而springboot使用全注解的形式)

          ①通過XML的形式進行bean與依賴注入

          通常有兩種: 設值注入&構造注入。

          設值注入就是指要被注入的類中定義有一個setter()方法,并在參數中定義需要注入的對象。

          構造注入就是指要被注入的類中聲明一個構造方法,并在此方法的參數中定義要注入的對象。

          ②注解的方式:

          注解包含三部分:

          | 組件類型注解--聲明當前類的功能與職責

          || 自動裝配注解--根據屬性特征自動注入對象

          ||| 元數據注解--更細化的輔助IoC容器管理對象的注解

          A、四種組件類型注解

          @Component:組件注解,通用注解,該注解描述的類將被IoC容器管理并實例化

          @Controller:語義注解,說明當前類是MVC應用中的控制類

          @Service:語義注解,說明當前類是Service業務服務類

          @Repository:語義注解,說明當前類作用于業務持久層,通常描述對應Dao類

          此外,在使用四種組件類型的注解時,必須開啟組件掃描,詳細配置如下:

          B、兩類自動裝配注解

          按類型裝配

          @Autowired

          @Inject

          按名稱裝配

          @Named

          @Resource

          優先設置name屬性,若未包含name屬性,會按照@Autowired注入

          C、元數據注解

          @Primary--按類型裝配時出現多個相同類型的對象,擁有此注解對象優先被注入

          @PostContruct:相當于init-method

          @PreDestory:相當于destory--method

          @Scope:設置對象Scope屬性

          @Value:為屬性注入靜態數據

          (3)為什么非使用依賴注入,我要用到一個其他對象時,new一個怎么就不好了。

          本質上都是創建對象,最大的區別還是生命周期的管理以及復雜依賴的處理。

          ①、生命周期

          比如一個類或者接口全程只要一個實例,用依賴注入的話只需要注冊成單例即可,如果自己實例化的話你需要擼一個單機模式(餓漢、懶漢、線程安全等模式)的類,并發下還要考慮線程安全。

          ②、復雜依賴

          如果這個類或者接口不依賴其他的類或者接口差異不明顯,如果依賴的類比較多的情況下(A依賴B,B又依賴C,C又依賴D,D又依賴其他)自己實例化會很麻煩。要創建A, 要先B、C、D先new一遍再new A。用ioc就快多了,A(B b),其他自動創建,是不是快多了。

          總結:在程序中如果不是必須同一個對象多個實例時,也就是一個對象只是在某個地方使用一下時new一下,依賴注入就比new一個對象更好,因為new一個對象必選面臨頻繁創建和銷毀內存實例對象的問題。而ioc管控下實例對象都是單例模式的,就是在程序運行時始終只有一個對象實例生成不需要頻繁創建和銷毀,也因為在內存中只有一個實例對象,減少內存開銷。

          (4)描述一下DispatcherServlet的工作流程?

          (5)SpringMVC如何區分控制器返回的是頁面還是數據(比如JSON格式的數據)

          使用@ResponseBody注解,該注解用于將Controller方法返回的對象,通過適當的HttpMessageConverter轉化為指定格式后,寫入到Response對象的body數據區。

          使用時機:返回的數據不是html標簽的頁面,而是其他某種格式的數據時(如json,xml等)。(如果是在程序中返回的html頁面代碼,也可以使用@ResponseBody,在HttpServletResponse寫入,設置ContentType為text/html)

          (6)Spring全家桶有哪些?

          Spring、Spring MVC、Spring Boot、Spring Cloud 、 Spring Security 、Spring Data

          (7)Spring普通類與工具類如何調用service層方法,為什么不能直接使用注解調用?

          Spring中的Service不是你想new就能new的,因為通過new實例化的對象脫離了Spring容器的管理,獲取不到注解的屬性值,所以會是null,就算調用service的類中有@Component注解加入了Spring容器管理,也還是null。

          新建SpringContextUtil類,在application.xml配置SpringContextUtil,最后使用DictService dictService = (DictService) SpringContextUtil.getBean("dictService");

          2、Springboot:

          (1) SpringBoot中如何進行單元測試?

          導入spring-boot-starter-test依賴。測試類使用注解@SpringBootTest,測試的方法上加@Test注解。

          (2) SpringBootApplication注解的作用。

          @SpringBootApplication注解是一個組合注解,@SpringBootApplication注解的源碼我們發現,它是由ComponentScan、SpringBootConfiguration、EnableAutoConfiguration等注解組合而成:

          (3) 在一個Springboot+mybatis+mysql+oracle+redis+aop功能的項目中,在pom.xml中需要引入哪些jar包依賴?

          進一步提問:以redis為例,springboot 1.x與springboot 2.x引入的jar包有何不同?

          在 springboot 1.5.x版本的默認的Redis客戶端是Jedis實現的,springboot 2.x版本中默認客戶端是用lettuce實現的。

          3、SpringCloud:

          (1)springCloud的核心組件有哪些,解決什么問題?

          Eureka(注冊中心)

          每個微服務都有一個EurekaClient組件,專門負責將這個服務的信息注冊到EurekaServer中,也就是告訴EurekaServer,自己在哪臺機器上,監聽著哪個端口。而EurekaServer是一個注冊中心,里面有一個注冊表,保存了各服務所在的機器和端口號。

          Feign(REST客戶端)

          Feign是一個聲明式REST客戶端,主要是為了簡便服務調用,更快捷、優雅地調用HTTPAPI。主要是實現原理是用動態代理,你要是調用哪個接口,本質就是調用Feign創建的動態代理。

          Ribbon(負載均衡)

          Ribbon的作用是負載均衡,會幫你在每次請求時選擇一臺機器,均勻地把請求分發到各個機器上,默認使用的最經典的RoundRobin輪詢算法(如果發起10次請求,那就先讓你請求第1臺機器、然后是第2臺機器、第3臺機器,接著再來—個循環,第1臺機器、第2臺機器。。。以此類推)

          Hystrix(熔斷器)

          微服務框架是許多服務互相調用的,要是不做任何保護的話,某一個服務掛了,就會引起連鎖反應,導致別的服務也掛。Hystrix是隔離、熔斷以及降級的一個框架。如果調用某服務報錯或者掛了,就對該服務熔斷,在5分鐘內請求此服務直接就返回一個默認值,不需要每次都卡幾秒,這個過程,就是所謂的熔斷。但是熔斷了之后就會少調用一個服務,此時需要做下標記,標記本來需要做什么業務,但是因為服務掛了,暫時沒有做,等熔斷的服務恢復了,就可以手工處理這些業務。這個過程,就是所謂的降級。

          Zuul(服務網關)

          Zuul微服務網關,負責網絡路由。假設你后臺部署了幾百個服務,現在有個前端兄弟要來調用這些服務,難不成你讓他把所有服務的名稱和地址全部記住,這是不現實的,所以一般微服務架構中都必然會設計一個網關,所有請求都往網關走,網關會根據請求中的一些特征,將請求轉發給后端的各個服務。而且有一個網關之后,還有很多好處,比如可以做統一的降級、限流、認證授權、安全,等等。

          總結步驟:①服務注冊—》②服務發現—》③負載均衡—》④服務調用—》⑤隔離、熔斷與降級—》⑥網關路由

          流程說明:各個服務啟動時,Eureka Client都會將服務注冊到Eureka Server,并且Eureka Client還可以反過來從Eureka Server拉取注冊表,從而知道其他服務在哪里。服務間發起請求的時候,基于Ribbon做負載均衡,從一個服務的多臺機器中選擇一臺?;贔eign的動態代理機制,根據注解和選擇的機器,拼接請求URL地址,發起請求。發起請求是通過Hystrix的線程池來走的,不同的服務走不同的線程池,實現了不同服務調用的隔離,避免了服務雪崩的問題。如果前端、移動端要調用后端系統,統一從Zuul網關進入,由Zuul網關轉發請求給對應的服務。

          (2)SpringCloud和Dubbo兩種微服務架構有何區別?

          Dubbo的定位始終是一款RPC框架,而SpringCloud的目標是微服務架構下的一站式解決方案。如果非要比較的話,Dubbo可以類比到NetflixOSS技術棧,而SpringCloud集成了NetflixOSS作為分布式服務治理解決方案,但除此之外SpringCloud還提供了配置、消息、安全、調用鏈跟蹤等分布式問題解決方案。

          (3)SpringBoot和SpringCloud 側重點分別在哪些方面?

          SpringBoot是Spring的一套快速配置腳手架,可以基于SpringBoot快速開發單個微服務,SpringCloud是一個基于SpringBoot實現的云應用開發工具;

          SpringBoot專注于快速、方便集成的單個微服務個體,SpringCloud關注全局的服務治理框架;

          SpringBoot使用了默認大于配置的理念,很多集成方案已經幫你選擇好了,能不配置就不配置,SpringCloud很大的一部分是基于SpringBoot來實現。SpringBoot可以離開SpringCloud獨立使用開發項目,但是SpringCloud離不開SpringBoot,屬于依賴的關系。

          (4)springcloud如何實現服務的注冊和調用?

          1)服務發布時,指定對應的服務名(服務名包括了IP地址和端口),將服務注冊到注冊中心(eureka 或者zookeeper)
          2)注冊中心加@EnableEurekaServer,服務用@EnableDiscoveryClient,然后用ribbon或feign進行服務直接的調用發現。(這一過程是springcloud自動實現 只需要在main方法添加@EnableDisscoveryClient。同一個服務修改端口就可以啟動多個實例)

          4、mybatis:

          (1)Mybatis中mapper.xml映射文件,通常都會寫一個Mapper接口與之對應,這個Mapper層接口是怎么能夠找到指定xml下的方法的?

          Mapper接口是沒有實現類的,當調用接口方法時,接口全限名(就是映射文件中的namespace的值)+方法名拼接字符串作為key值,可唯一定位一個MapperStatement。在Mybatis中,每一個<select>、<insert>、<update>、<delete>標簽,都會被解析為一個MapperStatement對象。

          (2)Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重復?

          不同的Xml映射文件,如果配置了namespace,那么id可以重復;如果沒有配置namespace,那么id不能重復;原因就是namespace+id是作為Map<String,MapperStatement>的key使用的,如果沒有namespace,就剩下id,那么,id重復會導致數據互相覆蓋。有了namespace,自然id就可以重復,namespace不同,namespace+id自然也就不同。

          (3)Mybatis中使用MySQL和Oracle分頁的區別

          MySQL分頁:(利用LIMIT關鍵字)計算參數為開始序號(startNum),要查的總條數(totalNum)

          Oracle分頁:(利用自帶的rownum)計算參數為開始序號(startNum),結束序號(endNum)【注意:Oracle分頁利用其自帶的rownum,但是rownum在表中不能使用>號(比如select rownum,a.* from A a where rownum > n,查出的都是空),但是可以使用<。這是因為rownum是一個總是從1開始的偽列,Oracle認為rownum>n(n>1的自然數)這種條件依舊不成立,所以查不到記錄】

          (4)Mybatis是如何將sql執行結果封裝為目標對象并返回的?

          第一種是使用resultMap標簽,逐一定義列名和對象屬性名之間的映射關系。

          第二種是使用sql列的別名功能,將列別名書寫為對象屬性名,比如T_NAME AS NAME,對象屬性名一般是name,小寫,但是列名不區分大小寫,Mybatis會忽略列名大小寫,智能找到與之對應對象屬性名,你甚至可以寫成T_NAME AS NaMe,Mybatis一樣可以正常工作。

          進一步提問:resultMap和resultType有啥區別?

          resultType:當使用resultType做SQL語句返回結果類型處理時,對于SQL語句查詢出的字段在相應的pojo中必須有和它相同的字段對應,而resultType中的內容就是pojo在本項目中的位置。

          resultMap:當使用resultMap做SQL語句返回結果類型處理時,通常需要先在mapper.xml中定義resultMap進行pojo和相應表字段的對應關系。然后再使用resultMap。

          (5)Mybatis xml映射文件中,除了常見的select、insert、updae、delete標簽之外,還有哪些標簽及其作用?

          除了這四個標簽,還有<resultMap>、<parameterMap>、<sql>、<include>、<selectKey>,一個9個標簽,其中<sql>為sql片段標簽,通過<include>標簽引入sql片段,<selectKey>為不支持自增的主鍵生成策略標簽。

          (6)為什么說Mybatis是半自動ORM映射工具?它與全自動的區別在哪里?

          hibernate對很多數據庫的操作已經進行了封裝,hibernate操作對象時,比如往數據庫添加一條記錄,直接save就可以了。(Hibernate屬于全自動ORM映射工具,使用Hibernate查詢關聯對象或者關聯集合對象時,可以根據對象關系模型直接獲取),而Mybatis在查詢關聯對象或關聯集合對象時,需要手動編寫sql來完成,雖然現在已經有不少程序可以自動生成xml文件,但還是需要自己調整sql,所以稱之為半自動ORM映射工具。這也從側面可以看出hibernate的拓展性不如Mybatis(hibernate做了很多封裝)。

          (7)mybatis 為什么大于不用轉義,小于必須轉義?

          mybatis不支持“<”,本質是xml不支持這個符號,<會引起xml格式的錯誤,xml文件中的標簽是 <…> 這種形式的, 所以當出現 “<” 號時, 會認為是一個標簽的開始。


          gt; 導出word我們常用的是通過POI實現導出。POI最擅長的是EXCEL的操作。word操作起來樣式控制還是太繁瑣了。今天我們介紹下通過FREEMARK來實現word模板導出。


          [TOC]

          # 開發準備

          - 本文實現基于springboot,所以項目中采用的都是springboot衍生的產品。首先我們在maven項目中引入freemark坐標。

          ```xml

          <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId></dependency>

          ```

          - 只需要引入上面的jar包 。 前提是繼承springboot坐標。就可以通過freemark進行word的導出了。

          ## 模板準備

          ![](http://oytmxyuek.bkt.clouddn.com/20200525001.jpg)

          - 上面是我們導出的一份模板。填寫規則也很簡單。只需要我們提前準備一份樣本文檔,然后將需要動態修改的通過`${}`進行占位就行了。我們導出的時候提供相應的數據就行了。這里注意一下`${c.no}`這種格式的其實是我們后期為了做集合遍歷的。這里先忽略掉。后面我們會著重介紹。

          ## 開發測試

          - 到了這一步說明我們的前期準備就已經完成了。剩下我們就通過freemark就行方法調用導出就可以了。

          - 首先我們構建freemark加載路徑。就是設置一下freemark模板路徑。模板路徑中存放的就是我們上面編寫好的模板。只不過這里的模板不是嚴格意義的word.而是通過word另存為xml格式的文件。

          ![](http://oytmxyuek.bkt.clouddn.com/20200525002.jpg)

          - 配置加載路徑

          ```//創建配置實例Configuration configuration = new Configuration();//設置編碼configuration.setDefaultEncoding("UTF-8");//ftl模板文件configuration.setClassForTemplateLoading(OfficeUtils.class, "/template");

          ```

          - 獲取模板類

          ```

          Template template = configuration.getTemplate(templateName);

          ```

          - 構建輸出對象

          ```

          Writer out = new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8"));

          ```

          - 導出數據到out

          ```

          template.process(dataMap, out);

          ```

          - 就上面四步驟我們就可以實現導出了。我們可以將加載配置路徑的放到全局做一次。剩下也就是我們三行代碼就可以搞定導出了。當然我們該做的異常捕獲這些還是需要的。[點我獲取源碼](https://gitee.com/zxhTom/office-multip.git)


          # 結果檢測

          ![](http://oytmxyuek.bkt.clouddn.com/20200525003.jpg)


          # 功能通用化思考

          - 上面我們只是簡單介紹一下freemark導出word的流程。關于細節方面我們都沒有進行深究。 - 細心的朋友會發現上面的圖片并沒有進行動態的設置。這樣子功能上肯定是說不過去的。圖片我們想生成我們自己設置的圖片。 - 還有一個細節就是復選框的問題。仔細觀察會發現復選框也沒有字段去控制??隙ㄒ彩菦]有辦法進行動態勾選的。 - 最后就是我們上面提到的就是主要安全措施那塊。那塊是我們的集合數據。通過模板我們是沒法控制的。 - 上面的問題我們freemark的word模板是無法實現的。有問題其實是好事。這樣我們才能進步。實際上freemark導出真正是基于ftl格式的文件的。只不過xml和ftl語法很像所以上面我們才說導出模板是xml的。實際上我們需要的ftl文件。如果是ftl文件那么上面的問題的復選框和集合都很好解決了。一個通過if標簽一個通過list標簽就可以解決了。圖片我們還是需要通過人為去替換

          ```

          <#if checkbox ??&& checkbox?seq_contains('窒息;')?string('true','false')=='true'>0052<#else>00A3</#if>

          <#list c as c>dosomethings()</#list>

          ```

          - 上面兩段代碼就是if 和 list語法

          # Dom4j實現智能化

          - 上面ftl雖然解決了導出的功能問題。但是還是不能實現智能化。我們想做的其實想通過程序自動根據我們word的配置去進行生成ftl文件。經過百度終究還是找到了對應的方法。Dom4j就是我們最終方法。我們可以通過在word進行特殊編寫。然后程序通過dom4j進行節點修改。通過dom4j我們的圖片問題也就迎刃而解了。下面主要說說針對以上三個問題的具體處理細節

          ## 復選框

          ![](http://oytmxyuek.bkt.clouddn.com/20200525checkbox.jpg)

          - 首先我們約定同一類型的復選框前需要`#{}`格式編寫。里面就是控制復選框的字段名。 - 然后我們通過dom4j解析xml。我們再看看復選框原本的格式在xml中

          ```<w:sym w:font="Wingdings 2" w:char="0052"/>

          ``` - 那么我們只需要通過dom4j獲取到w:sym標簽。在獲取到該標簽后對應的文本內容即#{zhuyaoweihaiyinsu}窒息;這個內容。 - 匹配出字段名zhuyaoweihaiyinsu進行if標簽控制內容

          ```

          <#if checkbox ??&& checkbox?seq_contains('窒息')?string('true',false')=='true'>0052<#else>00A3</#if>

          ```

          ### 部分源碼

          ```java

          Element root = document.getRootElement();List<Element> checkList = root.selectNodes("//w:sym");List<String> nameList = new ArrayList<>();Integer indext = 1;for (Element element : checkList) { Attribute aChar = element.attribute("char"); String checkBoxName = selectCheckBoxNameBySymElement(element.getParent()); aChar.setData(chooicedCheckBox(checkBoxName));}

          ```

          ## 集合

          ![](http://oytmxyuek.bkt.clouddn.com/20200525list.jpg)

          - 同樣的操作我們通過獲取到需要改變的標簽就可以了。集合和復選框不一樣。集合其實是我們認為規定出來的一種格式。在word中并沒有特殊標簽標示。所以我們約定的格式是`${a_b}`。首先我們通過遍歷word中所以文本通過正則驗證是否符合集合規范。符合我們獲取到當前的行然后在行標簽前添加#list標簽。 然后將${a_b}修改成${a.b} 至于為什么一開始不設置a.b格式的。我這里只想說是公司文化導致的。我建議搭建如果是自己實現這一套功能的話采用a.b格式最好。 ### 部分源碼

          ```java

          Element root = document.getRootElement(); //需要獲取所有標簽內容,判斷是否符合 List<Element> trList = root.selectNodes("//w:t"); //rowlist用來處理整行數據,因為符合標準的會有多列, 多列在同一行只需要處理一次。 List<Element> rowList = new ArrayList<>(); if (CollectionUtils.isEmpty(trList)) { return; } for (Element element : trList) { boolean matches = Pattern.matches(REGEX, element.getTextTrim()); if (!matches) { continue; } //符合約定的集合格式的才會走到這里 //提取出tableId 和columnId Pattern compile = Pattern.compile(REGEX); Matcher matcher = compile.matcher(element.getTextTrim()); String tableName = ""; String colName = ""; while (matcher.find()) { tableName = matcher.group(1); colName = matcher.group(2); } //此時獲取的是w:t中的內容,真正需要循環的是w:t所在的w:tr,這個時候我們需要獲取到當前的w:tr List<Element> ancestorTrList = element.selectNodes("ancestor::w:tr[1]"); /*List<Element> tableList = element.selectNodes("ancestor::w:tbl[1]"); System.out.println(tableList);*/ Element ancestorTr = null; if (!ancestorTrList.isEmpty()) { ancestorTr = ancestorTrList.get(0); //獲取表頭信息 Element titleAncestorTr = DomUtils.getInstance().selectPreElement(ancestorTr); if (!rowList.contains(ancestorTr)) { rowList.add(ancestorTr); List<Element> foreachList = ancestorTr.getParent().elements(); if (!foreachList.isEmpty()) { Integer ino = 0; Element foreach = null; for (Element elemento : foreachList) { if (ancestorTr.equals(elemento)) { //此時ancestorTr就是需要遍歷的行 , 因為我們需要將此標簽擴容到循環標簽匯中 foreach = DocumentHelper.createElement("#list"); foreach.addAttribute("name", tableName+" as "+tableName); Element copy = ancestorTr.createCopy(); replaceLineWithPointForeach(copy); mergeCellBaseOnTableNameMap(titleAncestorTr,copy,tableName); foreach.add(copy); break; } ino++; } if (foreach != null) { foreachList.set(ino, foreach); } } } else { continue; } } }

          ```

          ## 圖片

          ![](http://oytmxyuek.bkt.clouddn.com/20200525img.jpg)

          - 圖片和復選框類似。因為在word的xml中是通過特殊標簽處理的。但是我們的占位符不能通過以上占位符占位了。需要一張真實的圖片進行占位。因為只有是一張圖片word才會有圖片標簽。我們可以在圖片后通過`@{imgField}`進行占位。然后通過dom4j將圖片的base64字節碼用${imgField}占位。

          ### 部分源碼

          ```java

          //圖片索引下表Integer index = 1;//獲取根路徑Element root = document.getRootElement();//獲取圖片標簽List<Element> imgTagList = root.selectNodes("//w:binData");for (Element element : imgTagList) { element.setText(String.format("${img%s}",index++)); //獲取當前圖片所在的wp標簽 List<Element> wpList = element.selectNodes("ancestor::w:p"); if (CollectionUtils.isEmpty(wpList)) { throw new DomException("未知異常"); } Element imgWpElement = wpList.get(0); while (imgWpElement != null) { try { imgWpElement = DomUtils.getInstance().selectNextElement(imgWpElement); } catch (DomException de) { break; } //獲取對應圖片字段 List<Element> imgFiledList = imgWpElement.selectNodes("w:r/w:t"); if (CollectionUtils.isEmpty(imgFiledList)) { continue; } String imgFiled = getImgFiledTrimStr(imgFiledList); Pattern compile = Pattern.compile(REGEX); Matcher matcher = compile.matcher(imgFiled); String imgFiledStr = ""; while (matcher.find()) { imgFiledStr = matcher.group(1); boolean remove = imgWpElement.getParent().elements().remove(imgWpElement); System.out.println(remove); } if (StringUtils.isNotEmpty(imgFiledStr)) { element.setText(String.format("${%s}",imgFiledStr)); break; } }

          }

          ```


          # 基于word自動化導出(含源碼)

          - 以上就是我們實現導出的流程。通過上面的邏輯我們最終可以一套代碼復用了。源碼下載地址:https://gitee.com/zxhTom/office-multip.git


          ###### 參考網絡文章

          [dom操作xml](https://www.cnblogs.com/alsf/p/9278816.html)[dom生成xml](https://www.cnblogs.com/it-mh/p/11021716.html)[httpclient獲取反應流](https://blog.csdn.net/qw222pzx/article/details/97884917)[獲取jar路徑](https://blog.csdn.net/liangcha007/article/details/88526181)[itext實現套打](https://blog.csdn.net/flyfeifei66/article/details/6739950)[ftl常見語法](https://www.cnblogs.com/zhaoYuQing-java2015/p/6046697.html)[freemark官網](https://freemarker.apache.org/docs/ref_directive_list.html)[ftl判斷非空](https://www.iteye.com/blog/lj6684-1594769)[freemark自定義函數](https://blog.csdn.net/weixin_34174422/article/details/91867563)[freemark自定義函數java](https://blog.csdn.net/hzgzf/article/details/83399351)[freemark特殊字符轉義](https://blog.csdn.net/arsenic/article/details/8490098)[java實現word轉xml各種格式](https://www.cnblogs.com/Yesi/p/11195732.html)

          [加入戰隊](#addMe)

          # # <span id="addMe">加入戰隊</span>

          ## 微信公眾號

          ![微信公眾號](http://oytmxyuek.bkt.clouddn.com/weixin.jpeg)

          . Thymeleaf 簡介

          Thymeleaf 是新一代 Java 模板引擎,與 Velocity、FreeMarker 等傳統 Java 模板引擎不同,Thymeleaf 支持 HTML 原型,其文件后綴為“.html”,因此它可以直接被瀏覽器打開,此時瀏覽器會忽略未定義的 Thymeleaf 標簽屬性,展示 thymeleaf 模板的靜態頁面效果;當通過 Web 應用程序訪問時,Thymeleaf 會動態地替換掉靜態內容,使頁面動態顯示。

          html<!DOCTYPE html>
          <html lang="en" xmlns:th="http://www.thymeleaf.org">
          <head>
              <meta charset="UTF-8">
              <title>Title</title>
          </head>
          <body>
          <!--th:text 為 Thymeleaf 屬性,用于在展示文本-->
          <h1 th:text="歡迎您來到Thymeleaf">歡迎您訪問靜態頁面 HTML</h1>
          </body>
          </html>
          

          當直接使用瀏覽器打開時,瀏覽器展示結果如下。

          歡迎您訪問靜態頁面HTML

          當通過 Web 應用程序訪問時,瀏覽器展示結果如下。

          歡迎您來到Thymeleaf

          Thymeleaf 的特點:

          • 動靜結合:Thymeleaf 既可以直接使用瀏覽器打開,查看頁面的靜態效果,也可以通過 Web 應用程序進行訪問,查看動態頁面效果。
          • 開箱即用:Thymeleaf 提供了 Spring 標準方言以及一個與 SpringMVC 完美集成的可選模塊,可以快速的實現表單綁定、屬性編輯器、國際化等功能。
          • 多方言支持:它提供了 Thymeleaf 標準和 Spring 標準兩種方言,可以直接套用模板實現 JSTL、 OGNL 表達式;必要時,開發人員也可以擴展和創建自定義的方言。
          • 與 SpringBoot 完美整合:SpringBoot 為 Thymeleaf 提供了的默認配置,并且還為 Thymeleaf 設置了視圖解析器,因此 Thymeleaf 可以與 Spring Boot 完美整合。

          2. Thymeleaf 語法規則

          在使用 Thymeleaf 之前,首先要在頁面的 html 標簽中聲明名稱空間,示例代碼如下。

          htmlxmlns:th="http://www.thymeleaf.org"
          

          在 html 標簽中聲明此名稱空間,可避免編輯器出現 html 驗證錯誤,但這一步并非必須進行的,即使我們不聲明該命名空間,也不影響 Thymeleaf 的使用。

          2.1 標準表達式語法

          2.1.1 變量表達式

          使用${}包裹的表達式被稱為變量表達式,該表達式具有以下功能:

          • 獲取對象的屬性和方法 使用變量表達式可以獲取對象的屬性和方法,例如,獲取 person 對象的 lastName 屬性,表達式形式如下:
          • html
          • ${person.lastName}
          • 使用內置的基本對象 使用變量表達式還可以使用內置基本對象,獲取內置對象的屬性,調用內置對象的方法。 Thymeleaf 中常用的內置基本對象如下: 例如,我們通過以下 2 種形式,都可以獲取到 session 對象中的 map 屬性:
          • html
          • ${#session.getAttribute('map')} ${session.map}
          • 使用內置的工具對象
          • 例如,我們可以使用內置工具對象 strings 的 equals 方法,來判斷字符串與對象的某個屬性是否相等,代碼如下。
          • html
          • ${#strings.equals('編程幫',name)}

          2.1.2 選擇變量表達式

          選擇變量表達式與變量表達式功能基本一致,只是在變量表達式的基礎上增加了與 th:object 的配合使用。當使用 th:object 存儲一個對象后,我們可以在其后代中使用選擇變量表達式(*{...})獲取該對象中的屬性,其中,“*”即代表該對象。

          html<div th:object="${session.user}" >
              <p th:text="*{fisrtName}">firstname</p>
          </div>
          

          th:object 用于存儲一個臨時變量,該變量只在該標簽及其后代中有效,在后面的內容“th 屬性”中我詳細介紹。

          2.1.3 鏈接表達式

          不管是靜態資源的引用,還是 form 表單的請求,凡是鏈接都可以用鏈接表達式 (@{...})。

          鏈接表達式的形式結構如下:

          • 無參請求:@{/xxx}
          • 有參請求:@{/xxx(k1=v1,k2=v2)}

          例如使用鏈接表達式引入 css 樣式表,代碼如下。

          html<link href="asserts/css/signin.css" th:href="@{/asserts/css/signin.css}" rel="stylesheet">
          

          2.1.4 國際化表達式

          消息表達式一般用于國際化的場景。結構如下。

          htmlth:text="#{msg}"
          

          注意:此處了解即可,我們會在后面的章節中詳細介紹。

          2.1.5 片段引用表達式

          片段引用表達式用于在模板頁面中引用其他的模板片段,該表達式支持以下 2 中語法結構:

          • 推薦:~{templatename::fragmentname}
          • 支持:~{templatename::#id}

          以上語法結構說明如下:

          • templatename:模版名,Thymeleaf 會根據模版名解析完整路徑:/resources/templates/templatename.html,要注意文件的路徑。
          • fragmentname:片段名,Thymeleaf 通過 th:fragment 聲明定義代碼塊,即:th:fragment="fragmentname"
          • id:HTML 的 id 選擇器,使用時要在前面加上 # 號,不支持 class 選擇器。

          2.2 th 屬性

          Thymeleaf 還提供了大量的 th 屬性,這些屬性可以直接在 HTML 標簽中使用,其中常用 th 屬性及其示例如下表。

          屬性

          描述

          示例

          th:id

          替換 HTML 的 id 屬性

          <input id="html-id" th:id="thymeleaf-id" />

          th:text

          文本替換,轉義特殊字符

          <h1 th:text="hello,bianchengbang" >hello</h1>

          th:untext

          文本替換,文本替換,不轉義特殊字符

          <div th:utext="'<h1>歡迎來到編程幫!</h1>'" >歡迎你</div>

          th:object

          在父標簽選擇對象,子標簽使用 *{…} 選擇表達式選取值。沒有選擇對象,那子標簽使用選擇表達式和 ${…} 變量表達式是一樣的效果。同時即使選擇了對象,子標簽仍然可以使用變量表達式。

          <div th:object="${session.user}" > <p th:text="*{fisrtName}">firstname</p> </div>

          th:value

          替換 value 屬性

          <input th:value = "${user.name}" />

          th:with

          局部變量賦值運算

          <div th:with="isEvens = ${prodStat.count}%2 == 0" th:text="${isEvens}"></div>

          th:style

          設置樣式

          <div th:style="'color:#F00; font-weight:bold'">編程幫 www.biancheng.net</div>

          th:onclick

          點擊事件

          <td th:onclick = "'getInfo()'"></td>

          th:each

          遍歷,支持 Iterable、Map、數組等。

          <table> <tr th:each="m:${session.map}"> <td th:text="${m.getKey()}"></td> <td th:text="${m.getValue()}"></td> </tr></table>

          th:if

          根據條件判斷是否需要展示此標簽

          <a th:if ="${userId == collect.userId}">

          th:unless

          和 th:if 判斷相反,滿足條件時不顯示

          <div th:unless="${m.getKey()=='name'}" ></div>

          th:switch

          與 Java 的 switch case語句類似通常與 th:case 配合使用,根據不同的條件展示不同的內容

          <div th:switch="${name}"> <span th:case="a">編程幫</span> <span th:case="b">www.biancheng.net</span></div>

          th:fragment

          模板布局,類似 JSP 的 tag,用來定義一段被引用或包含的模板片段

          <footer th:fragment="footer">插入的內容</footer>

          th:insert

          布局標簽;將使用 th:fragment 屬性指定的模板片段(包含標簽)插入到當前標簽中

          <div th:insert="commons/bar::footer"></div>

          th:replace

          布局標簽;使用 th:fragment 屬性指定的模板片段(包含標簽)替換當前整個標簽

          <div th:replace="commons/bar::footer"></div>

          th:selected

          select 選擇框選中

          <select> <option>---</option> <option th:selected="${name=='a'}"> 編程幫 </option> <option th:selected="${name=='b'}"> www.biancheng.net </option></select>

          th:src

          替換 HTML 中的 src 屬性

          <img th:src="@{/asserts/img/bootstrap-solid.svg}" src="asserts/img/bootstrap-solid.svg" />

          th:inline

          內聯屬性;該屬性有 text、none、javascript 三種取值,在 <script> 標簽中使用時,js 代碼中可以獲取到后臺傳遞頁面的對象。

          <script type="text/javascript" th:inline="javascript"> var name = /*[[${name}]]*/ ianchengbang'; alert(name)</script>

          th:action

          替換表單提交地址

          <form th:action="@{/user/login}" th:method="post"></form>

          3. Thymeleaf 公共頁面抽取

          在 Web 項目中,通常會存在一些公共頁面片段(重復代碼),例如頭部導航欄、側邊菜單欄和公共的 js css 等。我們一般會把這些公共頁面片段抽取出來,存放在一個獨立的頁面中,然后再由其他頁面根據需要進行引用,這樣可以消除代碼重復,使頁面更加簡潔。

          3.1 抽取公共頁面

          Thymeleaf 作為一種優雅且高度可維護的模板引擎,同樣支持公共頁面的抽取和引用。我們可以將公共頁面片段抽取出來,存放到一個獨立的頁面中,并使用 Thymeleaf 提供的 th:fragment 屬性為這些抽取出來的公共頁面片段命名。 示例 1 將公共頁面片段抽取出來,存放在 commons.html 中,代碼如下。

          html<div th:fragment="fragment-name" id="fragment-id">
              <span>公共頁面片段</span>
          </div>
          

          3.2 引用公共頁面

          在 Thymeleaf 中,我們可以使用以下 3 個屬性,將公共頁面片段引入到當前頁面中。

          • th:insert:將代碼塊片段整個插入到使用了 th:insert 屬性的 HTML 標簽中;
          • th:replace:將代碼塊片段整個替換使用了 th:replace 屬性的 HTML 標簽中;
          • th:include:將代碼塊片段包含的內容插入到使用了 th:include 屬性的 HTML 標簽中。

          使用上 3 個屬性引入頁面片段,都可以通過以下 2 種方式實現。

          • ~{templatename::selector}:模板名::選擇器
          • ~{templatename::fragmentname}:模板名::片段名

          通常情況下,{} 可以省略,其行內寫法為 [[{...}]] 或 [({...})],其中 [[{...}]] 會轉義特殊字符,[(~{...})] 則不會轉義特殊字符。

          示例 2

          1. 在頁面 fragment.html 中引入 commons.html 中聲明的頁面片段,可以通過以下方式實現。
          html<!--th:insert 片段名引入-->
          <div th:insert="commons::fragment-name"></div>
          <!--th:insert id 選擇器引入-->
          <div th:insert="commons::#fragment-id"></div>
          ------------------------------------------------
          <!--th:replace 片段名引入-->
          <div th:replace="commons::fragment-name"></div>
          <!--th:replace id 選擇器引入-->
          <div th:replace="commons::#fragment-id"></div>
          ------------------------------------------------
          <!--th:include 片段名引入-->
          <div th:include="commons::fragment-name"></div>
          <!--th:include id 選擇器引入-->
          <div th:include="commons::#fragment-id"></div>
          
          1. 啟動 Spring Boot,使用瀏覽器訪問 fragment.html,查看源碼,結果如下。
          html<!--th:insert 片段名引入-->
          <div>
              <div id="fragment-id">
                  <span>公共頁面片段</span>
              </div>
          </div>
          <!--th:insert id 選擇器引入-->
          <div>
              <div id="fragment-id">
                  <span>公共頁面片段</span>
              </div>
          </div>
          ------------------------------------------------
          <!--th:replace 片段名引入-->
          <div id="fragment-id">
              <span>公共頁面片段</span>
          </div>
          <!--th:replace id 選擇器引入-->
          <div id="fragment-id">
              <span>公共頁面片段</span>
          </div>
          ------------------------------------------------
          <!--th:include 片段名引入-->
          <div>
              <span>公共頁面片段</span>
          </div>
          <!--th:include id 選擇器引入-->
          <div>
              <span>公共頁面片段</span>
          </div>
          

          3.3 傳遞參數

          Thymeleaf 在抽取和引入公共頁面片段時,還可以進行參數傳遞,大致步驟如下: 1.傳入參數; 2.使用參數。

          3.3.1 傳入參數

          引用公共頁面片段時,我們可以通過以下 2 種方式,將參數傳入到被引用的頁面片段中: 模板名::選擇器名或片段名(參數1=參數值1,參數2=參數值2) 模板名::選擇器名或片段名(參數值1,參數值2)

          注:若傳入參數較少時,一般采用第二種方式,直接將參數值傳入頁面片段中; 若參數較多時,建議使用第一種方式,明確指定參數名和參數值,。

          示例代碼如下:

          java<!--th:insert 片段名引入-->
          <div th:insert="commons::fragment-name(var1='insert-name',var2='insert-name2')"></div>
          <!--th:insert id 選擇器引入-->
          <div th:insert="commons::#fragment-id(var1='insert-id',var2='insert-id2')"></div>
          ------------------------------------------------
          <!--th:replace 片段名引入-->
          <div th:replace="commons::fragment-name(var1='replace-name',var2='replace-name2')"></div>
          <!--th:replace id 選擇器引入-->
          <div th:replace="commons::#fragment-id(var1='replace-id',var2='replace-id2')"></div>
          ------------------------------------------------
          <!--th:include 片段名引入-->
          <div th:include="commons::fragment-name(var1='include-name',var2='include-name2')"></div>
          <!--th:include id 選擇器引入-->
          <div th:include="commons::#fragment-id(var1='include-id',var2='include-id2')"></div>
          

          3.3.2 使用參數

          在聲明頁面片段時,我們可以在片段中聲明并使用這些參數,例如:

          java<!--使用 var1 和 var2 聲明傳入的參數,并在該片段中直接使用這些參數 -->
          <div th:fragment="fragment-name(var1,var2)" id="fragment-id">
              <p th:text="'參數1:'+${var1} + '-------------------參數2:' + ${var2}">...</p>
          </div>
          

          啟動 Spring Boot,使用瀏覽器訪問 fragment.html,結果如下圖。


          主站蜘蛛池模板: 亚洲高清美女一区二区三区| 亚洲一区中文字幕| 一区二区三区电影网| 国产成人av一区二区三区不卡 | 国产乱子伦一区二区三区| 国产SUV精品一区二区四 | 熟女大屁股白浆一区二区| 中文人妻无码一区二区三区| 亚洲一区二区电影| 日本一区二区三区久久| 亚洲欧美日韩一区二区三区| 国产在线精品一区二区中文| 久久久综合亚洲色一区二区三区 | 亚洲国产老鸭窝一区二区三区 | 亚洲性日韩精品一区二区三区 | 国产a久久精品一区二区三区| 中文字幕在线播放一区| 91福利视频一区| 午夜一区二区免费视频| 久久一区二区免费播放| 亚洲色一区二区三区四区| 国产美女口爆吞精一区二区| 国产91大片精品一区在线观看 | 亚洲乱码日产一区三区| 亚洲av永久无码一区二区三区| 一区二区三区四区在线播放| 亚洲一区二区影院| 理论亚洲区美一区二区三区| 久久精品道一区二区三区| 亚洲AV网一区二区三区| 亚洲色婷婷一区二区三区| 免费高清在线影片一区| 亚洲AV无码一区二区乱孑伦AS| 国产精品亚洲一区二区三区| 末成年女A∨片一区二区| 亚洲熟女乱综合一区二区| 一区二区无码免费视频网站| 中文字幕一区二区人妻| 波多野结衣电影区一区二区三区| 一区二区三区精品| 变态拳头交视频一区二区|