# Java 筆記部分
需要注意的點: 源碼、中間件、
JVM
、數據庫、微服務、分佈式、JUC
、IO
、操作系統、RPC
、調優、算法、redis
、設計模式、編譯原理
代碼優化
if...else
的優化
Function
函數式介面Supplier
供給型函數Consumer
消費型函數Runnable
無參無返回型函數Function
函數的表現形式為接收一個參數,並返回一個值。Supplier
、Consumer
和Runnable
可以看作Function
的一種特殊表現形式
- 使用小技巧
- 處理拋出異常的if
- 處理if分支操作
- 如果存在值執行消費操作,否則執行基於空的操作
Function
函數式介面
使用注解@FunctionalInterface
標識,並且只包含一個抽象方法的介面是函數式介面。函數式介面主要分為Supplier
供給型函數、Consumer
消費型函數、Runnable
無參無返回型函數和Function
有參有返回型函數。
使用小技巧
處理拋出異常
- 定義函數
定義一個拋出異常的形式的函數式介面, 這個介面只有參數沒有返回值是個消費型介面
@FunctionalInterface
public interface ThrowExceptionFunction {
void throwMessage(String message);
}
- 編寫判斷方法
創建工具類VUtils
並創建一個isTure
方法,方法的返回值為剛才定義的函數式介面-ThrowExceptionFunction
。ThrowExceptionFunction
的介面實現邏輯為當參數b
為true
時拋出異常
// In VUtils
public static ThrowExceptionFunction isTrue(boolean b){
return (errorMessage) -> {
if (b){
throw new RuntimeException(errorMessage);
}
};
}
- 使用方式
調用工具類參數參數後,調用函數式介面的throwMessage
方法傳入異常資訊。當出入的參數為false
時正常執行
@Test
void isTrue() {
VUtils.isTure(false).throwMessage("拋出的異常"); // 當爲true,正確拋出異常
}
處理if
分支操作
- 定義函數式介面
創建一個名為BranchHandle
的函數式介面,介面的參數為兩個Runnable
介面。這兩個兩個Runnable
介面分別代表了為true
或false
時要進行的操作
@FunctionalInterface
public interface BranchHandle {
void trueOrFalseHandle(Runnable trueHandle, Runnable falseHandle);
}
- 編寫判斷方法
創建一個名為isTureOrFalse
的方法,方法的返回值為剛才定義的函數式介面-BranchHandle
。
public static BranchHandle isTureOrFalse(boolean b){
return (trueHandle, falseHandle) -> {
if (b){
trueHandle.run();
} else {
falseHandle.run();
}
};
}
- 使用方式
參數為true
時,執行trueHandle
@Test
void isTrueOrFalse() {
VUtils.isTrueOrFalse(true).tureOrFalseHanle(
() -> {
System.out.println("This is True")
},
() -> {
System.out.println("This is False")
});
}
如果存在值執行消費操作,否則執行基於空的操作
- 定義函數
創建一個名為PresentOrElseHandler
的函數式介面,介面的參數一個為Consumer
介面。一個為Runnable
,分別代表值不為空時執行消費操作和值為空時執行的其他操作
public interface PresentOrElseHandler<T extends Object> {
void presentOrElseHandle(Consumer<? super T> action, Runnable emptyAction);
}
- 編寫判斷方法
創建一個名為isBlankOrNoBlank
的方法,方法的返回值為剛才定義的函數式介面-PresentOrElseHandler
。
public static PresentOrElseHandler<?> isBlankOrNoBlank(String str){
return (consumer, runnable) -> {
if (str == null || str.length() == 0){
runnable.run();
} else {
consumer.accept(str);
}
};
}
- 使用方式
調用工具類參數參數後,調用函數式介面的presentOrElseHandle
方法傳入一個Consumer
和Runnable
參數不為空時,列印參數
@Test
void isBlankOrNoBlank() {
VUtils.isBlankOrNoBlank("Hello")
.presentOrElseHandle(
System.out::println,
() -> {
System.out.println("空字元串");
});
}
Spring 部分
Spring 基礎
Spring 的奇技淫巧
嘗試提高 Spring 的吞吐量
非同步執行
實現方式二種:
-
使用非同步注解
@aysnc
、啟動類:添加@EnableAsync
注解 -
JDK 8本身有一個非常好用的Future類——
CompletableFuture
@AllArgsConstructor
public class AskThread implements Runnable{
private CompletableFuture<Integer> re = null;
public void run() {
int myRe = 0;
try {
myRe = re.get() * re.get();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(myRe);
}
public static void main(String[] args) throws InterruptedException {
final CompletableFuture<Integer> future = new CompletableFuture<>();
new Thread(new AskThread(future)).start();
//模擬長時間的計算過程
Thread.sleep(1000);
//告知完成結果
future.complete(60);
}
}
在該示例中,啟動一個線程,此時AskThread
對象還沒有拿到它需要的數據,執行到 myRe = re.get() * re.get()
會阻塞。我們用休眠1秒來模擬一個長時間的計算過程,並將計算結果告訴future
執行結果,AskThread
線程將會繼續執行。
public class Calc {
public static Integer calc(Integer para) {
try {
//模擬一個長時間的執行
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return para * para;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
final CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> calc(50))
.thenApply((i) -> Integer.toString(i))
.thenApply((str) -> "\"" + str + "\"")
.thenAccept(System.out::println);
future.get();
}
}
CompletableFuture.supplyAsync
方法構造一個CompletableFuture
實例,在supplyAsync()
方法中,它會在一個新線程中,執行傳入的參數。在這裏它會執行calc()
方法,這個方法可能是比較慢的,但這並不影響CompletableFuture
實例的構造速度,supplyAsync()
會立即返回。
而返回的CompletableFuture
實例就可以作為這次調用的契約,在將來任何場合,用於獲得最終的計算結果。
supplyAsync
用於提供返回值的情況,CompletableFuture
還有一個不需要返回值的非同步調用方法runAsync(Runnable runnable)
,一般我們在優化Controller
時,使用這個方法比較多。這兩個方法如果在不指定線程池的情況下,都是在ForkJoinPool.common
線程池中執行,而這個線程池中的所有線程都是Daemon
(守護)線程,所以,當主線程結束時,這些線程無論執行完畢都會退出系統。
核心代碼
CompletableFuture.runAsync(() ->
this.afterBetProcessor(betRequest,betDetailResult,appUser,id)
);
非同步調用使用Callable來實現
@RestController
public class HelloController {
private static final Logger logger = LoggerFactory.getLogger(HelloController.class);
@Autowired
private HelloService hello;
@GetMapping("/helloworld")
public String helloWorldController() {
return hello.sayHello();
}
/**
* 非同步調用restful
* 當controller返回值是Callable的時候,springmvc就會啟動一個線程將Callable交給TaskExecutor去處理
* 然後DispatcherServlet還有所有的spring攔截器都退出主線程,然後把response保持打開的狀態
* 當Callable執行結束之後,springmvc就會重新啟動分配一個request請求,然後DispatcherServlet就重新
* 調用和處理Callable非同步執行的返回結果, 然後返回視圖
*
* @return
*/
@GetMapping("/hello")
public Callable<String> helloController() {
logger.info(Thread.currentThread().getName() + " 進入helloController方法");
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
logger.info(Thread.currentThread().getName() + " 進入call方法");
String say = hello.sayHello();
logger.info(Thread.currentThread().getName() + " 從helloService方法返回");
return say;
}
};
logger.info(Thread.currentThread().getName() + " 從helloController方法返回");
return callable;
}
}
非同步調用的方式 WebAsyncTask
@RestController
public class HelloController {
private static final Logger logger = LoggerFactory.getLogger(HelloController.class);
@Autowired
private HelloService hello;
/**
* 帶超時時間的非同步請求 通過WebAsyncTask自定義客戶端超時間
*
* @return
*/
@GetMapping("/world")
public WebAsyncTask<String> worldController() {
logger.info(Thread.currentThread().getName() + " 進入helloController方法");
// 3s鐘沒返回,則認為超時
WebAsyncTask<String> webAsyncTask = new WebAsyncTask<>(3000, new Callable<String>() {
@Override
public String call() throws Exception {
logger.info(Thread.currentThread().getName() + " 進入call方法");
String say = hello.sayHello();
logger.info(Thread.currentThread().getName() + " 從helloService方法返回");
return say;
}
});
logger.info(Thread.currentThread().getName() + " 從helloController方法返回");
webAsyncTask.onCompletion(new Runnable() {
@Override
public void run() {
logger.info(Thread.currentThread().getName() + " 執行完畢");
}
});
webAsyncTask.onTimeout(new Callable<String>() {
@Override
public String call() throws Exception {
logger.info(Thread.currentThread().getName() + " onTimeout");
// 超時的時候,直接拋異常,讓外層統一處理超時異常
throw new TimeoutException("調用超時");
}
});
return webAsyncTask;
}
/**
* 非同步調用,異常處理,詳細的處理流程見MyExceptionHandler類
*
* @return
*/
@GetMapping("/exception")
public WebAsyncTask<String> exceptionController() {
logger.info(Thread.currentThread().getName() + " 進入helloController方法");
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
logger.info(Thread.currentThread().getName() + " 進入call方法");
throw new TimeoutException("調用超時!");
}
};
logger.info(Thread.currentThread().getName() + " 從helloController方法返回");
return new WebAsyncTask<>(20000, callable);
}
}
增加內嵌Tomcat的最大連接數
@Configuration
public class TomcatConfig {
@Bean
public ConfigurableServletWebServerFactory webServerFactory() {
TomcatServletWebServerFactory tomcatFactory = new TomcatServletWebServerFactory();
tomcatFactory.addConnectorCustomizers(new MyTomcatConnectorCustomizer());
tomcatFactory.setPort(8005);
tomcatFactory.setContextPath("/api-g");
return tomcatFactory;
}
class MyTomcatConnectorCustomizer implements TomcatConnectorCustomizer {
public void customize(Connector connector) {
Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
//設置最大連接數
protocol.setMaxConnections(20000);
//設置最大線程數
protocol.setMaxThreads(2000);
protocol.setConnectionTimeout(30000);
}
}
}
使用@ComponentScan()
定位掃包比@SpringBootApplication
掃包更快
默認tomcat容器改為Undertow(Jboss下的伺服器,Tomcat吞吐量5000,Undertow吞吐量8000)
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
改為
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
使用 BufferedWriter
進行緩衝
Deferred
方式實現非同步實現非同步調用
@RestController
public class AsyncDeferredController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final LongTimeTask taskService;
@Autowired
public AsyncDeferredController(LongTimeTask taskService) {
this.taskService = taskService;
}
@GetMapping("/deferred")
public DeferredResult<String> executeSlowTask() {
logger.info(Thread.currentThread().getName() + "進入executeSlowTask方法");
DeferredResult<String> deferredResult = new DeferredResult<>();
// 調用長時間執行任務
taskService.execute(deferredResult);
// 當長時間任務中使用deferred.setResult("world");這個方法時,會從長時間任務中返回,繼續controller裏面的流程
logger.info(Thread.currentThread().getName() + "從executeSlowTask方法返回");
// 超時的回調方法
deferredResult.onTimeout(new Runnable(){
@Override
public void run() {
logger.info(Thread.currentThread().getName() + " onTimeout");
// 返回超時資訊
deferredResult.setErrorResult("time out!");
}
});
// 處理完成的回調方法,無論是超時還是處理成功,都會進入這個回調方法
deferredResult.onCompletion(new Runnable(){
@Override
public void run() {
logger.info(Thread.currentThread().getName() + " onCompletion");
}
});
return deferredResult;
}
}
非同步調用可以使用AsyncHandlerInterceptor
進行攔截
@Component
public class MyAsyncHandlerInterceptor implements AsyncHandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(MyAsyncHandlerInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// HandlerMethod handlerMethod = (HandlerMethod) handler;
logger.info(Thread.currentThread().getName()+ "服務調用完成,返回結果給客戶端");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
if(null != ex){
System.out.println("發生異常:"+ex.getMessage());
}
}
@Override
public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 攔截之後,重新寫回數據,將原來的hello world換成如下字串
String resp = "my name is chhliu!";
response.setContentLength(resp.length());
response.getOutputStream().write(resp.getBytes());
logger.info(Thread.currentThread().getName() + " 進入afterConcurrentHandlingStarted方法");
}
}
Spring 源碼部分
Bean 的生命周期
PostProcesser
package io.github.andre_hjr;
public class MySelfBeanFactoryPostProcessor
implements BeanFactoryPostProcessor
{
System.out.println("调用自定义的beanFactoryPostProcessor");
}
**在相應的xml
文件中有如下創建:
<bean
<!-- tx.xml 的部分文件 -->
class="io.github.andre_hjr.MySelfBeanFactoryPostProcessor"></bean>
調用相關的測試代碼
public class TxTest {
public static void main(String[] args) {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "\//path");
ApplicationContext context = new ClassPathXmlApplicationContext("tx.xml");
B
}
}
對象的創建
三級緩存
一些問題
- 三級緩存中分別保存的是什麼對象
- 成品對象
- 半成品對象
- lambda運算式
- 如果只使用1級緩存行不行?
不行,因為成品和半成品對象會放到一起,在進行對象獲取的時候有可能獲取到半成品對象,這樣的對象是沒法使用的 - 如果只有二級緩存行不行?
getSingleton
doCreateBean
- 只有二級緩存的時候也可以解決迴圈依賴的問題
- 添加aop的實現之後,報錯,
Thismeans that said other beans do not use the final version of the bean.
- 三級緩存存在到底做了什麼事?
- 如果一個對象需要被代理,生成代理對象,那麼這個對象需要預先生成非代理對象嗎?
。 - 需要三級緩存到底做了什麼?
lambda:getEarlyBeanReference()
只要搞清楚這個方法的具體執行邏輯即可- 在當前方法中,有可能會用代理對象替換非代理對象,如果沒有三級緩存的話,那麼就無法得到代理對象,換句話說
- 在整個容器中,包含了同名對象的代理對象和非代理對象,你覺得可以嗎?
- 容器中,對象都是單例的,意味著根據名稱只能獲取一個對象的值,此時同時存在兩個對象的話,使用的時候應該取哪一個?無法判斷
- 誰也無法確認什麼時候會調用當前對象,是在其他對象的執行過程中來進行調用的,而不是人為指定的,所以必須要保證容器中任何時候都只有一個對象供外部調用,所以在三級緩存中,完整了一件代理對象替換非代理對象的工作,確定返回的是唯一的對象
- 如果一個對象需要被代理,生成代理對象,那麼這個對象需要預先生成非代理對象嗎?
- 三級緩存是為了解決在
aop
代理過程中產生的迴圈依賴問題,如果沒有aop
的話,二級緩存足矣解決迴圈依賴問題- 其實相當於是一個回調機制,當我都需要使用當前對象的時候,會判斷此對象是否需要被代理實現,如果直接替換,不需要直接返回非代理對象即可
- spring是一個框架,跟業務完全無關,框架如何知道你什麼時候用aop,什麼時候不用aop呢?
但是在調用時候的時候,如果出現此問題怎麼辦呢?最重要的一件事,走總體的流程,如果有,就處理,沒有就直接返回,不會有額外的任何影響
Spring-Boot 源碼部分
自動裝配實現的原理:
- 當啟動spring boot應用程式的時候, 會先創建
SpringApplication
的對象, 在對象的構造方法中會進行某些參數的初始化工作,最主要的是判斷當前應用程式的類型以及初始化器和監聽器,在這個過程中會加載整個應用程式中的spring.factories
檔, 將檔的內容放到緩存對象中, 方便後續獲取。 SpringApplication
對象創建完成之後, 開始執行run
方法, 來完成整個啟動, 啟動過程中最主要的有兩個方法, 第一個叫做prepareContext
, 第二個叫做refreshContext
, 在這兩個關鍵步驟中完整了自動裝配的核心功能, 前面的處理邏輯包含了上下文對象的創建,banner
的列印, 異常報告期的準備等各個準備工作, 方便後續來進行調用。- 在
prepareContext
方法中主要完成的是對上下文對象的初始化操作, 包括了屬性值的設置, 比如環境對象, 在整個過程中有一個非常重要的方法, 叫做load
,load
主要完成一件事, 將當前啟動類做為一個beanDefinition
註冊到registry
中, 方便後續在進行BeanFactoryPostProcessor
調用執行的時候, 找到對應的主類, 來完成@SpringBootApplicaiton
,@EnableAutoConfiguration
等注解的解析工作 - 在
refreshContext
方法中會進行整個容器刷新過程, 會調用中spring
中的refresh
方法,refresh
中有13個非常關鍵的方法, 來完成整個spring應用程式的啟動, 在自動裝配過程中, 會調用invokeBeanFactoryPostProcessor
方法, 在此方法中主要是對ConfigurationClassPostProcessor
類的處理, 這次是BFPP
的子類也是BDRPP
的子類, 在調用的時候會先調用BDRPP
中的postProcessBeanDefinitionRegistry
方法, 然後調用postProcessBeanFactory
方法, 在執行postProcessBeanDefinitionRegistry
的時候回解析處理各種注解, 包含@PropertySource
,@ComponentScan
,@ComponentScans
,@Bean
,@lmport
等注解, 最主要的是@lmport
注解的解析 - 在解析
@Import
注解的時候, 會有一個getImports
的方法, 從主類開始遞歸解析注解, 把所有包含@lmport
的注解都解析到, 然後在processlmport
方法中對Import
的類進行分類, 此處主要識別的時候AutoConfigurationImportSelect
歸屬於ImportSelect
的子類, 在後續過程中會調用deferredImportSelectorHandler
中的process
方法, 來完整EnableAutoConfiguration
的加載。
Kafka 部分
簡介
Apache Kafka 是一個分佈式的發佈-訂閲消息系統,能夠支撐海量數據傳遞,在離綫和實時的消息處理業務系統中,Kafka都有廣汎的應用,Kafka將消息持久化到磁盤中,並對消息創建了備份保證數據的安全,Kafka在保證了較高的處理速度的同時,又能保證數據處理的低延遲和數據的零丟失。
特點
- 高吞吐量、低延遲: Kafka每秒可以處理幾十萬條消息,它的延遲最低只有几毫秒,每個主題可以分爲多個分區,消費組對分區進行消費操作;
- 可拓展性: Kafka集群支持熱擴展
- 持久性、可靠性:消息被持久化到本地磁盤,并且支持數據備份、從而防止數據丟失;
- 容錯性:允許集群中節點失敗(若副本數量為
n
,則允許n - 1
個節點失敗); - 高并發:支持數簽個客戶端同時讀寫
使用場景
- 日誌收集:一個公司可以用Kafka可以收集各種服務的
log
,通過kafka
以統一介面服務的方式開放給各種consumer
.例如Hadoop
.Hbase
、Solr
等: - 消息系統:解耦和生產者和消費者、緩存消息等;
- 用戶活動跟蹤: Kafka經常被用來記錄web用戶或者app用戶的各種活動,如流覽網頁、搜索、點擊等活動,這些活動資訊被各個伺服器發佈到kafka的topic中,然後訂閱者通過訂閱這些topic來做即時的監控分析,或者裝載到Hadoop、數據倉庫中做離線分析和挖掘;
- 運營指標: Kafka也經常用來記錄運營監控數據。包括收集各種分佈式應用的數據,生產各種操作的集中回饋,比如報警和報告;
- 流式處理:比如
spark streaming
和storm
;