1. 程式人生 > >SpringBoot:Web開發

SpringBoot:Web開發

西部開源-秦疆老師:基於SpringBoot 2.1.6 的部落格教程 , 基於atguigu 1.5.x 視訊優化

秦老師交流Q群號: 664386224

未授權禁止轉載!編輯不易 , 轉發請註明出處!防君子不防小人,共勉!

簡介

好的,同學們,那麼接下來呢,我們開始學習SpringBoot與Web開發,從這一章往後,就屬於我們實戰部分的內容了;

其實SpringBoot的東西用起來非常簡單,因為SpringBoot最大的特點就是自動裝配。

使用SpringBoot的步驟:

  1. 建立一個SpringBoot應用,選擇我們需要的模組,SpringBoot就會預設將我們的需要的模組自動配置好
  2. 手動在配置檔案中配置部分配置專案就可以執行起來了
  3. 專注編寫業務程式碼,不需要考慮以前那樣一大堆的配置了。

要熟悉掌握開發,之前學習的自動配置的原理一定要搞明白!

比如SpringBoot到底幫我們配置了什麼?我們能不能修改?我們能修改哪些配置?我們能不能擴充套件?

  • 向容器中自動配置元件 : *** Autoconfiguration
  • 自動配置類,封裝配置檔案的內容:***Properties

沒事就找找類,看看自動裝配原理,我們之後來進行一個CRUD的實驗測試!

靜態資源對映規則

首先,我們搭建一個普通的SpringBoot專案,回顧一下HelloWorld程式!【演示】

那我們要引入我們小實驗的測試資源,我們專案中有許多的靜態資源,比如,css,js等檔案,這個SpringBoot怎麼處理呢?

 如果我們是一個web應用,我們的main下會有一個webapp,我們以前都是將所有的頁面導在這裡面的,對吧!

但是我們現在的pom呢,打包方式是為jar的方式,那麼這種方式SpringBoot能不能來給我們寫頁面呢?當然是可以的,但是SpringBoot對於靜態資源放置的位置,是有規定的!

我們先來聊聊這個靜態資源對映規則;

SpringBoot中,SpringMVC的web配置都在 WebMvcAutoConfiguration  這個配置裡面,我們可以去看看 WebMvcAutoConfigurationAdapter 中有很多配置方法;

比如:addResourceHandlers

  public void addResourceHandlers(ResourceHandlerRegistry registry) {
            if (!this.resourceProperties.isAddMappings()) {
                logger.debug("Default resource handling disabled");
            } else {
                Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
                CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
                if (!registry.hasMappingForPattern("/webjars/**")) {
                    this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
                }

                String staticPathPattern = this.mvcProperties.getStaticPathPattern();
                if (!registry.hasMappingForPattern(staticPathPattern)) {
                    this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
                }

            }
        }

讀一下原始碼:比如所有的 /webjars/** , 都需要去 classpath:/META-INF/resources/webjars/ 找對應的資源,那什麼是webjars呢?

webjars本質就是以jar包的方式引入我們的靜態資源 , 我們以前要匯入一個靜態資原始檔,直接匯入即可。使用SpringBoot需要使用webjars,我們可以去搜索一下

網站:https://www.webjars.org/ 【網站帶看,並引入jQuery測試】

要使用jQuery,我們只要要引入jQuery對應版本的pom依賴即可!【匯入完畢,檢視webjars目錄結構,並訪問Jquery.js檔案】

匯入依賴:

        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>jquery</artifactId>
            <version>3.3.1</version>
        </dependency>

檢視目錄結構:

訪問:只要是靜態資源,SpringBoot就會去對應的路徑尋找資源,我們這裡訪問 :http://localhost:8080/webjars/jquery/3.3.1/jquery.js

 

那我們專案中要是使用自己的靜態資源該怎麼匯入呢?我們看下一行程式碼;

我們去找staticPathPattern發現第二種對映規則 : /** , 訪問當前的專案任意資源,它會去找 resourceProperties 這個類,我們可以點進去看一下,

@ConfigurationProperties(
    prefix = "spring.resources",
    ignoreUnknownFields = false
)
public class ResourceProperties {
    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};

 

resourceProperties 可以設定和我們靜態資源有關的引數,this.staticLocations = CLASSPATH_RESOURCE_LOCATIONS; 這裡面指向了它會去尋找資源的資料夾;即上面陣列的內容。再向下看一個方法;它還會在根路徑下去尋找資源!

    private String[] appendSlashIfNecessary(String[] staticLocations) {
        String[] normalized = new String[staticLocations.length];

        for(int i = 0; i < staticLocations.length; ++i) {
            String location = staticLocations[i];
            normalized[i] = location.endsWith("/") ? location : location + "/";
        }

        return normalized;
    }

所以得出結論:

"classpath:/META-INF/resources/", 
"classpath:/resources/",
 "classpath:/static/", 
"classpath:/public/",
"/" :當前專案的根目錄

我們可以在resources根目錄下新建對應的資料夾,都可以存放我們的靜態檔案;

比如我們訪問 localhost:8080/1.js, 他就會去這些資料夾中尋找對應的靜態資原始檔;

靜態資原始檔夾說完後,我們繼續看原始碼!可以看到一個歡迎頁的對映,就是我們的首頁!

        @Bean
        public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext) {
            return new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this.getWelcomePage(), this.mvcProperties.getStaticPathPattern());
        }

點進去繼續看

        private Optional<Resource> getWelcomePage() {
            String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations());
            return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
        }

        private Resource getIndexHtml(String location) {
            return this.resourceLoader.getResource(location + "index.html");
        }

歡迎頁,靜態資原始檔夾下的所有index.html頁面;被 /** 對映。

比如我訪問 localhost:8080/    ,就會找靜態資原始檔夾下的 index.html 【測試一下】

我們繼續向下閱讀原始碼,可以看到一個好玩的功能,配置我們喜歡的圖示!

 @Configuration
        @ConditionalOnProperty(
            value = {"spring.mvc.favicon.enabled"},
            matchIfMissing = true
        )
        public static class FaviconConfiguration implements ResourceLoaderAware {
            private final ResourceProperties resourceProperties;
            private ResourceLoader resourceLoader;

            public FaviconConfiguration(ResourceProperties resourceProperties) {
                this.resourceProperties = resourceProperties;
            }

            public void setResourceLoader(ResourceLoader resourceLoader) {
                this.resourceLoader = resourceLoader;
            }

            @Bean
            public SimpleUrlHandlerMapping faviconHandlerMapping() {
                SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
                mapping.setOrder(-2147483647);
                mapping.setUrlMap(Collections.singletonMap("**/favicon.ico", this.faviconRequestHandler()));
                return mapping;
            }

            @Bean
            public ResourceHttpRequestHandler faviconRequestHandler() {
                ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
                requestHandler.setLocations(this.resolveFaviconLocations());
                return requestHandler;
            }

            private List<Resource> resolveFaviconLocations() {
                String[] staticLocations = WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter.getResourceLocations(this.resourceProperties.getStaticLocations());
                List<Resource> locations = new ArrayList(staticLocations.length + 1);
                Stream var10000 = Arrays.stream(staticLocations);
                ResourceLoader var10001 = this.resourceLoader;
                this.resourceLoader.getClass();
                var10000.map(var10001::getResource).forEach(locations::add);
                locations.add(new ClassPathResource("/"));
                return Collections.unmodifiableList(locations);
            }
        }

