2025/03/31

SpringBoot3 - Spring Boot Log

spring 提供的 Logging :: Spring Boot 有以下幾種

name package
Java Util Logging spring-boot-starter-logging
Logback spring-boot-starter-logging
Log4j2 spring-boot-starter-log4j2

spring-boot-starter-logging 是預設引用的,所有 spring-boot-starter-* 都會引用 spring-boot-starter-logging。不需要自行引用。

spring-boot-starter-logging 使用 Commons Logging,支援 Java Util Logging、Log4j2、Logback

使用 starter 預設會使用 Logback,透過 logback 支援 Java Util Logging, Commons Logging, Log4J, or SLF4J。

Log Format

2024-11-15T17:25:40.765+08:00  INFO 4924 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8080 (http)
  • Date & Time

  • Log Level

  • Process ID

  • --- separator

  • [Application name] 如果有設定 spring.application.name 才會出現

  • [Thread name]

  • Correlation ID 如果有設定 tracing 才會出現

  • Logger name 通常是來源 class name 的縮寫

  • log message

Console Output

預設會出現 ERROR-level, WARN-level, and INFO-level messages

如果在 CLI 有加上 java -jar Demo1.jar --debug debug flag,就會出現 core loggers (embedded container, Hibernate, and Spring Boot) 更多的資訊。這邊不代表 application 的 DEBUG level message 會出現

CLI 也可以加上 --trace ,會增加 core logger 資訊


Color-coded Output

terminal 有支援 ANSI 時,就可以增加顏色

ex: %clr(%5p)

Level Color
FATAL Red
ERROR Red
WARN Yellow
INFO Green
DEBUG Green
TRACE Green

可以自訂顏色

  • blue

  • cyan

  • faint

  • green

  • magenta

  • red

  • yellow

ex: %clr(%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX}){yellow}

File Output

spring boot 預設只會寫入 console log

要寫入 file log 必須設定 logging.file.namelogging.file.path,兩個都設定時,只會使用 logging.file.name。log file rotate 預設為 10M,log level 跟 console 一樣,會寫入 ERROR, WARN, INFO level

File Rotation

調整 log rotate 規則

要修改 application.properties(yaml),如果是 log4j 就要設定 log4j2.xml or log4j2-spring.xml

name desc
logging.logback.rollingpolicy.file-name-pattern filename pattern
logging.logback.rollingpolicy.clean-history-on-start 在 application 啟動時,要做 log cleanup
logging.logback.rollingpolicy.max-file-size logfile 的最大 size
logging.logback.rollingpolicy.total-size-cap log archives 刪除前的最大 size
logging.logback.rollingpolicy.max-history archive log files 保留幾個(default: 7)

Log Level

logging.level.<logger-name>=<level>

為 TRACE, DEBUG, INFO, WARN, ERROR, FATAL, or OFF 其中一個

logging.level.root 是設定 root logger

logging:
  level:
    root: "warn"
    org.springframework.web: "debug"
    org.hibernate: "error"

也可以設定環境變數,ex:

LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_WEB=DEBUG

Log Groups

ex: 一次設定所有 tomcat 相關的 logger level,group 合併後,再設定 level

在 applicaiton.yaml

logging:
  group:
    tomcat: "org.apache.catalina,org.apache.coyote,org.apache.tomcat"

logging:
  level:
    tomcat: "trace"

spring pre-defined logging groups

name loggers
web org.springframework.core.codec, org.springframework.http, org.springframework.web, org.springframework.boot.actuate.endpoint.web, org.springframework.boot.web.servlet.ServletContextInitializerBeans
sql org.springframework.jdbc.core, org.hibernate.SQL, org.jooq.tools.LoggerListener

Log Shutdown hook

要在 application 關閉前,release logging resource,要在 JVM exits 時,加上 shutdown hook

使用 jar 時,shutdown hook 會自動註冊

如要關閉 shutdown hook

logging:
  register-shutdown-hook: false

Custom Log Configuration

logging.config 決定 logging framework,然後就能使用該 framework 的設定檔

Logging System Customization
Logback logback-spring.xmllogback-spring.groovylogback.xml, or logback.groovy
Log4j2 log4j2-spring.xml or log4j2.xml
JDK (Java Util Logging) logging.properties

建議使用 logback-spring.xml,不要用 logback.xml,spring 可能會無法控制 log 初始化

以下設定參數,可轉換為環境變數

