Spring
1. 什么是 Spring 框架
Spring是一個輕量級Java開發(fā)框架
我們一般說 Spring 框架指的都是 Spring Framework,它是很多模塊的集合,使用這些模塊可以很方便地協(xié)助我們進行開發(fā),比如說 Spring 支持 IoC(Inverse of Control:控制反轉(zhuǎn)) 和 AOP(Aspect-Oriented Programming:面向切面編程)、可以很方便地對數(shù)據(jù)庫進行訪問、可以很方便地集成第三方組件(電子郵件,任務(wù),調(diào)度,緩存等等)、對單元測試支持比較好、支持 RESTful Java 應(yīng)用程序的開發(fā)。
- Spring5.x主要模塊
Core Container
Spring 框架的核心模塊,也可以說是基礎(chǔ)模塊,主要提供 IoC 依賴注入功能的支持。Spring 其他所有的功能基本都需要依賴于該模塊,我們從上面那張 Spring 各個模塊的依賴關(guān)系圖就可以看出來。
- spring-core :Spring 框架基本的核心工具類。
- spring-beans :提供對 bean 的創(chuàng)建、配置和管理等功能的支持。
- spring-context :提供對國際化、事件傳播、資源加載等功能的支持。
- spring-expression :提供對表達式語言(Spring Expression Language) SpEL 的支持,只依賴于 core 模塊,不依賴于其他模塊,可以單獨使用。
AOP
- spring-aspects :該模塊為與 AspectJ 的集成提供支持。
- spring-aop :提供了面向切面的編程實現(xiàn)。
- spring-instrument :提供了為 JVM 添加代理(agent)的功能。具體來講,它為 Tomcat 提供了一個織入代理,能夠為 Tomcat 傳遞類文 件,就像這些文件是被類加載器加載的一樣。沒有理解也沒關(guān)系,這個模塊的使用場景非常有限。
Data Access/Integration
- spring-jdbc :提供了對數(shù)據(jù)庫訪問的抽象 JDBC。不同的數(shù)據(jù)庫都有自己獨立的 API 用于操作數(shù)據(jù)庫,而 Java 程序只需要和 JDBC API 交互,這樣就屏蔽了數(shù)據(jù)庫的影響。
- spring-tx :提供對事務(wù)的支持。
- spring-orm :提供對 Hibernate、JPA 、iBatis 等 ORM 框架的支持。
- spring-oxm :提供一個抽象層支撐 OXM(Object-to-XML-Mapping),例如:JAXB、Castor、XMLBeans、JiBX 和 XStream 等。
- spring-jms : 消息服務(wù)。自 Spring Framework 4.1 以后,它還提供了對 spring-messaging 模塊的繼承。
Spring Web
- spring-web :對 Web 功能的實現(xiàn)提供一些最基礎(chǔ)的支持。
- spring-webmvc :提供對 Spring MVC 的實現(xiàn)。
- spring-websocket :提供了對 WebSocket 的支持,WebSocket 可以讓客戶端和服務(wù)端進行雙向通信。
- spring-webflux :提供對 WebFlux 的支持。WebFlux 是 Spring Framework 5.0 中引入的新的響應(yīng)式框架。與 Spring MVC 不同,它不需要 Servlet API,是完全異步。
Messaging
spring-messaging 是從 Spring4.0 開始新加入的一個模塊,主要職責(zé)是為 Spring 框架集成一些基礎(chǔ)的報文傳送應(yīng)用。
Spring Test
Spring 團隊提倡測試驅(qū)動開發(fā)(TDD)。有了控制反轉(zhuǎn) (IoC)的幫助,單元測試和集成測試變得更簡單。
Spring 的測試模塊對 JUnit(單元測試框架)、TestNG(類似 JUnit)、Mockito(主要用來 Mock 對象)、PowerMock(解決 Mockito 的問題比如無法模擬 final, static, private 方法)等等常用的測試框架支持的都比較好。
2. Spring,Spring MVC,Spring Boot 之間什么關(guān)系?
Spring 包含了多個功能模塊(上面剛剛提高過),其中最重要的是 Spring-Core(主要提供 IoC 依賴注入功能的支持) 模塊, Spring 中的其他模塊(比如 Spring MVC)的功能實現(xiàn)基本都需要依賴于該模塊。
Spring MVC 是 Spring 中的一個很重要的模塊,主要賦予 Spring 快速構(gòu)建 MVC 架構(gòu)的 Web 程序的能力。MVC 是模型(Model)、視圖(View)、控制器(Controller)的簡寫,其核心思想是通過將業(yè)務(wù)邏輯、數(shù)據(jù)、顯示分離來組織代碼。
使用 Spring 進行開發(fā)各種配置過于麻煩比如開啟某些 Spring 特性時,需要用 XML 或 Java 進行顯式配置。于是,Spring Boot 誕生了!
Spring 旨在簡化 J2EE 企業(yè)應(yīng)用程序開發(fā)。Spring Boot 旨在簡化 Spring 開發(fā)(減少配置文件,開箱即用?。?。
Spring Boot 只是簡化了配置,如果你需要構(gòu)建 MVC 架構(gòu)的 Web 程序,你還是需要使用 Spring MVC 作為 MVC 框架,只是說 Spring Boot 幫你簡化了 Spring MVC 的很多配置,真正做到開箱即用!
3. Spring IoC
IoC(Inverse of Control:控制反轉(zhuǎn)) 是一種設(shè)計思想,而不是一個具體的技術(shù)實現(xiàn)。IoC 的思想就是將原本在程序中手動創(chuàng)建對象的控制權(quán),交由Spring框架來管理。不過,IoC 并非 Spring 特有,在其他語言中也有應(yīng)用。
為什么叫控制反轉(zhuǎn)?
-
控制 :指的是對象創(chuàng)建(實例化、管理)的權(quán)力
-
反轉(zhuǎn) :控制權(quán)交給外部環(huán)境(Spring 框架、IoC 容器)
將對象之間的相互依賴關(guān)系交給 IoC 容器來管理,并由 IoC 容器完成對象的注入。這樣可以很大程度上簡化應(yīng)用的開發(fā),把應(yīng)用從復(fù)雜的依賴關(guān)系中解放出來。IoC 容器就像是一個工廠一樣,當(dāng)我們需要創(chuàng)建一個對象的時候,只需要配置好配置文件/注解即可,完全不用考慮對象是如何被創(chuàng)建出來的。
在實際項目中一個 Service 類可能依賴了很多其他的類,假如我們需要實例化這個 Service,你可能要每次都要搞清這個 Service 所有底層類的構(gòu)造函數(shù),這可能會把人逼瘋。如果利用 IoC 的話,你只需要配置好,然后在需要的地方引用就行了,這大大增加了項目的可維護性且降低了開發(fā)難度。
在 Spring 中, IoC 容器是 Spring 用來實現(xiàn) IoC 的載體, IoC 容器實際上就是個 Map(key,value),Map 中存放的是各種對象。
Spring 時代我們一般通過 XML 文件來配置 Bean,后來開發(fā)人員覺得 XML 文件來配置不太好,于是 SpringBoot 注解配置就慢慢開始流行起來
什么是 Spring Bean?
簡單來說,Bean 代指的就是那些被 IoC 容器所管理的對象。
我們需要告訴 IoC 容器幫助我們管理哪些對象,這個是通過配置元數(shù)據(jù)來定義的。配置元數(shù)據(jù)可以是 XML 文件、注解或者 Java 配置類。
<!-- Constructor-arg with 'value' attribute -->
<bean id="..." class="...">
<constructor-arg value="..."/>
</bean>
下圖簡單地展示了 IoC 容器如何使用配置元數(shù)據(jù)來管理對象。
org.springframework.beans
和 org.springframework.context
這兩個包是 IoC 實現(xiàn)的基礎(chǔ)
將一個類聲明為 Bean 的注解有哪些?
-
@Component
:通用的注解,可標(biāo)注任意類為Spring
組件。如果一個 Bean 不知道屬于哪個層,可以使用@Component
注解標(biāo)注。 -
@Repository
: 對應(yīng)持久層即 Dao 層,主要用于數(shù)據(jù)庫相關(guān)操作。 -
@Service
: 對應(yīng)服務(wù)層,主要涉及一些復(fù)雜的邏輯,需要用到 Dao 層。 -
@Controller
: 對應(yīng) Spring MVC 控制層,主要用戶接受用戶請求并調(diào)用 Service 層返回數(shù)據(jù)給前端頁面。
@Component 和 @Bean 的區(qū)別是什么?
-
@Component
注解作用于類,而@Bean
注解作用于方法。 -
@Component
通常是通過類路徑掃描來自動偵測以及自動裝配到 Spring 容器中(我們可以使用@ComponentScan
注解定義要掃描的路徑從中找出標(biāo)識了需要裝配的類自動裝配到 Spring 的 bean 容器中)。@Bean
注解通常是我們在標(biāo)有該注解的方法中定義產(chǎn)生這個 bean,@Bean
告訴了 Spring 這是某個類的實例,當(dāng)我需要用它的時候還給我。 -
@Bean
注解比@Component
注解的自定義性更強,而且很多地方我們只能通過@Bean
注解來注冊 bean。比如當(dāng)我們引用第三方庫中的類需要裝配到Spring
容器時,則只能通過@Bean
來實現(xiàn)。
@Bean
注解使用示例:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
上面的代碼相當(dāng)于下面的 xml 配置
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
下面這個例子是通過 @Component
無法實現(xiàn)的。
@Bean
public OneService getService(status) {
case (status) {
when 1:
return new serviceImpl1();
when 2:
return new serviceImpl2();
when 3:
return new serviceImpl3();
}
}
注入 Bean 的注解有哪些?
Spring 內(nèi)置的 @Autowired
以及 JDK 內(nèi)置的 @Resource
和 @Inject
都可以用于注入 Bean。
Annotaion | Package | Source |
---|---|---|
@Autowired |
org.springframework.bean.factory |
Spring 2.5+ |
@Resource |
javax.annotation |
Java JSR-250 |
@Inject |
javax.inject |
Java JSR-330 |
@Autowired
和@Resource
使用的比較多一些。
@Autowired 和 @Resource 的區(qū)別是什么?
Autowired
屬于 Spring 內(nèi)置的注解,默認(rèn)的注入方式為byType
(根據(jù)類型進行匹配),也就是說會優(yōu)先根據(jù)接口類型去匹配并注入 Bean(接口的實現(xiàn)類)。
這會有什么問題呢? 當(dāng)一個接口存在多個實現(xiàn)類的話,byType
這種方式就無法正確注入對象了,因為這個時候 Spring 會同時找到多個滿足條件的選擇,默認(rèn)情況下它自己不知道選擇哪一個。
這種情況下,注入方式會變?yōu)?byName
(根據(jù)名稱進行匹配),這個名稱通常就是類名(首字母小寫)。就比如說下面代碼中的 smsService
就是我這里所說的名稱,這樣應(yīng)該比較好理解了吧。
// smsService 就是我們上面所說的名稱
@Autowired
private SmsService smsService;
舉個例子,SmsService
接口有兩個實現(xiàn)類: SmsServiceImpl1
和 SmsServiceImpl2
,且它們都已經(jīng)被 Spring 容器所管理。
// 報錯,byName 和 byType 都無法匹配到 bean
@Autowired
private SmsService smsService;
// 正確注入 SmsServiceImpl1 對象對應(yīng)的 bean
@Autowired
private SmsService smsServiceImpl1;
// 正確注入 SmsServiceImpl1 對象對應(yīng)的 bean
// smsServiceImpl1 就是我們上面所說的名稱
@Autowired
@Qualifier(value = "smsServiceImpl1")
private SmsService smsService;
我們還是建議通過 @Qualifier
注解來顯示指定名稱而不是依賴變量的名稱。
@Resource
屬于 JDK 提供的注解,默認(rèn)注入方式為 byName
。如果無法通過名稱匹配到對應(yīng)的 Bean 的話,注入方式會變?yōu)?code>byType。
@Resource
有兩個比較重要且日常開發(fā)常用的屬性:name
(名稱)、type
(類型)。
public @interface Resource {
String name() default "";
Class<?> type() default Object.class;
}
如果僅指定 name
屬性則注入方式為byName
,如果僅指定type
屬性則注入方式為byType
,如果同時指定name
和type
屬性(不建議這么做)則注入方式為byType
+byName
。
// 報錯,byName 和 byType 都無法匹配到 bean
@Resource
private SmsService smsService;
// 正確注入 SmsServiceImpl1 對象對應(yīng)的 bean
@Resource
private SmsService smsServiceImpl1;
// 正確注入 SmsServiceImpl1 對象對應(yīng)的 bean(比較推薦這種方式)
@Resource(name = "smsServiceImpl1")
private SmsService smsService;
簡單總結(jié)一下:
-
@Autowired
是 Spring 提供的注解,@Resource
是 JDK 提供的注解。 -
Autowired
默認(rèn)的注入方式為byType
(根據(jù)類型進行匹配),@Resource
默認(rèn)注入方式為byName
(根據(jù)名稱進行匹配)。 - 當(dāng)一個接口存在多個實現(xiàn)類的情況下,
@Autowired
和@Resource
都需要通過名稱才能正確匹配到對應(yīng)的 Bean。Autowired
可以通過@Qualifier
注解來顯示指定名稱,@Resource
可以通過name
屬性來顯示指定名稱。
Bean 的作用域有哪些?
Spring 中 Bean 的作用域通常有下面幾種:
- singleton : IoC 容器中只有唯一的 bean 實例。Spring 中的 bean 默認(rèn)都是單例的,是對單例設(shè)計模式的應(yīng)用。
-
prototype : 每次獲取都會創(chuàng)建一個新的 bean 實例。也就是說,連續(xù)
getBean()
兩次,得到的是不同的 Bean 實例。 - request (僅 Web 應(yīng)用可用): 每一次 HTTP 請求都會產(chǎn)生一個新的 bean(請求 bean),該 bean 僅在當(dāng)前 HTTP request 內(nèi)有效。
- session (僅 Web 應(yīng)用可用) : 每一次來自新 session 的 HTTP 請求都會產(chǎn)生一個新的 bean(會話 bean),該 bean 僅在當(dāng)前 HTTP session 內(nèi)有效。
- application/global-session (僅 Web 應(yīng)用可用):每個 Web 應(yīng)用在啟動時創(chuàng)建一個 Bean(應(yīng)用 Bean),該 bean 僅在當(dāng)前應(yīng)用啟動時間內(nèi)有效。
- websocket (僅 Web 應(yīng)用可用):每一次 WebSocket 會話產(chǎn)生一個新的 bean。
如何配置 bean 的作用域呢?
xml 方式:
<bean id="..." class="..." scope="singleton"></bean>
注解方式:
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person personPrototype() {
return new Person();
}
單例 Bean 的線程安全問題了解嗎?
大部分時候我們并沒有在項目中使用多線程,所以很少有人會關(guān)注這個問題。單例 Bean 存在線程問題,主要是因為當(dāng)多個線程操作同一個對象的時候是存在資源競爭的。
常見的有兩種解決辦法:
- 在 Bean 中盡量避免定義可變的成員變量。
- 在類中定義一個
ThreadLocal
成員變量,將需要的可變成員變量保存在ThreadLocal
中(推薦的一種方式)。
不過,大部分 Bean 實際都是無狀態(tài)(沒有實例變量)的(比如 Dao、Service),這種情況下, Bean 是線程安全的。
Bean 的生命周期了解么?
- Bean 容器找到配置文件中 Spring Bean 的定義。
- Bean 容器利用 Java Reflection API 創(chuàng)建一個 Bean 的實例。
- 如果涉及到一些屬性值 利用
set()
方法設(shè)置一些屬性值。 - 如果 Bean 實現(xiàn)了
BeanNameAware
接口,調(diào)用setBeanName()
方法,傳入 Bean 的名字。 - 如果 Bean 實現(xiàn)了
BeanClassLoaderAware
接口,調(diào)用setBeanClassLoader()
方法,傳入ClassLoader
對象的實例。 - 如果 Bean 實現(xiàn)了
BeanFactoryAware
接口,調(diào)用setBeanFactory()
方法,傳入BeanFactory
對象的實例。 - 與上面的類似,如果實現(xiàn)了其他
*.Aware
接口,就調(diào)用相應(yīng)的方法。 - 如果有和加載這個 Bean 的 Spring 容器相關(guān)的
BeanPostProcessor
對象,執(zhí)行postProcessBeforeInitialization()
方法 - 如果 Bean 實現(xiàn)了
InitializingBean
接口,執(zhí)行afterPropertiesSet()
方法。 - 如果 Bean 在配置文件中的定義包含 init-method 屬性,執(zhí)行指定的方法。
- 如果有和加載這個 Bean 的 Spring 容器相關(guān)的
BeanPostProcessor
對象,執(zhí)行postProcessAfterInitialization()
方法 - 當(dāng)要銷毀 Bean 的時候,如果 Bean 實現(xiàn)了
DisposableBean
接口,執(zhí)行destroy()
方法。 - 當(dāng)要銷毀 Bean 的時候,如果 Bean 在配置文件中的定義包含 destroy-method 屬性,執(zhí)行指定的方法。
4. Spring AoP
談?wù)勛约簩τ?AOP 的了解
AOP(Aspect-Oriented Programming:面向切面編程)能夠?qū)⒛切┡c業(yè)務(wù)無關(guān),卻為業(yè)務(wù)模塊所共同調(diào)用的邏輯或責(zé)任(例如事務(wù)處理、日志管理、權(quán)限控制等)封裝起來,便于減少系統(tǒng)的重復(fù)代碼,降低模塊間的耦合度,并有利于未來的可拓展性和可維護性。
Spring AOP 就是基于動態(tài)代理的,如果要代理的對象,實現(xiàn)了某個接口,那么 Spring AOP 會使用 JDK Proxy,去創(chuàng)建代理對象,而對于沒有實現(xiàn)接口的對象,就無法使用 JDK Proxy 去進行代理了,這時候 Spring AOP 會使用 Cglib生成一個被代理對象的子類來作為代理,如下圖所示:
SpringAOPProcess
當(dāng)然你也可以使用 AspectJ !Spring AOP 已經(jīng)集成了 AspectJ ,AspectJ 應(yīng)該算的上是 Java 生態(tài)系統(tǒng)中最完整的 AOP 框架了。
AOP 切面編程設(shè)計到的一些專業(yè)術(shù)語:
術(shù)語 | 含義 |
---|---|
目標(biāo)(Target) | 被通知的對象 |
代理(Proxy) | 向目標(biāo)對象應(yīng)用通知之后創(chuàng)建的代理對象 |
連接點(JoinPoint) | 目標(biāo)對象的所屬類中,定義的所有方法均為連接點 |
切入點(Pointcut) | 被切面攔截 / 增強的連接點(切入點一定是連接點,連接點不一定是切入點) |
通知(Advice) | 增強的邏輯 / 代碼,也即攔截到目標(biāo)對象的連接點之后要做的事情 |
切面(Aspect) | 切入點(Pointcut)+通知(Advice) |
Weaving(織入) | 將通知應(yīng)用到目標(biāo)對象,進而生成代理對象的過程動作 |
Spring AOP 和 AspectJ AOP 有什么區(qū)別?
Spring AOP 屬于運行時增強,而 AspectJ 是編譯時增強。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字節(jié)碼操作(Bytecode Manipulation)。
Spring AOP 已經(jīng)集成了 AspectJ ,AspectJ 應(yīng)該算的上是 Java 生態(tài)系統(tǒng)中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加強大,但是 Spring AOP 相對來說更簡單,
如果我們的切面比較少,那么兩者性能差異不大。但是,當(dāng)切面太多的話,最好選擇 AspectJ ,它比 Spring AOP 快很多。
AspectJ 定義的通知類型有哪些?
- Before(前置通知):目標(biāo)對象的方法調(diào)用之前觸發(fā)
- After (后置通知):目標(biāo)對象的方法調(diào)用之后觸發(fā)
- AfterReturning(返回通知):目標(biāo)對象的方法調(diào)用完成,在返回結(jié)果值之后觸發(fā)
- AfterThrowing(異常通知) :目標(biāo)對象的方法運行中拋出 / 觸發(fā)異常后觸發(fā)。AfterReturning 和 AfterThrowing 兩者互斥。如果方法調(diào)用成功無異常,則會有返回值;如果方法拋出了異常,則不會有返回值。
- Around:(環(huán)繞通知)編程式控制目標(biāo)對象的方法調(diào)用。環(huán)繞通知是所有通知類型中可操作范圍最大的一種,因為它可以直接拿到目標(biāo)對象,以及要執(zhí)行的方法,所以環(huán)繞通知可以任意的在目標(biāo)對象的方法調(diào)用前后搞事,甚至不調(diào)用目標(biāo)對象的方法
多個切面的執(zhí)行順序如何控制?
1、通常使用@Order
注解直接定義切面順序
// 值越小優(yōu)先級越高
@Order(3)
@Component
@Aspect
public class LoggingAspect implements Ordered {
2、實現(xiàn)Ordered
接口重寫 getOrder
方法。
@Component
@Aspect
public class LoggingAspect implements Ordered {
// ....
@Override
public int getOrder() {
// 返回值越小優(yōu)先級越高
return 1;
}
}
5. Spring MVC
MVC 是模型(Model)、視圖(View)、控制器(Controller)的簡寫,其核心思想是通過將業(yè)務(wù)邏輯、數(shù)據(jù)、顯示分離來組織代碼。
想要真正理解 Spring MVC,我們先來看看 Model 1 和 Model 2 這兩個沒有 Spring MVC 的時代。
Model 1 時代
很多學(xué) Java 后端比較晚的朋友可能并沒有接觸過 Model 1 時代下的 JavaWeb 應(yīng)用開發(fā)。在 Model1 模式下,整個 Web 應(yīng)用幾乎全部用 JSP 頁面組成,只用少量的 JavaBean 來處理數(shù)據(jù)庫連接、訪問等操作。
這個模式下 JSP 即是控制層(Controller)又是表現(xiàn)層(View)。顯而易見,這種模式存在很多問題。比如控制邏輯和表現(xiàn)邏輯混雜在一起,導(dǎo)致代碼重用率極低;再比如前端和后端相互依賴,難以進行測試維護并且開發(fā)效率極低。
mvc-mode1
Model 2 時代
學(xué)過 Servlet 并做過相關(guān) Demo 的朋友應(yīng)該了解“Java Bean(Model)+ JSP(View)+Servlet(Controller) ”這種開發(fā)模式,這就是早期的 JavaWeb MVC 開發(fā)模式。
- Model:系統(tǒng)涉及的數(shù)據(jù),也就是 dao 和 bean。
- View:展示模型中的數(shù)據(jù),只是用來展示。
- Controller:處理用戶請求都發(fā)送給 ,返回數(shù)據(jù)給 JSP 并展示給用戶。
Model2 模式下還存在很多問題,Model2 的抽象和封裝程度還遠(yuǎn)遠(yuǎn)不夠,使用 Model2 進行開發(fā)時不可避免地會重復(fù)造輪子,這就大大降低了程序的可維護性和復(fù)用性。
于是,很多 JavaWeb 開發(fā)相關(guān)的 MVC 框架應(yīng)運而生比如 Struts2,但是 Struts2 比較笨重。
Spring MVC 時代
隨著 Spring 輕量級開發(fā)框架的流行,Spring 生態(tài)圈出現(xiàn)了 Spring MVC 框架, Spring MVC 是當(dāng)前最優(yōu)秀的 MVC 框架。相比于 Struts2 , Spring MVC 使用更加簡單和方便,開發(fā)效率更高,并且 Spring MVC 運行速度更快。
MVC 是一種設(shè)計模式,Spring MVC 是一款很優(yōu)秀的 MVC 框架。Spring MVC 可以幫助我們進行更簡潔的 Web 層的開發(fā),并且它天生與 Spring 框架集成。Spring MVC 下我們一般把后端項目分為 Service 層(處理業(yè)務(wù))、Dao 層(數(shù)據(jù)庫操作)、Entity 層(實體類)、Controller 層(控制層,返回數(shù)據(jù)給前臺頁面)。
Spring MVC 的核心組件有哪些?
記住了下面這些組件,也就記住了 SpringMVC 的工作原理。
-
DispatcherServlet
:核心的中央處理器,負(fù)責(zé)接收請求、分發(fā),并給予客戶端響應(yīng)。 -
HandlerMapping
:處理器映射器,根據(jù) uri 去匹配查找能處理的Handler
,并會將請求涉及到的攔截器和Handler
一起封裝。 -
HandlerAdapter
:處理器適配器,根據(jù)HandlerMapping
找到的Handler
,適配執(zhí)行對應(yīng)的Handler
; -
Handler
:請求處理器,處理實際請求的處理器。 -
ViewResolver
:視圖解析器,根據(jù)Handler
返回的邏輯視圖 / 視圖,解析并渲染真正的視圖,并傳遞給DispatcherServlet
響應(yīng)客戶端
SpringMVC 工作原理了解嗎?
Spring MVC 原理如下圖所示:
SpringMVC 工作原理的圖解我沒有自己畫,直接圖省事在網(wǎng)上找了一個非常清晰直觀的,原出處不明。
流程說明(重要):
- 客戶端(瀏覽器)發(fā)送請求,
DispatcherServlet
攔截請求。 -
DispatcherServlet
根據(jù)請求信息調(diào)用HandlerMapping
。HandlerMapping
根據(jù) uri 去匹配查找能處理的Handler
(也就是我們平常說的Controller
控制器) ,并會將請求涉及到的攔截器和Handler
一起封裝。 -
DispatcherServlet
調(diào)用HandlerAdapter
適配執(zhí)行Handler
。 -
Handler
完成對用戶請求的處理后,會返回一個ModelAndView
對象給DispatcherServlet
,ModelAndView
顧名思義,包含了數(shù)據(jù)模型以及相應(yīng)的視圖的信息。Model
是返回的數(shù)據(jù)對象,View
是個邏輯上的View
。 -
ViewResolver
會根據(jù)邏輯View
查找實際的View
。 -
DispaterServlet
把返回的Model
傳給View
(視圖渲染)。 - 把
View
返回給請求者(瀏覽器)
統(tǒng)一異常處理怎么做?
推薦使用注解的方式統(tǒng)一異常處理,具體會使用到 @ControllerAdvice
+ @ExceptionHandler
這兩個注解 。
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
@ExceptionHandler(BaseException.class)
public ResponseEntity<?> handleAppException(BaseException ex, HttpServletRequest request) {
//......
}
@ExceptionHandler(value = ResourceNotFoundException.class)
public ResponseEntity<ErrorReponse> handleResourceNotFoundException(ResourceNotFoundException ex, HttpServletRequest request) {
//......
}
}
這種異常處理方式下,會給所有或者指定的 Controller
織入異常處理的邏輯(AOP),當(dāng) Controller
中的方法拋出異常的時候,由被@ExceptionHandler
注解修飾的方法進行處理。
ExceptionHandlerMethodResolver
中 getMappedMethod
方法決定了異常具體被哪個被 @ExceptionHandler
注解修飾的方法處理異常。
@Nullable
private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
List<Class<? extends Throwable>> matches = new ArrayList<>();
//找到可以處理的所有異常信息。mappedMethods 中存放了異常和處理異常的方法的對應(yīng)關(guān)系
for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
if (mappedException.isAssignableFrom(exceptionType)) {
matches.add(mappedException);
}
}
// 不為空說明有方法處理異常
if (!matches.isEmpty()) {
// 按照匹配程度從小到大排序
matches.sort(new ExceptionDepthComparator(exceptionType));
// 返回處理異常的方法
return this.mappedMethods.get(matches.get(0));
}
else {
return null;
}
}
從源代碼看出:getMappedMethod()
會首先找到可以匹配處理異常的所有方法信息,然后對其進行從小到大的排序,最后取最小的那一個匹配的方法(即匹配度最高的那個)。
6. Spring 框架中用到了哪些設(shè)計模式?
-
工廠設(shè)計模式 : Spring 使用工廠模式通過
BeanFactory
、ApplicationContext
創(chuàng)建 bean 對象。 - 代理設(shè)計模式 : Spring AOP 功能的實現(xiàn)。
- 單例設(shè)計模式 : Spring 中的 Bean 默認(rèn)都是單例的。
-
模板方法模式 : Spring 中
jdbcTemplate
、hibernateTemplate
等以 Template 結(jié)尾的對數(shù)據(jù)庫操作的類,它們就使用到了模板模式。 - 包裝器設(shè)計模式 : 我們的項目需要連接多個數(shù)據(jù)庫,而且不同的客戶在每次訪問中根據(jù)需要會去訪問不同的數(shù)據(jù)庫。這種模式讓我們可以根據(jù)客戶的需求能夠動態(tài)切換不同的數(shù)據(jù)源。
- 觀察者模式: Spring 事件驅(qū)動模型就是觀察者模式很經(jīng)典的一個應(yīng)用。
-
適配器模式 : Spring AOP 的增強或通知(Advice)使用到了適配器模式、spring MVC 中也是用到了適配器模式適配
Controller
。 - …
7. Spring 事務(wù)
Spring/SpringBoot 模塊下專門有一篇是講 Spring 事務(wù)的,總結(jié)的非常詳細(xì),通俗易懂。
Spring 管理事務(wù)的方式有幾種?
-
編程式事務(wù) :在代碼中硬編碼(不推薦使用) : 通過
TransactionTemplate
或者TransactionManager
手動管理事務(wù),實際應(yīng)用中很少使用,但是對于你理解 Spring 事務(wù)管理原理有幫助。 -
聲明式事務(wù) :在 XML 配置文件中配置或者直接基于注解(推薦使用) : 實際是通過 AOP 實現(xiàn)(基于
@Transactional
的全注解方式使用最多)
Spring 事務(wù)中哪幾種事務(wù)傳播行為?
事務(wù)傳播行為是為了解決業(yè)務(wù)層方法之間互相調(diào)用的事務(wù)問題。
當(dāng)事務(wù)方法被另一個事務(wù)方法調(diào)用時,必須指定事務(wù)應(yīng)該如何傳播。例如:方法可能繼續(xù)在現(xiàn)有事務(wù)中運行,也可能開啟一個新事務(wù),并在自己的事務(wù)中運行。
正確的事務(wù)傳播行為可能的值如下:
1.TransactionDefinition.PROPAGATION_REQUIRED
使用的最多的一個事務(wù)傳播行為,我們平時經(jīng)常使用的@Transactional
注解默認(rèn)使用就是這個事務(wù)傳播行為。如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒有事務(wù),則創(chuàng)建一個新的事務(wù)。
2.TransactionDefinition.PROPAGATION_REQUIRES_NEW
創(chuàng)建一個新的事務(wù),如果當(dāng)前存在事務(wù),則把當(dāng)前事務(wù)掛起。也就是說不管外部方法是否開啟事務(wù),Propagation.REQUIRES_NEW
修飾的內(nèi)部方法會新開啟自己的事務(wù),且開啟的事務(wù)相互獨立,互不干擾。
3.TransactionDefinition.PROPAGATION_NESTED
如果當(dāng)前存在事務(wù),則創(chuàng)建一個事務(wù)作為當(dāng)前事務(wù)的嵌套事務(wù)來運行;如果當(dāng)前沒有事務(wù),則該取值等價于TransactionDefinition.PROPAGATION_REQUIRED
。
4.TransactionDefinition.PROPAGATION_MANDATORY
如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒有事務(wù),則拋出異常。(mandatory:強制性)
這個使用的很少。
若是錯誤的配置以下 3 種事務(wù)傳播行為,事務(wù)將不會發(fā)生回滾:
-
TransactionDefinition.PROPAGATION_SUPPORTS
: 如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前沒有事務(wù),則以非事務(wù)的方式繼續(xù)運行。 -
TransactionDefinition.PROPAGATION_NOT_SUPPORTED
: 以非事務(wù)方式運行,如果當(dāng)前存在事務(wù),則把當(dāng)前事務(wù)掛起。 -
TransactionDefinition.PROPAGATION_NEVER
: 以非事務(wù)方式運行,如果當(dāng)前存在事務(wù),則拋出異常。
Spring 事務(wù)中的隔離級別有哪幾種?
和事務(wù)傳播行為這塊一樣,為了方便使用,Spring 也相應(yīng)地定義了一個枚舉類:Isolation
public enum Isolation {
DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),
READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),
READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),
REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),
SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);
private final int value;
Isolation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
下面我依次對每一種事務(wù)隔離級別進行介紹:
-
TransactionDefinition.ISOLATION_DEFAULT
:使用后端數(shù)據(jù)庫默認(rèn)的隔離級別,MySQL 默認(rèn)采用的REPEATABLE_READ
隔離級別 Oracle 默認(rèn)采用的READ_COMMITTED
隔離級別. -
TransactionDefinition.ISOLATION_READ_UNCOMMITTED
:最低的隔離級別,使用這個隔離級別很少,因為它允許讀取尚未提交的數(shù)據(jù)變更,可能會導(dǎo)致臟讀、幻讀或不可重復(fù)讀 -
TransactionDefinition.ISOLATION_READ_COMMITTED
: 允許讀取并發(fā)事務(wù)已經(jīng)提交的數(shù)據(jù),可以阻止臟讀,但是幻讀或不可重復(fù)讀仍有可能發(fā)生 -
TransactionDefinition.ISOLATION_REPEATABLE_READ
: 對同一字段的多次讀取結(jié)果都是一致的,除非數(shù)據(jù)是被本身事務(wù)自己所修改,可以阻止臟讀和不可重復(fù)讀,但幻讀仍有可能發(fā)生。 -
TransactionDefinition.ISOLATION_SERIALIZABLE
: 最高的隔離級別,完全服從 ACID 的隔離級別。所有的事務(wù)依次逐個執(zhí)行,這樣事務(wù)之間就完全不可能產(chǎn)生干擾,也就是說,該級別可以防止臟讀、不可重復(fù)讀以及幻讀。但是這將嚴(yán)重影響程序的性能。通常情況下也不會用到該級別。
@Transactional(rollbackFor = Exception.class)注解了解嗎?
Exception
分為運行時異常 RuntimeException
和非運行時異常。事務(wù)管理對于企業(yè)應(yīng)用來說是至關(guān)重要的,即使出現(xiàn)異常情況,它也可以保證數(shù)據(jù)的一致性。
當(dāng) @Transactional
注解作用于類上時,該類的所有 public 方法將都具有該類型的事務(wù)屬性,同時,我們也可以在方法級別使用該標(biāo)注來覆蓋類級別的定義。如果類或者方法加了這個注解,那么這個類里面的方法拋出異常,就會回滾,數(shù)據(jù)庫里面的數(shù)據(jù)也會回滾。
在 @Transactional
注解中如果不配置rollbackFor
屬性,那么事務(wù)只會在遇到RuntimeException
的時候才會回滾,加上 rollbackFor=Exception.class
,可以讓事務(wù)在遇到非運行時異常時也回滾。
8. Spring Data JPA
如何使用 JPA 在數(shù)據(jù)庫中非持久化一個字段?
假如我們有下面一個類:
@Entity(name="USER")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "ID")
private Long id;
@Column(name="USER_NAME")
private String userName;
@Column(name="PASSWORD")
private String password;
private String secrect;
}
如果我們想讓secrect
這個字段不被持久化,也就是不被數(shù)據(jù)庫存儲怎么辦?我們可以采用下面幾種方法:
static String transient1; // not persistent because of static
final String transient2 = "Satish"; // not persistent because of final
transient String transient3; // not persistent because of transient
@Transient
String transient4; // not persistent because of @Transient
一般使用后面兩種方式比較多,我個人使用注解的方式比較多。
JPA 的審計功能是做什么的?有什么用?
審計功能主要是幫助我們記錄數(shù)據(jù)庫操作的具體行為比如某條記錄是誰創(chuàng)建的、什么時間創(chuàng)建的、最后修改人是誰、最后修改時間是什么時候。
@Data
@AllArgsConstructor
@NoArgsConstructor
@MappedSuperclass
@EntityListeners(value = AuditingEntityListener.class)
public abstract class AbstractAuditBase {
@CreatedDate
@Column(updatable = false)
@JsonIgnore
private Instant createdAt;
@LastModifiedDate
@JsonIgnore
private Instant updatedAt;
@CreatedBy
@Column(updatable = false)
@JsonIgnore
private String createdBy;
@LastModifiedBy
@JsonIgnore
private String updatedBy;
}
-
@CreatedDate
: 表示該字段為創(chuàng)建時間字段,在這個實體被 insert 的時候,會設(shè)置值 -
@CreatedBy
:表示該字段為創(chuàng)建人,在這個實體被 insert 的時候,會設(shè)置值@LastModifiedDate
、@LastModifiedBy
同理。
實體之間的關(guān)聯(lián)關(guān)系注解有哪些?
-
@OneToOne
: 一對一。 -
@ManyToMany
:多對多。 -
@OneToMany
: 一對多。 -
@ManyToOne
:多對一。
利用 @ManyToOne
和 @OneToMany
也可以表達多對多的關(guān)聯(lián)關(guān)系
9. Spring Security
有哪些控制請求訪問權(quán)限的方法?
-
permitAll()
:無條件允許任何形式訪問,不管你登錄還是沒有登錄。 -
anonymous()
:允許匿名訪問,也就是沒有登錄才可以訪問。 -
denyAll()
:無條件決絕任何形式的訪問。 -
authenticated()
:只允許已認(rèn)證的用戶訪問。 -
fullyAuthenticated()
:只允許已經(jīng)登錄或者通過 remember-me 登錄的用戶訪問。 -
hasRole(String)
: 只允許指定的角色訪問。 -
hasAnyRole(String)
: 指定一個或者多個角色,滿足其一的用戶即可訪問。 -
hasAuthority(String)
:只允許具有指定權(quán)限的用戶訪問 -
hasAnyAuthority(String)
:指定一個或者多個權(quán)限,滿足其一的用戶即可訪問。 -
hasIpAddress(String)
: 只允許指定 ip 的用戶訪問。
如何對密碼進行加密?
如果我們需要保存密碼這類敏感數(shù)據(jù)到數(shù)據(jù)庫的話,需要先加密再保存。
Spring Security 提供了多種加密算法的實現(xiàn),開箱即用,非常方便。這些加密算法實現(xiàn)類的父類是 PasswordEncoder
,如果你想要自己實現(xiàn)一個加密算法的話,也需要繼承 PasswordEncoder
。
PasswordEncoder
接口一共也就 3 個必須實現(xiàn)的方法。
public interface PasswordEncoder {
// 加密也就是對原始密碼進行編碼
String encode(CharSequence var1);
// 比對原始密碼和數(shù)據(jù)庫中保存的密碼
boolean matches(CharSequence var1, String var2);
// 判斷加密密碼是否需要再次進行加密,默認(rèn)返回 false
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
官方推薦使用基于 bcrypt 強哈希函數(shù)的加密算法實現(xiàn)類。
如何優(yōu)雅更換系統(tǒng)使用的加密算法?
如果我們在開發(fā)過程中,突然發(fā)現(xiàn)現(xiàn)有的加密算法無法滿足我們的需求,需要更換成另外一個加密算法,這個時候應(yīng)該怎么辦呢?
推薦的做法是通過 DelegatingPasswordEncoder
兼容多種不同的密碼加密方案,以適應(yīng)不同的業(yè)務(wù)需求。文章來源:http://www.zghlxwxcb.cn/news/detail-838252.html
從名字也能看出來,DelegatingPasswordEncoder
其實就是一個代理類,并非是一種全新的加密算法,它做的事情就是代理上面提到的加密算法實現(xiàn)類。在 Spring Security 5.0 之后,默認(rèn)就是基于 DelegatingPasswordEncoder
進行密碼加密的。文章來源地址http://www.zghlxwxcb.cn/news/detail-838252.html
- 為了降低Java開發(fā)的復(fù)雜性,Spring采取了以下4種關(guān)鍵策略
- 基于POJO的輕量級和最小侵入性編程;
- 通過依賴注入和面向接口實現(xiàn)松耦合;
- 基于切面和慣例進行聲明式編程;
- 通過切面和模板減少樣板式代碼
CSRF攻擊是什么,怎么預(yù)防
- CSRF 代表跨站請求偽造。這是一種攻擊,迫使最終用戶在當(dāng)前通過身份驗證的 Web 應(yīng)用程序上執(zhí)行不需要的操作。CSRF 攻擊專門針對狀態(tài)改變請求,而不是數(shù)據(jù)竊取,因為攻擊者無法查看對偽造請求的響應(yīng)。
spring boot的配置文件
Spring循環(huán)依賴問題怎么產(chǎn)生的,怎么解決的
到了這里,關(guān)于JAVA后端開發(fā)面試基礎(chǔ)知識(八)——Spring的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!