這是一篇優雅的Springboot2.0使用手冊

秒殺商品列表

商品名稱商品圖片商品原價秒殺價庫存數量詳情

Freemarker(自由標記)

POM

org.springframework.boot

spring-boot-starter-freemarker

配置文件

在application.properties中添加:

#Freemarker

spring.freemarker.allow-request-override=false

spring.freemarker.cache=true

spring.freemarker.check-template-location=true

spring.freemarker.charset=UTF-8

spring.freemarker.content-type=text/html

spring.freemarker.expose-request-attributes=false

spring.freemarker.expose-session-attributes=false

spring.freemarker.expose-spring-macro-helpers=false

#spring.freemarker.prefix=

#spring.freemarker.request-context-attribute=

#spring.freemarker.settings.*=

spring.freemarker.suffix=.ftl

spring.freemarker.template-loader-path=classpath:/templates/

#comma-separated list

#spring.freemarker.view-names= # whitelist of view names that can be resolved

後臺

在src/main/resources/創建一個templates文件夾,新網頁後綴為*.ftl

@RequestMapping("/freemarkerIndex")

public String index(Map result) {

result.put("nickname", "tEngSHe789");

result.put("old", "18");

result.put("my Blog", "HTTPS://blog.tengshe789.tech/");

List listResult = new ArrayList();

listResult.add("guanyu");

listResult.add("zhugeliang");

result.put("listResult", listResult);

return "index";

}

前臺

首頁

${nickname}

太假了吧哥們

你是真的21歲

其他

#if>

${user}

#list>

JSP

不建議用Springboot整合JSP,要的話一定要為war類型,否則會找不到頁面.,而且不要把JSP頁面存放在resources// jsp 不能被訪問到

POM

org.springframework.boot

spring-boot-starter-parent

2.0.4.RELEASE

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-starter-tomcat

org.apache.tomcat.embed

tomcat-embed-jasper

配置文件

在application.properties中添加:

spring.mvc.view.prefix=/WEB-INF/jsp/

spring.mvc.view.suffix=.jsp

後臺

在src/main/resources/創建一個templates文件夾,新網頁後綴為*.jsp

@Controller

public class IndexController {

@RequestMapping("/index")

public String index() {

return "index";

}

}

前臺

略略略

異步編程

要了解 WebFlux ,首先了解下什麼是Reactive響應式(反應式)編程 ,他是一種新的編程風格,其特點是異步或併發、事件驅動、推送PUSH機制以及觀察者模式的衍生。reactive應用(響應式應用)允許開發人員構建事件驅動(event-driven),可擴展性,彈性的反應系統:提供高度敏感的實時的用戶體驗感覺,可伸縮性和彈性的應用程序棧的支持,隨時可以部署在多核和雲計算架構。

Spring Webflux

Spring Boot Webflux 就是基於 Reactor 實現的。Spring Boot 2.0 包括一個新的 spring-webflux 模塊。該模塊包含對響應式 HTTP 和 WebSocket 客戶端的支持,以及對 REST,HTML 和 WebSocket 交互等程序的支持。一般來說,Spring MVC 用於同步處理,Spring Webflux 用於異步處理。

Spring Boot Webflux 有兩種編程模型實現,一種類似 Spring MVC 註解方式,另一種是使用其功能性端點方式。

這是一篇優雅的Springboot2.0使用手冊

WebFlux 支持的容器有 Tomcat、Jetty(Non-Blocking IO API) ,也可以像 Netty 和 Undertow 的本身就支持異步容器。在容器中 Spring WebFlux 會將輸入流適配成 Mono 或者 Flux 格式進行統一處理。

POM

官方實例

@RestController

public class PersonController {

private final PersonRepository repository;

public PersonController(PersonRepository repository) {

this.repository = repository;

}

@PostMapping("/person")

Mono create(@RequestBody Publisher personStream) {

return this.repository.save(personStream).then();

}

@GetMapping("/person")

Flux list() {

return this.repository.findAll();

}

@GetMapping("/person/{id}")

Mono

findById(@PathVariable String id) {

return this.repository.findOne(id);

}

}

Controller層

Spring Boot 2.0 這裡有兩條不同的線分別是:

  1. Spring Web MVC -> Spring Data
  2. Spring WebFlux -> Spring Data Reactive

如果使用 Spring Data Reactive ,原來的 Spring 針對 Spring Data (JDBC等)的事務管理會不起作用。因為原來的 Spring 事務管理(Spring Data JPA)都是基於 ThreadLocal 傳遞事務的,其本質是基於 阻塞 IO 模型,不是異步的。

但 Reactive 是要求異步的,不同線程裡面 ThreadLocal 肯定取不到值了。自然,我們得想想如何在使用 Reactive 編程是做到事務,有一種方式是 回調 方式,一直傳遞 conn :newTransaction(conn ->{})

因為每次操作數據庫也是異步的,所以 connection 在 Reactive 編程中無法靠 ThreadLocal 傳遞了,只能放在參數上面傳遞。雖然會有一定的代碼侵入行。進一步,也可以 kotlin 協程,去做到透明的事務管理,即把 conn 放到 協程的局部變量中去。 那 Spring Data Reactive Repositories 不支持 MySQL,進一步也不支持 MySQL 事務,怎麼辦?

答案是,這個問題其實和第一個問題也相關。 為啥不支持 MySQL,即 JDBC 不支持。大家可以看到 JDBC 是所屬 Spring Data 的。所以可以等待 Spring Data Reactive Repositories 升級 IO 模型,去支持 MySQL。也可以和上面也講到了,如何使用 Reactive 編程支持事務。

如果應用只能使用不強依賴數據事務,依舊使用 MySQL ,可以使用下面的實現,代碼如下:

Service 層

public interface CityService {

/**

* 獲取城市信息列表

*

* @return

*/

List findAllCity();

/**

* 根據城市 ID,查詢城市信息

*

* @param id

* @return

*/

City findCityById(Long id);

/**

* 新增城市信息

*

* @param city

* @return

*/

Long saveCity(City city);

/**

* 更新城市信息

*

* @param city

* @return

*/

Long updateCity(City city);

/**

* 根據城市 ID,刪除城市信息

*

* @param id

* @return

*/

Long deleteCity(Long id);

}