Spring Environment System Property Comments
logging.exception-conversion-word LOG_EXCEPTION_CONVERSION_WORD The conversion word used when logging exceptions.
logging.file.name LOG_FILE If defined, it is used in the default log configuration.
logging.file.path LOG_PATH If defined, it is used in the default log configuration.
logging.pattern.console CONSOLE_LOG_PATTERN The log pattern to use on the console (stdout).
logging.pattern.dateformat LOG_DATEFORMAT_PATTERN Appender pattern for log date format.
logging.charset.console CONSOLE_LOG_CHARSET The charset to use for console logging.
logging.threshold.console CONSOLE_LOG_THRESHOLD The log level threshold to use for console logging.
logging.pattern.file FILE_LOG_PATTERN The log pattern to use in a file (if LOG_FILE is enabled).
logging.charset.file FILE_LOG_CHARSET The charset to use for file logging (if LOG_FILE is enabled).
logging.threshold.file FILE_LOG_THRESHOLD The log level threshold to use for file logging.
logging.pattern.level LOG_LEVEL_PATTERN The format to use when rendering the log level (default %5p).
PID PID

使用 logback,可多使用這些參數

Spring Environment System Property Comments
logging.logback.rollingpolicy.file-name-pattern LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN Pattern for rolled-over log file names (default ${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz).
logging.logback.rollingpolicy.clean-history-on-start LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START Whether to clean the archive log files on startup.
logging.logback.rollingpolicy.max-file-size LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE Maximum log file size.
logging.logback.rollingpolicy.total-size-cap LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP Total size of log backups to be kept.
logging.logback.rollingpolicy.max-history LOGBACK_ROLLINGPOLICY_MAX_HISTORY

Logback Extension

logback-spring.xml 裡面有幾個 logback extensions

profile-specific cofiguration

根據 active profile,<springProfile> 可 include/exclude 部分設定

<springProfile name="staging">
    <!-- configuration to be enabled when the "staging" profile is active -->
</springProfile>

<springProfile name="dev | staging">
    <!-- configuration to be enabled when the "dev" or "staging" profiles are active -->
</springProfile>

<springProfile name="!production">
    <!-- configuration to be enabled when the "production" profile is not active -->
</springProfile>

environment properties

<springProperty scope="context" name="fluentHost" source="myapp.fluentd.host"
        defaultValue="localhost"/>
<appender name="FLUENT" class="ch.qos.logback.more.appenders.DataFluentAppender">
    <remoteHost>${fluentHost}</remoteHost>
    ...
</appender>

example

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <property name="LOG_PATH" value="/var/log/blog"/>
    <property name="LOG_FILENAME" value="spring-profiles"/>

    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <include resource="org/springframework/boot/logging/logback/console-appender.xml"/>

    <springProfile name="default,ide">
        <root level="INFO">
            <appender-ref ref="CONSOLE"/>
        </root>
    </springProfile>

    <springProfile name="test,prod">
        <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <encoder>
                <pattern>${FILE_LOG_PATTERN}</pattern>
            </encoder>
            <file>${LOG_PATH}/${LOG_FILENAME}.log</file>
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                <fileNamePattern>${LOG_PATH}/${LOG_FILENAME}-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
                <maxHistory>7</maxHistory>
                <maxFileSize>100MB</maxFileSize>
                <totalSizeCap>1GB</totalSizeCap>
            </rollingPolicy>
        </appender>

        <root level="INFO">
            <appender-ref ref="FILE"/>
        </root>
    </springProfile>

</configuration>

Log4j2 Extension

可在 log4j2-spring.xml 設定使用

profile-specific configuration

<SpringProfile name="staging">
    <!-- configuration to be enabled when the "staging" profile is active -->
</SpringProfile>

<SpringProfile name="dev | staging">
    <!-- configuration to be enabled when the "dev" or "staging" profiles are active -->
</SpringProfile>

<SpringProfile name="!production">
    <!-- configuration to be enabled when the "production" profile is not active -->
</SpringProfile> 

Environment Properties Lookup

<Properties>
    <Property name="applicationName">${spring:spring.application.name}</Property>
</Properties>

log4j2 system properties

ex: ConsoleAppender 在 windows 使用 Jansi

log4j2.skipJansi=false

2025/03/17

SpringBoot3 - Spring Boot 啟動過程

啟動入口

@SpringBootApplication
public class Demo1Application {

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

}

@SpringBootApplication 裡面就包含了

  • @SpringBootConfiguration

  • @EnableAutoConfiguration

    啟用應用的自動設定功能

  • @ComponentScan

    自動掃描 sub packages 裡面的 Spring Component


啟動 application

SpringApplication.run(Demo1Application.class);

也可以拆開

SpringApplication springApplication = new SpringApplication(Demo1Application.class);
springApplication.run(args);

也可以用 SpringApplicationBuilder

new SpringApplicationBuilder()
    .sources(Parent.class)
    .child(Demo1Application.class)
    .banerMode(Banner.Mode.OFF)
    .run(args);

jar 啟動

application 的 main 通常是在 console 啟動時被呼叫,如果打包成 jar,就會是另一種狀況

project 可用 mvn package 打包成 jar

|____org
| |____springframework
| | |____boot
| | | |____loader
| | | | |____ref
| | | | |____net
| | | | |____jarmode
| | | | |____launch
| | | | |____jar
| | | | |____zip
| | | | |____nio
| | | | |____log
|____META-INF
| |____MANIFEST.MF
| |____maven
| | |____tw.com.test
| | | |____demo1
| | | | |____pom.xml
| | | | |____pom.properties
| |____services
| | |____java.nio.file.spi.FileSystemProvider
|____BOOT-INF
| |____classes
| | |____tw
| | | |____com
| | | | |____test
| | | | | |____demo1
| | | | | | |____Demo1Application.class
| | |____application.yml
| |____layers.idx
| |____classpath.idx
| |____lib
  • BOOT-INF: 啟動 application 所需要的 classes

  • META-INF: application打包的相關描述檔案

  • org: spring boot 啟動所需要的引導類別


META-INF/MENIFEST.MF

Manifest-Version: 1.0
Created-By: Maven JAR Plugin 3.4.2
Build-Jdk-Spec: 17
Implementation-Title: demo1
Implementation-Version: 0.0.1-SNAPSHOT
Main-Class: org.springframework.boot.loader.launch.JarLauncher
Start-Class: tw.com.test.demo1.Demo1Application
Spring-Boot-Version: 3.3.5
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Spring-Boot-Layers-Index: BOOT-INF/layers.idx

Main-Class: 啟動的引導類別

Start-Class: application 啟動類別


啟動的 log 可透過設定關閉

spring:
  main:
    log-startup-info: false

或是

SpringApplication springApplication = new SpringApplication(Demo1Application.class);
springApplication.setLogStartupInfo(false);
springApplication.run(args);

啟動失敗分析

由 FailureAnalyzer interface 處理

ex: PortInUseFailureAnalyzer 可處理重複使用 TCP Port 的 exception

可以自訂一個 PortInUseFailureAnalyzer

package tw.com.test.demo1;

import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.boot.web.server.PortInUseException;

public class PortInUseFailureAnalyzer extends AbstractFailureAnalyzer<PortInUseException> {
    @Override
    protected FailureAnalysis analyze(Throwable rootFailure, PortInUseException cause) {
        return new FailureAnalysis(cause.getPort()+"PortInUse", "check "+cause.getPort(), cause);
    }
}

然後在 META-INF/spring.factories

org.springframework.boot.diagnostics.FailureAnalyzer=\
  tw.com.test.demo1.PortInUseFailureAnalyzer

也可以自訂自己的 applicaton exception,以及對應的 FailureAnalyzer

啟動 Banner

Text to ASCII Art Generator (TAAG) 這是將 text 轉換為 ascii 文字的網頁工具。

將要列印的 application 文字,轉換為 ascii 後,儲存到 /resources/banner.txt。啟動 application 時,就會看到 banner 已經換掉了。

以下是一些相關設定

# banner.txt encoding
spring.banner.charset=UTF-8
# banner.txt filepath
spring.banner.location=classpath:banner.txt
# console/log/off
spring.main.banner-mode=console

啟動事件與 listener

spring-boot-3.3.5.jar

META-INF/spring.factories 裡面註冊的 listener,這些都實作了 Spring 的 ApplicationListener interface

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.env.EnvironmentPostProcessorApplicationListener

啟動事件的順序

  1. ApplicationStartingEvent

    spring application 啟動前發送

  2. ApplicationEnvironmentPreparedEvent

    已知要在 context 使用 spring 的 Environment 時,在 context 建立之前發送

  3. ApplicationContextInitializedEvent

    ApplicationContext 準備好了,ApplicationContextInitializer 已被呼叫,在 bean 載入前發送

  4. ApplicationPreparedEvent

    在 context refresh 前,且在 bean 定義被載入後發送

  5. ApplicationStartedEvent

    在 Spring context refresh 以後,在 application / CLI runner 呼叫前發送

  6. AvailabilityChangeEvent

    跟著上一個事件後發送,ReadinessState.CORRECT,代表 application 已處於活動狀態

  7. ApplicationReadyEvent

    在 application / CLI runner 呼叫後發送

  8. AvailabilityChangeEvent

    跟著上一個事件後發送,ReadinessState.ACCEPTING_TRAFFIC,代表 application 已經準備接收 request

  9. ApplicationFailedEvent

    在啟動異常時發送

以上只有 SpringApplication 發出的 SpringApplicationEvents 事件。

以下事件會在 ApplicationPreparedEvent 後,ApplicationStartedEvent 前發送

  1. WebServerInitializedEvent

    WebServer 啟動後發送。包含 ServletWebServerInitialziedEvent, ReactiveWebServerInitializedEvent

  2. ContextRefreshedEvent

    在 ApplicationContext refresh 後發送


自訂事件 listener

Demo1ApplicationListener.java

package tw.com.test.demo1;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.ReadinessState;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class Demo1ApplicationListener implements ApplicationListener<AvailabilityChangeEvent> {
    @Override
    public void onApplicationEvent(AvailabilityChangeEvent event) {
        log.info("got event: {}, state: {}", event, event.getState());
        if( ReadinessState.ACCEPTING_TRAFFIC == event.getState() ) {
            log.info("applcation is started!");
        }
    }
}

啟動測試

2024-11-15T16:26:43.269+08:00  INFO 4595 --- [           main] t.c.test.demo1.Demo1ApplicationListener  : got event: org.springframework.boot.availability.AvailabilityChangeEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@5674e1f2, started on Fri Nov 15 16:26:40 CST 2024], state: CORRECT
2024-11-15T16:26:43.273+08:00  INFO 4595 --- [           main] t.c.test.demo1.Demo1ApplicationListener  : got event: org.springframework.boot.availability.AvailabilityChangeEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@5674e1f2, started on Fri Nov 15 16:26:40 CST 2024], state: ACCEPTING_TRAFFIC
2024-11-15T16:26:43.273+08:00  INFO 4595 --- [           main] t.c.test.demo1.Demo1ApplicationListener  : applcation is started!

