IOC是指將對象的創(chuàng)建和依賴關系的管理交給Spring容器來處理。
IOC控制反轉(zhuǎn)通常通過依賴注入來實現(xiàn),這可以通過XML配置或者注解來完成。
IOC可以幫助開發(fā)者減少代碼的復雜性,提高模塊之間的解耦,使得代碼更加靈活和可維護。
AOP允許開發(fā)者將橫切關注點(如日志、事務管理等)與業(yè)務邏輯分離,從而提供更好的模塊化。
在Spring中,AOP可以通過動態(tài)代理或者字節(jié)碼操作來實現(xiàn),常用的是動態(tài)代理。
AOP可以提高代碼的重用性,使得橫切關注點的修改更加集中和方便。
Spring AOP(Aspect Oriented Programming)的工作流程主要涉及到面向切面編程的核心概念,如切面、連接點和通知等。以下是Spring AOP的基本工作流程:
Spring AOP 是 Spring Framework 提供的一種 AOP 實現(xiàn)方式,是基于動態(tài)代理實現(xiàn)的。它允許在方法調(diào)用前、方法調(diào)用后、拋出異常時等切點進行切入通知,并通過動態(tài)代理技術織入切面。Spring AOP 只能在方法級別上生效。
AspectJ AOP 是一個獨立的 AOP 框架, 是基于字節(jié)碼操作實現(xiàn)的。它提供了比 Spring AOP 更為強大和靈活的 AOP 功能。AspectJ 可以在方法調(diào)用前、方法調(diào)用后、拋出異常時等切點進行切入通知,并且還支持構(gòu)造器、字段、對象初始化和異常處理等更多切點。AspectJ 可以通過編譯器織入(AspectJ編譯器),也可以使用代理織入(在運行時為目標對象創(chuàng)建代理,稱為 Spring AOP 的 AspectJ 代理模式)。
簡而言之,如果需要更強大的功能和更好的性能,可以選擇使用 AspectJ AOP;如果只需要簡單的切面編程并且希望保持代碼的簡單性,可以選擇使用 Spring AOP。
Spring AOP 提供了兩種代理方式:JDK 動態(tài)代理和 CGLIB 動態(tài)代理。
基于接口的代理,要求目標類實現(xiàn)一個接口,通過接口生成代理對象。這種方式的優(yōu)點是性能較好,但缺點是只能代理接口,不能代理類。
基于類的代理,不要求目標類實現(xiàn)接口,直接對類進行代理。這種方式的優(yōu)點是可以代理類和接口,但缺點是性能相對較差。
相對于 CGLIB 動態(tài)代理,JDK 動態(tài)代理的性能較好,因為它是基于接口的代理,生成的代理類較少,使用起來比較簡單,運行時開銷較小。
與 JDK 動態(tài)代理相比,CGLIB 動態(tài)代理不要求目標類必須實現(xiàn)接口,因此更加靈活,但代理對象的創(chuàng)建過程相對更為耗時。
Spring的依賴注入(Dependency Injection,簡稱DI)是通過Java的反射機制實現(xiàn)的。在Spring中,你可以使用XML配置文件或注解的方式,定義Bean之間的依賴關系,然后由Spring容器在運行時將這些依賴關系注入到相應的Bean中。
在所有這些方式中,Spring都使用了Java的反射機制來動態(tài)地創(chuàng)建Bean的實例,并設置其屬性或調(diào)用其構(gòu)造函數(shù)。反射機制允許Spring在運行時獲取類的信息,包括類的構(gòu)造函數(shù)、方法、屬性等,然后根據(jù)配置信息動態(tài)地創(chuàng)建和配置Bean。
通過編寫 XML 文件來配置 Spring 應用程序的組件、依賴項和行為。
使用注解(如 @Component, @Autowired, @Configuration 等)來配置應用程序的部分或全部組件,以及它們之間的依賴關系
使用純 Java 代碼配置 Spring 應用程序的組件、依賴項和行為,不需要 XML 文件。通常使用 @Configuration 和 @Bean 注解來實現(xiàn)。
當您使用FileSystemXmlApplicationContext來加載XML配置文件時,雖然您可以成功地創(chuàng)建和管理Bean,但是這些Bean不會被自動注入到Spring容器中進行托管。因此,當您嘗試使用@Autowired注解來獲取這些Bean時,Spring容器無法找到這些Bean并將它們注入到目標對象中。
解決方法是將通過FileSystemXmlApplicationContext加載的Bean手動注冊到Spring容器中。
具體步驟如下:
FileSystemXmlApplicationContext是Spring框架中的一個類,它用于從文件系統(tǒng)加載XML配置文件并創(chuàng)建應用程序上下文。它是AbstractApplicationContext的一個具體實現(xiàn),專門用于處理XML配置文件。
當你有一個XML配置文件,并且希望在啟動應用程序時立即加載它時,可以使用FileSystemXmlApplicationContext。
如果你的應用程序需要從文件系統(tǒng)中加載多個XML配置文件,你可以使用FileSystemXmlApplicationContext來加載它們。
public static void main(String[] args) { // 創(chuàng)建一個FileSystemXmlApplicationContext實例,加載XML配置文件 ApplicationContext context = new FileSystemXmlApplicationContext("conf/config.xml"); // 從ApplicationContext中獲取bean UserService userService = (UserService) context.getBean("userService"); // 調(diào)用bean的方法 userService.execute();}
在這個例子中,我們首先創(chuàng)建了一個FileSystemXmlApplicationContext實例,并指定了XML配置文件的路徑。然后,我們從ApplicationContext中獲取了一個名為"userService"的bean,并調(diào)用了它的execute()方法。
FileSystemXmlApplicationContext和ClassPathXmlApplicationContext都是Spring容器在加載XML配置文件時使用的接口實現(xiàn)類,它們之間的主要區(qū)別在于加載配置文件的方式和路徑。
FileSystemXmlApplicationContext是通過文件系統(tǒng)加載XML配置文件的方式來初始化Spring容器。
ClassPathXmlApplicationContext是通過類路徑(class path)加載XML配置文件的方式來初始化Spring容器。
ApplicationContextAware 是 Spring 框架中的一個接口,它允許實現(xiàn)該接口的類能夠訪問到當前的 ApplicationContext。換句話說,任何實現(xiàn)了 ApplicationContextAware 接口的類都可以獲得Spring容器中的bean引用。
public class MyService implements ApplicationContextAware { private ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; }}
如果需要精確注入某個特定名稱的bean,那么可以選擇@Resource,因為它會根據(jù)名稱進行匹配。
如果需要在多個相同類型的bean中選擇一個進行注入,那么可以選擇@Autowired,并結(jié)合@Qualifier注解來指定具體的bean。
在Spring框架中,Bean的作用域定義了Bean實例的生命周期和可見范圍。Spring框架提供了以下五種主要的Bean作用域:
這些作用域之間的主要區(qū)別在于Bean實例的生命周期、創(chuàng)建方式以及可見范圍。選擇合適的Bean作用域取決于應用程序的需求和設計。
作用域規(guī)定了bean實例的生命周期和在容器中的存儲方式。作用域確定了bean實例的創(chuàng)建、初始化和銷毀方式,以及在應用程序中使用這些bean的方式。
Spring內(nèi)部bean是指在另一個bean的內(nèi)部定義的bean。這些內(nèi)部bean的作用域受限于包含它們的外部bean,因此它們不能被應用程序的其他部分所訪問。內(nèi)部bean適合于那些只在外部bean中使用的小型、私有的bean。在XML配置文件中,內(nèi)部bean通常作為外部bean的屬性進行定義。
package com; public class Customer { private Person person;} class Person{ private int id; private String name; private int age;}
<bean id="CustomerBean" class="com.Customer"> <property name="person"> <bean class="com.person"> <property name="id" value=1 /> <property name="name" value="哪吒編程" /> <property name="age" value=18 /> </bean> </property></bean>
在這個例子中,"person"是一個內(nèi)部bean,它被嵌套在"CustomerBean"的屬性中。內(nèi)部bean的生命周期受外部bean控制,并且外部bean銷毀時,內(nèi)部bean也會被銷毀。 inner beans的使用可以幫助簡化配置文件,并在外部bean范圍內(nèi)限制bean的可見性。
Spring 框架本身并不保證單例Beans的線程安全性,因為線程安全性通常取決于Bean的實現(xiàn)方式,而不是容器本身。因此,開發(fā)者需要自行確保他們的單例Beans是線程安全的。
有幾種常見的策略可以幫助確保單例Beans的線程安全性:
總之,雖然Spring框架本身不保證單例Beans的線程安全性,但開發(fā)者可以通過上述策略來確保他們的Bean在多線程環(huán)境中能夠正常工作。
Spring Bean的自動裝配是指Spring容器根據(jù)預先定義的規(guī)則,自動在Spring應用程序上下文中將Bean與其他Bean進行關聯(lián)。這樣可以避免手動配置Bean之間的依賴關系,從而簡化了應用程序的配置。
Spring提供了以下幾種自動裝配的模式:
使用自動裝配可以減少配置工作,并且更易于維護。然而,過度依賴自動裝配也可能導致代碼不夠清晰,因此需要根據(jù)具體情況進行合理的選擇。
FileSystemResource 是從文件系統(tǒng)路徑加載文件,而 ClassPathResource 是從類路徑(classpath)中加載文件。
FileSystemResource 適用于加載本地文件系統(tǒng)中的文件,而 ClassPathResource 適用于加載應用程序內(nèi)部的資源文件,如配置文件、模板等。
FileSystemResource 需要提供文件的絕對路徑或相對路徑,而 ClassPathResource 只需要提供資源文件的相對路徑即可。
在XML配置文件中定義一個util:properties元素來創(chuàng)建一個Properties對象,然后將其注入到Bean中。
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd"> <!-- 定義Properties --> <util:properties id="myProperties" location="classpath:my-properties.properties"/> <!-- 注入Properties到Bean --> <bean id="myBean" class="com.example.MyBean"> <property name="properties" ref="myProperties"/> </bean> </beans>
如果你正在使用Java配置或者希望直接在代碼中使用注解,那么可以使用@Value注解來注入Properties。但請注意,@Value注解主要用于注入單個屬性值,而不是整個Properties對象。然而,你可以通過Spring的@ConfigurationProperties注解或者@PropertySource和Environment類來注入整個Properties對象。
BeanFactory是Spring框架中的一個核心接口,它主要用于管理和提供應用程序中的Bean實例。BeanFactory接口定義了Spring容器的基本規(guī)范和行為,它提供了一種機制來將配置文件中定義的Bean實例化、配置和管理起來。它負責實例化、定位、配置應用程序中的對象及建立這些對象間的依賴。BeanFactory的主要作用是提供Bean的創(chuàng)建、配置、初始化和銷毀等基本操作,它可以根據(jù)配置文件或注解來創(chuàng)建并管理Bean實例,并提供了各種方法來獲取和操作Bean實例。
FactoryBean接口定義了一種創(chuàng)建Bean的方式,它允許開發(fā)人員在Bean的創(chuàng)建過程中進行更多的自定義操作。通過實現(xiàn)FactoryBean接口,開發(fā)人員可以創(chuàng)建復雜的Bean實例,或者在Bean實例化之前進行一些額外的邏輯處理。
ApplicationContext則是BeanFactory的子接口,它提供了比BeanFactory更完整的功能。除了繼承BeanFactory的所有功能外,ApplicationContext還提供了國際化、資源文件訪問、監(jiān)聽器注冊等功能。
BeanFactory 是延遲初始化,即在真正需要使用到某個 Bean 時才會創(chuàng)建該 Bean。ApplicationContext 則是在容器啟動時就會預先加載所有的 Bean,所以它是預初始化。
BeanFactory 的注冊必須要在配置文件中指定,而 ApplicationContext 可以通過注解的方式自動掃描并注冊 Bean。
ApplicationContext 會管理 Bean 的完整生命周期,包括創(chuàng)建、初始化、銷毀等過程。而 BeanFactory 則只負責創(chuàng)建和管理 Bean 實例,不會對 Bean 進行生命周期管理。
Spring框架中有三級緩存。
Spring中最基本的緩存,用于存放完全初始化并確定類型的Bean實例。當一個Bean被創(chuàng)建并完成所有的初始化過程后,它會被轉(zhuǎn)移到這個緩存中。這個緩存是對外提供服務的主要緩存,當我們通過Spring獲取一個Bean時,首先會從這個緩存中查找是否有現(xiàn)成的對象。
存放的是已經(jīng)實例化但未初始化的bean。
保證了在多次循環(huán)依賴時,同一個類只會被構(gòu)建一次,從而確保了單例性質(zhì)。
用于存儲用于創(chuàng)建單例bean的ObjectFactory。
當依賴的bean實例創(chuàng)建完成后,Spring會使用這個ObjectFactory來創(chuàng)建bean實例,并從三級緩存中移除。
三級緩存的主要作用是解決循環(huán)依賴問題,特別是當涉及到AOP代理時。通過將代理的bean或普通bean提前暴露,使得依賴注入成為可能。
先了解一下什么是循環(huán)依賴?
當兩個或多個bean相互依賴,形成一個閉環(huán)時,就發(fā)生了循環(huán)依賴。
二級緩存存儲了尚未完成初始化的bean實例。當Spring檢測到循環(huán)依賴時,它可以將正在創(chuàng)建的bean放入二級緩存中,以便其他bean可以引用它。然而,二級緩存并不能解決所有循環(huán)依賴問題,特別是當涉及到AOP代理時。
AOP(面向切面編程)是Spring框架的一個重要特性,它允許開發(fā)者在不修改現(xiàn)有代碼的情況下,為應用程序添加新的行為。Spring AOP通常通過創(chuàng)建代理對象來實現(xiàn)這一點,這些代理對象在運行時增強目標對象的功能。
在循環(huán)依賴的場景中,如果涉及的bean需要被AOP代理,那么僅僅使用二級緩存是不夠的。因為二級緩存中的bean可能還沒有被AOP框架處理過,也就是說,它們可能還不是最終的代理對象。如果其他bean引用了這些未處理的bean,就會導致錯誤。
三級緩存就是為了解決這個問題而引入的。它存儲的不是實際的bean實例,而是創(chuàng)建這些bean的工廠對象(ObjectFactory)。當Spring檢測到循環(huán)依賴時,它會將ObjectFactory放入三級緩存中。這個工廠對象知道如何創(chuàng)建和(如果需要的話)代理目標bean。一旦循環(huán)依賴被解決,Spring就可以使用這個工廠對象來創(chuàng)建和返回最終的bean實例。
通過這種方式,三級緩存不僅解決了普通的循環(huán)依賴問題,還解決了涉及AOP代理的復雜循環(huán)依賴問題。它允許Spring在bean完全初始化(包括AOP代理)之前暴露引用,從而打破了循環(huán)依賴的限制。
因此,雖然二級緩存可以解決一些循環(huán)依賴問題,但三級緩存提供了更強大和靈活的解決方案,特別是當涉及到AOP代理時。
Spring中的事務傳播行為定義了七種類型,分別是:
PROPAGATION_REQUIRED:如果當前存在事務,則加入該事務;如果當前沒有事務,則創(chuàng)建一個新的事務。這是最常見的選擇。
PROPAGATION_SUPPORTS:支持當前事務,如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務方式執(zhí)行操作。
PROPAGATION_MANDATORY:支持當前事務,如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常。
PROPAGATION_REQUIRES_NEW:創(chuàng)建一個新的事務,如果當前存在事務,則把當前事務掛起。
PROPAGATION_NOT_SUPPORTED:以非事務方式執(zhí)行操作,如果當前存在事務,就把當前事務掛起。
PROPAGATION_NEVER:以非事務方式執(zhí)行,如果當前存在事務,則拋出異常。
PROPAGATION_NESTED:如果當前存在事務,則在嵌套事務內(nèi)執(zhí)行。如果當前沒有事務,則與PROPAGATION_REQUIRED類似。
這些傳播行為可以通過@Transactional注解的propagation屬性來設置,用于控制業(yè)務方法的事務行為。
開發(fā)者如果對@Transactional注解的工作原理和使用方式理解不深入,或者在使用時存在誤解,也可能導致事務失效。
在Spring中,如果@Transactional注解添加在不是public修飾的方法上,事務就會失效。因為Spring的事務是通過AOP代理實現(xiàn)的,而AOP代理需要目標方法能夠被外部訪問,所以只有public方法才能被代理。
如果事務方法所在的類沒有被加載到Spring IoC容器中,即該類沒有被Spring管理,那么Spring就無法實現(xiàn)代理,從而導致事務失效。
如果事務方法拋出的異常被catch處理,那么@Transactional注解無法感知到異常,因此無法回滾事務,導致事務失效。
如果內(nèi)部方法的事務傳播類型被配置為不支持事務的傳播類型,那么該方法的事務在Spring中就會失效。
如果在事務方法中調(diào)用了異步方法,那么異步方法中的事務可能會失效。
Spring Batch是一個輕量級、全功能且可擴展的開源批處理框架,用于處理大量數(shù)據(jù)操作的需求。它允許開發(fā)人員定義并運行大規(guī)模的批處理作業(yè),涵蓋了各種需求,包括數(shù)據(jù)遷移、ETL操作(抽取、轉(zhuǎn)換、加載)、報表生成等。
Spring Batch提供了豐富的功能,包括錯誤處理、事務管理、并發(fā)處理、監(jiān)控和跟蹤等,使得開發(fā)人員能夠輕松構(gòu)建可靠、高性能的批處理作業(yè)。通過使用配置簡單和易于擴展的Spring Batch框架,可以減少開發(fā)成本和時間,并且易于維護。
Spring Batch框架由多個重要組件組成,包括Job、Step、ItemReader、ItemProcessor和ItemWriter等。開發(fā)人員可以使用這些組件來定義和配置批處理作業(yè),以滿足特定的需求。同時,Spring Batch提供了豐富的支持,能夠與其他Spring框架(如Spring Boot、Spring Integration)及其他企業(yè)級技術(如JDBC、JMS、Hadoop、NoSQL數(shù)據(jù)庫)集成,進一步提升了批處理作業(yè)的靈活性和適用性。
在Spring中實現(xiàn)定時任務,可以使用@Scheduled注解。以下是一個簡單的示例:
首先,在Spring配置文件中開啟定時任務支持,添加task:annotation-driven/標簽:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd"> <!-- 開啟定時任務支持 --> <task:annotation-driven/></beans>
創(chuàng)建一個定時任務類,并在需要執(zhí)行定時任務的方法上添加@Scheduled注解:
import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;@Componentpublic class MyTask { // 每隔5秒執(zhí)行一次 @Scheduled(fixedRate = 5000) public void doTask() { System.out.println("執(zhí)行定時任務"); }}
@Required注解是Spring框架中的注解之一,用于標記Bean的屬性在配置時必須進行注入的。當在Bean配置中使用了@Required注解標記的屬性時,Spring在初始化Bean時會檢查這些屬性是否被正確注入,如果未被注入,則會拋出BeanInitializationException異常。
在Spring 5.x及更高版本中,@Required注解已經(jīng)被廢棄,因為它依賴于一些不推薦使用的特性。
推薦使用Java配置或XML配置中的required="true"屬性來指定必需的屬性。
推薦使用JSR-330中的@Inject或者Spring的@Autowired注解來代替@Required。這些注解在實現(xiàn)依賴注入時更加靈活,并且更容易用于各種場合。
以下是Spring框架中常用的一些注解及其應用場景:
@Component:用于聲明一個通用的組件,是其他注解的基礎。 @Controller:用于標記API層,即Web控制器。 @Service:用于標記業(yè)務邏輯層。 @Repository:用于標記數(shù)據(jù)訪問層,通常用于DAO實現(xiàn)類。 @Autowired:用于自動裝配Bean,可以按類型自動注入依賴。 @Resource:按照名稱自動裝配,與JNDI查找相關。 @Bean:標注在方法上,表示該方法返回的對象應該被注冊為Spring容器中的Bean。 @Configuration:表明該類是一個配置類,可以包含@Bean注解的方法。 @ComponentScan:用于指定Spring容器啟動時掃描的包路徑,以便發(fā)現(xiàn)并注冊帶有特定注解的類。 @Value:用于將外部屬性值注入到Bean中。 @Qualifier:與@Autowired一起使用,用于指定需要裝配的Bean的名稱。 @Scope:用于指定Bean的作用域,如singleton(單例)或prototype(原型)。 @Primary:用于指定當有多個相同類型的Bean時,優(yōu)先選擇哪個Bean進行裝配。 @Transactional:用于聲明事務管理,可以標注在類或方法上。 @RequestMapping:用于映射Web請求到特定的處理方法。 @GetMapping、@PostMapping、@PutMapping、@DeleteMapping、@PatchMapping:分別用于處理HTTP的GET、POST、PUT、DELETE和PATCH請求。 @RequestBody:用于將請求體中的JSON數(shù)據(jù)綁定到方法參數(shù)。 @ResponseBody:用于將方法返回值寫入響應體。 @PathVariable:用于從URL模板變量中提取參數(shù)。 @RequestParam:用于將請求參數(shù)綁定到方法參數(shù)。 @RequestHeader:用于將請求頭信息綁定到方法參數(shù)。 @CookieValue:用于將Cookie信息綁定到方法參數(shù)。 @SessionAttribute:用于從會話中獲取屬性值。 @RequestAttribute:用于從請求中獲取屬性值。 @ModelAttribute:用于在控制器方法執(zhí)行前添加模型屬性。 @ExceptionHandler:用于處理方法中拋出的異常。 @ControllerAdvice:用于定義全局的異常處理類。 @RestController:是@Controller和@ResponseBody的組合注解,用于RESTful Web服務。 @CrossOrigin:用于支持跨域請求。 @MatrixVariable:用于處理矩陣變量。
在REST風格的請求中,核心概念包括資源(Resource)和RESTful API(Application Programming Interface)。資源可以簡單理解為URI,表示一個網(wǎng)絡實體,具有唯一標識符。客戶端通過訪問這些標識符來對資源進行操作。RESTful API則是使用HTTP協(xié)議的不同方法來實現(xiàn)與資源的交互。
客戶端與服務器之間的交互通過HTTP協(xié)議進行,客戶端不需要知道服務器的實現(xiàn)細節(jié),只需要遵循HTTP協(xié)議。
在會話中生成一個唯一的Token,并將其嵌入到表單中。當表單被提交時,服務器檢查該Token是否有效,并確保其只被使用一次。 一旦處理完請求,服務器應廢棄該Token。
用戶點擊提交按鈕后,立即將其禁用,以防止用戶多次點擊。
提交成功后,將用戶重定向到一個新的頁面或消息提示頁,這樣用戶就無法再次點擊提交按鈕了。
對表單提交設置時間間隔限制,例如不允許在一分鐘內(nèi)連續(xù)提交。
對于敏感操作,要求用戶輸入驗證碼,這可以有效防止機器人或惡意軟件的自動提交。
如果重復提交會導致數(shù)據(jù)庫中的數(shù)據(jù)沖突,可以在數(shù)據(jù)庫字段上設置唯一性約束。
簡單工廠模式的本質(zhì)就是一個工廠類根據(jù)傳入的參數(shù),動態(tài)的決定實例化哪個類。
Spring中的BeanFactory就是簡單工廠模式的體現(xiàn),根據(jù)傳入一個唯一的標識來獲得bean對象。
應用程序?qū)ο蟮膭?chuàng)建及初始化職責交給工廠對象,工廠Bean。
定義工廠方法,然后通過config.xml配置文件,將其納入Spring容器來管理,需要通過factory-method指定靜態(tài)方法名稱。
Spring用的是雙重判斷加鎖的單例模式,通過getSingleton方法從singletonObjects中獲取bean。
Spring的AOP中,使用的Advice(通知)來增強被代理類的功能。Spring實現(xiàn)AOP功能的原理就是代理模式(① JDK動態(tài)代理,② CGLIB字節(jié)碼生成技術代理。)對類進行方法級別的切面增強。
裝飾器模式:動態(tài)的給一個對象添加一些額外的功能。
Spring的ApplicationContext中配置所有的DataSource。這些DataSource可能是不同的數(shù)據(jù)庫,然后SessionFactory根據(jù)用戶的每次請求,將DataSource設置成不同的數(shù)據(jù)源,以達到切換數(shù)據(jù)源的目的。
在Spring中有兩種表現(xiàn):
一種是類名中含有Wrapper,另一種是類名中含有Decorator。
定義對象間的一對多的關系,當一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都得到通知并自動更新。
Spring中觀察者模式一般用在listener的實現(xiàn)。
策略模式是行為性模式,調(diào)用不同的方法,適應行為的變化 ,強調(diào)父類的調(diào)用子類的特性 。
getHandler是HandlerMapping接口中的唯一方法,用于根據(jù)請求找到匹配的處理器。
Spring JdbcTemplate的query方法總體結(jié)構(gòu)是一個模板方法+回調(diào)函數(shù),query方法中調(diào)用的execute()是一個模板方法,而預期的回調(diào)doInStatement(Statement state)方法也是一個模板方法。
在Spring中,可以使用XML配置或者注解來注入一個Java Collection。
<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"> <bean id="stringList" class="java.util.ArrayList"> <constructor-arg> <list> <value>Item 1</value> <value>Item 2</value> <value>Item 3</value> </list> </constructor-arg> </bean></beans>
@Configurationpublic class AppConfig { @Bean public List<String> stringList() { return Arrays.asList("Item 1", "Item 2", "Item 3"); }}
在上面的示例中,我們使用@Configuration注解標注了一個配置類,并且在配置類中定義了一個返回List類型的方法,通過@Bean注解來將List實例注入Spring容器。
Spring使用一個內(nèi)部的任務調(diào)度器(TaskScheduler)來管理所有被 @Scheduled 注解的方法。
當Spring容器啟動時,它會自動掃描所有的Bean,找到被 @Scheduled 注解的方法,并將它們注冊到TaskScheduler中。
TaskScheduler會按照注解中指定的時間間隔或表達式來自動調(diào)用這些方法。
@Scheduled注解可以根據(jù)配置使用多線程執(zhí)行,發(fā)生異常時,根據(jù)配置決定是重試還是直接跳過。
深入一點:
Spring MVC的執(zhí)行流程遵循了前端控制器模式,通過DispatcherServlet協(xié)調(diào)各個組件來接收請求、匹配處理器、執(zhí)行處理器、渲染視圖的過程,實現(xiàn)了請求到視圖的端到端處理。
具體執(zhí)行流程如下:
Spring MVC是Spring框架的一個模塊,用于構(gòu)建基于MVC(Model-View-Controller)設計模式的Web應用程序。在Spring MVC中,有九大組件是非常重要的,它們分別是:
這九大組件共同構(gòu)成了Spring MVC的完整流程:
首先,DispatcherServlet接收用戶請求,然后通過HandlerMapping找到對應的Controller,再通過HandlerAdapter調(diào)用Controller的方法,方法返回的ModelAndView包含了模型數(shù)據(jù)和視圖信息,最后通過ViewResolver解析視圖,并通過View展示模型數(shù)據(jù)。在這個過程中,還可以通過LocaleResolver來解析用戶的語言和區(qū)域設置,以實現(xiàn)國際化。
@Controller:用于標識一個類為Spring MVC的控制器,處理客戶端的請求并返回相應的視圖。
@RequestMapping:用于將URL路徑映射到特定的處理方法,可用于類級別或方法級別,用于定義請求URL和處理請求的方法。
@ResponseBody:用于將方法的返回值直接作為HTTP Response的主體內(nèi)容返回,通常用于返回 JSON 或 XML 格式的數(shù)據(jù)。
@PathVariable:用于將請求URL中的模板變量映射到處理方法的參數(shù)中。
@RequestParam:用于將請求參數(shù)映射到處理方法的參數(shù)中,可以指定參數(shù)名稱、是否必須等。
@RequestParamMap:用于將所有的請求參數(shù)映射為一個Map類型的參數(shù)。
@ModelAttribute:用于將請求參數(shù)綁定到Model對象中,通常用于在視圖渲染之前填充模型數(shù)據(jù)。
@SessionAttributes:用于指定處理方法的返回值需要存儲到會話中的屬性值。
@InitBinder:用于定制數(shù)據(jù)綁定規(guī)則和初始化數(shù)據(jù)綁定規(guī)則。
@Validated:用于標記其后的方法參數(shù)需要進行驗證。
Spring MVC是基于Servlet API構(gòu)建的,而Struts2是通過Filter實現(xiàn)的。這意味著Spring MVC的入口是一個Servlet,而Struts2的入口是一個Filter。這導致了兩者在處理請求時的不同機制。
Spring MVC 的攔截器機制通過 HandlerInterceptor 接口進行實現(xiàn)。通過實現(xiàn)這個接口,你可以在處理程序執(zhí)行的前后插入邏輯,可以在請求處理之前或之后對請求進行預處理或后處理。攔截器可以用來實現(xiàn)日志記錄、權限校驗、國際化、主題切換等各種需求。
Struts 的攔截器機制是通過攔截器棧(interceptor stacks)實現(xiàn)的。在Struts2中,通過配置攔截器棧,可以在請求處理的各個階段插入預處理邏輯或后處理邏輯。攔截器可以用于日志記錄、權限校驗、異常處理、輸入驗證等。
Spring MVC 的配置更加靈活,可以使用 XML 配置、Java 注解或者 Java 類配置(JavaConfig)來配置控制器、視圖解析器等。
Struts 使用基于 XML 的配置文件來定義控制器(Action)、攔截器、結(jié)果視圖等。
Spring MVC強調(diào)依賴注入,通過Spring容器管理對象和組件,使得應用更加靈活和可測試。 Struts不提供像Spring那樣的依賴注入機制,更多地依賴于配置文件,因此在靈活性方面可能稍遜于Spring MVC。
由于Spring MVC的輕量級設計,其代碼量相對較少,運行時間也更快,因此在性能方面通常優(yōu)于Struts2。
@RestController和@Controller是Spring MVC中常用的注解,它們的主要區(qū)別在于返回值的不同:
@Controller:用于標識控制器類的注解。在Spring MVC中,使用@Controller注解標記的類表示該類是控制器,可以處理客戶端的請求,并返回相應的視圖。
@RestController:是Spring4.0引入的一個組合注解,用于標識RESTful風格的控制器類。它相當于@Controller和@ResponseBody的組合,表示該類處理RESTful請求,方法的返回值直接作為HTTP Response的主體內(nèi)容返回,而不是作為視圖進行解析。
因此,@RestController注解在處理HTTP請求時,會將方法的返回值直接轉(zhuǎn)換為JSON/XML等格式的數(shù)據(jù)返回給客戶端,而@Controller注解一般用于傳統(tǒng)的Web應用程序開發(fā),將方法的返回值解析為視圖進行渲染。
在Spring MVC中,請求路徑的匹配是通過DispatcherServlet和HandlerMapping來完成的。具體步驟如下:
在Spring MVC中,可以通過多種方式來配置請求路徑與Controller的映射關系,例如使用XML配置文件或者使用注解(如@RequestMapping)。
RequestMapping可以實現(xiàn)模糊匹配路徑,比如:
Spring MVC的執(zhí)行流程:
因此,DispatcherServlet(前端控制器)、HandlerMapping(處理器映射器)、Interceptors(攔截器)就是在調(diào)用controller接口前進行的操作。
最常見的方法,用于從請求參數(shù)中取值到控制器方法的參數(shù)上。
從URL路徑中提取參數(shù)時,可以使用@PathVariable注解。
在這個例子中,當發(fā)送一個GET請求到/users/123時,123會被解析為id參數(shù)的值。
@Controller public class MyController { @GetMapping("/users/{id}") public String getUser(@PathVariable Long id) { // 使用id參數(shù) return "user"; } }
當請求參數(shù)與對象屬性對應時,可以使用@ModelAttribute注解來綁定請求參數(shù)到JavaBean上。
當發(fā)送一個GET請求到/create?id=1&name=nezha&age=18時,請求參數(shù)會被綁定到User對象的屬性上。
@Controller public class MyController { @GetMapping("/create") public String create(@ModelAttribute User user) { // 使用user對象中的屬性 return "create"; } public static class User { private String id; private String name; private String age; // getters and setters } }
當請求體中包含JSON或XML數(shù)據(jù)時,可以使用@RequestBody注解來自動解析請求體并綁定到方法參數(shù)上。
還可以直接通過HttpServletRequest對象來獲取請求參數(shù),盡管這不是Spring MVC推薦的方式,因為它沒有利用Spring MVC的參數(shù)綁定特性。
在使用HttpServletRequest時,你需要自己處理參數(shù)的獲取和類型轉(zhuǎn)換。
@Controller public class MyController { @GetMapping("/oldWay") public String oldWay(HttpServletRequest request) { String query = request.getParameter("query"); // 使用query參數(shù) return "oldWay"; } }
#{}:被稱為占位符,在執(zhí)行SQL語句時,通過PreparedStatement對參數(shù)值進行預處理,MyBatis會將#{}替換為?,然后將參數(shù)值傳遞給SQL語句,這樣可以防止SQL注入攻擊。當使用#{}時,MyBatis會自動處理參數(shù)的轉(zhuǎn)義和引號等問題,確保參數(shù)作為一個完整的字符串被傳遞。
#{}主要用于替換 SQL 語句中的條件值,它不能直接用于替換 SQL 語句的片段或關鍵字,比如表名。
:稱為變量替換,使用{}時,它在SQL語句中直接替換成相應的參數(shù)值,它會將參數(shù)值直接拼接到SQL語句中,因此存在SQL注入的風險。
${}:可以替換 SQL 語句中的任何部分,包括列名、表名、SQL 關鍵字等。
在將參數(shù)傳遞給SQL語句之前,對參數(shù)值進行驗證,確保它們符合預期的格式或范圍。這可以通過正則表達式、字符串比較或其他邏輯來實現(xiàn)。
避免敏感操作,比如DROP、TRUNCATE 等。
對于可能引起注入的特殊字符,需要進行合適的轉(zhuǎn)義處理,比如對單引號、雙引號、分號等特殊字符進行轉(zhuǎn)義,防止它們被誤解為SQL命令的一部分。
確保數(shù)據(jù)庫用戶只有執(zhí)行必要操作的權限,避免給予過多的權限。這樣即使發(fā)生了 SQL 注入攻擊,攻擊者也只能執(zhí)行有限的操作。
記錄所有執(zhí)行的 SQL 語句,并監(jiān)控任何異常或可疑行為。這有助于及時發(fā)現(xiàn)并應對潛在的 SQL 注入攻擊。
MyBatis支持延遲加載,它允許在需要時動態(tài)地加載與某個對象關聯(lián)的數(shù)據(jù)。延遲加載可以幫助減少不必要的數(shù)據(jù)庫查詢,提高性能,并且提供了一種方便的方式來管理復雜對象之間的關聯(lián)關系。
延遲加載的原理是,在查詢主對象時,并不會立即加載關聯(lián)對象的信息,而是在真正需要使用這些關聯(lián)對象的時候再去發(fā)起對關聯(lián)對象的查詢。具體來說,延遲加載通常使用代理對象(Proxy)來實現(xiàn)。當主對象被查詢并加載到內(nèi)存中時,關聯(lián)對象并沒有被加載,而是創(chuàng)建一個代理對象來代替關聯(lián)對象的位置。當應用程序?qū)嶋H使用關聯(lián)對象的屬性或方法時,代理對象會攔截這些調(diào)用,并觸發(fā)對關聯(lián)對象數(shù)據(jù)的實際加載查詢,然后返回結(jié)果給應用程序。
在MyBatis中,延遲加載通常與二級緩存(二級緩存是一種全局性的緩存機制,可以跨多個會話對查詢進行緩存)結(jié)合使用,可以延遲加載對象的時候首先嘗試從二級緩存中獲取數(shù)據(jù),如果緩存中不存在再去查詢數(shù)據(jù)庫。
延遲加載可以提高查詢性能,特別是在處理大量數(shù)據(jù)或者復雜關聯(lián)查詢的時候。但是,它也會增加一些額外的內(nèi)存開銷,因為需要創(chuàng)建代理對象,并且在訪問數(shù)據(jù)時需要進行額外的數(shù)據(jù)庫查詢操作。因此,在使用延遲加載時需要根據(jù)具體的業(yè)務需求和性能要求進行權衡。
MyBatis的一級緩存是SqlSession級別的緩存,也就是說當我們執(zhí)行查詢之后,會將查詢的結(jié)果放在SqlSession的緩存中。
對于同一個SqlSession,當執(zhí)行相同的查詢時,MyBatis會先查看一級緩存中是否有相同的查詢結(jié)果,如果有,則直接返回緩存的結(jié)果,而不再去數(shù)據(jù)庫中查詢。
一級緩存是MyBatis默認開啟的,它可以減少對數(shù)據(jù)庫的訪問,提高查詢性能。
MyBatis的二級緩存是Mapper級別的緩存,它可以跨SqlSession共享緩存數(shù)據(jù)。
二級緩存是在Mapper的映射文件配置開啟的,我們可以在Mapper的映射文件中配置元素來開啟二級緩存。
使用二級緩存時,要確保查詢的 SQL 語句和參數(shù)是完全相同的,否則 MyBatis 會認為它們是不同的查詢,從而不會從二級緩存中獲取數(shù)據(jù)。
對于頻繁更新的數(shù)據(jù),不建議使用二級緩存,因為頻繁的更新會導致緩存失效,反而降低性能。
SimpleExecutor是MyBatis默認的執(zhí)行器,它對每個SQL語句的執(zhí)行進行了封裝,每次都會生成一個新的Statement對象,并執(zhí)行SQL語句,SimpleExecutor適用于短時、簡單的操作。
ReuseExecutor是一種復用的執(zhí)行器,它會在多次執(zhí)行相同SQL語句時重用Statement對象,從而減少了Statement對象的創(chuàng)建和銷毀,提升了性能。
BatchExecutor是一種執(zhí)行器,用于批量操作。當我們需要執(zhí)行批量的SQL語句時,可以使用BatchExecutor來提高性能。BatchExecutor通過快速執(zhí)行批量的SQL語句,來減少與數(shù)據(jù)庫的交互次數(shù),提高操作的效率。
Spring集成MyBatis時,執(zhí)行器的設置通常是在Spring的配置文件中進行的。你可以通過配置SqlSessionFactoryBean的executorType屬性來指定執(zhí)行器類型。例如:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="configLocation" value="classpath:mybatis-config.xml" /> <property name="executorType" value="REUSE"/> </bean>
這個設置會影響所有的SqlSession,但不建議全局修改執(zhí)行器類型,因為這可能會對性能產(chǎn)生不必要的影響。
當獲取SqlSession對象時,可以指定所需的執(zhí)行器類型。這種方式更加靈活,允許根據(jù)不同的操作需求選擇不同的執(zhí)行器。
SqlSession是MyBatis中用于執(zhí)行數(shù)據(jù)庫操作的核心接口,它提供了多種方法來執(zhí)行SQL語句和映射操作。
通過SqlSessionFactory獲取SqlSession對象時,可以配置執(zhí)行器的類型。
使用SqlSessionFactory的openSession(ExecutorType)方法來獲取指定類型的SqlSession。ExecutorType是一個枚舉類型,包含了MyBatis支持的三種執(zhí)行器:SIMPLE、REUSE和BATCH。
import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.Reader; Reader reader = Resources.getResourceAsReader("mybatis-config.xml"); // 創(chuàng)建SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); // 獲取指定執(zhí)行器類型的SqlSession // 使用SIMPLE執(zhí)行器 SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.SIMPLE); // 或者使用REUSE執(zhí)行器 // SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.REUSE); // 或者使用BATCH執(zhí)行器 // SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); try { // 使用sqlSession執(zhí)行數(shù)據(jù)庫操作... // ... } finally { // 關閉SqlSession sqlSession.close(); }
可以通過在映射文件中配置和標簽來定義相應的執(zhí)行器,在這些標簽中可以針對具體的SQL語句配置執(zhí)行器類型。
<select id="selectBlog" parameterType="int" resultType="Blog" statementType="PREPARED" useCache="true" flushCache="true"> SELECT * FROM BLOG WHERE ID = #{id}</select>
在上面的示例中,可以看到標簽指定了該查詢的執(zhí)行器類型為“PREPARED”,也就是預處理執(zhí)行器。
BatchExecutor 通過批處理的方式提高性能。它允許在 JDBC 客戶端緩存多條 SQL 語句,然后在緩存滿或手動刷新時,將這些語句打包一起發(fā)送到數(shù)據(jù)庫執(zhí)行。這種方式可以有效減少網(wǎng)絡通信次數(shù)和數(shù)據(jù)庫交互的開銷,從而提高系統(tǒng)性能。
網(wǎng)絡通信次數(shù):通過一次發(fā)送多條 SQL 語句,減少了與數(shù)據(jù)庫服務器之間的往返次數(shù),從而減少了網(wǎng)絡延遲的影響。數(shù)據(jù)庫交互開銷:數(shù)據(jù)庫在處理批量請求時,可以優(yōu)化執(zhí)行計劃,減少編譯和準備時間,進一步提高執(zhí)行效率。
(1)使用參數(shù)化的SQL語句 在編寫SQL語句時,應使用參數(shù)化的方式來構(gòu)建SQL,而不是將用戶輸入的值直接拼接到SQL字符串中。這樣可以確保用戶輸入的內(nèi)容不會被解釋為SQL命令。 #{}占位符會自動對輸入值進行轉(zhuǎn)義,可以有效防止SQL注入攻擊。
<select id="getUser" parameterType="string" resultType="User"> SELECT * FROM users WHERE username = #{username}</select>
(2)使用MyBatis的動態(tài)SQL MyBatis的動態(tài)SQL允許根據(jù)條件動態(tài)拼接SQL語句,可以在拼接SQL的過程中對用戶輸入的內(nèi)容進行轉(zhuǎn)義或其他處理,從而防止SQL注入攻擊。
<select id="getUsers" parameterType="map" resultType="User"> SELECT * FROM users <where> <if test="username != null"> AND username = #{username} </if> </where></select>
動態(tài) SQL 的主要作用在于,根據(jù)運行時不同的條件,動態(tài)地生成不同的 SQL 語句,從而實現(xiàn)更靈活、更高效的數(shù)據(jù)庫操作。
常見的動態(tài) SQL 標簽:
這些動態(tài) SQL 標簽大大增強了 MyBatis 的靈活性,使得開發(fā)者能夠根據(jù)不同的業(yè)務邏輯,動態(tài)地構(gòu)建 SQL 語句,從而提高了開發(fā)效率和代碼的可維護性。
在 MyBatis 的 XML 映射文件中,不同的 XML 映射文件之間的 ID 是可以重復的。因為每個 XML 映射文件都是獨立的,它們之間不會相互影響。但是在同一張 XML 映射文件中,每個元素中的 ID 必須是唯一的,不能重復。
當 MyBatis 接收到 Java 接口方法的調(diào)用時,它會首先查找是否有與該方法相關的 SQL 映射語句。如果有,MyBatis 會解析該 SQL 語句,并根據(jù)傳入的參數(shù)值動態(tài)地生成最終的 SQL 語句。然后,MyBatis 會執(zhí)行這個 SQL 語句,并將查詢結(jié)果映射回 Java 對象,最后返回給調(diào)用者。
在接口綁定中,MyBatis 提供了兩種實現(xiàn)方式:
在 MyBatis 的全局配置文件(如 mybatis-config.xml)中,通過元素引入 SQL 映射文件,將映射文件與 Java 接口關聯(lián)起來。
在 Java 接口方法上添加 @Select、@Insert、@Update、@Delete 等注解,直接編寫 SQL 語句。
public interface UserMapper { @Select("SELECT * FROM user WHERE id = #{id}") User getUserById(int id);}
當 SQL 語句中只有一個參數(shù)時,MyBatis 無需指定參數(shù)的類型或名稱,直接使用 #{} 來引用參數(shù)。MyBatis 會自動將這個參數(shù)綁定到 SQL 語句中。
① 使用順序:MyBatis 會按照參數(shù)的順序進行綁定,可以使用 #{param1}、#{param2} 等來引用參數(shù)。
<select id="selectUserByUsernameAndPassword" resultType="User"> SELECT * FROM user WHERE username = #{param1} AND password = #{param2} </select>
② 使用 @Param 注解:在 Mapper 接口的方法參數(shù)上添加 @Param 注解,為參數(shù)指定一個名稱,然后在 XML 中使用這個名稱來引用參數(shù)。
User selectUserByUsernameAndPassword(@Param("username") String username, @Param("password") String password);
<select id="selectUserByCriteria" resultType="User"> SELECT * FROM user WHERE username = #{username} AND age = #{age} </select>
③ 使用 Map 或 JavaBean:將多個參數(shù)封裝到一個 Map 或 JavaBean 中,然后在 XML 中引用 Map 的 key 或 JavaBean 的屬性。
User selectUserByCriteria(Map<String, Object> criteria);
<select id="selectUserByCriteria" resultType="User"> SELECT * FROM user WHERE username = #{username} AND age = #{age} </select>
對于復雜類型(如 JavaBean 或自定義類型),MyBatis 會自動映射這些類型的屬性到 SQL 語句中。只需要在 XML 中使用 #{} 引用這些屬性的名稱即可。
public class UserCriteria { private String username; private int age; // getters and setters }
<select id="selectUserByCriteria" resultType="User"> SELECT * FROM user WHERE username = #{username} AND age = #{age} </select>
對于結(jié)果集的映射,MyBatis 提供了 @Results 和 @Result 注解,用于在接口方法上直接定義結(jié)果集的映射關系,而無需編寫 XML 映射文件。這些注解可以指定如何將數(shù)據(jù)庫中的列映射到 Java 對象的屬性上。
<insert id="saveUserBatch" parameterClass="java.util.List"> insert all <iterate conjunction=" "> into USER ( ID, NAME, SAVE_DATE ) values <![CDATA[ ( #list[].id#, #list[].name#, SYSDATE ) ]]> </iterate> select * from dual</insert>
MyBatis:是一個半自動化的持久層框架,需要手動編寫SQL語句和ResultMap。MyBatis支持注解配置和XML配置,可以在XML文件中編寫動態(tài)SQL語句。
Hibernate:是一個全自動化的ORM框架,它會自動生成SQL語句,開發(fā)者無需關心SQL的生成與結(jié)果映射。
MyBatis:由于SQL語句是手動編寫的,所以可以對SQL進行精細的優(yōu)化,提高性能。但是,由于所有SQL都是依賴數(shù)據(jù)庫書寫的,所以MyBatis的擴展性、遷移性相對較差。
Hibernate:支持多種數(shù)據(jù)庫,通過配置可以方便地進行數(shù)據(jù)庫切換。Hibernate使用反射機制實現(xiàn)持久化對象操作,使得業(yè)務層與具體數(shù)據(jù)庫分開,降低了數(shù)據(jù)庫之間遷移的成本。
MyBatis:提供了一級緩存和二級緩存機制,但主要是依賴于開發(fā)者手動管理和配置。
Hibernate:提供了更為強大的緩存機制,包括一級緩存和二級緩存,并支持多種緩存策略,能夠顯著提高性能。
建議在需要支持多種數(shù)據(jù)庫兼容性和簡化持久層開發(fā)時使用Hibernate。
Hibernate作為一個對象關系映射(ORM)框架,它的優(yōu)勢在于能夠?qū)ava對象與數(shù)據(jù)庫表之間建立映射關系,從而使得開發(fā)者可以使用面向?qū)ο蟮姆绞竭M行數(shù)據(jù)庫操作,而無需直接編寫SQL語句。
Hibernate 使用 SLF4J 或其他日志框架(如 Log4j、Logback 等)進行日志記錄。你可以在 Hibernate 的配置中啟用 SQL 語句的日志記錄。
對于 Log4j,你可以在 log4j.properties 或 log4j.xml 文件中添加以下配置:
log4j.logger.org.hibernate.SQL=DEBUG log4j.logger.org.hibernate.type.descriptor.sql=TRACE
如果你使用的是 Logback,可以在 logback.xml 中添加:
<logger name="org.hibernate.SQL" level="DEBUG" /> <logger name="org.hibernate.type.descriptor.sql" level="TRACE" />
這樣,Hibernate 就會打印出執(zhí)行的 SQL 語句以及相關的綁定參數(shù)。
在 Hibernate 的配置文件中(如 hibernate.cfg.xml 或通過注解配置),你可以設置 show_sql 屬性為 true 來在控制臺打印 SQL 語句。但是,請注意,這只會打印 SQL 語句本身,不會顯示綁定的參數(shù)值。
format_sql 屬性為 true 來格式化打印的 SQL 語句,使其更易讀。
<hibernate-configuration> <session-factory> <!-- 其他配置 --> <property name="show_sql">true</property> <property name="format_sql">true</property> </session-factory> </hibernate-configuration>
HQL(Hibernate Query Language)是Hibernate專有的查詢語言,它類似于SQL,但操作的是實體對象而非數(shù)據(jù)庫表。HQL查詢可以更加直觀地進行對象之間的關聯(lián)查詢,同時減少對底層數(shù)據(jù)庫結(jié)構(gòu)的依賴。
OID(Object Identifier)是 Hibernate 中每個持久化對象的唯一標識符。OID 檢索是通過調(diào)用 get() 或 load() 方法來獲得一個持久化對象的方式。這兩個方法的區(qū)別在于當對象不存在時,get() 方法返回 null,而 load() 方法會拋出 ObjectNotFoundException 異常。
QBC(Query By Criteria)是一種以面向?qū)ο蟮姆绞綐?gòu)建查詢的方法。它使用Criteria API來構(gòu)建查詢條件,這種方式更加靈活,可以在編譯時檢查屬性和關聯(lián)的正確性。
這種方式利用實體類的內(nèi)部關聯(lián)進行查找,不需要通過特定的方法和工具。例如,如果有一個 User 對象和一個與之關聯(lián)的 Order 對象,可以通過調(diào)用 user.getOrders() 方法來獲得該 User 的所有訂單。
在某些情況下,開發(fā)者可能需要直接使用原生SQL語句進行查詢,以便利用特定數(shù)據(jù)庫的特性或編寫復雜的查詢。Hibernate通過session.createSQLQuery()方法支持這種查詢方式。
我們首先創(chuàng)建了一個 SessionFactory 實例,然后開啟一個 Session。我們構(gòu)建了一個 HQL 查詢字符串,該字符串指定了從 User 實體中選擇所有字段,并且 name 屬性等于指定的參數(shù)值。我們使用 session.createQuery() 方法來創(chuàng)建一個 Query 對象,并指定了查詢返回的結(jié)果類型為 User。接著,我們設置查詢參數(shù),并執(zhí)行查詢來獲取結(jié)果列表。
public class HQLQueryExample { public static void main(String[] args) { // 創(chuàng)建 SessionFactory,這通常是在應用啟動時完成的,而不是在每次查詢時 SessionFactory sessionFactory = new Configuration() .configure("hibernate.cfg.xml") // 加載 Hibernate 配置文件 .addAnnotatedClass(User.class) // 注冊實體類 .buildSessionFactory(); try (Session session = sessionFactory.openSession()) { // 開啟事務 session.beginTransaction(); // 創(chuàng)建 HQL 查詢 String hql = "FROM User WHERE name = :name"; // HQL 查詢,從 User 實體選擇所有字段 // 創(chuàng)建查詢對象 Query<User> query = session.createQuery(hql, User.class); // 設置參數(shù) query.setParameter("name", "nezha soft"); // 執(zhí)行查詢并獲取結(jié)果列表 List<User> users = query.getResultList(); // 處理查詢結(jié)果 for (User user : users) { System.out.println("User ID: " + user.getId() + ", Name: " + user.getName() + ", Email: " + user.getEmail()); } // 提交事務 session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); } finally { // 關閉 SessionFactory(通常在應用關閉時) sessionFactory.close(); } } }
@Entity @Table(name = "users") public class User { @Id private Long id; private String name; private String email; // 省略構(gòu)造器、getter 和 setter 方法 }
代碼與HQL查詢類似,下面是關鍵代碼:
// 使用 get() 方法進行 OID 檢索 User user = session.get(User.class, userId);
代碼與HQL查詢類似,下面是關鍵代碼:
// 創(chuàng)建 Criteria 對象 Criteria criteria = session.createCriteria(Employee.class); // 添加查詢條件 criteria.add(Restrictions.eq("firstName", "John")); criteria.add(Restrictions.gt("salary", 50000.0)); // 執(zhí)行查詢并獲取結(jié)果 List<Employee> employees = criteria.list();
Hibernate 的對象導航檢索是指通過已經(jīng)加載到內(nèi)存中的持久化對象來訪問其關聯(lián)的其他對象。這種檢索方式基于對象之間的關系,而不是直接通過數(shù)據(jù)庫查詢。
在上面的示例中,我們首先通過用戶的 ID 使用 session.get() 方法檢索用戶對象。然后,我們直接訪問用戶的 orders 屬性(這是一個 List),這是通過對象導航實現(xiàn)的。如果 orders 列表尚未加載到內(nèi)存中(例如,如果使用了延遲加載),那么可能需要顯式地初始化它,這通常通過 Hibernate 的 Hibernate.initialize() 方法或訪問列表中的元素來觸發(fā)。
請注意,對象導航檢索的性能取決于 FetchType 的設置以及是否啟用了延遲加載。如果 FetchType 設置為 LAZY(延遲加載),那么關聯(lián)的對象在首次訪問其屬性之前不會被加載。這可以減少初始加載時的數(shù)據(jù)庫交互次數(shù),但可能會增加后續(xù)訪問關聯(lián)對象時的數(shù)據(jù)庫交互次數(shù)。因此,在設計應用程序時,需要權衡初始加載時間和后續(xù)訪問性能。
首先,假設我們有兩個實體類:User 和 Order,它們之間存在一對多的關系,即一個用戶可以擁有多個訂單。
@Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY) private List<Order> orders; // 省略構(gòu)造器、getter 和 setter 方法 }
@Entity @Table(name = "orders") public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String orderNumber; private double amount; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") private User user; // 省略構(gòu)造器、getter 和 setter 方法 }
現(xiàn)在,我們將使用對象導航來檢索一個用戶及其關聯(lián)的訂單:
// 使用 OID 檢索獲取用戶對象 User user = session.get(User.class, userId); // 檢查用戶是否存在 if (user != null) { // 使用對象導航訪問用戶的訂單列表 if (user.getOrders() != null) { // 確保訂單列表已經(jīng)被加載或者沒有被延遲加載(取決于 FetchType) for (Order order : user.getOrders()) { ... } }}
代碼與HQL查詢類似,下面是關鍵代碼:
// 創(chuàng)建原生 SQL 查詢 String sql = "SELECT * FROM employees WHERE first_name = :firstName"; NativeQuery<Employee> nativeQuery = session.createNativeQuery(sql, Employee.class); nativeQuery.setParameter("firstName", "John"); // 執(zhí)行查詢并獲取結(jié)果列表 List<Employee> employees = nativeQuery.getResultList();
在Hibernate中,實體類可以被定義為final。然而,如果實體類被定義為final,那么Hibernate在進行一些代理和延遲加載等操作時可能會遇到一些限制。因為Hibernate通常會創(chuàng)建代理類來對實體類進行一些操作,而final類無法被繼承,所以可能會導致Hibernate無法生成有效的代理類或拋出異常。
通常情況下,建議不要將實體類定義為final,以免出現(xiàn)潛在的問題。如果需要限制對實體類的繼承,可以通過其他方式來實現(xiàn),如在類的設計中采用不可變對象模式、使用其他方式來限制繼承等。
使用對象類型Integer允許實體屬性值為null,而基礎數(shù)據(jù)類型int則不允許。
對于基本數(shù)據(jù)類型int,JVM直接在棧內(nèi)存中為其分配空間,因此訪問速度更快。Integer對象在堆內(nèi)存中分配空間,并通過引用訪問。雖然現(xiàn)代JVM的性能優(yōu)化已經(jīng)使得這種差異變得很小,但在處理大量數(shù)據(jù)或高并發(fā)場景下,基本數(shù)據(jù)類型的性能優(yōu)勢可能會更加明顯。
Spring Security 是實現(xiàn)權限驗證的常用框架,通常需要三張表來實現(xiàn)基本的權限驗證。
Spring Security提供了一套全面的安全服務,包括身份認證、權限授權等功能。在實現(xiàn)權限驗證時,通常會使用到該框架提供的多種機制和擴展點。例如,通過實現(xiàn)UserDetailsService接口并覆寫里面的用戶認證方法,可以自定義用戶的認證邏輯。此外,權限管理過程包括鑒權管理和授權管理,即判斷用戶是否有權訪問某個資源以及如何將權限分配給用戶。
對于權限驗證所需的表結(jié)構(gòu),RBAC(Role-Based Access Control,基于角色的訪問控制)模型是一個常見的設計模式。在這個模型中,通常至少需要三張表:用戶表、角色表、權限表。用戶表存儲用戶信息,角色表定義了不同的角色,而權限表則規(guī)定了不同角色可以執(zhí)行的操作。除此之外,還需要兩張關系表來維護用戶和角色之間、角色和權限之間的關系。這些表共同構(gòu)成了權限驗證的基礎數(shù)據(jù)結(jié)構(gòu)。
在設計和實施權限驗證系統(tǒng)時,開發(fā)者需要根據(jù)實際業(yè)務需求和技術選型來決定具體的實現(xiàn)方式和所需表結(jié)構(gòu)。
RBAC 的核心思想是將權限賦予角色,然后將角色賦予用戶。這種分離讓系統(tǒng)管理員能夠更容易地管理和控制資源的訪問權限。通過 RBAC,可以對用戶的權限進行集中管理,確保授權的一致性。 RBAC 主要包括三種基本權利:用戶、角色和權限。當用戶和角色是多對多的關系,當用戶角色變更時只需要對關系進行更改即可,簡化了權限管理工作。RBAC 通過模塊化的方式進行權限管理,可以減少權限管理的復雜性,提高系統(tǒng)的安全性。
這是RBAC的基礎模型。在RBAC0中,角色是權限的集合,用戶則被分配到這些角色中。用戶通過其角色獲得訪問資源的權限。RBAC0模型主要關注用戶、角色和權限之間的基本關系。
RBAC1在RBAC0的基礎上增加了角色繼承的概念。在RBAC1中,角色可以繼承其他角色的權限,形成一個角色層次結(jié)構(gòu)。這種繼承關系使得權限的管理更加靈活和方便,可以滿足更復雜的權限控制需求。
RBAC2是RBAC的擴展模型,引入了約束的概念。這些約束可以是靜態(tài)的,也可以是動態(tài)的,用于限制角色、權限和用戶之間的關聯(lián)關系。例如,可以設置約束來防止某個角色被賦予過多的權限,或者限制某個用戶只能被分配到特定的角色中。
RBAC3結(jié)合了RBAC1和RBAC2的特性,既支持角色繼承,又允許定義約束來限制權限的分配和使用。這使得RBAC3成為一個功能全面且高度可配置的權限管理模型。
在實現(xiàn)RBAC(基于角色的訪問控制)時,需要考慮以下因素:
Java RBAC攔截器權限控制的關鍵代碼如下:
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface Permission { String[] value() default {};}
public class PermissionInterceptor implements HandlerInterceptor { @Autowired private UserService userService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 獲取請求的URL String url = request.getRequestURI(); // 獲取當前登錄用戶的角色列表 List<String> roles = userService.getRolesByUsername(request.getRemoteUser()); // 判斷是否有訪問該URL的權限 if (hasPermission(url, roles)) { return true; } else { response.sendError(HttpServletResponse.SC_FORBIDDEN); return false; } } private boolean hasPermission(String url, List<String> roles) { // 根據(jù)URL和角色列表判斷是否有訪問權限 // 這里可以根據(jù)具體的業(yè)務邏輯進行判斷,例如查詢數(shù)據(jù)庫等 // 如果具有訪問權限,返回true;否則返回false return true; }}
@Configurationpublic class WebMvcConfig implements WebMvcConfigurer { @Autowired private PermissionInterceptor permissionInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(permissionInterceptor).addPathPatterns("/**"); }}
@RestControllerpublic class UserController { @Permission({"ROLE_ADMIN", "ROLE_USER"}) @GetMapping("/users") public List<User> getUsers() { // ... }}
當用戶訪問/users接口時,攔截器會根據(jù)用戶的角色列表判斷是否具有訪問權限。如果具有訪問權限,則允許訪問;否則返回403 Forbidden錯誤。
本文鏈接:http://www.tebozhan.com/showinfo-26-83628-0.html81道SSM經(jīng)典面試題總結(jié)
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。郵件:2376512515@qq.com