2021年1月14日 星期四

[SpringBoot 1.5] SpringMVC自動配置

 

以下是SpringBoot文件的網址

https://docs.spring.io/spring-boot/docs/1.5.12.RELEASE/reference/htmlsingle/#boot-features-developing-web-applications


1. auto-configuration

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

Spring Boot自動配置好了SpringMVC

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

他的自動配置包含了以下Spring的初始特色

  • Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.

ContentNegotiatinViewResolver:用來組合所有視圖解析器的

他自動配置了ViewResolver(視圖解析器:根據方法來取得試圖對象(View), 由視圖對象決定可轉載或著重定向)

制定方式: 

在main application下面定義回傳ViewResolver的方法

回傳一個私有的繼承ViewResolver的類別

@Bean
public ViewResolver myViewResolver(){
return new MyViewResolver();
}

private class MyViewResolver implements ViewResolver {
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
}

我們如何知道這個會work呢?

主要是使用debug模式觀察DispatcherServlet中的doDispatch方法(用control+n找)

將中斷點下在這裡

當我們啟動之後, 訪問一個頁面, 我們就會看到

在this中他有multipartResolver文件上傳的, localeResolver國際化功能的Resolver

而我們要看的是viewResolvers中有我們的MyViewResolver的確進來了

也就是只要通過添加組件(Bean)就能夠使用





  • Support for serving static resources, including support for WebJars (see below).靜態資源文件夾路徑 webjars

  • Static index.html support. 靜態首頁訪問

  • Custom Favicon support (see below).favicon.ico

  • Automatic registration of ConverterGenericConverterFormatter beans.

    • 自動註冊ConverterGenericConverterFormatter
    • Converter:轉換器;public String hello(User user);類別轉換使用
    • Formatter:格式化器;2017-12-17===Date;
  • 自己添加的格式化器转换器,我们只需要放在容器中即可;

  • Support for HttpMessageConverters (see below).

    • HttpMessageConverters:SpringMVC用來轉換Http請求和響應的;User---json;

    • HttpMessageConverters:是從容器中確定的;獲取所有的HttpMessageConverters;

      ​ 自己给容器中添加HttpMessageConverter,只需要將自己的组件注册到容器中(@Bean,@Component)

  • Automatic registration of MessageCodesResolver (see below).

    • 定義錯誤代碼產生規則
  • Automatic use of a ConfigurableWebBindingInitializer bean (see below).

    • 我們可以配置一个ConfigurableWebBindingInitializer來替換默認的;(添加到容器)

---------------------------

2. 擴展SpringMVC

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

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

-------------------------

這段的意思就是~如果有些Spring MVC的功能不在初始設定中

但是你想要使用的話, 你可以新增一個類別符合他的設定, 他就會掃得到裡面的設定

那要注意的是, 如果加上@EnableWebMvc的話

對於SpringMVC的配置, 你就要自己全部重配了

連初始會去掃靜態頁面的功能, 也會需要自己手動建立導頁

------------------------

比方說以下我們想使用Spring轉頁功能, 將hello頁面轉到success頁面

 <mvc:view-controller path="/hello" view-name="success"/>
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/hello"/>
            <bean></bean>
        </mvc:interceptor>
    </mvc:interceptors>

以上在SpringMVC中的設置

我們在SpringBoot中可以用以下方式完成

先建立config.MymvcConfig類別, 去繼承WebMvcConfigurerAdapter類別

其中的addViewController方法就能完成我們要做的事(可用control+o查看所有方法)

 

//使用WebMvcConfigurerAdapter 可以來擴展SpringMVC的功能
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
//control+O 可以找到父類所有可用方法

@Override
public void addViewControllers(ViewControllerRegistry registry) {
//super.addViewControllers(registry);
//瀏覽器發送/atguigu請求來到success
registry.addViewController("/atguigu").setViewName("success");
}
}

-----------------------------------

原理:

​ 1)、WebMvcAutoConfiguration是SpringMVC的自動配置類

​ 2)、在做其他配置時會導入:@Import(EnableWebMvcConfiguration.class)

	@Configuration
	public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
		private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();

	// 從容器中獲取所有WebMvcConfigurer
	@Autowired(required = false)
	public void setConfigurers(List<WebMvcConfigurer> configurers) {
		if (!CollectionUtils.isEmpty(configurers)) {
			this.configurers.addWebMvcConfigurers(configurers);
			// 一個參考實現:將所有的WebMvcConfigurer相關配置都來一起調用;
              //  @Override
              //  public void addViewControllers(ViewControllerRegistry registry) {
              //      for (WebMvcConfigurer delegate : this.delegates) {
              //          delegate.addViewControllers(registry);
              //      }
              /  }
		}
	}