Runner

在 application 啟動前,執行一些 code

  • ApplicationRunner

  • CommandLineRunner

@Component 或是 lambda 的 @Bean兩種寫法

package tw.com.test.demo1;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@SpringBootApplication
@Slf4j
public class Demo1Application {

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

    @Component
    public class Demo1ApplicationRunner implements ApplicationRunner {
        @Override
        public void run(ApplicationArguments args) throws Exception {
            log.info("ApplicationRunner runner args={}", args);
        }
    }
//    @Bean
//    public ApplicationRunner applicationRunner() {
//        return new Demo1ApplicationRunner();
//    }

    @Bean
    public ApplicationRunner applicationRunner2() {
        return (args) -> {
            log.info("CommandLineRunner2 args={}", args);
        };
    }

    @Bean
    public CommandLineRunner commandLineRunner() {
        return (args) -> {
            log.info("CommandLineRunner args={}", args);
        };
    }
}

執行結果

2024-11-15T16:57:46.247+08:00  INFO 4793 --- [           main] tw.com.test.demo1.Demo1Application       : CommandLineRunner args={}
2024-11-15T16:57:46.248+08:00  INFO 4793 --- [           main] tw.com.test.demo1.Demo1Application       : CommandLineRunner2 args=org.springframework.boot.DefaultApplicationArguments@d535a3d
2024-11-15T16:57:46.249+08:00  INFO 4793 --- [           main] tw.com.test.demo1.Demo1Application       : ApplicationRunner runner args=org.springframework.boot.DefaultApplicationArguments@d535a3d