"**/favicon.ico" , 就是我們的圖示定義的格式!也是在我們的靜態資原始檔夾下定義,我們可以自定義來測試一下 【測試】

自己放一個圖示進去,然後在配置檔案中關閉SpringBoot預設的圖示!

#關閉預設圖示
spring.mvc.favicon.enabled=false

清除瀏覽器快取!重新整理網頁,發現圖示已經變成自己的了!

我們也可以自己通過配置檔案來指定一下,哪些資料夾是需要我們放靜態資原始檔的,在application.properties中配置;

spring.resources.static-locations=classpath:/hello/,classpath:/kuang/

一旦自己定義了靜態資料夾的路徑,原來的就都會失效了!

模板引擎

前端交給我們的頁面,是html頁面。如果是我們以前開發,我們需要把他們轉成jsp頁面,jsp好處就是當我們查出一些資料轉發到JSP頁面以後,我們可以用jsp輕鬆實現資料的顯示,及互動等。jsp支援非常強大的功能,包括能寫Java程式碼,但是呢,我們現在的這種情況,SpringBoot這個專案首先是以jar的方式,不是war,像第二,我們用的還是嵌入式的Tomcat,所以呢,他現在預設是不支援jsp的。

那不支援jsp,如果我們直接用純靜態頁面的方式,那給我們開發會帶來非常大的麻煩,那怎麼辦呢,SpringBoot推薦你可以來使用模板引擎。

那麼這模板引擎,我們其實大家聽到很多,其實jsp就是一個模板引擎,還有以用的比較多的freemarker,包括SpringBoot給我們推薦的Thymeleaf,模板引擎有非常多,但再多的模板引擎,他們的思想都是一樣的,什麼樣一個思想呢我們來看一下這張圖。

模板引擎的作用就是我們來寫一個頁面模板,比如有些值呢,是動態的,我們寫一些表示式。而這些值,從哪來呢,我們來組裝一些資料,我們把這些資料找到。然後把這個模板和這個資料交給我們模板引擎,模板引擎按照我們這個資料幫你把這表示式解析、填充到我們指定的位置,然後把這個資料最終生成一個我們想要的內容給我們寫出去,這就是我們這個模板引擎,不管是jsp還是其他模板引擎,都是這個思想。只不過呢,就是說不同模板引擎之間,他們可能這個語法有點不一樣。其他的我就不介紹了,我主要來介紹一下SpringBoot給我們推薦的Thymeleaf模板引擎,這模板引擎呢,是一個高階語言的模板引擎,他的這個語法更簡單。而且呢,功能更強大。

我們呢,就來看一下這個模板引擎,那既然要看這個模板引擎。首先,我們來看SpringBoot裡邊怎麼用。

第一步:引入thymeleaf , 怎麼引入呢,對於springboot來說,什麼事情不都是一個start的事情嘛,我們去在專案中引入一下。給大家三個網址:

1、Thymeleaf 官網:https://www.thymeleaf.org/

2、Thymeleaf 在Github 的主頁:https://github.com/thymeleaf/thymeleaf

3、Spring官方文件:“https://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/htmlsingle/#using-boot-starter” , 找到我們對應的版本

        <!--thymeleaf模板-->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-java8time</artifactId>
        </dependency>

maven會自動下載jar包,我們可以去看下下載的東西;

使用Thymeleaf

前面呢,我們已經引入了Thymeleaf,那這個要怎麼使用呢?
我們首先得按照SpringBoot的自動配置原理看一下我們這個Thymeleaf的自動配置規則,在按照那個規則,我們進行使用。
我們去找一下Thymeleaf的自動配置類;

這個包下有我們的thymeleaf,看我們的配置類,我們選擇部分

@ConfigurationProperties(
    prefix = "spring.thymeleaf"
)
public class ThymeleafProperties {
    private static final Charset DEFAULT_ENCODING;
    public static final String DEFAULT_PREFIX = "classpath:/templates/";
    public static final String DEFAULT_SUFFIX = ".html";
    private boolean checkTemplate = true;
    private boolean checkTemplateLocation = true;
    private String prefix = "classpath:/templates/";
    private String suffix = ".html";
    private String mode = "HTML";
    private Charset encoding;
}

我們可以在其中看到預設的字首和字尾!我們只需要把我們的html頁面放在類路徑下的templates下,thymeleaf就可以幫我們自動渲染了。

我們可以去測試一下 , 寫一個Controller,跳轉到一個指定頁面,這個指定頁面需要在 類路徑下的模板目錄下 【演示】

使用thymeleaf什麼都不需要配置,只需要將他放在指定的資料夾下即可!

Thymeleaf語法

 要學習語法,還是參考官網文件最為準確,我們找到對應的版本看一下;

Thymeleaf 官網:https://www.thymeleaf.org/ , 簡單看一下官網!我們去下載Thymeleaf的官方文件!

我們做個最簡單的練習 : 我們需要查出一些資料,在頁面中展示

我們去在controller編寫一個請求,放進去一些資料;

    @RequestMapping("/success")
    public String success(Model model){
        //存入資料
        model.addAttribute("msg","Hello,Thymeleaf");
        //classpath:/templates/success.html
        return "success";
    }

 

我們要使用thymeleaf,需要在html檔案中匯入名稱空間的約束,方便提示。我們可以去官方文件的#3中看一下名稱空間拿來過來

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

 

我們去編寫下前端頁面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>狂神說</title>
</head>
<body>
<h1>Success</h1>

<!--th:text就是將div中的內容設定為它指定的值,和之前學習的Vue一樣-->
<div th:text="${msg}"></div>
</body>
</html>

 

OK,入門搞定,我們來認真學習一下Thymeleaf的使用語法!

一、我們可以使用任意的 th:attr 來替換Html中原生屬性的值!【測試】  全部屬性可以參考官網文件#10; th語法

二、我們能寫那些表示式呢?我們可以看到官方文件 #4

Simple expressions:(表示式語法)
Variable Expressions: ${...}:獲取變數值;OGNL; 1)、獲取物件的屬性、呼叫方法 2)、使用內建的基本物件: #18 #ctx : the context object. #vars: the context variables. #locale : the context locale. #request : (only in Web Contexts) the HttpServletRequest object. #response : (only in Web Contexts) the HttpServletResponse object. #session : (only in Web Contexts) the HttpSession object. #servletContext : (only in Web Contexts) the ServletContext object. ${session.foo} 3)、內建的一些工具物件:       #execInfo : information about the template being processed.       #messages : methods for obtaining externalized messages inside variables expressions, in the same way as they would be obtained using #{…} syntax.       #uris : methods for escaping parts of URLs/URIs       #conversions : methods for executing the configured conversion service (if any).       #dates : methods for java.util.Date objects: formatting, component extraction, etc.       #calendars : analogous to #dates , but for java.util.Calendar objects.       #numbers : methods for formatting numeric objects.       #strings : methods for String objects: contains, startsWith, prepending/appending, etc.       #objects : methods for objects in general.       #bools : methods for boolean evaluation.       #arrays : methods for arrays.       #lists : methods for lists.       #sets : methods for sets.       #maps : methods for maps.       #aggregates : methods for creating aggregates on arrays or collections.       #ids : methods for dealing with id attributes that might be repeated (for example, as a result of an iteration).
============================================================================================== Selection Variable Expressions: *{...}:選擇表示式:和${}在功能上是一樣; 補充:配合 th:object="${session.user}:
<div th:object="${session.user}"> <p>Name: <span th:text="*{firstName}">Sebastian</span>.</p> <p>Surname: <span th:text="*{lastName}">Pepper</span>.</p> <p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p> </div> Message Expressions: #{...}:獲取國際化內容 Link URL Expressions: @{...}:定義URL; @{/order/process(execId=${execId},execType='FAST')}
Fragment Expressions: ~{...}:片段引用表示式 <div th:insert="~{commons :: main}">...</div> Literals(字面量) Text literals: 'one text' , 'Another one!' ,… Number literals: 0 , 34 , 3.0 , 12.3 ,… Boolean literals: true , false Null literal: null Literal tokens: one , sometext , main ,… Text operations:(文字操作) String concatenation: + Literal substitutions: |The name is ${name}| Arithmetic operations:(數學運算) Binary operators: + , - , * , / , % Minus sign (unary operator): - Boolean operations:(布林運算) Binary operators: and , or Boolean negation (unary operator): ! , not Comparisons and equality:(比較運算) Comparators: > , < , >= , <= ( gt , lt , ge , le ) Equality operators: == , != ( eq , ne ) Conditional operators:條件運算(三元運算子) If-then: (if) ? (then) If-then-else: (if) ? (then) : (else) Default: (value) ?: (defaultvalue) Special tokens: No-Operation: _

 

 測試練習

 我們編寫一個Controller,放一些資料

    @RequestMapping("/success2")
    public String success2(Map<String,Object> map){
        //存入資料
        map.put("msg","<h1>Hello</h1>");
        map.put("users", Arrays.asList("qinjiang","kuangshen"));
        //classpath:/templates/success.html
        return "success";
    }

 

前端

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>狂神說</title>
</head>
<body>
<h1>Success</h1>

<div th:text="${msg}"></div>
<!--不轉義-->
<div th:utext="${msg}"></div>

<!--遍歷資料-->
<!--th:each每次遍歷都會生成當前這個標籤:官網#9-->
<h4 th:each="user :${users}" th:text="${user}"></h4>

<h4>
    <!--行內寫法:官網#12-->
    <span th:each="user:${users}">[[${user}]]</span>
</h4>

</body>
</html>

很多樣式,我們即使現在學習了,也會忘記,所以我們在學習過程中,需要使用什麼,根據官方文件來查詢,才是最重要的,要熟練使用官方文件!

SpringMVC自動配置

在進行測試前,我們還需要知道一個東西,就是SpringBoot 對我們的SpringMVC還做了哪些配置,包括如何擴充套件,如何定製。只有把這些都搞清楚了,我們在之後使用才會更加得心應手。 途徑一:原始碼分析,途徑二:官方文件

地址 : https://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/html/boot-features-developing-web-applications.html

我們來閱讀一段官方文件:

29.1.1 Spring MVC Auto-configuration
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.

The auto-configuration adds the following features on top of Spring’s defaults:

//包含檢視解析器 Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
//支援靜態資原始檔夾的路徑,以及webjars Support for serving static resources, including support for WebJars (covered later in this document)).
//自動註冊了Converter:【轉換器,這就是我們網頁提交資料到後臺自動封裝成為物件的東西,比如把18字串自動轉換為int型別】
//Formatter:【格式化器,比如頁面給我們了一個2019-8-10,它會給我們自動格式化為Date物件】 Automatic registration of Converter, GenericConverter, and Formatter beans.
//HttpMessageConverters:SpringMVC用來轉換Http請求和響應的的,比如我們要把一個User物件轉換為JSON字串,可以去看官網文件解釋; Support for HttpMessageConverters (covered later in this document).
//定義錯誤程式碼生成規則的 Automatic registration of MessageCodesResolver (covered later in this document).
//首頁定製 Static index.html support.
//圖示定製 Custom Favicon support (covered later in this document).
//初始化資料繫結器:幫我們把請求資料繫結到JavaBean中! Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).
If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components. If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.

我們來仔細對照,看一下它怎麼實現的,它告訴我們SpringBoot已經幫我們自動配置好了SpringMVC,然後自動配置了哪些東西呢?

ContentNegotiatingViewResolver

自動配置了ViewResolver,就是我們之前學習的SpringMVC的檢視解析器:即根據方法的返回值取得檢視物件(View),然後由檢視物件決定如何渲染(轉發,重定向)。我們去看看這裡的原始碼:我們找到 WebMvcAutoConfiguration , 然後搜尋ContentNegotiatingViewResolver。找到如下方法!

        @Bean //我們在這裡確實看到已經給容器中註冊了一個bean
        @ConditionalOnBean({ViewResolver.class})
        @ConditionalOnMissingBean(
            name = {"viewResolver"},
            value = {ContentNegotiatingViewResolver.class}
        )
        public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
            ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
            resolver.setContentNegotiationManager((ContentNegotiationManager)beanFactory.getBean(ContentNegotiationManager.class));
            resolver.setOrder(-2147483648);
            return resolver;
        }

我們可以點進這類看看!找到對應的解析檢視的程式碼

註解說明:@Nullable 即引數可為null

    @Nullable
    public View resolveViewName(String viewName, Locale locale) throws Exception {
        RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
        Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
        List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
        if (requestedMediaTypes != null) {
//獲取候選的檢視物件 List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
//選擇一個最適合的檢視物件,然後把這個物件返回 View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs); if (bestView != null) { return bestView; } } String mediaTypeInfo = this.logger.isDebugEnabled() && requestedMediaTypes != null ? " given " + requestedMediaTypes.toString() : ""; if (this.useNotAcceptableStatusCode) { if (this.logger.isDebugEnabled()) { this.logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo); } return NOT_ACCEPTABLE_VIEW; } else { this.logger.debug("View remains unresolved" + mediaTypeInfo); return null; } }

我們繼續點進去看,他是怎麼獲得候選的檢視的呢? getCandidateViews中看到他是把所有的檢視解析器拿來,進行while迴圈,挨個解析!

 Iterator var5 = this.viewResolvers.iterator();

所以得出結論:ContentNegotiatingViewResolver 這個檢視解析器就是用來組合所有的檢視解析器的 

我們再去研究下他的組合邏輯,看到有個屬性viewResolvers,看看它是在哪裡進行賦值的!

  protected void initServletContext(ServletContext servletContext) {
//這裡它是從beanFactory工具中獲取容器中的所有檢視解析器,ViewRescolver.class , 把所有的檢視解析器來組合的 Collection<ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.obtainApplicationContext(), ViewResolver.class).values(); ViewResolver viewResolver; if (this.viewResolvers == null) { this.viewResolvers = new ArrayList(matchingBeans.size());

既然它是在容器中去找檢視解析器,我們是否可以猜想,我們就可以去實現定製了呢?

我們可以自己給容器中去新增一個檢視解析器;這個類就會幫我們自動的將它組合進來;我們去實現一下

我們在我們的主程式中去寫一個檢視解析器來試試;

    @Bean //放到bean中
    public ViewResolver myViewResolver(){
        return new MyViewResolver();
    }

    //我們寫一個靜態內部類,檢視解析器就需要實現ViewResolver介面
    private static class MyViewResolver implements ViewResolver{
        @Override
        public View resolveViewName(String s, Locale locale) throws Exception {
            return null;
        }
    }

怎麼看我們自己寫的檢視解析器有沒有起作用呢?我們給dispatcherServlet中的 doDispatch方法加個斷點進行除錯一下,因為所有的請求都會走到這個方法中

我們啟動我們的專案,然後隨便訪問一個頁面,看一下Debug資訊;

找到this;

找到檢視解析器,我們看到我們自己定義的就在這裡了;

所以說,我們如果想要使用自己定製化的東西,我們只需要給容器中新增這個元件就好了!剩下的事情SpringBoot就會幫我們做了

轉換器和格式化器

找到格式化轉換器

        @Bean
        public FormattingConversionService mvcConversionService() {
//拿到配置檔案中的格式化規則 WebConversionService conversionService = new WebConversionService(this.mvcProperties.getDateFormat()); this.addFormatters(conversionService); return conversionService; }

點選去

    public String getDateFormat() {
        return this.dateFormat;
    }

可以看到在我們的Properties檔案中,我們可以進行自動配置它!如果註冊了自己的格式化方式,就會註冊到Bean中,否則不會註冊

我們可以在配置檔案中配置日期格式化的規則:

修改SpringBoot的預設配置

方式一

這麼多的自動配置,原理都是一樣的,通過這個WebMVC的自動配置原理分析,我們要學會一種學習方式,通過原始碼探究,得出結論;這個結論一定是屬於自己的,而且一通百通。

SpringBoot的底層,大量用到了這些設計細節思想,所以,沒事需要多閱讀原始碼!得出結論;

SpringBoot在自動配置很多元件的時候,先看容器中有沒有使用者自己配置的(如果使用者自己配置@bean),如果有就用使用者配置的,如果沒有就用自動配置的;如果有些元件可以存在多個,比如我們的檢視解析器,就將使用者配置的和自己預設的組合起來!

擴充套件使用SpringMVC

If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.

我們要做的就是編寫一個@Configuration註解類,並且型別要為WebMvcConfigurer,還不能標註@EnableWebMvc註解;我們去自己寫一個;

我們新建一個包叫config,寫一個類MyMvcConfig;

package com.kuang.myproject.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

//應為型別要求為WebMvcConfigurer,所以我們實現其介面
//可以使用自定義類擴充套件MVC的功能
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        //瀏覽器傳送/kuang , 就會跳轉到success頁面;
        registry.addViewController("/kuang").setViewName("success");
    }
}

我們去瀏覽器訪問一下:

確實也跳轉過來了!所以說,我們要擴充套件SpringMVC,官方就推薦我們這麼去使用,既保SpringBoot留所有的自動配置,也能用我們擴充套件的配置!

 我們可以去分析一下原理:

  1. WebMvcAutoConfiguration 是 SpringMVC的自動配置類,裡面有一個類WebMvcAutoConfigurationAdapter
  2. 這個類上有一個註解,在做其他自動配置時會匯入:@Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
  3. 我們點進EnableWebMvcConfiguration這個類看一下,它繼承了一個父類:DelegatingWebMvcConfiguration,這個父類中有這樣一段程式碼
  4.     private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
    
       //從容器中獲取所有的webmvcConfigurer @Autowired(required = false) public void setConfigurers(List<WebMvcConfigurer> configurers) { if (!CollectionUtils.isEmpty(configurers)) { this.configurers.addWebMvcConfigurers(configurers); } }

    我們可以在這個類中去尋找一個我們剛才設定的viewController當做參考,發現它呼叫了一個

    this.configurers.addViewControllers(registry);

    我們點進去看一下

        public void addViewControllers(ViewControllerRegistry registry) {
            Iterator var2 = this.delegates.iterator();
    
            while(var2.hasNext()) {
    //將所有的WebMvcConfigurer相關配置來一起呼叫!包括我們自己配置的和Spring給我們配置的 WebMvcConfigurer delegate = (WebMvcConfigurer)var2.next(); delegate.addViewControllers(registry); } }
  5. 所以得出結論:所有的WebMvcConfiguration都會被作用,不止Spring自己的配置類,我們自己的配置類當然也會被呼叫;

全面接管SpringMVC

 If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.

全面接管即:SpringBoot對SpringMVC的自動配置不需要了,所有都是我們自己去配置!只需在我們的配置類中要加一個@EnableWebMvc.

我們看下如果我們全面接管了SpringMVC了,我們之前SpringBoot給我們配置的靜態資源對映一定會無效,我們可以去測試一下;

 不加註解之前,訪問首頁

給配置類加上註解:@EnableWebMvc

我們發現所有的SpringMVC自動配置都失效了!迴歸到了最初的樣子;

當然,我們開發中,不推薦使用全面接管SpringMVC

思考問題?為什麼加了一個註解,自動配置就失效了!我們看下原始碼:

1.這裡發現它是匯入了一個類,我們可以繼續進去看

@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}

2.它繼承了一個父類

public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
}

3.我們來回顧一下Webmvc自動配置類

@Configuration
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
//這個註解的意思就是:容器中沒有這個元件的時候,這個自動配置類才生效 @ConditionalOnMissingBean({WebMvcConfigurationSupport.class}) @AutoConfigureOrder(-2147483638) @AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class}) public class WebMvcAutoConfiguration { }

4. 總結一句話:@EnableWebMvc將WebMvcConfigurationSupport元件匯入進來了;而匯入的WebMvcConfigurationSupport只是SpringMVC最基本的功能!

在SpringBoot中會有非常多的***Configurer幫助我們進行擴充套件配置,只要看見了這個,我們就應該多留心注意~

RestfulCRUD

準備工作

我們現在可以來匯入我們的所有提供的資源!

pojo 及 dao 放到專案對應的路徑下:

pojo實體類

package com.kuang.myproject.pojo;

public class Department {

    private Integer id;
    private String departmentName;

    public Department() {
    }
    
    public Department(int i, String string) {
        this.id = i;
        this.departmentName = string;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getDepartmentName() {
        return departmentName;
    }

    public void setDepartmentName(String departmentName) {
        this.departmentName = departmentName;
    }

    @Override
    public String toString() {
        return "Department [id=" + id + ", departmentName=" + departmentName + "]";
    }
    
}
Department.java
package com.kuang.myproject.pojo;

import java.util.Date;

public class Employee {

    private Integer id;
    private String lastName;

    private String email;
    //1 male, 0 female
    private Integer gender;
    private Department department;
    private Date birth;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Integer getGender() {
        return gender;
    }

    public void setGender(Integer gender) {
        this.gender = gender;
    }

    public Department getDepartment() {
        return department;
    }

    public void setDepartment(Department department) {
        this.department = department;
    }

    public Date getBirth() {
        return birth;
    }

    public void setBirth(Date birth) {
        this.birth = birth;
    }
    public Employee(Integer id, String lastName, String email, Integer gender,
                    Department department) {
        super();
        this.id = id;
        this.lastName = lastName;
        this.email = email;
        this.gender = gender;
        this.department = department;
        this.birth = new Date();
    }

    public Employee() {
    }

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", lastName='" + lastName + '\'' +
                ", email='" + email + '\'' +
                ", gender=" + gender +
                ", department=" + department +
                ", birth=" + birth +
                '}';
    }
    
    
}
Employee.java

 

dao層

package com.kuang.myproject.dao;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import com.kuang.myproject.pojo.Department;
import org.springframework.stereotype.Repository;

@Repository
public class DepartmentDao {

    private static Map<Integer, Department> departments = null;
    
    static{
        departments = new HashMap<Integer, Department>();
        
        departments.put(101, new Department(101, "D-AA"));
        departments.put(102, new Department(102, "D-BB"));
        departments.put(103, new Department(103, "D-CC"));
        departments.put(104, new Department(104, "D-DD"));
        departments.put(105, new Department(105, "D-EE"));
    }
    
    public Collection<Department> getDepartments(){
        return departments.values();
    }
    
    public Department getDepartment(Integer id){
        return departments.get(id);
    }
    
}
DepartmentDao
package com.kuang.myproject.dao;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import com.kuang.myproject.pojo.Department;
import com.kuang.myproject.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;


@Repository
public class EmployeeDao {

    private static Map<Integer, Employee> employees = null;
    
    @Autowired
    private DepartmentDao departmentDao;
    
    static{
        employees = new HashMap<Integer, Employee>();

        employees.put(1001, new Employee(1001, "E-AA", "[email protected]", 1, new Department(101, "D-AA")));
        employees.put(1002, new Employee(1002, "E-BB", "[email protected]", 1, new Department(102, "D-BB")));
        employees.put(1003, new Employee(1003, "E-CC", "[email protected]", 0, new Department(103, "D-CC")));
        employees.put(1004, new Employee(1004, "E-DD", "[email protected]", 0, new Department(104, "D-DD")));
        employees.put(1005, new Employee(1005, "E-EE", "[email protected]", 1, new Department(105, "D-EE")));
    }
    
    private static Integer initId = 1006;
    
    public void save(Employee employee){
        if(employee.getId() == null){
            employee.setId(initId++);
        }
        
        employee.setDepartment(departmentDao.getDepartment(employee.getDepartment().getId()));
        employees.put(employee.getId(), employee);
    }
    
    public Collection<Employee> getAll(){
        return employees.values();
    }
    
    public Employee get(Integer id){
        return employees.get(id);
    }
    
    public void delete(Integer id){
        employees.remove(id);
    }
}
EmployeeDao

 

匯入完畢這些之後,我們還需要匯入我們的前端頁面,及靜態資原始檔!

  • css,js等放在static資料夾下
  • html放在templates資料夾下

準備工作:OK!!!

首頁實現

要求一:預設訪問首頁

方式一:寫一個controller實現!

    //會解析到templates目錄下的index.html頁面
    @RequestMapping({"/","/index.html"})
    public String index(){
        return "index";
    }

 

方式二:自己編寫MVC的擴充套件配置

package com.kuang.myproject.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index.html").setViewName("index");
    }
}

 

