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

tengshe789發表於2018-08-07

最近再研究springboot的原理?頗有收穫,現在讓我分享一下springboot如何使用吧~

想要解鎖更多新姿勢?請訪問我的部落格

啥是Springboot

和書上理解的不同,我認為Springboot是一個優秀的快速搭建框架,他通過maven繼承方式新增依賴來整合很多第三方工具,可以避免各種麻煩的配置,有各種內嵌容器簡化Web專案,還能避免依賴的干擾,它內建tomcat,jetty容器,使用的是java app執行程式,而不是傳統的用把war放在tomcat等容器中執行

和JFinal的區別

JFinal是國人出品的一個web + orm 框架 ,JFinal,優點是開發迅速、程式碼量少、學習簡單、功能強大、輕量級、易擴充套件。核心就是極致簡潔。他沒有商業機構的支援,所以宣傳不到位,少有人知。

Springboot相比與JFinal最大的優點就是支援的功能非常多,可以非常方便的將spring的各種框架如springframework , spring-mvc, spring-security, spring-data-jpa, spring-cache等等整合起來進行自動化配置 ,而且生態 比較好,很多產品都對Springboot做出一定支援。

與Springcloud的區別

可以這麼理解,Springboot裡面包含了Springcloud,Springcloud只是Springboot裡面的一個元件而已。

Springcloud提供了相當完整的微服務架構。而微服務架構,本質來說就是分散式架構,意味著你要將原來是一個整體的專案拆分成一個個的小型專案,然後利用某種機制將其聯合起來,例如服務治理、通訊框架等基礎設施。

SpringBoot和SpringMVC區別

SpringBoot的Web元件,預設整合的是SpringMVC框架。

快速使用

要往下看的話,注意了?

  • Springboot 2.x 要求 JDK 1.8 環境及以上版本。另外,Springboot 2.x 只相容 Spring Framework 5.0 及以上版本。
  • 為 Springboot 2.x 提供了相關依賴構建工具是 Maven,版本需要 3.2 及以上版本。使用 Gradle 則需要 1.12 及以上版本。
  • 建議用IntelliJ IDEA IntelliJ IDEA (簡稱 IDEA)

建立專案

我已經好久沒用Eclipse了,要知道Eclipse是建立一個maven專案在引入Springboot依賴建立的。

下面我分享一下用IDEA建立Springboot的方法。

1533536250534

很簡單,在這個介面裡面就可以建立Springboot了。接下來在新增一些元件。

1533536423232

大功告成!

寫一個DEMO

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

建立一個conntroller包,編寫一個樣列。

package cn.tengshe789.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/demo")
public class SampleController {

    @RequestMapping("/hello")
    public String index() {
        return "Hello World";
    }

}
複製程式碼

接下來在他同級包或者上一級的包內,建立一個主方法MainApplication。方法內容;

@SpringBootApplication
@EnableAsync
//@ComponentScan("cn.tengshe789.controller")
//@EnableAutoConfiguration
public class MainApplication {
    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
    }
}
複製程式碼

在瀏覽器輸入http://127.0.0.1:8080/demo/hello/,就可以啟動了!

SpringApplication.run

Springboot將他標識為啟動類,用它啟動Springboot專案

基礎註解解釋

@RestController

在上加上RestController 表示修飾該Controller所有的方法返回JSON格式,直接可以編寫Restful介面。就相當於@Controller+@ResponseBody這種實現

@SpringBootApplication

用在啟動Springboot中,相當於@ComponentScan+@EnableAutoConfiguration+@Configuration

@ComponentScan("cn.tengshe789.controller")

控制器掃包範圍。

@EnableAutoConfiguration

他讓 Spring Boot 根據我們應用所宣告的依賴來對 Spring 框架進行自動配置。意思是,建立專案時新增的spring-boot-starter-web新增了Tomcat和Spring MVC,所以auto-configuration將假定你正在開發一個web應用並相應地對Spring進行設定。

配置檔案

properties

規則:

1、名用大寫比較規範

2、=兩邊別打空格

3、名值對寫完後別打分號

自定義引數

name=tengshe789
複製程式碼

多環境配置

spring.profiles.active=pre

application-dev.properties:開發環境
application-test.properties:測試環境
application-prod.properties:生產環境
複製程式碼

修改埠號

server.port=8888 
server.context-path=/tengshe789
複製程式碼

yaml

規則:

  1. 使用空格 Space 縮排表示分層,不同層次之間的縮排可以使用不同的空格數目,但是同層元素一定左對齊,即前面空格數目相同(不能使用 Tab,各個系統 Tab對應的 Space 數目可能不同,導致層次混亂)
  2. ‘#’表示註釋,只能單行註釋,從#開始處到行尾
  3. 破折號後面跟一個空格(a dash and space)表示列表
  4. 用冒號和空格表示鍵值對 key: value
  5. 簡單資料(scalars,標量資料)可以不使用引號括起來,包括字串資料。用單引號或者雙引號括起來的被當作字串資料,在單引號或雙引號中使用C風格的轉義字元
server:
  port:  8080
  context-path: /springboot
複製程式碼

xml

Springboot官方不推薦xml,略

Web開發

一個專案用Springboot,十有八九就是用於Web開發。首先讓我們看看Springboot怎麼快速開發Web把

如何訪問靜態資源

請在resources目錄下建立static資料夾,在該位置放置一個靜態資源。

目錄:src/main/resources/static

1533537772267

啟動程式後,嘗試訪問http://localhost:8080/img.xxx/。就可以訪問了。

關於渲染Web頁面

在之前的快速使用的示例中,我們都是通過新增@RestController來處理請求,所以返回的內容為json物件。那麼如果需要渲染html頁面的時候,要如何實現呢?

模板引擎方法

Springboot依然可以實現動態HTML,並且提供了多種模板引擎的預設配置支援,Springboot官方文件有如下推薦的模板引擎:

· Thymeleaf

· FreeMarker

· Velocity

· Groovy

· Mustache

Springboot官方建議避免使用JSP,若一定要使用JSP將無法實現Spring Boot的多種特性。

在Springboot中,預設的模板配置路徑都時:src/main/resources/templates。當然也可以修改這個路徑,具體如何修改,可在各模板引擎的配置屬性中查詢並修改。

Thymeleaf(胸腺)

這裡還是用我寫的一個秒殺專案作為參考栗子。秒殺商城

POM

	<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
複製程式碼

配置檔案

application.properties中新增:

#thymeleaf
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.cache=false
spring.thymeleaf.servlet.content-type=text/html
spring.thymeleaf.enabled=true
spring.thymeleaf.encoding=UTF-8
# 一代填 spring.thymeleaf.mode=HTML5
spring.thymeleaf.mode=HTML
複製程式碼

後臺

在src/main/resources/建立一個templates資料夾,新網頁字尾為*.html

 @RequestMapping("/to_list")
    public String list(Model model,MiaoshaUser user) {
    	model.addAttribute("user", user);
    	//查詢商品列表
    	List<GoodsVo> goodsList = goodsService.listGoodsVo();
    	model.addAttribute("goodsList", goodsList);
        return "goods_list";
    }
複製程式碼

前臺

這裡注意Thymeleaf語法,Thymeleaf很像HTML,不同之處在標籤加了一個th字首

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>商品列表</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <!-- jquery -->
    <script type="text/javascript" th:src="@{/js/jquery.min.js}"></script>
</head>
<body>

<div class="panel panel-default" >
    <div class="panel-heading">秒殺商品列表</div>
    <table class="table" id="goodslist">
        <tr><td>商品名稱</td><td>商品圖片</td><td>商品原價</td><td>秒殺價</td><td>庫存數量</td><td>詳情</td></tr>
        <tr  th:each="goods,goodsStat : ${goodsList}">
            <td th:text="${goods.goodsName}"></td>
            <td ><img th:src="@{${goods.goodsImg}}" width="100" height="100" /></td>
            <td th:text="${goods.goodsPrice}"></td>
            <td th:text="${goods.miaoshaPrice}"></td>
            <td th:text="${goods.stockCount}"></td>
            <td><a th:href="'/goods_detail.htm?goodsId='+${goods.id}">詳情</a></td>
        </tr>
    </table>
</div>
</body>
</html>
複製程式碼

Freemarker(自由標記)

POM

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-freemarker</artifactId>
	</dependency>
複製程式碼

配置檔案

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<String, Object> result) {
		result.put("nickname", "tEngSHe789");
        result.put("old", "18");
		result.put("my Blog", "HTTPS://blog.tengshe789.tech/");
		List<String> listResult = new ArrayList<String>();
		listResult.add("guanyu");
		listResult.add("zhugeliang");
		result.put("listResult", listResult);
		return "index";
	}
複製程式碼

前臺

<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8" />
<title>首頁</title>
</head>
<body>
	  ${nickname}
<#if old=="18">
            太假了吧哥們
      <#elseif old=="21">
            你是真的21歲
     <#else>
        其他      
	  
	  </#if>	  
	 <#list userlist as user>
	   ${user}
	 </#list>
</body> 
</html>
複製程式碼

JSP

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

POM

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.4.RELEASE</version>
	</parent>
	<dependencies>
		<!-- SpringBoot web 核心元件 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
		</dependency>
	<!-- SpringBoot 外部tomcat支援 -->	
	<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-jasper</artifactId>
		</dependency>
	</dependencies>
複製程式碼

配置檔案

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 註解方式,另一種是使用其功能性端點方式。

img

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

官方例項

@RestController
public class PersonController {
	private final PersonRepository repository;
 
    public PersonController(PersonRepository repository) {
            this.repository = repository;
    }
 
    @PostMapping("/person")
    Mono<Void> create(@RequestBody Publisher<Person> personStream) {
            return this.repository.save(personStream).then();
    }
 
    @GetMapping("/person")
    Flux<Person> list() {
            return this.repository.findAll();
    }
 
    @GetMapping("/person/{id}")
    Mono<Person> 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<City> 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

<dependency>
   	 <groupId>org.springframework.boot</groupId>
	 <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
複製程式碼

配置檔案

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

    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>1.3.2</version>
    </dependency>
複製程式碼

配置檔案

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<GoodsVo> listGoodsVo();
}
複製程式碼

建立service

@Service
public class GoodsService {

    @Autowired
    GoodsDao goodsDao;

    /*
     * 展示商品列表
     */
    public List<GoodsVo> listGoodsVo() {
        return goodsDao.listGoodsVo();
    }
}
複製程式碼

Mybatis整合分頁外掛PageHelper

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

POM

		<dependency>
			<groupId>com.github.pagehelper</groupId>
			<artifactId>pagehelper-spring-boot-starter</artifactId>
			<version>1.2.5</version>
		</dependency>
複製程式碼

配置檔案

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<User> findUserList();
}
複製程式碼
Service層
@Service
public class UserService {
	@Autowired
	private UserMapper userDao;

	/**
	 * page 當前頁數<br>
	 * size 當前展示的資料<br>	
	 */
	public PageInfo<User> findUserList(int page, int size) {
		// 開啟分頁外掛,放在查詢語句上面
		PageHelper.startPage(page, size);
		List<User> listUser = userDao.findUserList();
		// 封裝分頁之後的資料
		PageInfo<User> pageInfoUser = new PageInfo<User>(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

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
複製程式碼

配置檔案

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<User, Integer> {
}
複製程式碼
Controller
@RestController
public class IndexController {
	@Autowired
	private UserDao userDao;

	@RequestMapping("/jpaFindUser")
	public Object jpaIndex(User user) {
		Optional<User> 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 , BitronixNarayana類事務管理器

類事務管理器Atomikos

POM
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
複製程式碼
配置檔案
# 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

  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-quartz-starter</artifactId>
    </dependency>
複製程式碼

配置檔案

# 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

<!-- spring boot start -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
			<exclusions>
				<!-- 排除自帶的logback依賴 -->
				<exclusion>
					<groupId>org.springframework.boot</groupId>
					<artifactId>spring-boot-starter-logging</artifactId>
				</exclusion>
			</exclusions>
		</dependency>

		<!-- springboot-log4j -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-log4j</artifactId>
			<version>1.3.8.RELEASE</version>
		</dependency>

複製程式碼

配置檔案

檔名稱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

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

複製程式碼

程式碼

@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<String> 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

<dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.0</version>
</dependency>
複製程式碼

程式碼

類中新增@Slf4j 註解即可。使用是直接輸入log全域性變數

Lombok的其他用法

@Data 標籤,生成getter/setter toString()等方法 
@NonNull : 讓你不在擔憂並且愛上NullPointerException 
@CleanUp : 自動資源管理:不用再在finally中新增資源的close方法 
@Setter/@Getter : 自動生成setget方法 
@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

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
複製程式碼

新建ehcache.xml 檔案

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
	updateCheck="false">
	<diskStore path="java.io.tmpdir/Tmp_EhCache" />

	<!-- 預設配置 -->
	<defaultCache maxElementsInMemory="5000" eternal="false"
		timeToIdleSeconds="120" timeToLiveSeconds="120"
		memoryStoreEvictionPolicy="LRU" overflowToDisk="false" />

	<cache name="baseCache" maxElementsInMemory="10000"
		maxElementsOnDisk="100000" />

</ehcache>

複製程式碼

配置資訊介紹

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
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
複製程式碼
配置檔案

單機

#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<String, String> 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
	<dependency>
      <groupId>redis.clients</groupId>
      <artifactId>jedis</artifactId>
    </dependency>
複製程式碼
配置類
@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

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
複製程式碼

配置資訊

# 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

		<!--服務端-->
		<dependency>
			<groupId>de.codecentric</groupId>
			<artifactId>spring-boot-admin-starter-server</artifactId>
			<version>2.0.0</version>
		</dependency>
		<!--客戶端-->
		<dependency>
			<groupId>de.codecentric</groupId>
			<artifactId>spring-boot-admin-starter-client</artifactId>
			<version>2.0.0</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
		<dependency>
			<groupId>org.jolokia</groupId>
			<artifactId>jolokia-core</artifactId>
		</dependency>
		<dependency>
			<groupId>com.googlecode.json-simple</groupId>
			<artifactId>json-simple</artifactId>
			<version>1.1</version>
複製程式碼

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. 減少垃圾回收次數
內部調優

1533633207629

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

1533633302622

外部調優

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

使用工具java visual vm

1533633302622

使用工具java console

1533633885354

將Servlet容器從Tomcat變成Undertow

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

Undertow

POM

首先,從依賴資訊裡移除 Tomcat 配置

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
			<exclusions>
				<exclusion>
					<groupId>org.springframework.boot</groupId>
					<artifactId>spring-boot-starter-tomcat</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
複製程式碼

然後新增 Undertow:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
複製程式碼

Tomcat 優化

見?Spring Boot Memory Performance

熱部署

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

原理

使用類載入器classroad來檢測位元組碼檔案,然後重新載入到jvm記憶體中

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

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

應用場景

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

Dev-tools

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

POM

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<optional>true</optional>
			<scope>true</scope>
		</dependency>
複製程式碼

原理

  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

<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<maimClass>com.itmayiedu.app.App</maimClass>
				</configuration>
				<executions>
					<execution>
						<goals>
							<goal>repackage</goal>
						</goals>
					</execution>
				</executions>

			</plugin>
		</plugins>
	</build>
複製程式碼

參考文獻

JdbcTemplate

SpringBoot分頁外掛PageHelper

jpa

Spring For All 社群 Spring 官方教程翻譯

SpringBoot使用Redis快取

Spring Boot Admin簡單使用

Spring Boot 效能優化

WebFlux

感謝以上大大們~!

廣告時間:想要了解更多精彩新姿勢?請訪問我的部落格

相關文章