2025/03/10

SpringBoot3 - Spring Boot Starter

starter 包含 自動設定及相關 library

官方都是以 spring-boot-starter-* 命名。第三方要以 *-spring-boot-starter 命名,ex: druid-spring-boot-starter

分類

application

name desc
spring-boot-starter 核心 starter,含自動設定、log 及支援 YAML
spring-boot-starter-amqp Spring AMQP, Rabbit MQ
spring-boot-starter-aop Spring AOP
spring-boot-starter-artemis Apache Artemis,支援 JMS 的 MQ
spring-boot-starter-batch Spring Batch
spring-boot-starter-cache Spring Cache
spring-boot-starter-data-cassandra Cassandra + Spring Data Cassandra
spring-boot-starter-data-cassandra-reactive Cassandra + Spring Data Cassandra Reactive
spring-boot-starter-data-couchbase Couchbase + Spring Data Couchbase
spring-boot-starter-data-couchbase-reactive Couchbase + Spring Data Couchbase Reactive
spring-boot-starter-data-elasticsearch Elasticsearch + Spring Data Elasticsearch
spring-boot-starter-data-jdbc Spring Data JDBC
spring-boot-starter-data-jpa Spring Data JPA + Hibernate
spring-boot-starter-data-ldap Spring Data LDAP
spring-boot-starter-data-mongodb MongoDB + Spring Data MongoDB
spring-boot-starter-data-mongodb-reactive MongoDB + Spring Data MongoDB Reactive
spring-boot-starter-data-neo4j Neo4J + Spring Data Neo4J
spring-boot-starter-data-r2dbc Spring Data R2DBC
spring-boot-starter-data-redis Redis + Spring Data Redis + Lettuce
spring-boot-starter-data-redis-reactive Redis + Spring Data Redis Reactive + Lettuce Client
spring-boot-starter-data-rest Spring Data REST + Spring Data repositories,輸出 REST
spring-boot-starter-freemarker 以 FreeMarker View 建立 Spring Web application
spring-boot-starter-graphql Spring GraphQL
spring-boot-starter-grovvy-templates Groovy View 建立 Spring Web application
spring-boot-starter-hateoas Spring MVC + Spring HATEOAS 建立 RESTful Web application
spring-boot-starter-integration Spring Integration
spring-boot-starter-jdbc JDBC + HikariCP connection pool
spring-boot-starter-jersey JAX-RS + Jersey 建立 RESTful Web application,可替代 spring-boot-starter-web
spring-boot-starter-jooq jOOQ 存取 SQL database。可替代 spring-boot-starter-data-jpa 或 spring-boot-starter-jdbc
spring-boot-starter-json 讀寫 JSON
spring-boot-starter-mail Java Mail + Spring Mail Sender
spring-boot-starter-mustache 以 Mustache view 建立 Web Application
spring-boot-starter-oauth2-client Spring Security's OAuth2/OpenID 客戶端連線
spring-boot-starter-oauth2-resource-server Spring Security's OAuth2 資源伺服器
spring-boot-starter-quartz Quartz
spring-boot-starter-rsocket RSocket client and server
spring-boot-starter-security Spring Security
spring-boot-starter-test JUnit Jupiter + Hamcrest + Mockito
spring-boot-starter-thymeleaf Thymeleaf View 建立 MVC web application
spring-boot-starter-validation Java Bean Validation + Hibernate Validator
spring-boot-starter-web Spring MVC 建立 RESTful Web application,以 Tomcat 為內嵌伺服器
spring-boot-starter-web-services Spring Web Services
spring-boot-starter-webflux Spring Reactive Web 建立 WebFlux application
spring-boot-starter-websocket Spring WebSocket

