[TOC]
# 前言
為了更好的深入學習Spring核心技術,在這里整理了Spring相關的常見核心知識,和面試知識點,本文將通過淺顯易懂的語言和代碼實現,相信可以在最短的時間內進行鞏固學習。
本文參考:
* [理解并實現一個IOC容器](https://github.com/biezhi/java-bible/blob/master/ioc/index.md)
* [【源碼實現】Java-Guide/Spring學習與面試.md at master · Snailclimb/Java-Guide](https://github.com/Snailclimb/Java-Guide/blob/master/%E4%B8%BB%E6%B5%81%E6%A1%86%E6%9E%B6/Spring%E5%AD%A6%E4%B9%A0%E4%B8%8E%E9%9D%A2%E8%AF%95.md)
* [一起來談談 Spring AOP! - 掘金](https://juejin.im/post/5aa7818af265da23844040c6)
* [黑馬程序員Spring2016學習筆記](https://github.com/Only-lezi/spring-learning/tree/master/spring-learning-article)
* [Spring學習總結(二)——靜態代理、JDK與CGLIB動態代理、AOP+IoC - 張果 - 博客園](http://www.cnblogs.com/best/p/5679656.html)
* [Spring IoC有什么好處呢? - 知乎](https://www.zhihu.com/question/23277575/answer/169698662)
* [極客學院Spring Wiki](http://wiki.jikexueyuan.com/project/spring/transaction-management.html)
* [【必讀】Spring W3Cschool教程](https://www.w3cschool.cn/wkspring/f6pk1ic8.html)
* [【必讀】Java新手如何學習Spring、Struts、Hibernate三大框架? - 知乎,深度好文](https://www.zhihu.com/question/21142149)
個人的使用體會
* **spring**:核心提供依賴注入
* **spring?mvc**:?mvc框架
* **spring?boot**:說不太出什么感覺,跟mvc比就感覺配置少了好多
* **spring?cloud**:圍繞微服務的一套東西
from 2018/7/27
# 一、基礎概念
## 1\. JavaBean
**JavaBean是一種組件技術**,就好像你做了一個扳子,而這個扳子會在很多地方被拿去用,這個扳子也提供多種功能(你可以拿這個扳子扳、錘、撬等等),而這個扳子就是一個組件。
**JavaBean是一個遵循特定寫法的Java類**,它通常具有如下特點:
* 這個Java類必須具有一個無參的構造函數
* 屬性必須私有化。
* 私有化的屬性必須通過public類型的方法暴露給其它程序,并且方法的命名也必須遵守一定的命名規范。
* 這個類應是可序列化的。(比如可以實現Serializable 接口,用于實現bean的持久性)
許多開發者把JavaBean看作遵從特定命名約定的POJO。 簡而言之,當一個POJO可序列化,有一個無參的構造函數,使用getter和setter方法來訪問屬性時,他就是一個JavaBean。
~~~java
package gacl.javabean.study;
/**
* @author gacl
* Person類就是一個最簡單的JavaBean
*/
public class Person {
//Person類封裝的私有屬性
// 姓名 String類型
private String name;
// 性別 String類型
private String sex;
// 年齡 int類型
private int age;
/**
* 無參數構造方法
*/
public Person() {
}
//Person類對外提供的用于訪問私有屬性的public方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
~~~
JavaBean在J2EE開發中,通常用于封裝數據,對于遵循以上寫法的JavaBean組件,其它程序可以通過反射技術實例化JavaBean對象,并且通過反射那些遵守命名規范的方法,從而獲知JavaBean的屬性,進而調用其屬性保存數據。
## 2\. Bean
* **Bean的中文含義是“豆子”,Bean的含義是可重復使用的Java組件**。所謂組件就是一個由可以自行進行內部管理的一個或幾個類所組成、外界不了解其內部信息和運行方式的群體。使用它的對象只能通過接口來操作。
* Bean并不需要繼承特別的基類(BaseClass)或實現特定的接口(Interface)。Bean的編寫規范使Bean的容器(Container)能夠分析一個Java類文件,并將其方法(Methods)翻譯成屬性(Properties),即把Java類作為一個Bean類使用。Bean的編寫規范包括Bean類的構造方法、定義屬性和訪問方法編寫規則。
* Java Bean是基于Java的組件模型,由**屬性、方法和事件**3部分組成。在該模型中,JavaBean可以被修改或與其他組件結合以生成新組件或完整的程序。它是一種Java類,通過封裝成為具有某種功能或者處理某個業務的對象。因此,也可以通過嵌在JSP頁面內的Java代碼訪問Bean及其屬性。
## 3\. 傳統Javabean與Spring中的bean的區別
Javabean已經沒人用了
springbean可以說是javabean的發展, 但已經完全不是一回事兒了
\*\*用處不同:\*\*傳統javabean更多地作為值傳遞參數,而spring中的bean用處幾乎無處不在,任何組件都可以被稱為bean。
\*\*寫法不同:\*\*傳統javabean作為值對象,要求每個屬性都提供getter和setter方法;但spring中的bean只需為接受設值注入的屬性提供setter方法。
\*\*生命周期不同:\*\*傳統javabean作為值對象傳遞,不接受任何容器管理其生命周期;spring中的bean有spring管理其生命周期行為。
所有可以被spring容器實例化并管理的java類都可以稱為bean。
原來服務器處理頁面返回的值都是直接使用request對象,后來增加了javabean來管理對象,所有頁面值只要是和javabean對應,就可以用類.GET屬性方法來獲取值。javabean不只可以傳參數,也可以處理數據,相當與把一個服務器執行的類放到了頁面上,使對象管理相對不那么亂(對比asp的時候所有內容都在頁面上完成)。
spring中的bean,是通過配置文件、javaconfig等的設置,有spring自動實例化,用完后自動銷毀的對象。讓我們只需要在用的時候使用對象就可以,不用考慮如果創建類對象(這就是spring的注入)。一般是用在服務器端代碼的執行上。
## 4\. POJO
POJO?和JavaBean是我們常見的兩個關鍵字,一般容易混淆,POJO全稱是Plain Ordinary Java Object / Pure Old Java Object,中文可以翻譯成:普通Java類,**具有一部分getter/setter方法的那種類就可以稱作POJO**,但是JavaBean則比 POJO復雜很多, Java Bean 是可復用的組件,對 Java Bean 并沒有嚴格的規范,理論上講,任何一個 Java 類都可以是一個 Bean 。但通常情況下,由于 Java Bean 是被容器所創建(如 Tomcat) 的,所以 Java Bean 應具有一個無參的構造器,另外,通常 Java Bean 還要實現 Serializable 接口用于實現 Bean 的持久性。 Java Bean 是不能被跨進程訪問的
一般在web應用程序中建立一個數據庫的映射對象時,我們只能稱它為POJO。 POJO(Plain Old Java Object)這個名字用來強調它是一個普通java對象,而不是一個特殊的對象。 2005年11月時,“POJO”主要用來指代那些沒用遵從特定的Java對象模型,約定或框架如EJB的Java對象. 理想地講,一個POJO是一個不受任何限制的Java對象(除了Java語言規范)。例如一個POJO不應該是
~~~
1. 擴展預定的類,如 public class Foo extends javax.servlet.http.HttpServlet { ...
2. 實現預定的接口,如 public class Bar implements javax.ejb.EntityBean { ...
3. 包含預定的標注,如 @javax.ejb.Entity public class Baz{ ...
~~~
然后,因為技術上的困難及其他原因,許多兼容POJO風格的軟件產品或框架事實上仍然要求使用預定的標注,譬如用于更方便的持久化。
# 二、Spring核心技術
## 1\. IOC(控制反轉)
### 1.1 什么是IOC
IoC(Inversion of Control),意為控制反轉,不是什么技術,而是一種設計思想。Ioc意味著**將你設計好的對象交給容器控制,而不是傳統的在你的對象內部直接控制**。
如何理解好Ioc呢?理解好Ioc的關鍵是要明確“誰控制誰,控制什么,為何是反轉(有反轉就應該有正轉了),哪些方面反轉了”,那我們來深入分析一下:
* **誰控制誰,控制什么**:傳統Java SE程序設計,我們直接在對象內部通過new進行創建對象,是程序主動去創建依賴對象;而IoC是有專門一個容器來創建這些對象,即由Ioc容器來控制對 象的創建;誰控制誰?當然是IoC 容器控制了對象;控制什么?那就是主要控制了外部資源獲取(不只是對象包括比如文件等)。
* **為何是反轉,哪些方面反轉了**:有反轉就有正轉,傳統應用程序是由我們自己在對象中主動控制去直接獲取依賴對象,也就是正轉;而反轉則是由容器來幫忙創建及注入依賴對象;為何是反轉?因為由容器幫我們查找及注入依賴對象,對象只是被動的接受依賴對象,所以是反轉;哪些方面反轉了?依賴對象的獲取被反轉了。
**簡單來說**
> 正轉:比如有一個類,在類里面有方法(不是靜態的方法),調用類里面的方法,創建類的對象,使用對象調用方法,創建類對象的過程,需要new出來對象
>
> 反轉:把對象的創建不是通過new方式實現,而是交給Spring配置創建類對象
【必讀】IOC深度理解,請轉向:[深入淺出IOC](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/JavaWeb/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BAIOC.md)
### 1.2 IoC能做什么
IoC 不是一種技術,只是一種思想,一個重要的面向對象編程的法則,它能指導我們如何設計出松耦合、更優良的程序。傳統應用程序都是由我們在類內部主動創建依賴對象,從而導致類與類之間高耦合,難于測試;有了IoC容器后,把創建和查找依賴對象的控制權交給了容器,由容器進行注入組合對象,所以對象與對象之間是松散耦合,這樣也方便測試,利于功能復用,更重要的是使得程序的整個體系結構變得非常靈活。
其實IoC對編程帶來的最大改變不是從代碼上,而是從思想上,發生了“主從換位”的變化。應用程序原本是老大,要獲取什么資源都是主動出擊,但是在IoC/DI思想中,應用程序就變成被動的了,被動的等待IoC容器來創建并注入它所需要的資源了。
IoC很好的體現了面向對象設計法則之一—— 好萊塢法則:“別找我們,我們找你”;即由IoC容器幫對象找相應的依賴對象并注入,而不是由對象主動去找。
### 1.3 IoC和DI
**DI—Dependency Injection,即“依賴注入”**:組件之間依賴關系由容器在運行期決定,形象的說,即由容器動態的將某個依賴關系注入到組件之中。依賴注入的目的并非為軟件系統帶來更多功能,而是為了提升組件重用的頻率,并為系統搭建一個靈活、可擴展的平臺。通過依賴注入機制,我們只需要通過簡單的配置,而無需任何代碼就可指定目標需要的資源,完成自身的業務邏輯,而不需要關心具體的資源來自何處,由誰實現。
理解DI的關鍵是:“**誰依賴誰,為什么需要依賴,誰注入誰,注入了什么**”,那我們來深入分析一下:
* **誰依賴于誰:**當然是應用程序依賴于IoC容器;
* **為什么需要依賴:**應用程序需要IoC容器來提供對象需要的外部資源;
* **誰注入誰:**很明顯是IoC容器注入應用程序某個對象,應用程序依賴的對象;
* **注入了什么:**就是注入某個對象所需要的外部資源(包括對象、資源、常量數據)。
IoC和DI由什么關系呢?其實它們是同一個概念的不同角度描述,由于控制反轉概念比較含糊(可能只是理解為容器控制對象這一個層面,很難讓人想到誰來維護對象關系),所以2004年大師級人物Martin Fowler又給出了一個新的名字:“依賴注入”,相對IoC 而言,**“依賴注入”**明確描述了**“被注入對象依賴IoC容器配置依賴對象”**。
對于Spring Ioc這個核心概念,我相信每一個學習Spring的人都會有自己的理解。這種概念上的理解沒有絕對的標準答案,仁者見仁智者見智。 理解了IoC和DI的概念后,一切都將變得簡單明了,剩下的工作只是在框架中堆積木而已,下一節來看看Spring是怎么用的
### 1.4 IOC底層原理 (降低類之間的耦合度)
* 底層原理使用技術
* xml配置文件
* dom4j解決xml
* 工廠設計模式
* 反射
* 原理
~~~java
//偽代碼
//需要實例化的類
public class UserService{
}
public class UserServlet{
//得到UserService的對象
//原始的做法:new 對象(); 來創建
//經過spring后
UserFactory.getService(); //(下面兩步的代碼調用的)
}
~~~
**第一步:創建xml配置文件,配置要創建的對象類**
~~~xml
<bean id="userService" class="cn.blinkit.UserService"/>
~~~
**第二步:創建工廠類,使用dom4j解析配置文件+反射**
~~~java
public class Factory {
//返回UserService對象的方法
public static UserService getService() {
//1.使用dom4j來解析xml文件
//根據id值userService,得到id值對應的class屬性值
String classValue = "class屬性值";
//2.使用反射來創建類對象
Class clazz = Class.forName(classValue);
//創建類的對象
UserService service = clazz.newInstance();
return service;
}
}
~~~
超詳細原理講解:[java-bible/4.principle.md at master · biezhi/java-bible](https://github.com/biezhi/java-bible/blob/master/ioc/4.principle.md)
[](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/spring-ioc.png)
### 1.5 Spring中怎么用
#### (1)配置文件方式
我們在Spring中是這樣獲取對象的:
~~~java
public static void main(String[] args) {
ApplicationContext context = new FileSystemXmlApplicationContext("applicationContext.xml");
Lol lol = (Lol) context.getBean("lol");
lol.gank();
}
~~~
一起看看Spring如何讓它生效呢,在`applicationContext.xml`配置文件中是醬紫的:
~~~xml
<bean id="lol" class="com.biezhi.test.Lol">
<property name="name" value="劍圣" />
</bean>
~~~
`Lol`類是這樣的:
~~~java
public class Lol {
private String name;
public Lol() {
}
public void gank(){
System.out.println(this.name + "在gank!!");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
~~~
上面的代碼運行結果自然是`劍圣在gank!!`。
#### (2)注解方式
Spring更高級的用法,在3.0版本之后有了基于Annotation的注入實現,為毛每次都要配置`Xml`看到都蛋疼。。
首先還是要在`xml`中配置啟用注解方式
~~~xml
<context:annotation-config/>
~~~
這樣就能使用注解驅動依賴注入了,下面是一個使用場景
~~~java
public class Lol {
@Autowired
private DuangService duangService ;
public void buyDuang(String name, int money) {
duangService.buy(name, money);
}
}
~~~
~~~java
@Service("duangService")
public class DuangService {
public void buy(String name, int money){
if(money > 0){
System.out.println(name + "買了" + money + "毛錢的特效,裝逼成功!");
} else{
System.out.println(name + "沒錢還想裝逼,真是匪夷所思");
}
}
}
~~~
這只是一個簡單的例子,劍圣打野的時候想要買5毛錢的三殺特效,嗯。。雖然不符合邏輯
此時`DuangService`已經注入到`Lol`對象中,運行代碼的結果(這里是例子,代碼不能運行的)就是:
~~~java
德瑪買了5毛錢的特效,裝逼成功!
~~~
## 2\. DI(依賴注入)
### 2.1 什么是依賴注入
在依賴注入的模式下,創建被調用者得工作不再由調用者來完成,創建被調用者實例的工作通常由Spring容器完成,然后注入調用者。**創建對象時,向類里的屬性設置值**
### 2.2 為什么使用依賴注入
為了實現代碼/模塊之間松耦合。
### 2.3 為什么要實現松耦合
上層調用下層,上層依賴于下層,當下層劇烈變動時上層也要跟著變動,這就會導致模塊的復用性降低而且大大提高了開發的成本。
一般情況下抽象的變化概率很小,讓用戶程序依賴于抽象,實現的細節也依賴于抽象。即使實現細節不斷變動,只要抽象不變,客戶程序就不需要變化。這大大降低了客戶程序與實現細節的耦合度。
### 2.4 IOC和DI區別
1. IOC控制反轉,把對象創建交給Spring配置
2. DI依賴注入,向類里面屬性注入值
3. 關系,依賴注入不能單獨存在,需要在IOC基礎上完成操作
### 2.5 依賴注入方式
1. 使用set方法注入
2. 使用有參構造注入
3. 使用接口注入
說明:Spring框架中支持前兩種方式
#### (1)使用set方法注入
~~~xml
<bean id="person" class="cn.wang.property.Person">
<!--set方法注入屬性
name屬性值:類中定義的屬性名稱
value屬性值:設置具體的值
-->
<property name="pname" value="zs"></property>
</bean>1234567
~~~
#### (2)使用有參構造注入
~~~java
public class Person {
private String pname;
public void setPname(String pname) {
this.pname = pname;
}
}
~~~
~~~xml
<bean id="user" class="cn.wang.ioc.User">
<!--構造方法注入屬性-->
<constructor-arg name="pname" value="Tony"></constructor-arg>
</bean>
~~~
#### (3)注入對象類型屬性
* 創建service和dao類,在service中得到dao
具體實現過程
* 在service中把dao作為屬性,生成dao的set方法
~~~java
public class UserService {
// 1.定義UserDao類型屬性
private UserDao userDao;
// 2.生成set方法
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
~~~
1. 配置文件注入關系
~~~java
<bean id="userDao" class="cn.wang.property.UserDao">
<property name="name" value="Tom"></property>
</bean>
<bean id="userService" class="cn.wang.property.UserService">
<!--name屬性值:UserService類里的屬性名稱-->
<!--ref屬性:UserDao類配置bean標簽中的id值-->
<property name="userDao" ref="userDao"></property>
</bean>12345678
~~~
#### (4)p名稱空間注入
[](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/ioc-p1.png)
[](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/ioc-p2.png)
#### (5)注入復雜類型屬性
~~~java
<!-- 注入復雜類型屬性值 -->
<!--
String pname;
String[] arrs;
List<String> list;
Map<String, String> map;
Properties props;
-->
<bean id="person" class="cn.wang.property.Person">
<property name="pname" value="zs"></property>
<property name="arrs">
<list>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</list>
</property>
<property name="list">
<list>
<value>qqq</value>
<value>www</value>
<value>eee</value>
</list>
</property>
<property name="map">
<map>
<entry key="001" value="Tom"></entry>
<entry key="002" value="Amy"></entry>
<entry key="003" value="Jim"></entry>
</map>
</property>
<property name="props">
<props>
<prop key="username">admin</prop>
<prop key="passwprd">admin</prop>
</props>
</property>
</bean>
~~~
## 3\. AOP(面向切面編程)
[](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/spring-aop.png)
### 3.1 什么是AOP
AOP(Aspect Oriented Programming )稱為面向切面編程,擴展功能不是修改源代碼實現,在程序開發中主要用來解決一些系統層面上的問題,比如日志,事務,權限等待,Struts2的攔截器設計就是基于AOP的思想,是個比較經典的例子。
* AOP:面向切面編程,擴展功能不修改源代碼實現
* AOP采取**橫向抽取機制**,取代了傳統**縱向繼承**體系重復性代碼(性能監視、事務管理、安全檢查、緩存)
**spring的底層采用兩種方式進行增強**
~~~
第一:Spring傳統AOP 純java實現,在運行期,對目標對象進行代理,織入增強代碼
第二:AspectJ第三方開源技術,Spring已經整合AspectJ,提供對AspectJ注解的支持,開發AOP程序 更加容易(企業主流)
~~~
### 3.2 底層原理
[](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/aop2.png)
#### 第一種 JDK 自帶的動態代理技術
JDK動態代理必須基于接口進行代理
作用:使用代理可以對目標對象進行性能監控(計算運行時間)、安全檢查(是否具有權限)、 記錄日志等。
注意:必須要有接口才能進行代理,代理后對象必須轉換為接口類型
#### 第二種 CGLIB(CodeGenerationLibrary)是一個開源項目
Spring使用CGlib 進行AOP代理, hibernate 也是支持CGlib(默認使用 javassist )需要下載cglib 的jar包(Spring 最新版本3.2 內部已經集成了cglib ,**無需下載cglib的jar**)
**作用:可以為目標類,動態創建子類,對目標類方法進行代理(無需接口)**
原理:Spring AOP 底層,會判斷用戶是根據接口代理還是目標類代理,如果針對接口代理就使用JDK代理,如果針對目標類代理就使用Cglib代理。
[](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/aop1.png)
### 3.3 AOP操作術語
以下面代碼為例:
~~~java
public class User {
public void add() {...}
public void delete() {...}
public void update() {...}
public void query() {...}
}
~~~
* **Joinpoint(連接點)(重要)**
* 類里面可以被增強的方法,這些方法稱為連接點
* **Pointcut(切入點)(重要)**
* 所謂切入點是指我們要對哪些 Joinpoint 進行攔截的定義
* **Advice(通知/增強)(重要)**
* 所謂通知是指攔截到 Joinpoint 之后所要做的事情就是通知。通知分為**前置通知,后置通知,異常通知,最終通知,環繞通知**(方法之前和方法之后)
* **Aspect(切面)**:
* 把增強應用到具體方法上面,過程成為切面。
* **Introduction(引介)**
* 引介是一種特殊的通知在不修改類代碼的前提下, Introduction 可以在運行期為類動態地添加一些方法或 Field。
* **Target(目標對象)**
* 代理的目標對象(要增強的類)
* **Weaving(織入)**
* 是把增強應用到目標的過程,把 advice 應用到 target的過程
* **Proxy(代理)**
* 一個類被 AOP 織入增強后,就產生一個結果代理類
### 3.4 Spring的AOP操作
* 在Spring里面進行Aop操作,使用aspectj實現
* aspectj不是Spring的一部分,和Spring一起使用進行Aop操作
* Spring2.0以后新增了對aspectj的支持
* 使用aspectj實現aop有兩種方式
* 基于aspectj的xml配置
* 基于aspectj的注解方式
#### (1)AOP準備操作
1、除了導入基本的jar包之外,還需要導入aop相關的jar包:
~~~
aopalliance-1.0.jar
aspectjweaver-1.8.7.jar
spring-aspects-5.0.4.RELEASE.jar
spring-aop-5.0.4.RELEASE.jar
~~~
2、創建Spring核心配置文件 除了引入了約束spring-beans之外還需要引入新約束spring-aop
~~~xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-aop.xsd">
</beans>12345678
~~~
#### (2)使用表達式配置切入點
1. 切入點:實際增強的方法
2. 常用的表達式 execution(? ()) (1)對包內的add方法進行增強`execution(* cn.blinkit.aop.Book.add(..))`(2)\* 是對類里面的所有方法進行增強`execution(* cn.blinkit.aop.Book.*(..))`(3)*.*是所有的類中的方法進行增強`execution(* *.*(..))`(4)匹配所有save開頭的方法`execution(* save*(..))`
### 3.5 使用xml實現AOP
**aop配置代碼:**Book
~~~java
public class Book {
public void add() {
System.out.println("add......");
}
}
~~~
MyBook
~~~java
public class MyBook {
public void before1() {
System.out.println("前置增強......");
}
public void after1() {
System.out.println("后置增強......");
}
//環繞通知
public void around1(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//方法之前
System.out.println("方法之前.....");
//執行被增強的方法
proceedingJoinPoint.proceed();
//方法之后
System.out.println("方法之后......");
}
}
~~~
xml配置
~~~xml
<!--1. 配置對象-->
<bean id="book" class="cn.blinkit.aop.Book"></bean>
<bean id="myBook" class="cn.blinkit.aop.MyBook"></bean>
<!--2. 配置aop操作-->
<aop:config>
<!--2.1 配置切入點-->
<aop:pointcut id="pointcut1" expression="execution(* cn.blinkit.aop.Book.*(..))"></aop:pointcut>
<!--2.2 配置切面
把增強用到方法上面
-->
<aop:aspect ref="myBook">
<!--
aop:before :前置通知
aop:after :后置通知
aop:around :環繞通知
配置增強類型
method : 增強類里面使用哪個方法作為前置
-->
<aop:before method="before1" pointcut-ref="pointcut1"></aop:before>
<aop:after method="after1" pointcut-ref="pointcut1"></aop:after>
<aop:around method="around1" pointcut-ref="pointcut1"></aop:around>
</aop:aspect>
</aop:config>
~~~
測試代碼
~~~java
public class AOPTest {
@Test
public void testBook() {
ApplicationContext context = new ClassPathXmlApplicationContext("cn/blinkit/aop/spring-aop.xml");
Book book = (Book) context.getBean("book");
book.add();
}
}
~~~
### 3.6 使用注解實現AOP
1. 創建對象 (1)創建Book和MyBook**(增強類)**對象
2. 在spring核心配置文件中,開啟aop操作 具體操作見xml配置文件代碼:
~~~xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 1.開啟aop操作 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!-- 2.配置對象 -->
<bean id="book" class="com.jxs.aspectj.Book"></bean>
<bean id="nbBook" class="com.jxs.aspectj.NBBook"></bean>
</beans>
~~~
3. 在增強類上面使用注解完成aop操作 (1)類上面加上`@Aspect`(2)方法上面加上`@Before(value = "execution(* cn.blinkit.aop.anno.Book.*(..))")``@After(value = "表達式")``@Around(value = "表達式")`等...
**Book**
~~~java
public class Book {
public void add() {
System.out.println("add...注解版本...");
}
}
~~~
**MyBook增強類**
~~~java
@Aspect
public class MyBook {
//在方法上面使用注解完成增強配置
@Before(value = "execution(* cn.blinkit.aop.anno.Book.*(..))")
public void before1() {
System.out.println("前置增強...注解版本...");
}
@After(value = "execution(* cn.blinkit.aop.anno.Book.*(..))")
public void after1() {
System.out.println("后置增強...注解版本...");
}
//環繞通知
@Around(value = "execution(* cn.blinkit.aop.anno.Book.*(..))")
public void around1(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//方法之前
System.out.println("方法之前...注解版本...");
//執行被增強的方法
proceedingJoinPoint.proceed();
//方法之后
System.out.println("方法之后...注解版本...");
}
}
~~~
**xml配置**
~~~xml
<!--開啟aop操作-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!--創建對象-->
<bean id="book" class="cn.blinkit.aop.anno.Book"></bean>
<bean id="myBook" class="cn.blinkit.aop.anno.MyBook"></bean>
~~~
### 3.7 為什么需要代理模式?
假設需實現一個計算的類Math、完成加、減、乘、除功能,如下所示:
~~~java
package com.zhangguo.Spring041.aop01;
public class Math {
//加
public int add(int n1,int n2){
int result=n1+n2;
System.out.println(n1+"+"+n2+"="+result);
return result;
}
//減
public int sub(int n1,int n2){
int result=n1-n2;
System.out.println(n1+"-"+n2+"="+result);
return result;
}
//乘
public int mut(int n1,int n2){
int result=n1*n2;
System.out.println(n1+"X"+n2+"="+result);
return result;
}
//除
public int div(int n1,int n2){
int result=n1/n2;
System.out.println(n1+"/"+n2+"="+result);
return result;
}
}
~~~
現在需求發生了變化,要求項目中所有的類在執行方法時輸出執行耗時。最直接的辦法是修改源代碼,如下所示:
~~~java
package com.zhangguo.Spring041.aop01;
import java.util.Random;
public class Math {
//加
public int add(int n1,int n2){
//開始時間
long start=System.currentTimeMillis();
lazy();
int result=n1+n2;
System.out.println(n1+"+"+n2+"="+result);
Long span= System.currentTimeMillis()-start;
System.out.println("共用時:"+span);
return result;
}
//減
public int sub(int n1,int n2){
//開始時間
long start=System.currentTimeMillis();
lazy();
int result=n1-n2;
System.out.println(n1+"-"+n2+"="+result);
Long span= System.currentTimeMillis()-start;
System.out.println("共用時:"+span);
return result;
}
//乘
public int mut(int n1,int n2){
//開始時間
long start=System.currentTimeMillis();
lazy();
int result=n1*n2;
System.out.println(n1+"X"+n2+"="+result);
Long span= System.currentTimeMillis()-start;
System.out.println("共用時:"+span);
return result;
}
//除
public int div(int n1,int n2){
//開始時間
long start=System.currentTimeMillis();
lazy();
int result=n1/n2;
System.out.println(n1+"/"+n2+"="+result);
Long span= System.currentTimeMillis()-start;
System.out.println("共用時:"+span);
return result;
}
//模擬延時
public void lazy()
{
try {
int n=(int)new Random().nextInt(500);
Thread.sleep(n);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
~~~
缺點:
1. 工作量特別大,如果項目中有多個類,多個方法,則要修改多次。
2. 違背了設計原則:開閉原則(OCP),對擴展開放,對修改關閉,而為了增加功能把每個方法都修改了,也不便于維護。
3. 違背了設計原則:單一職責(SRP),每個方法除了要完成自己本身的功能,還要計算耗時、延時;每一個方法引起它變化的原因就有多種。
4. 違背了設計原則:依賴倒轉(DIP),抽象不應該依賴細節,兩者都應該依賴抽象。而在Test類中,Test與Math都是細節。
解決:
* 使用靜態代理可以解決部分問題(請往下看...)
### 3.8 靜態代理
?1、定義抽象主題接口
~~~java
package com.zhangguo.Spring041.aop02;
/**
* 接口
* 抽象主題
*/
public interface IMath {
//加
int add(int n1, int n2);
//減
int sub(int n1, int n2);
//乘
int mut(int n1, int n2);
//除
int div(int n1, int n2);
}
~~~
2、主題類,算術類,實現抽象接口
~~~java
package com.zhangguo.Spring041.aop02;
/**
* 被代理的目標對象
*真實主題
*/
public class Math implements IMath {
//加
public int add(int n1,int n2){
int result=n1+n2;
System.out.println(n1+"+"+n2+"="+result);
return result;
}
//減
public int sub(int n1,int n2){
int result=n1-n2;
System.out.println(n1+"-"+n2+"="+result);
return result;
}
//乘
public int mut(int n1,int n2){
int result=n1*n2;
System.out.println(n1+"X"+n2+"="+result);
return result;
}
//除
public int div(int n1,int n2){
int result=n1/n2;
System.out.println(n1+"/"+n2+"="+result);
return result;
}
}
~~~
3、代理類
~~~java
package com.zhangguo.Spring041.aop02;
import java.util.Random;
/**
* 靜態代理類
*/
public class MathProxy implements IMath {
//被代理的對象
IMath math=new Math();
//加
public int add(int n1, int n2) {
//開始時間
long start=System.currentTimeMillis();
lazy();
int result=math.add(n1, n2);
Long span= System.currentTimeMillis()-start;
System.out.println("共用時:"+span);
return result;
}
//減法
public int sub(int n1, int n2) {
//開始時間
long start=System.currentTimeMillis();
lazy();
int result=math.sub(n1, n2);
Long span= System.currentTimeMillis()-start;
System.out.println("共用時:"+span);
return result;
}
//乘
public int mut(int n1, int n2) {
//開始時間
long start=System.currentTimeMillis();
lazy();
int result=math.mut(n1, n2);
Long span= System.currentTimeMillis()-start;
System.out.println("共用時:"+span);
return result;
}
//除
public int div(int n1, int n2) {
//開始時間
long start=System.currentTimeMillis();
lazy();
int result=math.div(n1, n2);
Long span= System.currentTimeMillis()-start;
System.out.println("共用時:"+span);
return result;
}
//模擬延時
public void lazy()
{
try {
int n=(int)new Random().nextInt(500);
Thread.sleep(n);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
~~~
4、測試運行
~~~java
package com.zhangguo.Spring041.aop02;
public class Test {
IMath math=new MathProxy();
@org.junit.Test
public void test01()
{
int n1=100,n2=5;
math.add(n1, n2);
math.sub(n1, n2);
math.mut(n1, n2);
math.div(n1, n2);
}
}
~~~
5、小結
通過靜態代理,是否完全解決了上述的4個問題:
**已解決:**
* 解決了“開閉原則(OCP)”的問題,因為并沒有修改Math類,而擴展出了MathProxy類。
* 解決了“依賴倒轉(DIP)”的問題,通過引入接口。
* 解決了“單一職責(SRP)”的問題,Math類不再需要去計算耗時與延時操作,但從某些方面講MathProxy還是存在該問題。
**未解決:**
* 如果項目中有多個類,則需要編寫多個代理類,工作量大,不好修改,不好維護,不能應對變化。
如果要解決上面的問題,可以使用動態代理。
### 3.9 動態代理,使用JDK內置的Proxy實現
只需要一個代理類,而不是針對每個類編寫代理類。
在上一個示例中修改代理類MathProxy如下:
~~~java
package com.zhangguo.Spring041.aop03;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Random;
/**
* 動態代理類
*/
public class DynamicProxy implements InvocationHandler {
//被代理的對象
Object targetObject;
/**
* 獲得被代理后的對象
* @param object 被代理的對象
* @return 代理后的對象
*/
public Object getProxyObject(Object object){
this.targetObject=object;
return Proxy.newProxyInstance(
targetObject.getClass().getClassLoader(), //類加載器
targetObject.getClass().getInterfaces(), //獲得被代理對象的所有接口
this); //InvocationHandler對象
//loader:一個ClassLoader對象,定義了由哪個ClassLoader對象來生成代理對象進行加載
//interfaces:一個Interface對象的數組,表示的是我將要給我需要代理的對象提供一組什么接口,如果我提供了一組接口給它,那么這個代理對象就宣稱實現了該接口(多態),這樣我就能調用這組接口中的方法了
//h:一個InvocationHandler對象,表示的是當我這個動態代理對象在調用方法的時候,會關聯到哪一個InvocationHandler對象上,間接通過invoke來執行
}
/**
* 當用戶調用對象中的每個方法時都通過下面的方法執行,方法必須在接口
* proxy 被代理后的對象
* method 將要被執行的方法信息(反射)
* args 執行方法時需要的參數
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//被織入的內容,開始時間
long start=System.currentTimeMillis();
lazy();
//使用反射在目標對象上調用方法并傳入參數
Object result=method.invoke(targetObject, args);
//被織入的內容,結束時間
Long span= System.currentTimeMillis()-start;
System.out.println("共用時:"+span);
return result;
}
//模擬延時
public void lazy()
{
try {
int n=(int)new Random().nextInt(500);
Thread.sleep(n);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
~~~
測試運行:
~~~java
package com.zhangguo.Spring041.aop03;
public class Test {
//實例化一個MathProxy代理對象
//通過getProxyObject方法獲得被代理后的對象
IMath math=(IMath)new DynamicProxy().getProxyObject(new Math());
@org.junit.Test
public void test01()
{
int n1=100,n2=5;
math.add(n1, n2);
math.sub(n1, n2);
math.mut(n1, n2);
math.div(n1, n2);
}
IMessage message=(IMessage) new DynamicProxy().getProxyObject(new Message());
@org.junit.Test
public void test02()
{
message.message();
}
}
~~~
小結:
JDK內置的Proxy動態代理可以在運行時動態生成字節碼,而沒必要針對每個類編寫代理類。中間主要使用到了一個接口InvocationHandler與Proxy.newProxyInstance靜態方法,參數說明如下:
使用內置的Proxy實現動態代理有一個問題:**被代理的類必須實現接口,未實現接口則沒辦法完成動態代理。**
如果項目中有些類沒有實現接口,則不應該為了實現動態代理而刻意去抽出一些沒有實例意義的接口,通過cglib可以解決該問題。
### 3.10 動態代理,使用cglib實現
CGLIB(Code Generation Library)是一個開源項目,是一個強大的,高性能,高質量的Code生成類庫,它可以在運行期擴展Java類與實現Java接口,通俗說cglib可以在運行時動態生成字節碼。
1、引用cglib,通過maven
2、使用cglib完成動態代理,大概的原理是:cglib繼承被代理的類,重寫方法,織入通知,動態生成字節碼并運行,因為是繼承所以final類是沒有辦法動態代理的。具體實現如下:
~~~java
package com.zhangguo.Spring041.aop04;
import java.lang.reflect.Method;
import java.util.Random;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/*
* 動態代理類
* 實現了一個方法攔截器接口
*/
public class DynamicProxy implements MethodInterceptor {
// 被代理對象
Object targetObject;
//Generate a new class if necessary and uses the specified callbacks (if any) to create a new object instance.
//Uses the no-arg constructor of the superclass.
//動態生成一個新的類,使用父類的無參構造方法創建一個指定了特定回調的代理實例
public Object getProxyObject(Object object) {
this.targetObject = object;
//增強器,動態代碼生成器
Enhancer enhancer=new Enhancer();
//回調方法
enhancer.setCallback(this);
//設置生成類的父類類型
enhancer.setSuperclass(targetObject.getClass());
//動態生成字節碼并返回代理對象
return enhancer.create();
}
// 攔截方法
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 被織入的橫切內容,開始時間 before
long start = System.currentTimeMillis();
lazy();
// 調用方法
Object result = methodProxy.invoke(targetObject, args);
// 被織入的橫切內容,結束時間
Long span = System.currentTimeMillis() - start;
System.out.println("共用時:" + span);
return result;
}
// 模擬延時
public void lazy() {
try {
int n = (int) new Random().nextInt(500);
Thread.sleep(n);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
~~~
測試運行:
~~~java
package com.zhangguo.Spring041.aop04;
public class Test {
//實例化一個DynamicProxy代理對象
//通過getProxyObject方法獲得被代理后的對象
Math math=(Math)new DynamicProxy().getProxyObject(new Math());
@org.junit.Test
public void test01()
{
int n1=100,n2=5;
math.add(n1, n2);
math.sub(n1, n2);
math.mut(n1, n2);
math.div(n1, n2);
}
//另一個被代理的對象,不再需要重新編輯代理代碼
Message message=(Message) new DynamicProxy().getProxyObject(new Message());
@org.junit.Test
public void test02()
{
message.message();
}
}
~~~
**小結**
使用cglib可以實現動態代理,即使被代理的類沒有實現接口,但被代理的類必須不是final類。
## 4\. Spring Ioc容器
### 4.1 Bean作用域
Spring 框架支持以下五個作用域,如果你使用 web-aware ApplicationContext 時,其中三個是可用的。
| 作用域 | 描述 |
| --- | --- |
| singleton | 在spring IoC容器僅存在一個Bean實例,Bean以單例方式存在,默認值 |
| prototype | 每次從容器中調用Bean時,都返回一個新的實例,即每次調用getBean()時,相當于執行newXxxBean() |
| request | 每次HTTP請求都會創建一個新的Bean,該作用域僅適用于WebApplicationContext環境 |
| session | 同一個HTTP Session共享一個Bean,不同Session使用不同的Bean,僅適用于WebApplicationContext環境 |
| global-session | 一般用于Portlet應用環境,改作用于僅適用于WebApplicationContext環境 |
### 4.2 Bean 的生命周期
在傳統的Java應用中,bean的生命周期很簡單。使用Java關鍵字new進行bean實例化,然后該bean就可以使用了。一旦該bean不再被使用,則由Java自動進行垃圾回收。
相比之下,Spring容器中的bean的生命周期就顯得相對復雜多了。正確理解Spring bean的生命周期非常重要,因為你或許要利用Spring提供的擴展點來自定義bean的創建過程。下圖展示了bean裝載到Spring應用上下文中的一個典型的生命周期過程。
[](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/bean-life.png)
上圖bean在Spring容器中從創建到銷毀經歷了若干階段,每一階段都可以針對Spring如何管理bean進行個性化定制
**正如你所見,在bean準備就緒之前,bean工廠執行了若干啟動步驟。我們對上圖進行詳細描述:**
1. Spring 對 Bean 進行實例化;
* 相當于程序中的new Xx()
2. Spring 將值和 Bean 的引用注入進 Bean 對應的屬性中;
3. **如果Bean實現了 BeanNameAware 接口**,Spring 將 Bean 的 ID 傳遞給setBeanName()方法
* 實現BeanNameAware清主要是為了通過Bean的引用來獲得Bean的ID,一般業務中是很少有在Bean的ID的
4. **如果Bean實現了BeanFactoryAware接口**,Spring將調用setBeanDactory(BeanFactory bf)方法并把BeanFactory容器實例作為參數傳入。
* 實現BeanFactoryAware 主要目的是為了獲取Spring容器,如Bean通過Spring容器發布事件等
5. **如果Bean實現了ApplicationContextAwaer接口**,Spring容器將調用setApplicationContext(ApplicationContext ctx)方法,將bean所在的應用上下文的引用傳入進來
* 作用與BeanFactory類似都是為了獲取Spring容器,不同的是Spring容器在調用setApplicationContext方法時會把它自己作為setApplicationContext 的參數傳入,而Spring容器在調用setBeanDactory前需要程序員自己指定(注入)setBeanDactory里的參數BeanFactory
6. **如果Bean實現了BeanPostProcess接口**,Spring將調用它們的postProcessBeforeInitialization(預初始化)方法
* 作用是在Bean實例創建成功后對進行增強處理,如對Bean進行修改,增加某個功能
7. **如果Bean實現了InitializingBean接口**,Spring將調用它們的afterPropertiesSet方法,作用與在配置文件中對Bean使用init-method聲明初始化的作用一樣,都是在Bean的全部屬性設置成功后執行的初始化方法。
8. **如果Bean實現了BeanPostProcess接口**,Spring將調用它們的postProcessAfterInitialization(后初始化)方法
* 作用與6的一樣,只不過6是在Bean初始化前執行的,而這個是在Bean初始化后執行的,時機不同
9. 經過以上的工作后,Bean將一直駐留在應用上下文中給應用使用,直到應用上下文被銷毀
10. **如果Bean實現了DispostbleBean接口**,Spring將調用它的destory方法,作用與在配置文件中對Bean使用destory-method屬性的作用一樣,都是在Bean實例銷毀前執行的方法。
### 4.3 BeanFactory 和ApplicationContext(Bean工廠和應用上下文)
Bean 工廠(com.springframework.beans.factory.BeanFactory)是Spring 框架最核心的接口,它提供了高級IoC 的配置機制。
應用上下文(com.springframework.context.ApplicationContext)建立在BeanFactory 基礎之上。
幾乎所有的應用場合我們都直接使用ApplicationContext 而非底層的BeanFactory。
[](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/pics/beanfactory.jpg)
ApplicationContext 的初始化和BeanFactory有一個重大的區別:
* BeanFactory在初始化容器時,并未實例化Bean,直到第一次訪問某個Bean 時才實例目標Bean;
* 而ApplicationContext 則在初始化應用上下文時就實例化所有單實例的Bean 。
~~~xml
ApplicationContext ctx = new ClassPathXmlApplicationContext("com/baobaotao/context/beans.xml");
ApplicationContext ctx = new FileSystemXmlApplicationContext("com/baobaotao/context/beans.xml");
ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[]{"conf/beans1.xml","conf/beans2.xml"});
~~~
參考資料:
* [BeanFactory 和ApplicationContext(Bean工廠和應用上下文) - 品互網絡](http://www.pinhuba.com/spring/101250.htm)
- 一.JVM
- 1.1 java代碼是怎么運行的
- 1.2 JVM的內存區域
- 1.3 JVM運行時內存
- 1.4 JVM內存分配策略
- 1.5 JVM類加載機制與對象的生命周期
- 1.6 常用的垃圾回收算法
- 1.7 JVM垃圾收集器
- 1.8 CMS垃圾收集器
- 1.9 G1垃圾收集器
- 2.面試相關文章
- 2.1 可能是把Java內存區域講得最清楚的一篇文章
- 2.0 GC調優參數
- 2.1GC排查系列
- 2.2 內存泄漏和內存溢出
- 2.2.3 深入理解JVM-hotspot虛擬機對象探秘
- 1.10 并發的可達性分析相關問題
- 二.Java集合架構
- 1.ArrayList深入源碼分析
- 2.Vector深入源碼分析
- 3.LinkedList深入源碼分析
- 4.HashMap深入源碼分析
- 5.ConcurrentHashMap深入源碼分析
- 6.HashSet,LinkedHashSet 和 LinkedHashMap
- 7.容器中的設計模式
- 8.集合架構之面試指南
- 9.TreeSet和TreeMap
- 三.Java基礎
- 1.基礎概念
- 1.1 Java程序初始化的順序是怎么樣的
- 1.2 Java和C++的區別
- 1.3 反射
- 1.4 注解
- 1.5 泛型
- 1.6 字節與字符的區別以及訪問修飾符
- 1.7 深拷貝與淺拷貝
- 1.8 字符串常量池
- 2.面向對象
- 3.關鍵字
- 4.基本數據類型與運算
- 5.字符串與數組
- 6.異常處理
- 7.Object 通用方法
- 8.Java8
- 8.1 Java 8 Tutorial
- 8.2 Java 8 數據流(Stream)
- 8.3 Java 8 并發教程:線程和執行器
- 8.4 Java 8 并發教程:同步和鎖
- 8.5 Java 8 并發教程:原子變量和 ConcurrentMap
- 8.6 Java 8 API 示例:字符串、數值、算術和文件
- 8.7 在 Java 8 中避免 Null 檢查
- 8.8 使用 Intellij IDEA 解決 Java 8 的數據流問題
- 四.Java 并發編程
- 1.線程的實現/創建
- 2.線程生命周期/狀態轉換
- 3.線程池
- 4.線程中的協作、中斷
- 5.Java鎖
- 5.1 樂觀鎖、悲觀鎖和自旋鎖
- 5.2 Synchronized
- 5.3 ReentrantLock
- 5.4 公平鎖和非公平鎖
- 5.3.1 說說ReentrantLock的實現原理,以及ReentrantLock的核心源碼是如何實現的?
- 5.5 鎖優化和升級
- 6.多線程的上下文切換
- 7.死鎖的產生和解決
- 8.J.U.C(java.util.concurrent)
- 0.簡化版(快速復習用)
- 9.鎖優化
- 10.Java 內存模型(JMM)
- 11.ThreadLocal詳解
- 12 CAS
- 13.AQS
- 0.ArrayBlockingQueue和LinkedBlockingQueue的實現原理
- 1.DelayQueue的實現原理
- 14.Thread.join()實現原理
- 15.PriorityQueue 的特性和原理
- 16.CyclicBarrier的實際使用場景
- 五.Java I/O NIO
- 1.I/O模型簡述
- 2.Java NIO之緩沖區
- 3.JAVA NIO之文件通道
- 4.Java NIO之套接字通道
- 5.Java NIO之選擇器
- 6.基于 Java NIO 實現簡單的 HTTP 服務器
- 7.BIO-NIO-AIO
- 8.netty(一)
- 9.NIO面試題
- 六.Java設計模式
- 1.單例模式
- 2.策略模式
- 3.模板方法
- 4.適配器模式
- 5.簡單工廠
- 6.門面模式
- 7.代理模式
- 七.數據結構和算法
- 1.什么是紅黑樹
- 2.二叉樹
- 2.1 二叉樹的前序、中序、后序遍歷
- 3.排序算法匯總
- 4.java實現鏈表及鏈表的重用操作
- 4.1算法題-鏈表反轉
- 5.圖的概述
- 6.常見的幾道字符串算法題
- 7.幾道常見的鏈表算法題
- 8.leetcode常見算法題1
- 9.LRU緩存策略
- 10.二進制及位運算
- 10.1.二進制和十進制轉換
- 10.2.位運算
- 11.常見鏈表算法題
- 12.算法好文推薦
- 13.跳表
- 八.Spring 全家桶
- 1.Spring IOC
- 2.Spring AOP
- 3.Spring 事務管理
- 4.SpringMVC 運行流程和手動實現
- 0.Spring 核心技術
- 5.spring如何解決循環依賴問題
- 6.springboot自動裝配原理
- 7.Spring中的循環依賴解決機制中,為什么要三級緩存,用二級緩存不夠嗎
- 8.beanFactory和factoryBean有什么區別
- 九.數據庫
- 1.mybatis
- 1.1 MyBatis-# 與 $ 區別以及 sql 預編譯
- Mybatis系列1-Configuration
- Mybatis系列2-SQL執行過程
- Mybatis系列3-之SqlSession
- Mybatis系列4-之Executor
- Mybatis系列5-StatementHandler
- Mybatis系列6-MappedStatement
- Mybatis系列7-參數設置揭秘(ParameterHandler)
- Mybatis系列8-緩存機制
- 2.淺談聚簇索引和非聚簇索引的區別
- 3.mysql 證明為什么用limit時,offset很大會影響性能
- 4.MySQL中的索引
- 5.數據庫索引2
- 6.面試題收集
- 7.MySQL行鎖、表鎖、間隙鎖詳解
- 8.數據庫MVCC詳解
- 9.一條SQL查詢語句是如何執行的
- 10.MySQL 的 crash-safe 原理解析
- 11.MySQL 性能優化神器 Explain 使用分析
- 12.mysql中,一條update語句執行的過程是怎么樣的?期間用到了mysql的哪些log,分別有什么作用
- 十.Redis
- 0.快速復習回顧Redis
- 1.通俗易懂的Redis數據結構基礎教程
- 2.分布式鎖(一)
- 3.分布式鎖(二)
- 4.延時隊列
- 5.位圖Bitmaps
- 6.Bitmaps(位圖)的使用
- 7.Scan
- 8.redis緩存雪崩、緩存擊穿、緩存穿透
- 9.Redis為什么是單線程、及高并發快的3大原因詳解
- 10.布隆過濾器你值得擁有的開發利器
- 11.Redis哨兵、復制、集群的設計原理與區別
- 12.redis的IO多路復用
- 13.相關redis面試題
- 14.redis集群
- 十一.中間件
- 1.RabbitMQ
- 1.1 RabbitMQ實戰,hello world
- 1.2 RabbitMQ 實戰,工作隊列
- 1.3 RabbitMQ 實戰, 發布訂閱
- 1.4 RabbitMQ 實戰,路由
- 1.5 RabbitMQ 實戰,主題
- 1.6 Spring AMQP 的 AMQP 抽象
- 1.7 Spring AMQP 實戰 – 整合 RabbitMQ 發送郵件
- 1.8 RabbitMQ 的消息持久化與 Spring AMQP 的實現剖析
- 1.9 RabbitMQ必備核心知識
- 2.RocketMQ 的幾個簡單問題與答案
- 2.Kafka
- 2.1 kafka 基礎概念和術語
- 2.2 Kafka的重平衡(Rebalance)
- 2.3.kafka日志機制
- 2.4 kafka是pull還是push的方式傳遞消息的?
- 2.5 Kafka的數據處理流程
- 2.6 Kafka的腦裂預防和處理機制
- 2.7 Kafka中partition副本的Leader選舉機制
- 2.8 如果Leader掛了的時候,follower沒來得及同步,是否會出現數據不一致
- 2.9 kafka的partition副本是否會出現腦裂情況
- 十二.Zookeeper
- 0.什么是Zookeeper(漫畫)
- 1.使用docker安裝Zookeeper偽集群
- 3.ZooKeeper-Plus
- 4.zk實現分布式鎖
- 5.ZooKeeper之Watcher機制
- 6.Zookeeper之選舉及數據一致性
- 十三.計算機網絡
- 1.進制轉換:二進制、八進制、十六進制、十進制之間的轉換
- 2.位運算
- 3.計算機網絡面試題匯總1
- 十四.Docker
- 100.面試題收集合集
- 1.美團面試常見問題總結
- 2.b站部分面試題
- 3.比心面試題
- 4.騰訊面試題
- 5.哈羅部分面試
- 6.筆記
- 十五.Storm
- 1.Storm和流處理簡介
- 2.Storm 核心概念詳解
- 3.Storm 單機版本環境搭建
- 4.Storm 集群環境搭建
- 5.Storm 編程模型詳解
- 6.Storm 項目三種打包方式對比分析
- 7.Storm 集成 Redis 詳解
- 8.Storm 集成 HDFS 和 HBase
- 9.Storm 集成 Kafka
- 十六.Elasticsearch
- 1.初識ElasticSearch
- 2.文檔基本CRUD、集群健康檢查
- 3.shard&replica
- 4.document核心元數據解析及ES的并發控制
- 5.document的批量操作及數據路由原理
- 6.倒排索引
- 十七.分布式相關
- 1.分布式事務解決方案一網打盡
- 2.關于xxx怎么保證高可用的問題
- 3.一致性hash原理與實現
- 4.微服務注冊中心 Nacos 比 Eureka的優勢
- 5.Raft 協議算法
- 6.為什么微服務架構中需要網關
- 0.CAP與BASE理論
- 十八.Dubbo
- 1.快速掌握Dubbo常規應用
- 2.Dubbo應用進階
- 3.Dubbo調用模塊詳解
- 4.Dubbo調用模塊源碼分析
- 6.Dubbo協議模塊