具體案例在我參考博主的 Github

路由器類 Router

創建一個 Route 類來定義 RESTful HTTP 路由

請參考聊聊 Spring Boot 2.x 那些事兒

@Async

需要執行異步方法時,在方法上加上@Async之後,底層使用多線程技術 。啟動加上需要@EnableAsync

數據訪問

整合JdbcTemplate

使用這個需要spring-boot-starter-parent版本要在1.5以上

POM

org.springframework.boot

spring-boot-starter-jdbc

配置文件

在application.properties中添加:

# jdbc模板

spring.datasource.url=jdbc:mysql://localhost:3306/test

spring.datasource.username=root

spring.datasource.password=123456

spring.datasource.driver-class-name=com.mysql.jdbc.Driver

後臺

創建一個Service

@Service

public class UserServiceImpl implements UserService {

@Autowired

private JdbcTemplate jdbcTemplate;

public void createUser(String name, Integer age) {

jdbcTemplate.update("insert into users values(null,?,?);", name, age);

}

}

整合Mybatis

這裡用我寫的一個秒殺項目作為參考栗子。秒殺商城

POM

org.mybatis.spring.boot

mybatis-spring-boot-starter

1.3.2

配置文件

在application.properties中添加:

#mybatis

mybatis.type-aliases-package=cn.tengshe789.domain

mybatis.configuration.map-underscore-to-camel-case=true

mybatis.configuration.default-fetch-size=100

mybatis.configuration.default-statement-timeout=3000

mybatis.mapperLocations = classpath:cn/tengshe789/dao/*.xml

後臺

創建一個Dao(Mapper 代碼)

@Mapper

@Component

public interface GoodsDao {

@Select("select g.*,mg.stock_count, mg.start_date, mg.end_date,mg.miaosha_price from miaosha_goods mg left join goods g on mg.goods_id = g.id")

public List listGoodsVo();

}

創建service

@Service

public class GoodsService {

@Autowired

GoodsDao goodsDao;

/*

* 展示商品列表

*/

public List listGoodsVo() {

return goodsDao.listGoodsVo();

}

}

Mybatis整合分頁插件PageHelper

PageHelper 是一款好用的開源免費的 Mybatis 第三方物理分頁插件

POM

com.github.pagehelper

pagehelper-spring-boot-starter

1.2.5

配置文件

在application.properties中添加:

# 配置日誌

logging.level.cn.tengshe789.dao=DEBUG

# Pagehelper

pagehelper.helperDialect=mysql

pagehelper.reasonable=true

pagehelper.supportMethodsArguments=true

pagehelper.params=count=countSql

pagehelper.page-size-zero=true

或者在application.yml中添加:

# 與mybatis整合

mybatis:

config-location: classpath:mybatis.xml

mapper-locations:

- classpath:cn/tengshe789/dao/*.xml

# 分頁配置

pagehelper:

helper-dialect: mysql

reasonable: true

support-methods-arguments: true

params: count=countSql

代碼

實體層面

@Data

public class User {

private Integer id;

private String name;

}

Dao層

public interface UserDao {

@Select("SELECT * FROM USERS ")

List findUserList();

}

Service層

@Service

public class UserService {

@Autowired

private UserMapper userDao;

/**

* page 當前頁數

* size 當前展示的數據

*/

public PageInfo findUserList(int page, int size) {

// 開啟分頁插件,放在查詢語句上面

PageHelper.startPage(page, size);

List listUser = userDao.findUserList();

// 封裝分頁之後的數據

PageInfo pageInfoUser = new PageInfo(listUser);

return pageInfoUser;

}

}

整合SpringJPA

spring-data-jpa三個步驟:

  1. 聲明持久層的接口,該接口繼承 Repository(或Repository的子接口,其中定義了一些常用的增刪改查,以及分頁相關的方法)。
  2. 在接口中聲明需要的業務方法。Spring Data 將根據給定的策略生成實現代碼。
  3. 在 Spring 配置文件中增加一行聲明,讓 Spring 為聲明的接口創建代理對象。配置了 jpa:repositories 後,Spring 初始化容器時將會掃描 base-package 指定的包目錄及其子目錄,為繼承 Repository 或其子接口的接口創建代理對象,並將代理對象註冊為 Spring Bean,業務層便可以通過 Spring 自動封裝的特性來直接使用該對象。

詳情:JPA官方網站

POM

org.springframework.boot

spring-boot-starter-data-jpa

配置文件

Springboot 默認使用hibernate作為JPA的實現 。需要在application.properties中添加:

# hibernate

spring.datasource.url=jdbc:mysql://localhost:3306/test?useSSL=false

spring.datasource.username=root

spring.datasource.password=root

spring.datasource.tomcat.max-active=100

spring.datasource.tomcat.max-idle=200

spring.datasource.tomcat.initialSize=20

spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect

代碼

Domain

@Data

@Entity(name = "users")

public class UserEntity {

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)

private Integer id;

@Column(name = "name")

private String name;

@Column(name = "age")

private Integer age;

}

註解的意思:

@Entity會被spring掃描並加載,

@Id註解在主鍵上

@Column name="call_phone" 指該字段對應的數據庫的字段名,如果相同就不需要定義。數據庫下劃線間隔和代碼中的駝峰法視為相同,如數據庫字段create_time等價於Java類中的createTime,因此不需要用@Column註解。

Dao層

此時需要繼承Repository接口~

public interface UserDao extends JpaRepository {

}

Controller

@RestController

public class IndexController {

@Autowired

private UserDao userDao;

@RequestMapping("/jpaFindUser")

public Object jpaIndex(User user) {

Optional userOptional = userDao.findById(user.getId());

User result = userOptional.get();

return reusltUser == null ? "沒有查詢到數據" : result;

}

}

多數據源

很多公司都會使用多數據庫,一個數據庫存放共同的配置或文件,另一個數據庫是放垂直業務的數據。所以說需要一個項目中有多個數據源

這玩意原理很簡單,根據不同包名,加載不同數據源。

配置文件

在application.properties中添加:

# datasource1