如果官方沒有的 starter,可使用第三方自制的 Spring Boot Starter,ex: Dubbo, ZooKeeper, MyBatis

production

name desc
spring-boot-starter-actuator Sprign Boot Actuator,正式環境的監控與應用管理

technical

可排除或替換預設的技術套件

name desc
spring-boot-starter-jetty 以 Jetty 為 servlet container,替代 spring-boot-starter-tomcat
spring-boot-starter-log4j2 log4j2,替代 spring-boot-starter-logging
spring-boot-starter-logging Logback
spring-boot-starter-reactor-netty 以 Netty 作為內嵌的 reactive http server
spring-boot-starter-tomcat 以 Tomcat 為內嵌的 servlet container,這是預設的,也被用在 spring-boot-starter-web
spring-boot-starter-undertow 以 Undertow 作為內嵌的 servlet container,可替代 spring-boot-starter-tomcat

ex: 使用 Jetty 替代 tomcat

修改pom.xml

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <!-- exclude tomcat -->
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
            <!-- <artifactId>spring-boot-starter-undertow</artifactId> -->
        </dependency>

自動設定

所有自動設定的類別都是由 spring-boot-autoconfigure 模組提供的

ref: # 深入理解自動配置原理之@SpringApplcation


MailSender

spring-boot-start-mail 提供了

  • org.springframework.mail.javamail.JavaMailSender

  • org.springframework.mail.javamail.JavaMailSenderImpl

另外有一個自動設定類別

  • org.springframework.boot.autoconfigure.mail.MailSnederAutoConfiguration

該類別被註冊到這個檔案裡面,檔案內容就是自動設定類別的字串

/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

pom.xml

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

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>

application.yml

spring:
  mail:
    host: smtp.gmail.com
    username: service@larzio.com
    password: XXXXXX
    properties:
      "[mail.smtp.socketFactory.class]": javax.net.ssl.SSLSocketFactory
      "[mail.smtp.socketFactory.fallback]": false
      "[mail.smtp.socketFactory.port]": 465
      "[mail.smtp.connectiontimeout]": 5000
      "[mail.smtp.timeout]": 3000
      "[mail.smtp.writetimeout]": 5000

mail:
  from: service@test.com
  fromname: service
  bcc:
  subject: Spring Boot Mail Test

Demo1Application.java

package tw.com.test.demo1;

import lombok.RequiredArgsConstructor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.web.bind.annotation.RestController;

@EnableConfigurationProperties({MailProperties.class})
@RequiredArgsConstructor
@SpringBootApplication
@RestController
public class Demo1Application {
    public static void main(String[] args) {
        SpringApplication.run(Demo1Application.class);
    }
}

MailProperties.java

package tw.com.test.demo1;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Data
@ConfigurationProperties(prefix = "mail")
public class MailProperties {
    private String from;
    private String fromname;
    private String bcc;
    private String subject;

}

EmailController.java

package tw.com.test.demo1;

import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import java.io.UnsupportedEncodingException;

@Slf4j
@RequiredArgsConstructor
@RestController
public class EmailController {
    private final JavaMailSender javaMailSender;
    private final MailProperties mailProperties;

    @RequestMapping("/sendSimpleEmail")
    @ResponseBody
    public boolean sendSimpleEmail(@RequestParam("email") String email, @RequestParam("text") String text) {
        try {
            SimpleMailMessage msg = createSimpleMsg(email, text);
            javaMailSender.send(msg);
        } catch (Exception ex) {
            log.error("Error:", ex);
            return false;
        }
        return true;
    }

    @RequestMapping("/sendMimeEmail")
    @ResponseBody
    public boolean sendMimeEmail(@RequestParam("email") String email, @RequestParam("text") String text) {
        try {
            MimeMessage msg = createMimeMsg(email, text, "java.png");
            javaMailSender.send(msg);
        } catch (Exception ex) {
            log.error("Error:", ex);
            return false;
        }
        return true;
    }

    /**
     * @param email
     * @param text
     * @param attachmentClassPathFilename
     * @return
     * @throws MessagingException
     * @throws UnsupportedEncodingException
     */
    private MimeMessage createMimeMsg(String email, String text, String attachmentClassPathFilename) throws MessagingException, UnsupportedEncodingException {
        MimeMessage msg = javaMailSender.createMimeMessage();
        MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(msg, true);
        mimeMessageHelper.setFrom(mailProperties.getFrom(), mailProperties.getFromname());
        mimeMessageHelper.setTo(email);
        if (!mailProperties.getBcc().equals("")) {
            mimeMessageHelper.setBcc(mailProperties.getBcc());
        }
        mimeMessageHelper.setSubject(mailProperties.getSubject());
        mimeMessageHelper.setText(text);
        mimeMessageHelper.addAttachment(attachmentClassPathFilename, new ClassPathResource(attachmentClassPathFilename));
        return msg;
    }

    /**
     * @param email
     * @param text
     * @return
     */
    private SimpleMailMessage createSimpleMsg(String email, String text) {
        SimpleMailMessage msg = new SimpleMailMessage();
        msg.setFrom(mailProperties.getFrom());
        msg.setTo(email);
        if (!mailProperties.getBcc().equals("")) {
            msg.setBcc(mailProperties.getBcc());
        }
        msg.setSubject(mailProperties.getSubject());
        msg.setText(text);
        return msg;
    }

}

測試

http://localhost:8080/sendMimeEmail?email=charley@maxkit.com.tw&text=hello

http://localhost:8080/sendSimpleEmail?email=charley@maxkit.com.tw&text=hello