解決了這個問題,我們還需要解決一個資源匯入的問題;

我們講我們專案的啟動名改掉

server.servlet.context-path=/kuang

 

現在你訪問localhost:8080  就不行了,需要訪問localhost:8080/kuang

為了保證資源匯入穩定,我們建議在所有資源匯入時候使用 th:去替換原有的資源路徑!

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

 

比如以上這樣,無論我們專案名稱如何變化,它都可以自動的尋找到!看原始碼就可以看出來區別

 

頁面國際化

第一步 :編寫國際化配置檔案,抽取頁面需要顯示的國際化頁面訊息。我們可以去登入頁面檢視一下

先在IDEA中統一設定properties的編碼問題!

 

我們在resources資原始檔下新建一個i18n目錄,建立一個login.propetries檔案,還有一個login_zh_CN.properties,發現IDEA自動識別了我們要做國際化操作;資料夾變了

我們可以在這上面去新建一個檔案;

彈出如下頁面:我們再新增一個英文的;

這樣就快捷多了!

 

接下來,我們就來編寫配置,我們可以看到idea下面有另外一個檢視;

這個檢視我們點選 + 號就可以直接新增屬性了;我們新建一個login.tip,可以看到邊上有三個檔案框可以輸入

我們新增一下首頁的內容!

然後依次新增其他頁面內容即可!

 

然後去檢視我們的配置檔案;

login.properties : 預設

login.btn=登入
login.password=密碼
login.remember=記住我
login.tip=請登入
login.username=使用者名稱

 

 英文:

login.btn=Sign in
login.password=Password
login.remember=Remember me
login.tip=Please sign in
login.username=Username

 

中文:

login.btn=登入
login.password=密碼
login.remember=記住我
login.tip=請登入
login.username=使用者名稱

 

 OK,搞定!

 

第二步 :我們去看一下SpringBoot對國際化的自動配置!

這裡又涉及到一個類: MessageSourceAutoConfiguration ,裡面有一個方法,這裡發現SpringBoot已經自動配置好了管理我們國際化資原始檔的元件 ResourceBundleMessageSource;