spring.datasource.test1.driver-class-name = com.mysql.jdbc.Driver

spring.datasource.test1.jdbc-url =jdbc:mysql://localhost:3306/test01?useUnicode=true&characterEncoding=utf-8

spring.datasource.test1.username = root

spring.datasource.test1.password = 123456

# datasource2

spring.datasource.test2.driver-class-name = com.mysql.jdbc.Driver

spring.datasource.test2.jdbc-url =jdbc:mysql://localhost:3306/test02?useUnicode=true&characterEncoding=utf-8

spring.datasource.test2.username = root

spring.datasource.test2.password = 123456

代碼

添加配置

數據庫1的

//DataSource01

@Configuration // 註冊到springboot容器中

@MapperScan(basePackages = "tech.tengshe789.test01", sqlSessionFactoryRef = "test1SqlSessionFactory")

public class DataSource1Config {

/**

* @methodDesc: 功能描述:(配置test1數據庫)

* @author: tEngSHe789

*/

@Bean(name = "test1DataSource")

@ConfigurationProperties(prefix = "spring.datasource.test1")

@Primary

public DataSource testDataSource() {

return DataSourceBuilder.create().build();

}

/**

* @methodDesc: 功能描述:(test1 sql會話工廠)

*/

@Bean(name = "test1SqlSessionFactory")

@Primary

public SqlSessionFactory testSqlSessionFactory(@Qualifier("test1DataSource") DataSource dataSource)

throws Exception {

SqlSessionFactoryBean bean = new SqlSessionFactoryBean();

bean.setDataSource(dataSource);

//加載mapper(不需要)

bean.setMapperLocations(

new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/test1/*.xml"));

return bean.getObject();

}

/**

*

* @methodDesc: 功能描述:(test1 事物管理)

*/

@Bean(name = "test1TransactionManager")

@Primary

public DataSourceTransactionManager testTransactionManager(@Qualifier("test1DataSource") DataSource dataSource) {

return new DataSourceTransactionManager(dataSource);

}

@Bean(name = "test1SqlSessionTemplate")

@Primary

public SqlSessionTemplate testSqlSessionTemplate(

@Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {

return new SqlSessionTemplate(sqlSessionFactory);

}

}

數據庫2的同理。

Dao

public interface User1Dao {

@Insert("insert into users values(null,#{name},#{age});")

public int addUser(@Param("name") String name, @Param("age") Integer age);

}

注意事項

在多數據源的情況下,使用@Transactional註解時,應該指定事務管理者@Transactional(transactionManager = "test1TransactionManager")

事物管理

怎麼進行事物管理呢,簡單,往下看。

找到service實現類,加上@Transactional 註解就行,此@Transactional註解來自org.springframework.transaction.annotation包 ,不是來自javax.transaction 。而且@Transactional不僅可以註解在方法上,也可以註解在類上。當註解在類上的時候意味著此類的所有public方法都是開啟事務的。如果類級別和方法級別同時使用了@Transactional註解,則使用在類級別的註解會重載方法級別的註解。

注意:Springboot提供了一個@EnableTransactionManagement註解在配置類上來開啟聲明式事務的支持。註解@EnableTransactionManagement是默認打開的,想要關閉事務管理,想要在程序入口將這個註解改為false

分佈式事物管理

啥是分佈式事務呢,比如我們在執行一個業務邏輯的時候有兩步分別操作A數據源和B數據源,當我們在A數據源執行數據更改後,在B數據源執行時出現運行時異常,那麼我們必須要讓B數據源的操作回滾,並回滾對A數據源的操作。這種情況在支付業務時常常出現,比如買票業務在最後支付失敗,那之前的操作必須全部回滾,如果之前的操作分佈在多個數據源中,那麼這就是典型的分佈式事務回滾

瞭解了什麼是分佈式事務,那分佈式事務在java的解決方案就是JTA(即Java Transaction API)。

springboot官方提供了 Atomikos , Bitronix ,Narayana 的類事務管理器

類事務管理器Atomikos

POM

org.springframework.boot

spring-boot-starter-jta-atomikos

配置文件

# Mysql 1

mysql.datasource.test1.url = jdbc:mysql://localhost:3306/test01?useUnicode=true&characterEncoding=utf-8

mysql.datasource.test1.username = root

mysql.datasource.test1.password = 123456

mysql.datasource.test1.minPoolSize = 3

mysql.datasource.test1.maxPoolSize = 25

mysql.datasource.test1.maxLifetime = 20000

mysql.datasource.test1.borrowConnectionTimeout = 30

mysql.datasource.test1.loginTimeout = 30

mysql.datasource.test1.maintenanceInterval = 60

mysql.datasource.test1.maxIdleTime = 60

# Mysql 2

mysql.datasource.test2.url =jdbc:mysql://localhost:3306/test02?useUnicode=true&characterEncoding=utf-8

mysql.datasource.test2.username =root

mysql.datasource.test2.password =123456

mysql.datasource.test2.minPoolSize = 3

mysql.datasource.test2.maxPoolSize = 25

mysql.datasource.test2.maxLifetime = 20000

mysql.datasource.test2.borrowConnectionTimeout = 30

mysql.datasource.test2.loginTimeout = 30

mysql.datasource.test2.maintenanceInterval = 60

mysql.datasource.test2.maxIdleTime = 60

讀取配置文件信息

以下是讀取數據庫1的配置文件

@Data

@ConfigurationProperties(prefix = "mysql.datasource.test1")

public class DBConfig1 {

private String url;

private String username;

private String password;

private int minPoolSize;

private int maxPoolSize;

private int maxLifetime;

private int borrowConnectionTimeout;

private int loginTimeout;

private int maintenanceInterval;

private int maxIdleTime;

private String testQuery;

}

讀取數據庫2的配置文件略

創建數據源

數據源1:

@Configuration

// basePackages 最好分開配置 如果放在同一個文件夾可能會報錯

@MapperScan(basePackages = "tech.tengshe789.test01", sqlSessionTemplateRef = "testSqlSessionTemplate")

public class MyBatisConfig1 {

// 配置數據源

@Primary

@Bean(name = "testDataSource")

public DataSource testDataSource(DBConfig1 testConfig) throws SQLException {

MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();

mysqlXaDataSource.setUrl(testConfig.getUrl());

mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

mysqlXaDataSource.setPassword(testConfig.getPassword());

mysqlXaDataSource.setUser(testConfig.getUsername());

mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();

xaDataSource.setXaDataSource(mysqlXaDataSource);

xaDataSource.setUniqueResourceName("testDataSource");

xaDataSource.setMinPoolSize(testConfig.getMinPoolSize());

xaDataSource.setMaxPoolSize(testConfig.getMaxPoolSize());

xaDataSource.setMaxLifetime(testConfig.getMaxLifetime());

xaDataSource.setBorrowConnectionTimeout(testConfig.getBorrowConnectionTimeout());

xaDataSource.setLoginTimeout(testConfig.getLoginTimeout());

xaDataSource.setMaintenanceInterval(testConfig.getMaintenanceInterval());

xaDataSource.setMaxIdleTime(testConfig.getMaxIdleTime());

xaDataSource.setTestQuery(testConfig.getTestQuery());

return xaDataSource;

}

@Primary

@Bean(name = "testSqlSessionFactory")

public SqlSessionFactory testSqlSessionFactory(@Qualifier("testDataSource") DataSource dataSource)

throws Exception {

SqlSessionFactoryBean bean = new SqlSessionFactoryBean();

bean.setDataSource(dataSource);

return bean.getObject();

}

@Primary

@Bean(name = "testSqlSessionTemplate")

public SqlSessionTemplate testSqlSessionTemplate(

@Qualifier("testSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {

return new SqlSessionTemplate(sqlSessionFactory);

}

}

數據庫2略

如何啟動

@EnableConfigurationProperties(value = { DBConfig1.class, DBConfig2.class })

@SpringBootApplication

public class MainApplication {

public static void main(String[] args) {

SpringApplication.run(MainApplication.class, args);

}

}

定時任務

在做項目時有時候會有定時器任務的功能,比如某某時間應該做什麼,多少秒應該怎麼樣之類的。

spring支持多種定時任務的實現。我們來介紹下使用Quartz 和Scheduler

Spring Schedule

Spring Schedule 實現定時任務有兩種方式 1. 使用XML配置定時任務, 2. 使用 @Scheduled 註解。

代碼

固定等待時間 @Scheduled(fixedDelay = 時間間隔 )

固定間隔時間 @Scheduled(fixedRate = 時間間隔 )

@Component

public class ScheduleJobs {

public final static long SECOND = 1 * 1000;

FastDateFormat fdf = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss");

@Scheduled(fixedDelay = SECOND * 2)

public void fixedDelayJob() throws InterruptedException {

TimeUnit.SECONDS.sleep(2);

System.out.println("[FixedDelayJob Execute]"+fdf.format(new Date()));

}

}

Corn表達式 @Scheduled(cron = Corn表達式)

@Component

public class ScheduleJobs {

public final static long SECOND = 1 * 1000;

FastDateFormat fdf = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss");

@Scheduled(cron = "0/4 * * * * ?")

public void cronJob() {

System.out.println("[CronJob Execute]"+fdf.format(new Date()));

}

}

啟動

要在主方法上加上@EnableScheduling

Quartz

POM

org.springframework.boot

spring-boot-starter-quartz-starter

配置文件

# spring boot 2.x 已集成Quartz,無需自己配置

spring.quartz.job-store-type=jdbc

spring.quartz.properties.org.quartz.scheduler.instanceName=clusteredScheduler

spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO

spring.quartz.properties.org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX

spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate

spring.quartz.properties.org.quartz.jobStore.tablePrefix=QRTZ_

spring.quartz.properties.org.quartz.jobStore.isClustered=true

spring.quartz.properties.org.quartz.jobStore.clusterCheckinInterval=10000

spring.quartz.properties.org.quartz.jobStore.useProperties=false

spring.quartz.properties.org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool

spring.quartz.properties.org.quartz.threadPool.threadCount=10

spring.quartz.properties.org.quartz.threadPool.threadPriority=5

spring.quartz.properties.org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true

配置類

@Configuration

public class QuartzConfig {

@Bean

public JobDetail uploadTaskDetail() {

return JobBuilder.newJob(UploadTask.class).withIdentity("uploadTask").storeDurably().build();

}

@Bean

public Trigger uploadTaskTrigger() {

CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("*/5 * * * * ?");

return TriggerBuilder.newTrigger().forJob(uploadTaskDetail())

.withIdentity("uploadTask")

.withSchedule(scheduleBuilder)

.build();

}

}

實現類

創建一個配置類,分別制定具體任務類和觸發的規則

@Configuration

@DisallowConcurrentExecution

public class UploadTask extends QuartzJobBean {

@Resource

private TencentYunService tencentYunService;

@Override

protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {

System.out.println("任務開始");

try {

Thread.sleep(6000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("任務結束");

}

}

@DisallowConcurrentExecution禁止併發執行

併發執行方面,系統默認為true,即第一個任務還未執行完整,第二個任務如果到了執行時間,則會立馬開啟新線程執行任務,這樣如果我們是從數據庫讀取信息,兩次重複讀取可能出現重複執行任務的情況,所以我們需要將這個值設置為false,這樣第二個任務會往後推遲,只有在第一個任務執行完成後才會執行第二個任務

日誌管理

log4j

POM

org.springframework.boot

spring-boot-starter

org.springframework.boot

spring-boot-starter-logging

org.springframework.boot

spring-boot-starter-log4j

1.3.8.RELEASE

配置文件

文件名稱log4j.properties

#log4j.rootLogger=CONSOLE,info,error,DEBUG

log4j.rootLogger=info,error,CONSOLE,DEBUG

log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender

log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout

log4j.appender.CONSOLE.layout.ConversionPattern=%d{yyyy-MM-dd-HH-mm} [%t] [%c] [%p] - %m%n

log4j.logger.info=info

log4j.appender.info=org.apache.log4j.DailyRollingFileAppender

log4j.appender.info.layout=org.apache.log4j.PatternLayout

log4j.appender.info.layout.ConversionPattern=%d{yyyy-MM-dd-HH-mm} [%t] [%c] [%p] - %m%n

log4j.appender.info.datePattern='.'yyyy-MM-dd

log4j.appender.info.Threshold = info

log4j.appender.info.append=true

#log4j.appender.info.File=/home/admin/pms-api-services/logs/info/api_services_info

log4j.appender.info.File=/Users/dddd/Documents/testspace/pms-api-services/logs/info/api_services_info

log4j.logger.error=error

log4j.appender.error=org.apache.log4j.DailyRollingFileAppender

log4j.appender.error.layout=org.apache.log4j.PatternLayout

log4j.appender.error.layout.ConversionPattern=%d{yyyy-MM-dd-HH-mm} [%t] [%c] [%p] - %m%n

log4j.appender.error.datePattern='.'yyyy-MM-dd

log4j.appender.error.Threshold = error

log4j.appender.error.append=true

#log4j.appender.error.File=/home/admin/pms-api-services/logs/error/api_services_error

log4j.appender.error.File=/Users/dddd/Documents/testspace/pms-api-services/logs/error/api_services_error

log4j.logger.DEBUG=DEBUG

log4j.appender.DEBUG=org.apache.log4j.DailyRollingFileAppender

log4j.appender.DEBUG.layout=org.apache.log4j.PatternLayout

log4j.appender.DEBUG.layout.ConversionPattern=%d{yyyy-MM-dd-HH-mm} [%t] [%c] [%p] - %m%n

log4j.appender.DEBUG.datePattern='.'yyyy-MM-dd

log4j.appender.DEBUG.Threshold = DEBUG

log4j.appender.DEBUG.append=true

#log4j.appender.DEBUG.File=/home/admin/pms-api-services/logs/debug/api_services_debug

log4j.appender.DEBUG.File=/Users/dddd/Documents/testspace/pms-api-services/logs/debug/api_services_debug

使用

private static final Logger logger = LoggerFactory.getLogger(IndexController.class);

複製代碼

使用AOP統一處理Web請求日誌

POM

org.springframework.boot

spring-boot-starter-aop

代碼

@Aspect

@Component

public class WebLogAspect {

private static final Logger logger = LoggerFactory.getLogger(WebLogAspect.class);

@Pointcut("execution(public * tech.tengshe789.controller.*.*(..))")

public void webLog() {

}

@Before("webLog()")

public void doBefore(JoinPoint joinPoint) throws Throwable {

// 接收到請求,記錄請求內容

ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

HttpServletRequest request = attributes.getRequest();

// 記錄下請求內容

logger.info("URL : " + request.getRequestURL().toString());

logger.info("HTTP_METHOD : " + request.getMethod());

logger.info("IP : " + request.getRemoteAddr());

Enumeration enu = request.getParameterNames();

while (enu.hasMoreElements()) {

String name = (String) enu.nextElement();

logger.info("name:{},value:{}", name, request.getParameter(name));

}

}

@AfterReturning(returning = "ret", pointcut = "webLog()")

public void doAfterReturning(Object ret) throws Throwable {

// 處理完請求,返回內容

logger.info("RESPONSE : " + ret);

}

}

lombok 插件

非常簡單的辦法

POM

org.projectlombok

lombok

1.18.0

代碼

類中添加@Slf4j 註解即可。使用是直接輸入log全局變量

Lombok的其他用法

@Data 標籤,生成getter/setter toString()等方法

@NonNull : 讓你不在擔憂並且愛上NullPointerException

@CleanUp : 自動資源管理:不用再在finally中添加資源的close方法

@Setter/@Getter : 自動生成set和get方法

@ToString : 自動生成toString方法

@EqualsAndHashcode : 從對象的字段中生成hashCode和equals的實現

@NoArgsConstructor/@RequiredArgsConstructor/@AllArgsConstructor

自動生成構造方法

@Data : 自動生成set/get方法,toString方法,equals方法,hashCode方法,不帶參數的構造方法

@Value : 用於註解final類

@Builder : 產生複雜的構建器api類

@SneakyThrows : 異常處理(謹慎使用)

@Synchronized : 同步方法安全的轉化

@Getter(lazy=true) :

@Log : 支持各種logger對象,使用時用對應的註解,如:@Log4

攔截器

攔截器,在AOP(Aspect-Oriented Programming)中用於在某個方法或字段被訪問之前,進行攔截,然後在之前或之後加入某些操作。攔截是AOP的一種實現策略。

(1)攔截器是基於java的反射機制的,而過濾器是基於函數回調。

(2)攔截器不依賴於servlet容器,而過濾器依賴於servlet容器。

(3)攔截器只能對Controller請求起作用,而過濾器則可以對幾乎所有的請求起作用。

(4)在Controller的生命週期中,攔截器可以多次被調用,而過濾器只能在容器初始化時被調用一次。

過濾器(filter)和攔截器(interceptor)是有區別的,詳情 ,他們的執行順序: 先filter 後 interceptor

->過濾器應用場景:設置編碼字符、過濾銘感字符

->攔截器應用場景:攔截未登陸用戶、審計日誌

自定義攔截器

代碼

註冊攔截器

@Configuration

public class WebAppConfig {

@Autowired

private LoginIntercept loginIntercept;

@Bean

public WebMvcConfigurer WebMvcConfigurer() {

return new WebMvcConfigurer() {

public void addInterceptors(InterceptorRegistry registry) {

registry.addInterceptor(loginIntercept).addPathPatterns("/*");

};

};

}

}

創建模擬登錄攔截器,驗證請求是否有token參數

@Slf4j

@Component

public class LoginIntercept implements HandlerInterceptor {

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)

throws Exception {

log.info("開始攔截登錄請求....");

String token = request.getParameter("token");

if (StringUtils.isEmpty(token)) {

response.getWriter().println("not found token");

return false;

}

return true;

}

}

緩存

在 Spring Boot中,通過@EnableCaching註解自動化配置合適的緩存管理器(CacheManager),Spring Boot根據下面的順序去偵測緩存提供者: Generic , JCache (JSR-107), EhCache 2.x ,Hazelcast , Infinispan ,Redis ,Guava , Simple

EhCache

POM

org.springframework.boot

spring-boot-starter-cache

新建ehcache.xml 文件

xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"

updateCheck="false">

timeToIdleSeconds="120" timeToLiveSeconds="120"

memoryStoreEvictionPolicy="LRU" overflowToDisk="false" />

maxElementsOnDisk="100000" />

配置信息介紹

name:緩存名稱。

maxElementsInMemory:緩存最大個數。

eternal:對象是否永久有效,一但設置了,timeout將不起作用。

timeToIdleSeconds:設置對象在失效前的允許閒置時間(單位:秒)。僅當eternal=false對象不是永久有效時使用,可選屬性,默認值是0,也就是可閒置時間無窮大。

timeToLiveSeconds:設置對象在失效前允許存活時間(單位:秒)。最大時間介於創建時間和失效時間之間。僅當eternal=

false對象不是永久有效時使用,默認是0.,也就是對象存活時間無窮大。

overflowToDisk:當內存中對象數量達到maxElementsInMemory時,Ehcache將會對象寫到磁盤中。

diskSpoolBufferSizeMB:這個參數設置DiskStore(磁盤緩存)的緩存區大小。默認是30MB。每個Cache都應該有自己的一個緩衝區。

maxElementsOnDisk:硬盤最大緩存個數。

diskPersistent:是否緩存虛擬機重啟期數據 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.

diskExpiryThreadIntervalSeconds:磁盤失效線程運行時間間隔,默認是120秒。

memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理內存。默認策略是LRU(最近最少使用)。你可以設置為FIFO(先進先出)或是LFU(較少使用)。

clearOnFlush:內存數量最大時是否清除。

關於註解和代碼使用

@CacheConfig(cacheNames = "baseCache")

public interface UserDao {

@Select("select * from users where name=#{name}")

@Cacheable

UserEntity findName(@Param("name") String name);

}

清除緩存

@Autowired

private CacheManager cacheManager;

@RequestMapping("/remoKey")

public void remoKey() {

cacheManager.getCache("baseCache").clear();

}

啟動

主方法啟動時加上@EnableCaching即可

Redis

使用自帶驅動器連接

使用RedisTemplate 連接

POM

org.springframework.boot

spring-boot-starter-data-redis

配置文件

單機

#redis

# Redis數據庫索引(默認為0)

spring.redis.database=0

# Redis服務器地址

spring.redis.host=127.0.0.1

# Redis服務器連接端口

spring.redis.port=6379

# Redis服務器連接密碼(默認為空)

spring.redis.password=

# 連接池最大連接數(使用負值表示沒有限制)

spring.redis.pool.max-active=8

# 連接池最大阻塞等待時間(使用負值表示沒有限制)

spring.redis.pool.max-wait=-1

# 連接池中的最大空閒連接

spring.redis.pool.max-idle=8

# 連接池中的最小空閒連接

spring.redis.pool.min-idle=0

# 連接超時時間(毫秒)

spring.redis.timeout=0

集群或哨兵模式

#Matser的ip地址

redis.hostName=192.168.177.128

#端口號

redis.port=6382

#如果有密碼

redis.password=

#客戶端超時時間單位是毫秒 默認是2000

redis.timeout=10000

#最大空閒數

redis.maxIdle=300

#連接池的最大數據庫連接數。設為0表示無限制,如果是jedis 2.4以後用redis.maxTotal

#redis.maxActive=600

#控制一個pool可分配多少個jedis實例,用來替換上面的redis.maxActive,如果是jedis 2.4以後用該屬性

redis.maxTotal=1000

#最大建立連接等待時間。如果超過此時間將接到異常。設為-1表示無限制。

redis.maxWaitMillis=1000

#連接的最小空閒時間 默認1800000毫秒(30分鐘)

redis.minEvictableIdleTimeMillis=300000

#每次釋放連接的最大數目,默認3

redis.numTestsPerEvictionRun=1024

#逐出掃描的時間間隔(毫秒) 如果為負數,則不運行逐出線程, 默認-1

redis.timeBetweenEvictionRunsMillis=30000

#是否在從池中取出連接前進行檢驗,如果檢驗失敗,則從池中去除連接並嘗試取出另一個

redis.testOnBorrow=true

#在空閒時檢查有效性, 默認false

redis.testWhileIdle=true

#redis集群配置

spring.redis.cluster.nodes=192.168.177.128:7001,192.168.177.128:7002,192.168.177.128:7003,192.168.177.128:7004,192.168.177.128:7005,192.168.177.128:7006

spring.redis.cluster.max-redirects=3

#哨兵模式

#redis.sentinel.host1=192.168.177.128

#redis.sentinel.port1=26379

#redis.sentinel.host2=172.20.1.231

#redis.sentinel.port2=26379

配置類

@Configuration

@EnableCaching

public class RedisConfig extends CachingConfigurerSupport{

@Value("${spring.redis.host}")

private String host;

@Value("${spring.redis.port}")

private int port;

@Value("${spring.redis.timeout}")

private int timeout;

//自定義緩存key生成策略

// @Bean

// public KeyGenerator keyGenerator() {

// return new KeyGenerator(){

// @Override

// public Object generate(Object target, java.lang.reflect.Method method, Object... params) {

// StringBuffer sb = new StringBuffer();

// sb.append(target.getClass().getName());

// sb.append(method.getName());

// for(Object obj:params){

// sb.append(obj.toString());

// }

// return sb.toString();

// }

// };

// }

//緩存管理器

@Bean

public CacheManager cacheManager(@SuppressWarnings("rawtypes") RedisTemplate redisTemplate) {

RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);

//設置緩存過期時間

cacheManager.setDefaultExpiration(10000);

return cacheManager;

}

@Bean

public RedisTemplate redisTemplate(RedisConnectionFactory factory){

StringRedisTemplate template = new StringRedisTemplate(factory);

setSerializer(template);//設置序列化工具

template.afterPropertiesSet();

return template;

}

private void setSerializer(StringRedisTemplate template){

@SuppressWarnings({ "rawtypes", "unchecked" })

Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

ObjectMapper om = new ObjectMapper();

om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);

om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

jackson2JsonRedisSerializer.setObjectMapper(om);

template.setValueSerializer(jackson2JsonRedisSerializer);

}

}

Dao

@Mapper

@CacheConfig(cacheNames = "users")

public interface UserMapper {

@Insert("insert into user(name,age) values(#{name},#{age})")

int addUser(@Param("name")String name,@Param("age")String age);

@Select("select * from user where id =#{id}")

@Cacheable(key ="#p0")

User findById(@Param("id") String id);

@CachePut(key = "#p0")

@Update("update user set name=#{name} where id=#{id}")

void updataById(@Param("id")String id,@Param("name")String name);

//如果指定為 true,則方法調用後將立即清空所有緩存

@CacheEvict(key ="#p0",allEntries=true)

@Delete("delete from user where id=#{id}")

void deleteById(@Param("id")String id);

}

@Cacheable將查詢結果緩存到redis中,(key="#p0")指定傳入的第一個參數作為redis的key。

@CachePut,指定key,將更新的結果同步到redis中

@CacheEvict,指定key,刪除緩存數據,allEntries=true,方法調用後將立即清除緩存

使用Jedis連接

要注意,redis在5.0版本以後不支持Jedis

POM

redis.clients

jedis

配置類

@Data

@Component

@ConfigurationProperties(prefix="redis")

public class RedisConfig {

private String host;

private int port;

private int timeout;//秒

private String password;

private int poolMaxTotal;

private int poolMaxIdle;

private int poolMaxWait;//秒

}

@Service

public class RedisPoolFactory {

@Autowired

RedisConfig redisConfig;

@Bean

public JedisPool edisPoolFactory() {

JedisPoolConfig poolConfig = new JedisPoolConfig();

poolConfig.setMaxIdle(redisConfig.getPoolMaxIdle());

poolConfig.setMaxTotal(redisConfig.getPoolMaxTotal());

poolConfig.setMaxWaitMillis(redisConfig.getPoolMaxWait() * 1000);

JedisPool jp = new JedisPool(poolConfig, redisConfig.getHost(), redisConfig.getPort(),

redisConfig.getTimeout()*1000, redisConfig.getPassword(), 0);

return jp;

}

}

監控中心

Springboot監控中心是幹什麼的呢?他是針對微服務的 服務狀態、Http請求資源進行監控,可以看到服務器內存變化(堆內存、線程、日誌管理),可以檢測服務配置連接地址是否可用(模擬訪問,懶加載),可以統計有多少Bean有什麼單例多例,可以統計SpringMVC有多少@RequestMapping

Actuator

Actuator是spring boot的一個附加功能,可幫助你在應用程序生產環境時監視和管理應用程序。

可以使用HTTP的各種請求來監管,審計,收集應用的運行情況.返回的是json

缺點:沒有可視化界面。

在springboot2.0中,Actuator的端點(endpoint)現在默認映射到/application,比如,/info 端點現在就是在/application/info。但你可以使用management.context-path來覆蓋此默認值。

POM

org.springframework.boot

spring-boot-starter-actuator

配置信息

# Actuator 通過下面的配置啟用所有的監控端點,默認情況下,這些端點是禁用的;

management:

endpoints:

web:

exposure:

include: "*"

spring:

profiles:

active: prod

datasource:

driver-class-name: com.mysql.jdbc.Driver

url: jdbc:mysql://127.0.0.1:3306/test

username: root

password: 123456

Actuator訪問路徑

通過actuator/+端點名就可以獲取相應的信息。

路徑 作用 /actuator/beans 顯示應用程序中所有Spring bean的完整列表。 /actuator/configprops 顯示所有配置信息。 /actuator/env 陳列所有的環境變量。 /actuator/mappings 顯示所有@RequestMapping的url整理列表。 /actuator/health 顯示應用程序運行狀況信息 up表示成功 down失敗 /actuator/info 查看自定義應用信息 Admin-UI分佈式微服務監控中心

Admin-UI底層使用actuator,實現監控信息 的界面

POM

de.codecentric

spring-boot-admin-starter-server

2.0.0

de.codecentric

spring-boot-admin-starter-client

2.0.0

org.springframework.boot

spring-boot-starter-actuator

org.jolokia

jolokia-core

com.googlecode.json-simple

json-simple

1.1

application.yml配置文件

//服務端

spring:

application:

name: spring-boot-admin-server

//客戶端

spring:

boot:

admin:

client:

url: http://localhost:8080

server:

port: 8081

management:

endpoints:

web:

exposure:

include: "*"

endpoint:

health:

show-details: ALWAYS

性能優化

掃包優化

默認情況下,我們會使用 @SpringBootApplication 註解來自動獲取應用的配置信息,但這樣也會給應用帶來一些副作用。使用這個註解後,會觸發自動配置( auto-configuration )和 組件掃描 ( component scanning ),這跟使用 @Configuration、@EnableAutoConfiguration 和 @ComponentScan 三個註解的作用是一樣的。這樣做給開發帶來方便的同時,也會有三方面的影響:

1、會導致項目啟動時間變長。當啟動一個大的應用程序,或將做大量的集成測試啟動應用程序時,影響會特別明顯。

2、會加載一些不需要的多餘的實例(beans)。

3、會增加 CPU 消耗。

針對以上三個情況,我們可以移除 @SpringBootApplication 和 @ComponentScan 兩個註解來禁用組件自動掃描,然後在我們需要的 bean 上進行顯式配置。

SpringBoot JVM參數調優

各種參數

參數名稱 含義 默認值 -Xms 初始堆大小 物理內存的1/64(<1GB) 默認(MinHeapFreeRatio參數可以調整)空餘堆內存小於40%時,JVM就會增大堆直到-Xmx的最大限制. -Xmx 最大堆大小 物理內存的1/4(<1GB) 默認(MaxHeapFreeRatio參數可以調整)空餘堆內存大於70%時,JVM會減少堆直到 -Xms的最小限制 -Xmn 年輕代大小(1.4or lator) 注意:此處的大小是(eden+ 2 survivor space).與jmap -heap中顯示的New gen是不同的。 整個堆大小=年輕代大小 + 年老代大小 + 持久代大小. 增大年輕代後,將會減小年老代大小.此值對系統性能影響較大,Sun官方推薦配置為整個堆的3/8 -XX:NewSize 設置年輕代大小(for 1.3/1.4) -XX:MaxNewSize 年輕代最大值(for 1.3/1.4) -XX:PermSize 設置持久代(perm gen)初始值 物理內存的1/64 -XX:MaxPermSize 設置持久代最大值 物理內存的1/4 -Xss 每個線程的堆棧大小 JDK5.0以後每個線程堆棧大小為1M,以前每個線程堆棧大小為256K.更具應用的線程所需內存大小進行 調整.在相同物理內存下,減小這個值能生成更多的線程.但是操作系統對一個進程內的線程數還是有限制的,不能無限生成,經驗值在3000~5000左右 一般小的應用, 如果棧不是很深, 應該是128k夠用的 大的應用建議使用256k。這個選項對性能影響比較大,需要嚴格的測試。(校長) 和threadstacksize選項解釋很類似,官方文檔似乎沒有解釋,在論壇中有這樣一句話:"” -Xss is translated in a VM flag named ThreadStackSize” 一般設置這個值就可以了。 -XX:ThreadStackSize Thread Stack Size (0 means use default stack size) [Sparc: 512; Solaris x86: 320 (was 256 prior in 5.0 and earlier); Sparc 64 bit: 1024; Linux amd64: 1024 (was 0 in 5.0 and earlier); all others 0.] -XX:NewRatio 年輕代(包括Eden和兩個Survivor區)與年老代的比值(除去持久代) -XX:NewRatio=4表示年輕代與年老代所佔比值為1:4,年輕代佔整個堆棧的1/5 Xms=Xmx並且設置了Xmn的情況下,該參數不需要進行設置。 -XX:SurvivorRatio Eden區與Survivor區的大小比值 設置為8,則兩個Survivor區與一個Eden區的比值為2:8,一個Survivor區佔整個年輕代的1/10 -XX:LargePageSizeInBytes 內存頁的大小不可設置過大, 會影響Perm的大小 =128m -XX:+UseFastAccessorMethods 原始類型的快速優化 -XX:+DisableExplicitGC 關閉System.gc() 這個參數需要嚴格的測試 -XX:MaxTenuringThreshold 垃圾最大年齡 如果設置為0的話,則年輕代對象不經過Survivor區,直接進入年老代. 對於年老代比較多的應用,可以提高效率.如果將此值設置為一個較大值,則年輕代對象會在Survivor區進行多次複製,這樣可以增加對象再年輕代的存活 時間,增加在年輕代即被回收的概率 該參數只有在串行GC時才有效. -XX:+AggressiveOpts 加快編譯 -XX:+UseBiasedLocking 鎖機制的性能改善 -Xnoclassgc 禁用垃圾回收 -XX:SoftRefLRUPolicyMSPerMB 每兆堆空閒空間中SoftReference的存活時間 1s softly reachable objects will remain alive for some amount of time after the last time they were referenced. The default value is one second of lifetime per free megabyte in the heap -XX:PretenureSizeThreshold 對象超過多大是直接在舊生代分配 0 單位字節 新生代採用Parallel Scavenge GC時無效 另一種直接在舊生代分配的情況是大的數組對象,且數組中無外部引用對象. -XX:TLABWasteTargetPercent TLAB佔eden區的百分比 1% -XX:+CollectGen0First FullGC時是否先YGC false 調優策略

  1. 初始化堆內存和最大堆相同
  2. 減少垃圾回收次數

內部調優

這是一篇優雅的Springboot2.0使用手冊

輸入 -XX:+PrintGCDetails 是為了在控制檯顯示回收的信息

這是一篇優雅的Springboot2.0使用手冊

外部調優

進入對應jar的目錄,在CMD輸入java -server -Xms32m -Xmx32m -jar springboot.jar

使用工具java visual vm

這是一篇優雅的Springboot2.0使用手冊

使用工具java console

這是一篇優雅的Springboot2.0使用手冊

將Servlet容器從Tomcat變成Undertow

Undertow 是一個採用 Java 開發的靈活的高性能 Web 服務器,提供包括阻塞和基於 NIO 的非堵塞機制。Undertow 是紅帽公司的開源產品,是 JBoss默認的 Web 服務器。

Undertow

POM

首先,從依賴信息裡移除 Tomcat 配置

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-starter-tomcat

然後添加 Undertow:

org.springframework.boot

spring-boot-starter-undertow

Tomcat 優化

見Spring Boot Memory Performance

熱部署

熱部署,就是在應用程序在不停止的情況下,自動實現新的部署

原理

使用類加載器classroad來檢測字節碼文件,然後重新加載到jvm內存中

第一步:檢測本地.class文件變動(版本號,修改時間不一樣)

第二步:自動監聽,實現部署

應用場景

本地開發時,可以提高運行環境

Dev-tools

spring-boot-devtools 是一個為開發者服務的一個模塊,其中最重要的功能就是自動應用代碼更改到最新的App上面去

POM

org.springframework.boot

spring-boot-devtools

true

true

原理

  1. devtools會監聽classpath下的文件變動,並且會立即重啟應用(發生在保存時機),因為其採用的虛擬機機制,該項重啟是很快的。
  2. devtools可以實現頁面熱部署(即頁面修改後會立即生效,這個可以直接在application.properties文件中配置spring.thymeleaf.cache=false來實現(注意:不同的模板配置不一樣)

發佈打包

Jar類型打包方式

1.使用mvn clean package 打包

2.使用java –jar 包名

war類型打包方式

1.使用mvn celan package 打包

2.使用java –jar 包名

外部Tomcat運行

1.使用mvn celan package 打包

2.將war包 放入到tomcat webapps下運行即可。

注意:springboot2.0內置tomcat8.5.25,建議使用外部Tomcat9.0版本運行即可,否則報錯版本不兼容。

POM

org.apache.maven.plugins

maven-compiler-plugin

1.8

1.8

org.springframework.boot

spring-boot-maven-plugin

com.itmayiedu.app.App

repackage

喜歡的點下關注,謝謝,每天都會分享Java相關文章。


分享到:


相關文章: