Spring IOC
什么是 IoC ?
IoC (Inversion of Control 控制反轉)是一種設計思想,而不是一個具體的技術實現。IoC 的思想就是將原本在程序中手動創建對象的控制權,交由給 Spring 框架來管理。
為什么叫控制反轉?
- 控制:指的是對象的創建(實例化,管理)的權利
- 反轉:控制權交給外部環境(Spring 框架、Ioc 容器)
????????將對象之間的相互依賴關系交給 IoC 容器來管理,并由 IoC 容器來完成對象的注入。這樣可以很大程度上簡化應用的開發,并做到解耦合,把應用從復雜的依賴關系中抽取出來,開發人員只需要關注對象的使用,而不需要關心對象是如何創建的。
? ? ? ? 在實際項目中,一個 Service 類可能依賴很多其他的類。假如我們需要實例化這個 Service ,每次都需要搞清楚這個 Service 所有底層類的構造函數。如果使用 IoC的話,只需要配置好Bean ,在需要的地方注入就行了,大大降低了項目的可維護性和開發難度。
什么是 Spring Bean?
可以簡單地理解為 Bean 指代的就是那些 被 IoC 容器所管理的對象。一般在 Spring Boot 中使用以下幾種注解聲明:
- Component : 通用注解,可以標注任意類為 Spring 組件。如果一個類不清楚分在那一層,可使用 @Component 注解標注
- @Repository:對應持久層 即 Dao 層,主要用于數據庫相關操作
- @Service:對應服務層,主要設計一些復雜的邏輯處理
- @Controller:對應 控制層 , 主要用于接收用戶的請求并調用 Service。
IoC 和 DI 有什么區別?IoC 可以理解為以中國控制反轉的設計思想,而 DI 可以理解為這種設計思想的具體實現方式
IoC 解決了什么問題?
- 對象之間的耦合度降低
- 資源管理變得容易
例如,一個針對 User 的操作,利用 Service 和 Dao 層進行開發
在沒有使用 IoC 思想的情況下,Service 層想要使用 Dao 層的話,需要通過 new 關鍵字在 UserServiceImpl 中手動 new 一個 UserDao 的具體實現類 UserDaoImpl。
但是,如果開發過程中出現新的需求,針對 UserDao 接口 開發出另一個 具體的實現類。由于 Service 層依賴了 UserDao 的具體實現,所以我們需要修改 UserServiceImpl 中 new 的對象。這就導致,如果我們的項目中很多地方都依賴于 UserDao 的實現類的話,就需要修改每一處,這就導致維護起來非常麻煩。
使用 IoC 的思想,我們將對象的控制權交給 Spring 容器管理后,我們在使用的時候直接向 IoC 容器 “要” 就行了。
Spring AOP
什么是 AOP?
AOP(Aspect Oriented Programming)即面向切面編程。
AOP 的目的是將橫切關注點(例如日志管理、事務管理、權限控制、接口控制)從核心業務邏輯中分離出來,通過動態代理,字節碼操作等技術,實現代碼的復用和解耦,提高代碼的可維護性和可擴展性。
OOP(面向對象編程)
AOP 和 OOP 其實并不沖突,兩者互補
OOP的目的是將業務邏輯按照對象的屬性和行為進行封裝,通過類、對象、繼承、多態等概念,實現代碼的模塊化和層次化,提高代碼的可讀性和可維護性
AOP 為什么叫面向切面編程?
因為 AOP 的核心思想是將橫切關注點從核心業務邏輯中分離出來,形成一個一個的切面。
- 橫切關注點:多個類或對象中的公共行為(如日志記錄、事務管理、權限控制、接口限流等)
- 切面(Aspect):對橫切關注點進行封裝的類,一個切面是一個類。切面可以定義多個通知,用來實現具體的功能
- 連接點(JoinPoint):連接點是方法調用或方法執行時的某個特定時刻(如方法調用、拋出異常等)
- 通知(Advice):通知就是切面在某個連接點要執行的操作。通知有五種類型分別是:前置通知(Before)、后置通知(After)、返回通知(AfterReturning)、異常通知(AfterThrowing)和 環繞通知(Around)。
- 切點(Pointcut):一個切點是一個表達式,它用來匹配哪些連接點需要被切面所增強。切點可以通過注解、正則表達式、邏輯運算等方式來定義。比如?
execution(* com.xyz.service..*(..))
匹配?com.xyz.service
?包及其子包下的類或接口。 - 織入(Weaving):織入是將切面和目標對象連接起來的過程,也就是將通知應用到切點匹配的連接點上。常見的織入時機有兩種,分別是編譯期織入(AspectJ)和運行期織入(AspectJ)
使用示例
AOP解決了什么問題?
OOP不能很好的處理一些分散在多個類或對象中的公共行為,這些行為通常被稱為 橫切關注點 。如果我們在每個類或者對象中都重復實現這些行為就會導致代碼的冗余、復雜和難以維護。
AOP 可以將橫切關注點 從 核心業務邏輯 中分離出來,實現關注點的分離。
比如說日志記錄,記錄每次訪問請求的參數和信息:
@Aspect
@Slf4j
@Component
@Order(0)
public class AopLog {private static final String START_TIME = "request-start";/***切入點* */@Pointcut("execution( * com.whgcdx.demo1.controller..*Controller.*(..))")public void log(){}/*** 環繞操作*/@Around("log()")public Object aroundLog(ProceedingJoinPoint point) throws Throwable {Object result = point.proceed();log.info("【返回值】:{}", JSON.toJSONString(result));return result;}/**** 前置操作*/@Before("log()")public void beforeLog(JoinPoint point){//利用 RequestContextHolder 獲取 HttpServletRequest 對象ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest httpServletRequest = servletRequestAttributes.getRequest();UserAgent userAgent = UserAgentUtil.parse(httpServletRequest.getHeader(Header.USER_AGENT.toString()));//重組請求信息StringBuffer sb = new StringBuffer();sb.append("---------------------------------收到請求-------------------------------------------");sb.append("\r\n【瀏覽器類型】: " + userAgent.getBrowser() + " 【版本號】: " + userAgent.getVersion());sb.append("\r\n【user-agent】: " + httpServletRequest.getHeader(Header.USER_AGENT.toString()));sb.append("\r\n【訪問URL】: " + httpServletRequest.getRequestURI());sb.append("\r\n【訪問Method】: " + httpServletRequest.getMethod());sb.append("\r\n【訪問IP】: " + httpServletRequest.getRemoteAddr());sb.append("\r\n【訪問類名】:"+ point.getSignature().getDeclaringTypeName() + ", 【訪問方法名】:" + point.getSignature().getName());Map<String ,String[]> paramerMap = httpServletRequest.getParameterMap();sb.append("\r\n【請求參數】: " + paramerMap);log.info(sb.toString());httpServletRequest.setAttribute(START_TIME,System.currentTimeMillis());}/*****/@After("log()")public void afterLog(){ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();Long start= (Long) request.getAttribute(START_TIME);Long end=System.currentTimeMillis();log.info("【請求耗時】:{}ms",end-start);String header=request.getHeader(Header.USER_AGENT.toString());UserAgent userAgent=UserAgentUtil.parse(header);log.info("【操作系統】:{},【原始User-Agent】:{}",userAgent.getOs().toString(),header);}}