​ 3)、容器中所有的WebMvcConfigurer都會一起起作用;

​ 4)、我們的配置類也會被調用;

​ 效果:SpringMVC的自動配置和我們的擴展配置都會起作用。


參考 https://github.com/sheep-cloud/Spring-Boot

2021年1月12日 星期二

[SpringBoot 1.5] 引入webjars和靜態頁面

 1 /webjars/**, 都去classpath:/Meta-INF/resources/webjars/找資源

webjars:以jar包的方式引入靜態資源

可以到網站 https://www.webjars.org/ 找尋需要用到的jar包

比方說我們找到Jquery3.3.1版本的Maven設置

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

然後我們將其貼到SpingBoot專案的pom.xml當中

重新download jar檔案

我們啟動服務後, 就可以在 

http://localhost:8080/webjars/jquery/3.3.1/jquery.js

找到完整的檔案

2 靜態資源可以被找到的路徑如下

"classpath:/META-INF/resources/",
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/"
"/":當前項目的根路徑

3歡迎頁面

也就是index.html頁面

在所有靜態資源下的index.html頁面都會被找到

當你有此頁面的時候

只要輸入 http://localhost:8080/

他會自動導入到首頁



2021年1月11日 星期一

[SpringBoot 1.5] SLF4j使用

查詢slf4j可以找到以下連結

http://www.slf4j.org/

點進去以後再點選SLF4J user manual

其中他就有把範例列出來

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld {
 
public static void main(String[] args) {
   
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
    logger
.info("Hello World");
 
}
}











這一張圖的意思就是,SLF4J API 是中間抽象層

如果要將日誌印出來, 實現層有不同的做法, 都是屬於藍色的部分

而由於有些日誌項目推出較早, 不是基於SLF4J實現的, 因此需要中間的adaption layer

來做中間的轉換, 各實現層也有不同的jar包要導入

然而~你想使用slf4j+logback你可能會遇到Spring是用commons-loggin而

hibernate是用jboss-logging的問題, 其他框架, 也有可能是用不同的紀錄log模式

就會需要做替換的動作, 可以參考首頁中點入legacy APIs.











這裡所做的就是替換的意思

將commons logging API 替換成jcl-over-slf4j.jar

將log4j API 替換成log4j-over-slf4j.jar

因此如何將系統中所有的日誌統一到slf4j

1 將系統中其他日誌框架先排除出去

2 用中間包來替換原有的日誌框架

3 我們導入sl4j其他的實現

----------------------------------------------------------

而Spring Boot也是這樣替換的

這部分我們可以看到pom.xml檔案

點Diagrams->Show Dependencies

其中

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<version>2.4.1</version>
<scope>compile</scope>
</dependency>
他底層依賴關係如下









這部分沒觀察出來怎麼替換的, 

就我的觀察看起來蠻像jul-to-slf4j他使用maven的dependencies對到SLF4J

有引進slf4j的依賴

講者是說由其他的配置轉成slf4j然後用logback的方式進行實現


-------------------------

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class SpringBoot03LoggingApplicationTests {
Logger logger =LoggerFactory.getLogger(getClass());
@Test
void contextLoads() {
logger.trace("trace日誌");
logger.debug("debug日誌");
logger.info("info日誌");
logger.warn("warn日誌");
logger.error("error日誌");
}

}
優先順序從低到高, 是從trace到error
預設只會顯示info 以上的log, 除非有設logging.level
--------------------------
appication.properties檔案
這樣寫檔會寫在C巢下面, 記得filename要重複path頭
logging.level.com.atguigu=trace
logging.file.path=/spring/logger
logging.file.name=/spring/logger/app.log

而 如果要使用 logback-spring.xml 可以參考

基本款

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<property name="APP_NAME" value="ApplicationName"/>
<property name="LOG_FILE_PATH" value="/LogFile/${APP_NAME}/logs/"/>
<!--設置 Log 輸出格式-->
<property name="PATTERN_FORMAT" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
<contextName>logback</contextName>
<!--輸出到 Console-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${PATTERN_FORMAT}</pattern>
</encoder>
</appender>

<!--輸出到檔案-->
<appender name="info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE_PATH}/%d{yyyy-MM-dd,aux}/logback.info.%d{yyyy-MM-dd}.log</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>


<root level="info">
<appender-ref ref="console" />
<appender-ref ref="info" />
</root>

</configuration>

如果想將不同級別的檔案拆開來存放可以使用

filter 放在appender中就可以

<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>info</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
範例如下
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<property name="APP_NAME" value="ApplicationName"/>
<property name="LOG_FILE_PATH" value="/LogFile/${APP_NAME}/logs/"/>
<!--設定 Log 輸出格式-->
<property name="PATTERN_FORMAT" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
<contextName>logback</contextName>
<!--輸出到 Console-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${PATTERN_FORMAT}</pattern>
</encoder>
</appender>

<!-- info level 輸出到檔案-->
<appender name="info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE_PATH}/%d{yyyy-MM-dd,aux}/logback.info.%d{yyyy-MM-dd}.log</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>info</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>

<!-- warn level 輸出到檔案-->
<appender name="warn" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE_PATH}/%d{yyyy-MM-dd,aux}/logback.warn.%d{yyyy-MM-dd}.log</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>warn</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>

<!-- error level 輸出到檔案-->
<appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE_PATH}/%d{yyyy-MM-dd,aux}/logback.error.%d{yyyy-MM-dd}.log</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>error</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>

<root level="info">
<appender-ref ref="console" />
<appender-ref ref="info" />
<appender-ref ref="warn" />
<appender-ref ref="error" />
</root>

</configuration>
參考來源
邦哥不會寫程式

2020年12月16日 星期三

JPQL 括號中

   select dwi.workflowitem, dw.workflow as ww_id

           from d_workflowitems dwi

           join d_workflows dw

             on dw.id = dwi.workflow

          where dwi.workflow = p_wf_id

            and dwi.status not in (1, 7)

            and dwi.workflowitem not in

                (select closewf.excluded_workflowitem

                   from aax2sw.k_close_workflows closewf

                  where closewf.workflow = dw.workflow)

          order by dwi.id;


像以上的語法

如果用JPQL寫

請勿將括號中的東西分到另外一個Repo中寫

因為當括號中取不出來的時候

not in() 沒東西, 會發生sql敘述句不完整問題

2020年11月30日 星期一

[SpringBoot 1.5] 的Profile-配置多環境

1 多Profile文件

我們在主配置文件編寫的時候, 文件名可以是applpication-{profile}.properties/yml

默認使用application.properties的配置;


2 yml 支持多文檔塊方式

注意~這裡如果使用yml, 那application.properties的部分就要先註掉, 相關properties結尾也是

然後, 貼下以下這些程式碼, 試著在active: 的後面加上記得要空格 dev或是prod

另外---做為區隔模塊的部分, 必須要靠最左邊才有用

server:
port: 8084
spring:
profiles:
active:
---
server:
port: 8085
spring:
profiles: dev
---
server:
port: 8086
spring:
profiles: prod


3激活指定profile


在配置文件中指定spring.profiles.acrive=dev

步驟:

首先在resouse中產生一個新的file檔案 application-dev.properties檔案

內容為 server.port=8082


另外產生一個新的file檔案application-prod.properties檔案

內容為server.port=80


然後我們在原本的application.properties中加入以下這段

server.port=8081

spring.profiles.active=dev

這時候我們運行, 可以發現




而當我們改成

spring.profiles.active=prod

結果為




這樣我們就學會怎麼使用了

另外~也可以

從Configuration中的Environment區域去塞參數

如下




2020年11月23日 星期一

[SpringBoot 1.5] 配置 @PropertySource、@ImportResource、@Bean

 我們前面看過@ConfigurationProperties與application.properties的合用方式

那如果我們有一些配置文件想要單獨提取出來使用

可以另外產生一個properties檔案專為其使用

以下我們先產生一個person.properties檔案如下

person.lastname=李四
person.age=18
person.birth=2017/12/15
person.boss=false
person.maps.k1=v1
person.maps.k2=14
person.lists=a,b,c
person.dog.name=dog
person.dog.age=15

然後我們在原本的類別上加入@PropertySource

@PropertySource:加載指定配置文件

@PropertySource(value={"classpath:person.properties"})
@Component
@ConfigurationProperties(prefix = "person")
//@Validated
public class Person {

//@Value("${person.lastname}")
//@Email
private String lastname;
private Integer age;
private Boolean boss;
private Date birth;
private Map<String, Object> maps;
private List<Object> lists;
private Dog dog;

這裡不同的是要給value, 裡面放classpath加指定

@ImportResource: 導入Spring的配置文件, 讓配置文件裡面的內容生效

由於Spring Boot裡面沒有Spring的配置文件, 我們自己編寫的配置文件, 也不能自動識別;

想讓Spring的配置文件生效, 加載進來; @ImportResource標註在一個配置類上

首先先造一個類為 service.HelloService

public class HelloService {
}

接下來產生一個beans.xml作管理

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="helloService" class="com.atguigu.springboot.service.HelloService"></bean>
</beans>

然後在測試類中加入以下

@Autowired
ApplicationContext ioc;

@Test
public void testHelloService(){
boolean b= ioc.containsBean("helloService");
System.out.println(b);
}

我們會發現結果印出來為false

這是因為剛才提到的Spring Boot 裡面沒有Spring配置文件

如果需要使用到

我們要在SpringBootApplication的類上面加入以下這段

@ImportResource(locations = {"classpath:beans.xml"})
@SpringBootApplication
public class SpringBoot02ConfigApplication {

public static void main(String[] args) {
SpringApplication.run(SpringBoot02ConfigApplication.class, args);
}

}

我們會發現, 這樣run test就能印出true了

然而

SpringBoot並不推薦使用beans.xml方式作管理

而是使用配置類作管理

@Configuration:指明當前類別是一個配置類; 就是來替代之前的Spring配置文件

@Bean: 將方法的返回值添加到容器中; 容器中這個組件組件默認的id就是方法名

接下來我們產生一個config.MyAppConfig檔案如以下

@Configuration
public class MyAppConfig {

@Bean
public HelloService helloService(){
System.out.println("配置類@Bean給容器中添加組建了...");
return new HelloService();
}
}

然後並且把剛剛在SpringBootApplication類別上面的註解先註解掉

//@ImportResource(locations = {"classpath:beans.xml"})
@SpringBootApplication
public class SpringBoot02ConfigApplication {

public static void main(String[] args) {
SpringApplication.run(SpringBoot02ConfigApplication.class, args);
}

}

再來run一下我們的測試

@Test
public void testHelloService(){
boolean b= ioc.containsBean("helloService");
System.out.println(b);
}

這裡要注意"helloService"是對到helloService()方法

如果改成helloService02(), 那要用"helloService02"才找得到



2020年11月20日 星期五

[SpringBoot 1.5] @ConfigurationProperties 與 @Value 使用介紹與主要差異

 前面一篇我們已經看過yaml與@ConfigurationProperties的合用

現在我們來看看@Value的使用

在appplication.properties為如下的情況

person.lastname=張三
person.age=18
person.birth=2017/12/15
person.boss=false
person.maps.k1=v1
person.maps.k2=14
person.lists=a,b,c
person.dog.name=dog
person.dog.age=15
我們只要在屬性上加上
@Value("${person.lastname}")
這樣就能夠取到值了
如下圖

但是要特別注意, 如果是複雜的類型封裝是不支持的
也就是下面的Map是無法用value注入
而另外一方面@ConfigurationProperties還有一個特殊功能-資料驗證
要做到這個要再加兩件事
1在類別上加上@Validated
2在屬性上加入要驗證的如@Email
另外阿~有可能@Email會報紅字
就需要在dependencies中加入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
我加了之後再重新下載maven資源, 發現他還是報錯
後來我重開之後, 他pom檔案沒有紅字, 試一下就能用
@Component
@ConfigurationProperties(prefix = "person")
@Validated
public class Person {

//@Value("${person.lastname}")
@Email
private String lastname;
我們的lastname為我們的驗證項目
但是其中的字串為"張三", 並不是email格式
於是run之後就會有以下這一段







這樣就具有驗證效果了

下面我們示範在Controller中@Value的使用方式
我們先造一個HelloController
另一個私有的變數並冠以@Value

@RestController
public class HelloController {
@Value("${person.lastname}")
private String name;

@RequestMapping("/sayHello")
public String sayHello(){
return "Hello "+name;
}
}

然後我們開啟服務, 訪問一下,就能得到下圖
那@ConfigurationProperties 與 @Value 使用介紹與主要差異
為以下下圖


如果說, 我們只是在某個業務邏輯中需要獲取一下配置文件中的某項值, 使用@Value;
如果說, 我們專門編寫了一個javaBean來和配置文件進行映射, 我們就值接使用@ConfigurationProperties

以上內容請參考 尚硅谷IT培訓學校
https://www.youtube.com/watch?v=eyqiiWbBlMs&list=PLmOn9nNkQxJEFsK2HVO9-WA55Z7LZ2N0S&index=13




















AI 時代的軟體工程

  AI 時代的軟體工程:從「代碼寫手」到「系統指揮官」的轉型之路 2026 年,軟體工程正經歷自編譯器發明以來最大的範式轉移。AI 不再只是 IDE 側邊欄的輔助工具,而是進化為具備自主性的 Agent(代理人) ,這場變革正重新定義「工程師」的核心價值。 一、 現狀:AI 普...