public class MessageSourceAutoConfiguration {
    private static final Resource[] NO_RESOURCES = new Resource[0];

    public MessageSourceAutoConfiguration() {
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.messages") //我們的配置檔案可以直接放在類路徑下叫: messages.properties, 就可以進行國際化操作了
    public MessageSourceProperties messageSourceProperties() {
        return new MessageSourceProperties();
    }

    @Bean
    public MessageSource messageSource(MessageSourceProperties properties) {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        if (StringUtils.hasText(properties.getBasename())) {
        //設定國際化檔案的基礎名(去掉語言國家程式碼的) messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename()))); } if (properties.getEncoding() != null) { messageSource.setDefaultEncoding(properties.getEncoding().name()); } messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale()); Duration cacheDuration = properties.getCacheDuration(); if (cacheDuration != null) { messageSource.setCacheMillis(cacheDuration.toMillis()); } messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat()); messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage()); return messageSource; } }

 

 

 我們真實 的情況是放在了i18n目錄下,所以我們要去配置這個messages的路徑;

spring.messages.basename=i18n.login

第三步 : 去頁面獲取國際化的值;

檢視Thymeleaf的文件,找到message取值操作為: #{...}。

我們去頁面測試下;

其餘同理!IDEA還有提示,非常智慧的!

我們可以去開啟專案,訪問一下,發現已經自動識別為中文的了!

但是我們想要更好!可以根據按鈕自動切換中文英文!

在Spring中有一個國際化的Locale (區域資訊物件);裡面有一個叫做LocaleResolver (獲取區域資訊物件)的解析器

我們去我們webmvc自動配置檔案,尋找一下!看到SpringBoot預設配置了

        @Bean
        @ConditionalOnMissingBean
        @ConditionalOnProperty(
            prefix = "spring.mvc",
            name = {"locale"}
        )
        public LocaleResolver localeResolver() {
//容器中沒有就自己配,有的話就用使用者配置的 if (this.mvcProperties.getLocaleResolver() == org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties.LocaleResolver.FIXED) { return new FixedLocaleResolver(this.mvcProperties.getLocale()); } else { AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); localeResolver.setDefaultLocale(this.mvcProperties.getLocale()); return localeResolver; } }

 

 AcceptHeaderLocaleResolver 這個類中有一個方法

    public Locale resolveLocale(HttpServletRequest request) {
        Locale defaultLocale = this.getDefaultLocale();
//預設的就是根據請求頭帶來的區域資訊獲取Locale進行國際化 if (defaultLocale != null && request.getHeader("Accept-Language") == null) { return defaultLocale; } else { Locale requestLocale = request.getLocale(); List<Locale> supportedLocales = this.getSupportedLocales(); if (!supportedLocales.isEmpty() && !supportedLocales.contains(requestLocale)) { Locale supportedLocale = this.findSupportedLocale(request, supportedLocales); if (supportedLocale != null) { return supportedLocale; } else { return defaultLocale != null ? defaultLocale : requestLocale; } } else { return requestLocale; } } }

 

那假如我們現在想點選連結讓我們的國際化資源生效,就需要讓我們自己的locale生效!

我們去自己寫一個自己的LocaleResolver,可以在連結上攜帶區域資訊!

修改一下前端頁面的跳轉連線;

//這裡傳入引數不需要使用 ?  使用 (key=value)
<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a> <a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>

 

我們去寫一個處理的元件類

package com.kuang.myproject.component;

import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

//可以在連結上攜帶區域資訊
public class MyLocaleResolver implements LocaleResolver {

    //解析請求
    @Override
    public Locale resolveLocale(HttpServletRequest request) {

        String language = request.getParameter("l");
        Locale locale = Locale.getDefault(); //如果沒有獲取到就使用系統預設的
        //如果請求連結不為空
        if (!StringUtils.isEmpty(language)){
            //分割請求引數
            String[] split = language.split("_");
            //國家,地區
            locale = new Locale(split[0],split[1]);
        }
        return locale;
    }

    @Override
    public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {

    }
}

 

為了讓我們的區域化資訊能夠生效,我們需要再配置一下這個元件!在我們自己的MvcConofig下新增bean;

    @Bean
    public LocaleResolver localeResolver(){
        return new MyLocaleResolver();
    }

 

我們重啟專案,來訪問一下,發現點選按鈕可以實現成功切換!

登入 + 攔截器

我們這裡就先不連線資料庫了,輸入任意使用者名稱都可以登入成功!

宣告一個之前沒有提到的問題:

templates下的頁面只能通過Controller跳轉實現,而static下的頁面是能直接被外界訪問的,就能正常訪問了。

我們把登入頁面的表單提交地址寫一個controller!

<form class="form-signin" action="dashboard.html" th:action="@{/user/login}" method="post">
//這裡面的所有表單標籤都需要加上一個name屬性
</form>

 

去編寫對應的controller

package com.kuang.myproject.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class LoginController {

    //@RequestMapping(value = "/user/login",method = RequestMethod.POST)
    @PostMapping("/user/login")
    public String login(@RequestParam("username") String username,
                        @RequestParam("password") String password,
                        Model model){

        if (!StringUtils.isEmpty(username) && "123456".equals(password)){
            //登入成功!
            return "dashboard";
        }else {
            //登入失敗!存放錯誤資訊
            model.addAttribute("msg","使用者名稱密碼錯誤");
            return "index";
        }

    }

}

頁面存在快取,所以我們需要禁用模板引擎的快取

#禁用模板快取
spring.thymeleaf.cache=false

 模板引擎修改後,想要實時生效!頁面修改完畢後,IDEA小技巧 : Ctrl + F9  重新編譯!

OK ,測試登入成功!如果模板出現500錯誤,參考處理連線:https://blog.csdn.net/fengzyf/article/details/83341479

登入成功,發現了靜態資源匯入問題!

登入失敗的話,我們需要將後臺資訊輸出到前臺,可以在首頁標題下面加上判斷!

<!--判斷是否顯示,使用if, ${}可以使用工具類,可以看thymeleaf的中文文件-->
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>

重新整理測試 :

優化,登入成功後,由於是轉發,連結不變,我們可以重定向到首頁!

我們再新增一個檢視控制對映,在我們的自己的MyMvcConfig中:

registry.addViewController("/main.html").setViewName("dashboard");

將 Controller 的程式碼改為重定向;

//登入成功!防止表單重複提交,我們重定向
return "redirect:/main.html";

重定向成功之後!我們解決了之前資源沒有載入進來的問題!後臺主頁正常顯示!

但是又發現新的問題,我們可以直接登入到後臺主頁,不用登入也可以實現!

怎麼處理這個問題呢?我們可以使用攔截器機制,實現登入檢查!

我們先自定義一個攔截器

package com.kuang.myproject.component;

import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LoginHandlerInterceptor implements HandlerInterceptor {
    //目標方法執行之前
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Object user = request.getSession().getAttribute("loginUser");
        if (user == null){//未登入,返回登入頁面
            request.setAttribute("msg","沒有許可權,請先登入");
            request.getRequestDispatcher("/index.html").forward(request,response);
            return false;
        }else {
            //登入,放行
            return true;
        }
    }
}

然後將攔截器註冊到我們的SpringMVC配置類當中!

    //註冊攔截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //註冊攔截器,及攔截請求和要剔除哪些請求!
        //我們還需要過濾靜態資原始檔,否則樣式顯示不出來
        registry.addInterceptor(new LoginHandlerInterceptor())
            .addPathPatterns("/**").excludePathPatterns("/index.html","/","/user/login","/asserts/**");
    }

 

我們然後在後臺主頁,獲取使用者登入的資訊

<!--後臺主頁顯示登入使用者的資訊-->
[[${session.loginUser}]]

然後我們登入測試!完美!

員工列表功能

要求 : 我們需要使用 Restful風格實現我們的crud操作!

看看一些具體的要求,就是我們小實驗的架構;

我們根據這些要求,來完成第一個功能,就是我們的員工列表功能!

我們在主頁點選Customers,就顯示列表頁面;我們去修改下

1.將首頁的側邊欄Customers改為員工管理

2.a連結新增請求

<a class="nav-link" href="#" th:href="@{/emps}">員工管理</a>

 

3.將list放在emp資料夾下

 

4.編寫處理請求的controller

@Controller
public class EmployeeController {

    @Autowired
    EmployeeDao employeeDao;

    //查詢所有員工,返回列表頁面
    @GetMapping("/emps")
    public String list(Model model){
        Collection<Employee> employees = employeeDao.getAll();
        //將結果放在請求中
        model.addAttribute("emps",employees);
        return "emp/list";
    }

}

 

我們啟動專案,測試一下看是否能夠跳轉,測試OK!我們只需要將資料渲染進去即可!

但是發現一個問題,側邊欄和頂部都相同,我們是不是應該將它抽取出來呢?

Thymeleaf 公共頁面元素抽取

1.抽取公共片段 th:fragment  定義模板名

2.引入公共片段  th:insert  插入模板名

我們來抽取一下,使用list列表做演示!我們要抽取頭部,nav標籤

我們在dashboard中將nav部分定義一個模板名;

        <nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar" >
            <!--後臺主頁顯示登入使用者的資訊-->
            <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]</a>
            <input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
            <ul class="navbar-nav px-3">
                <li class="nav-item text-nowrap">
                    <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Sign out</a>
                </li>
            </ul>
        </nav>

 

然後我們在list頁面中去引入,可以刪掉原來的nav

        <!--引入抽取的topbar-->
        <!--模板名 : 會使用thymeleaf的前後綴配置規則進行解析
        使用~{模板::標籤名}-->
        <div th:insert="~{dashboard::topbar}"></div>

 

效果:可以看到已經成功載入過來了!

 

 除了使用insert插入,還可以使用replace替換,或者include包含,三種方式會有一些小區別,可以見名知義;

我們使用replace替換,可以解決div多餘的問題,可以檢視thymeleaf的文件學習

側邊欄也是同理,當做練手,可以也同步一下!

保證這一步做完!

我們發現一個小問題,側邊欄啟用的問題,它總是啟用第一個;按理來說,這應該是動態的才對!

為了重用更清晰,我們建立一個commons資料夾,專門存放公共頁面;

我們去頁面中引入一下

<div th:replace="~{commons/bar::topbar}"></div>
<div th:replace="~{commons/bar::sitebar}"></div>

 

我們先測試一下,保證所有的頁面沒有出問題!ok!

我們來解決我們側邊欄啟用問題!

1.將首頁的超連結地址改到專案中

2.我們在a標籤中加一個判斷,使用class改變標籤的值;

 <a class="nav-link active" th:class="${activeUrl=='main.html'?'nav-link active':'nav-link'}" href="#" th:href="@{/main.html}">
其餘同理,判斷請求攜帶的引數,進行啟用測試

 

3.修改請求連結

<div th:replace="~{commons/bar::sitebar(activeUrl='main.html')}"></div>
<div th:replace="~{commons/bar::sitebar(activeUrl='emps')}"></div>
其餘要用都是同理。

 

我們重新整理頁面,去測試一下,OK,動態啟用搞定!

現在我們來遍歷我們的員工