循序漸進學 Spring (下):從注解、AOP到底層原理與整合實戰

文章目錄

    • 7. 自動裝配 (Autowiring)
      • 7.1 XML 自動裝配
      • 7.2 使用注解實現自動裝配
        • `@Autowired` vs `@Resource`
    • 8. 使用注解開發(完全體)
      • 8.1 定義 Bean (`@Component` 及其衍生注解)
      • 8.2 注入屬性 (`@Value`)
      • 8.3 注入對象
      • 8.4 定義作用域 (`@Scope`)
      • 8.5 小結:XML vs. 注解
    • 9. 使用 Java 配置 (JavaConfig)
    • 10、代理模式
      • 10.1 靜態代理
        • 角色分析
        • 代碼實現
        • 優缺點
      • 10.2 靜態代理再理解
        • 代碼實現
      • 10.3 動態代理
        • 代碼實現
      • 10.4 動態代理再理解
        • 通用處理器
        • 測試
        • 動態代理的好處
    • 11、AOP(TODO:未手敲)
      • 11.1 什么是AOP
      • 11.2 AOP在Spring中的作用
      • 11.3 使用Spring實現AOP
        • 1. 導入AOP依賴
        • 方式一:使用Spring原生API接口
        • 方式二:自定義類來實現AOP
        • 方式三:使用注解實現
        • **知識點:`<aop:aspectj-autoproxy/>`**
      • 11.4 三種實現 AOP 方式的對比
        • 相同點 (Core Principles)
        • 不同點 (Evolution & Style)
    • 12、整合MyBatis
      • 整合步驟概覽
        • 1. 導入Maven依賴
        • 2. 配置Maven靜態資源過濾
      • 12.1 回憶MyBatis
        • 1. 實體類 (User.java)
        • 2. MyBatis核心配置文件 (mybatis-config.xml)
        • 3. Mapper接口 (UserMapper.java)
        • 4. Mapper XML文件 (UserMapper.xml)
        • 5. 測試類
      • 12.2 MyBatis-Spring核心概念
      • 12.3 整合實現方式一:SqlSessionTemplate
        • 1. 創建Spring數據層配置文件 (spring-dao.xml)
        • 2. 創建Mapper實現類 (UserMapperImpl.java)
        • 3. 創建Spring主配置文件 (applicationContext.xml)
        • 4. 測試
      • 12.4 整合實現方式二:SqlSessionDaoSupport
        • 1. 修改Mapper實現類 (UserMapperImpl2.java)
        • 2. 修改Spring配置 (applicationContext.xml)
        • 3. 測試 (類似方式一)
    • 13、聲明式事務
      • 13.1 回顧事務
      • 13.2 事務失效場景測試
      • 13.3 Spring中的事務管理
        • 配置聲明式事務(XML方式)
        • 再次測試
    • 寫在最后
    • 參考

你好,我是 ZzzFatFish,歡迎回到我的 Spring 學習筆記。在 循序漸進學 Spring (上):從 IoC/DI 核心原理到 XML 配置實戰 中,我們深入探討了 Spring 的核心 IoC/DI 以及 XML 配置的各種方式。這次,我們將更進一步,探索如何讓 Spring 變得更“聰明”,從自動裝配開始,逐步走向現代化的注解和 Java 配置,并最終揭開 AOP 的神秘面紗,完成與 MyBatis 的整合實戰。

7. 自動裝配 (Autowiring)

自動裝配是 Spring IoC 容器的一項強大功能,它可以自動滿足 Bean 之間的依賴關系,減少 XML 中的顯式配置。

7.1 XML 自動裝配

通過在 <bean> 標簽上設置 autowire 屬性實現。

環境準備

public class Cat { public void shout() { System.out.println("喵~"); } }
public class Dog { public void shout() { System.out.println("汪~"); } }
public class People {private Cat cat;private Dog dog;private String name;// ... getters and setters and toString
}

beans.xml

<bean id="cat" class="com.github.subei.pojo.Cat"/>
<bean id="dog" class="com.github.subei.pojo.Dog"/><!-- 手動裝配 (傳統方式) -->
<bean id="peopleManual" class="com.github.subei.pojo.People"><property name="cat" ref="cat"/><property name="dog" ref="dog"/>
</bean><!-- 手動裝配 (傳統方式2) -->
<bean id="people" class="com.github.pojo.People" p:cat-ref="cat" p:dog-ref="dog"/><!-- 自動裝配 byName -->
<bean id="peopleByName" class="com.github.subei.pojo.People" autowire="byName"/><!-- 自動裝配 byType -->
<bean id="peopleByType" class="com.github.subei.pojo.People" autowire="byType"/>
  • autowire="byName": Spring 會在容器中查找 idPeople 類中屬性名cat, dog)相同的 Bean,并進行注入。
  • autowire="byType": Spring 會在容器中查找與 People 類中屬性類型Cat, Dog)相匹配的 Bean,并進行注入。
    • 注意:使用 byType 時,容器中同類型的 Bean 必須是唯一的,否則會拋出異常。
public class MyTest {@Testpublic void test(){ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");People people = context.getBean("people", People.class);people.getCat().shout();people.getDog().shout();}
}

7.2 使用注解實現自動裝配

注解是目前更主流的自動裝配方式,它將配置信息直接寫在 Java 類中,更為便捷。

步驟 1:開啟注解支持
在 XML 配置文件中,需要添加 context 命名空間,并開啟注解掃描。

<?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:context="http://www.springframework.org/schema/context"xsi:schemaLocation="..."><!-- 開啟基于注解的配置,它會激活 @Autowired, @Resource 等注解 --><context:annotation-config/><!-- 仍然需要將 Bean 定義在 XML 中 --><bean id="cat" class="com.github.subei.pojo.Cat"/><bean id="dog" class="com.github.subei.pojo.Dog"/><bean id="people" class="com.github.subei.pojo.People"/></beans>

步驟 2:在類中使用注解

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import javax.annotation.Resource;public class People {@Autowired // 1. 使用 @Autowired@Qualifier("cat") // 2. 當有多個同類型Bean時,用 @Qualifier 指定名稱private Cat cat;// @Autowired// private Dog dog;@Resource(name = "dog") // 3. 使用 @Resource (更推薦)private Dog dog;// ...
}
@Autowired vs @Resource

這是非常重要的一個區別點:

特性@Autowired (Spring 提供)@Resource (JSR-250, Java 標準)
裝配順序1. 按類型 (byType) 在容器中查找。1. 按名稱 (byName) 查找。
2. 如果找到多個,再按名稱 (byName) 查找。2. 如果按名稱找不到,再按類型 (byType) 查找。
3. 如果仍未確定,可配合 @Qualifier("beanId") 使用。3. 如果按類型也找不到或找到多個,則報錯。
依賴強依賴 Spring 框架。是 Java 標準,減少了對 Spring 的耦合。
常用場景簡單場景,或需要 @Qualifier 精準控制時。推薦使用,其裝配順序更符合直覺,更明確。
  • @Autowired(required = false):可以標注某個屬性不是必須的,如果容器中找不到對應的 Bean,該屬性為 null 而不會報錯。

觀點:有人認為“注解一時爽,維護火葬場”,是因為當項目龐大、依賴關系復雜時,注解分散在各個類中,不如 XML 集中管理來得清晰。但在現代開發中,注解的便利性已成為主流,配合良好的設計可以很好地管理。


8. 使用注解開發(完全體)

之前的注解只是用于自動裝配,Bean本身還是在XML中定義的。現在我們學習使用注解來定義Bean,從而可以完全替代XML。

步驟 1:開啟組件掃描
使用 <context:component-scan> 來代替 <context:annotation-config/>。它不僅會開啟注解支持,還會掃描指定包下的類,并將帶有特定注解的類自動注冊為 Bean。

<?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:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!--指定要掃描的包,Spring 會自動查找這個包及其子包下所有帶注解的類。這個標簽包含了 <context:annotation-config/> 的功能。
--><context:component-scan base-package="com.github.pojo"/></beans>

步驟 2:使用注解定義 Bean 及其屬性

8.1 定義 Bean (@Component 及其衍生注解)

  • @Component: 通用的組件注解,表示這個類是一個由 Spring 管理的 Bean。默認的 beanId 是類名首字母小寫(如 User -> user)。
    • @Component("customId") 可以自定義 beanId。

為了更好地標識分層架構,@Component 有三個衍生注解,它們在功能上完全相同,但語義更清晰:

  • @Repository: 用于標注數據訪問層 (DAO) 的組件。
  • @Service: 用于標注業務邏輯層 (Service) 的組件。
  • @Controller: 用于標注表現層 (Controller) 的組件。
import org.springframework.stereotype.Component;@Component // 等價于 <bean id="user" class="com.github.subei.pojo.User"/>
public class User {// 相當于  <property name="name" value="subeiLY"/>@Value("subeiLY")public String name;// ...
}

8.2 注入屬性 (@Value)

@Value 注解用于注入基本類型和 String 類型的值。

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;@Component
public class User {// 相當于 <property name="name" value="subeiLY"/>@Value("subeiLY")public String name;
}

8.3 注入對象

使用 @Autowired@Resource,與第 7 節相同。

8.4 定義作用域 (@Scope)

使用 @Scope 注解來定義 Bean 的作用域。

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;@Component
@Scope("prototype") // 等價于 <bean ... scope="prototype"/>
public class User { ... }

8.5 小結:XML vs. 注解

  • XML:配置集中,一目了然,適用于所有場景。但當 Bean 數量多時,會很繁瑣。
  • 注解:配置分散在代碼中,開發便捷。但對于第三方庫的類(我們無法修改源碼),則無法使用注解。

最佳實踐(混合使用)

  • XML:負責整體配置,如數據源、事務管理器、組件掃描 <context:component-scan> 等。
  • 注解:負責業務類(Controller, Service, DAO)的 Bean 定義和依賴注入。

9. 使用 Java 配置 (JavaConfig)

從 Spring 3.0 開始,官方提供了完全基于 Java 類來進行配置的方式,可以徹底擺脫 XML 文件。

步驟 1:創建配置類
配置類使用 @Configuration 標注,它本身也是一個 @Component

package com.github.subei.config;import com.github.subei.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;@Configuration // 聲明這是一個配置類,替代 beans.xml
@ComponentScan("com.github.subei.pojo") // 掃描組件,等同于 <context:component-scan>
@Import(AnotherConfig.class) // 導入其他配置類,等同于 <import>
public class MyConfig {// @Bean 注解表示這個方法將返回一個對象,該對象將被注冊為 Spring 容器中的 Bean。// 方法名 `getUser` 默認成為 bean 的 id。// 返回值類型 `User` 相當于 <bean> 標簽的 class 屬性。@Beanpublic User getUser() {return new User(); // 返回要注入到容器中的對象}
}

步驟 2:創建實體類(可以與JavaConfig配合使用)

package com.github.subei.pojo;import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;// @Component注解可以讓 @ComponentScan 掃描到
@Component 
public class User {private String name;public String getName() { return name; }@Value("KANGSHIFU") // 注入屬性值public void setName(String name) { this.name = name; }// ... toString
}

步驟 3:測試
當完全使用 Java 配置時,需要用 AnnotationConfigApplicationContext 來加載容器。

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class MyTest {public static void main(String[] args) {// 通過 AnnotationConfigApplicationContext 來獲取容器,參數是配置類的 Class 對象ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);// beanId 默認為方法名 "getUser"User user = (User) context.getBean("getUser");System.out.println(user.getName());}
}

好的,我已經幫你把筆記重新整理和排版,并針對你遇到的問題進行了一些補充說明。我保留了你所有的原始內容,包括你的個人備注,只是讓整體結構更清晰、更易于閱讀。


10、代理模式

為什么要學習代理模式?

因為這就是 Spring AOP 的底層實現原理!

代理模式的分類:

  • 靜態代理
  • 動態代理

10.1 靜態代理

角色分析
  • 抽象角色 (Subject):一般會使用接口或者抽象類來定義。
  • 真實角色 (Real Subject):被代理的角色,真正執行業務邏輯的類。
  • 代理角色 (Proxy):代理真實角色,在真實角色執行前后,可以附加一些操作。
  • 客戶 (Client):訪問代理角色的人。
代碼實現

1. 接口 (Rent.java)

package com.github.subei.demo;// 租房
public interface Rent {public void rent();
}

2. 真實角色 (Host.java)

package com.github.subei.demo;// 房東
public class Host implements Rent{public void rent(){System.out.println("房東要出租房子!");}
}

3. 代理角色 (Proxy.java)

package com.github.subei.demo;public class Proxy implements Rent { // 注意:代理類也應該實現同一個接口private Host host;public Proxy() {}public Proxy(Host host) {this.host = host;}public void rent(){seeHouse();host.rent(); // 調用真實角色的方法contract();fare();}// 看房public void seeHouse(){System.out.println("中介帶你看房!");}// 收中介費public void fare(){System.out.println("收中介費!");}// 簽合同public void contract(){System.out.println("和你簽合同!");}
}

4. 客戶端 (Client.java)

package com.github.subei.demo;public class Client {public static void main(String[] args) {// 房東要租房子Host host = new Host();// 代理,中介幫房東租房子,但是代理角色一般會有一些附屬操作!Proxy proxy = new Proxy(host);// 我們不直接找房東,而是直接找中介租房proxy.rent();}
}
優缺點
  • 優點:
    • 可以使得我們的真實角色更加純粹,不再去關注一些公共的業務。
    • 公共的業務由代理來完成,實現了業務的分工。
    • 公共業務發生擴展時,方便集中管理。
  • 缺點:
    • 一個真實角色就會產生一個代理角色,代碼量會翻倍,開發效率會變低。

10.2 靜態代理再理解

以用戶管理業務為例,日志功能就是可以被代理的公共業務。

代碼實現

1. 抽象角色 (UserService.java)

package com.github.subei.demo2;// 實現增刪改查業務
public interface UserService {void add();void delete();void update();void query();
}

2. 真實角色 (UserServiceImpl.java)

package com.github.subei.demo2;public class UserServiceImpl implements UserService {public void add() {System.out.println("添加用戶");}public void delete() {System.out.println("刪除用戶");}public void update() {System.out.println("更新用戶");}public void query() {System.out.println("查詢用戶");}
}

3. 代理角色 (UserServiceProxy.java)

package com.github.subei.demo2;public class UserServiceProxy implements UserService {private UserServiceImpl userService;public void setUserService(UserServiceImpl userService) {this.userService = userService;}public void add() {log("add");userService.add();}public void delete() {log("delete");userService.delete();}public void update() {log("update");userService.update();}public void query() {log("query");userService.query();}// 日志方法public void log(String msg){System.out.println("執行了 " + msg + " 方法");}
}

4. 客戶端 (Client.java)

package com.github.subei.demo2;public class Client {public static void main(String[] args) {// 真實業務對象UserServiceImpl userService = new UserServiceImpl();// 代理類UserServiceProxy proxy = new UserServiceProxy();// 設置要代理的真實對象proxy.setUserService(userService);proxy.add();proxy.query();}
}

思考: 這種開發模式像是縱向的業務開發中,切入了橫向的功能(如日志)。

我們想要靜態代理的好處,又不想要它的缺點,于是就有了 動態代理

10.3 動態代理

  • 動態代理和靜態代理的角色一樣。
  • 動態代理的代理類是動態生成的,不是我們直接寫好的。
  • 動態代理分類:
    • 基于接口的動態代理 — JDK 動態代理【本例使用】
    • 基于類的動態代理 — CGLIB
    • Java 字節碼實現 — Javassist

需要了解兩個核心類: java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler

代碼實現

1. 抽象角色 (Rent.java)

package com.github.subei.demo3;public interface Rent {void rent();
}

2. 真實角色 (Host.java)

package com.github.subei.demo3;public class Host implements Rent {public void rent(){System.out.println("房東要出租房子!");}
}

3. 代理處理程序 (ProxyInvocationHandler.java)

package com.github.subei.demo3;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;// 會用這個類,自動生成代理類
public class ProxyInvocationHandler implements InvocationHandler {// 被代理的接口private Rent rent;public void setRent(Rent rent){this.rent = rent;}// 生成得到代理類public Object getProxy(){return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this);}// 處理代理實例,并返回代理結果// 這個方法是代理對象調用任何接口方法時都會執行的public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 動態代理的本質就是利用反射機制seeHouse();// 調用真實對象的方法Object result = method.invoke(rent, args);fare();return result;}// 附加操作public void seeHouse(){System.out.println("中介帶你看房!");}public void fare(){System.out.println("收中介費!");}
}

4. 客戶端 (Client.java)

package com.github.subei.demo3;public class Client {public static void main(String[] args) {// 真實角色Host host = new Host();// 代理角色:現在沒有具體的代理類,只有一個處理器ProxyInvocationHandler handler = new ProxyInvocationHandler();// 通過調用程序來處理我們要調用的接口對象!handler.setRent(host);// 動態生成對應的代理類!Rent proxy = (Rent) handler.getProxy(); proxy.rent();}
}

核心: 一個動態代理處理器,一般代理某一類業務,可以代理實現了同一接口的多個類。

10.4 動態代理再理解

我們可以編寫一個通用的動態代理處理器,讓它能代理任何對象。

通用處理器
package com.github.subei.Demo4;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;// 通用的動態代理處理器
public class ProxyInvocationHandler implements InvocationHandler {// 被代理的對象,設置為Object類型private Object target;public void setTarget(Object target){this.target = target;}// 生成得到代理類public Object getProxy(){return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);}// 處理代理實例,并返回代理結果public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {log(method.getName());Object result = method.invoke(target, args);return result;}public void log(String methodName){System.out.println("執行了 " + methodName + " 方法");}
}
測試
package com.github.subei.Demo4;import com.github.subei.demo2.UserService;
import com.github.subei.demo2.UserServiceImpl;public class Client {public static void main(String[] args) {// 真實角色UserServiceImpl userService = new UserServiceImpl();// 代理角色處理器ProxyInvocationHandler pih = new ProxyInvocationHandler();// 設置要代理的對象pih.setTarget(userService);// 動態生成代理類!UserService proxy = (UserService)pih.getProxy();proxy.add();}
}
動態代理的好處
  • 使得我們的真實角色更加純粹,不再關注公共業務。
  • 公共業務由代理完成,實現了業務分工。
  • 公共業務發生擴展時,方便集中管理。
  • 一個動態代理處理器可以代理多個類,只要它們實現了接口。

11、AOP(TODO:未手敲)

11.1 什么是AOP

AOP(Aspect Oriented Programming),意為:面向切面編程。通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。

AOP是OOP(面向對象編程)的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生范型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。

11.2 AOP在Spring中的作用

提供聲明式事務;允許用戶自定義切面。

AOP 核心概念:

  • 橫切關注點 (Cross-cutting concerns)
    跨越應用程序多個模塊的功能。即與我們業務邏輯無關,但我們又需要關注的部分,就是橫切關注點。如:日志、安全、緩存、事務等等。
  • 切面 (Aspect)
    橫切關注點被模塊化的特殊對象。在代碼中通常是一個
  • 通知 (Advice)
    切面必須要完成的工作,即切面類中的方法
  • 目標 (Target)
    被通知的對象,即被代理的真實對象。
  • 代理 (Proxy)
    向目標對象應用通知之后創建的對象。
  • 切入點 (Pointcut)
    切面通知**執行的“地點”**的定義。
  • 連接點 (JoinPoint)
    與切入點匹配的執行點,在Spring中,連接點就是方法的執行

Spring AOP 中,通過 Advice 定義橫切邏輯,Spring 支持 5 種類型的 Advice。

核心思想: AOP 就是在不改變原有代碼的情況下,去增加新的功能。

11.3 使用Spring實現AOP

1. 導入AOP依賴

使用AOP織入,需要導入aspectjweaver依賴包。

<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.4</version> <!-- 版本號可根據項目情況調整 -->
</dependency>
方式一:使用Spring原生API接口

1. 業務接口和實現類

// UserService.java
package com.github.subei.service;
public interface UserService {void add();void delete();void select();void update();
}// UserServiceImpl.java
package com.github.subei.service;
public class UserServiceImpl implements UserService{public void add() { System.out.println("增加了一個用戶"); }public void delete() { System.out.println("刪除了一個用戶"); }public void select() { System.out.println("查詢了一個用戶"); }public void update() { System.out.println("更新了一個用戶"); }
}

2. 前置/后置增強類

// Log.java (前置通知)
package com.github.subei.log;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;public class Log implements MethodBeforeAdvice {// method: 要執行的目標對象的方法// args: 參數// target: 目標對象public void before(Method method, Object[] args, Object target) throws Throwable {System.out.println(target.getClass().getName() + "的" + method.getName() + "方法被執行了!");}
}// AfterLog.java (后置通知)
package com.github.subei.log;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;public class AfterLog implements AfterReturningAdvice {// returnValue: 返回值public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {System.out.println("執行了" + target.getClass().getName() + "的" + method.getName() + "方法,返回結果為:" + returnValue);}
}

3. Spring配置 (applicationContext.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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><!-- 注冊bean --><bean id="userService" class="com.github.subei.service.UserServiceImpl"/><bean id="log" class="com.github.subei.log.Log"/><bean id="afterLog" class="com.github.subei.log.AfterLog"/><!-- 方式一: 使用原生Spring API接口 --><aop:config><!-- 切入點: expression:表達式, execution(要執行的位置!* * * *) --><aop:pointcut id="pointcut" expression="execution(* com.github.subei.service.UserServiceImpl.*(..))"/><!-- 配置通知器 aop:advisor --><aop:advisor advice-ref="log" pointcut-ref="pointcut"/><aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/></aop:config></beans>

4. 測試

import com.github.subei.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class MyTest {public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");// 動態代理代理的是接口,所以要用接口類型接收UserService userService = context.getBean("userService", UserService.class);userService.select();}
}
方式二:自定義類來實現AOP

1. 編寫自定義切面類 (POJO)

package com.github.subei.diy;public class DiyPointCut {public void before(){System.out.println("---------方法執行前---------");}public void after(){System.out.println("---------方法執行后---------");}
}

或:

public class DiyPointCut {// "前置通知"方法現在可以接收一個JoinPoint對象public void before(JoinPoint jp) {System.out.println("---------方法執行前---------");// 1. 獲取目標對象System.out.println("目標對象: " + jp.getTarget());// 2. 獲取方法簽名,從而得到方法名和類名System.out.println("攔截的方法: " + jp.getSignature().getName());System.out.println("方法所屬類: " + jp.getSignature().getDeclaringTypeName());// 3. 獲取方法的參數System.out.println("方法參數: " + Arrays.toString(jp.getArgs()));}// "后置通知"方法同樣可以獲取這些信息public void after(JoinPoint jp) {System.out.println("---------方法執行后---------");System.out.println("方法 " + jp.getSignature().getName() + " 執行完畢。");}
}

2. Spring配置

<!-- 注冊自定義切面 bean -->
<bean id="diy" class="com.github.subei.diy.DiyPointCut"/><aop:config><!-- 使用 aop:aspect 定義切面 --><aop:aspect ref="diy"><!-- 定義切入點 --><aop:pointcut id="point" expression="execution(* com.github.subei.service.UserServiceImpl.*(..))"/><!-- 定義通知 --><aop:before method="before" pointcut-ref="point" /><aop:after method="after" pointcut-ref="point"/></aop:aspect>
</aop:config>

3. 測試 (同上)

方式三:使用注解實現

1. 編寫注解實現的切面類

package com.github.subei.diy;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;// 使用注解方式實現AOP
@Aspect // 標注這個類是一個切面
public class AnnotationPointCut {@Before("execution(* com.github.subei.service.UserServiceImpl.*(..))")public void before(){System.out.println("---------方法執行前(注解)---------");}@After("execution(* com.github.subei.service.UserServiceImpl.*(..))")public void after(){System.out.println("---------方法執行后(注解)---------");}// 在環繞增強中,我們可以給定一個參數,代表我們要獲取處理切入的點@Around("execution(* com.github.subei.service.UserServiceImpl.*(..))")public void around(ProceedingJoinPoint jp) throws Throwable {System.out.println("環繞前");Signature signature = jp.getSignature(); // 獲得簽名System.out.println("簽名: " + signature);// 執行目標方法: proceedObject proceed = jp.proceed();System.out.println("環繞后");System.out.println("執行結果: " + proceed);}
}

2. Spring配置文件

<!-- 第三種方法: 使用注解方式實現 --><!-- 1. 注冊帶有@Aspect注解的切面類 -->
<bean id="annotationPointCut" class="com.github.subei.diy.AnnotationPointCut"/><!-- 2. 開啟注解支持!(這會讓Spring自動尋找@Aspect注解的bean并創建代理) -->
<aop:aspectj-autoproxy/>
知識點:<aop:aspectj-autoproxy/>
  • 通過AOP命名空間的 <aop:aspectj-autoproxy /> 聲明,可以自動為Spring容器中那些配置了 @AspectJ 切面的bean創建代理,織入切面。
  • 它有一個 proxy-target-class 屬性,默認為 false
    • proxy-target-class="false" (默認): 使用 JDK動態代理 織入增強。目標類必須實現接口。
    • proxy-target-class="true": 使用 CGLIB動態代理 技術織入增強。即使目標類沒有實現接口,也可以創建代理。
    • 注意: 即使 proxy-target-class 設置為 false,如果目標類沒有聲明任何接口,Spring 將自動切換到使用 CGLIB。

11.4 三種實現 AOP 方式的對比

對比維度 (Feature)方式一:Spring API接口方式二:自定義類 + XML配置方式三:注解方式 (@AspectJ)
實現方式切面類必須實現Spring特定的接口,如 MethodBeforeAdvice切面類是一個普通的Java類 (POJO),不需要實現任何接口。切面類是一個普通的Java類 (POJO),但使用 @Aspect 注解標識。
與Spring框架的耦合度。代碼強依賴于org.springframework.aop.*包,可移植性差。。切面類本身完全不依賴Spring,可以獨立存在。中等。依賴于org.aspectj.*注解包,但與Spring核心API解耦。
配置方式完全通過XML配置。使用<aop:advisor>標簽將通知和切入點綁定。完全通過XML配置。使用<aop:aspect>標簽引用切面Bean,并指定方法。XML中只需開啟注解支持<aop:aspectj-autoproxy/>,具體邏輯在Java類中通過注解完成。
易用性與可讀性。代碼最繁瑣,需要為不同類型的通知創建不同的類,結構分散。中等。邏輯與配置分離,需要來回查看XML和Java文件才能理解整體。。邏輯、切入點、通知類型都集中在一個類中,代碼即配置,內聚性高,可讀性最好。
功能強大性較弱。僅提供基本的前置、后置等通知,功能有限。較強。支持前置、后置、環繞、異常等所有通知類型,但配置在XML中。最強。完全支持AspectJ的所有功能,特別是@Around環繞通知,可以精確控制目標方法的執行。
當前主流用法已過時,基本不再使用。在一些需要將AOP配置與業務代碼完全解耦的遺留項目中可能見到。絕對主流,是當前Spring/Spring Boot項目開發的首選和推薦方式。
相同點 (Core Principles)
  1. 核心思想一致:三種方式都是為了實現AOP(面向切面編程),將橫切關注點(如日志、事務)與業務邏輯代碼分離。
  2. 底層實現一致:它們的底層都依賴于Spring在運行時創建動態代理對象(JDK動態代理或CGLIB代理)來織入切面邏輯。
  3. 依賴一致:都需要在項目中引入 aspectjweaver 這個依賴包。
  4. 切入點表達式一致:定義切入點時,使用的 execution() 表達式語法是完全相同的。
不同點 (Evolution & Style)
  1. 最大區別在于“耦合度”和“配置風格”

    • 方式一強耦合、純XML配置。代碼和Spring API綁死。
    • 方式二低耦合、純XML配置。代碼是干凈的POJO,但AOP的“身份”和行為完全由外部XML賦予。
    • 方式三中等耦合、注解驅動。代碼通過注解自我聲明其AOP“身份”和行為,XML只負責開啟總開關。
  2. 代碼的侵入性不同

    • 方式一侵入性最強,因為它強制你的類去實現它的接口。
    • 方式二和方式三對業務代碼都是非侵入式的,這也是AOP提倡的。
  3. 發展趨勢和推薦度不同
    這三種方式清晰地展示了Spring AOP的演進路線:從早期與框架緊密綁定的API,到配置與代碼分離,再到最終使用注解將配置與邏輯內聚。方式三(注解)無疑是目前最佳的實踐,因為它兼顧了低耦合、高可讀性和強大的功能。

簡單來說,你可以這樣理解它們的演進:

  • 方式一:“你(代碼)必須聽我的(框架),按我的規矩來寫。”
  • 方式二:“你(代碼)做你自己的事,我(XML)來告訴你什么時候該做什么。”
  • 方式三:“你(代碼)自己決定自己該做什么,并告訴大家(通過注解),我(框架)負責讓你生效就行。”

12、整合MyBatis

整合步驟概覽

  1. 導入相關Jar包
  2. 編寫配置文件
  3. 編寫代碼與測試
1. 導入Maven依賴
<!-- JUnit 測試框架 -->
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version>
</dependency><!-- MyBatis 核心包 -->
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.3</version>
</dependency><!-- MySQL 數據庫驅動 -->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version>
</dependency><!-- Spring 相關包 -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.2.12.RELEASE</version>
</dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.1.10.RELEASE</version>
</dependency><!-- AOP 織入器 -->
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.6</version>
</dependency><!-- 【關鍵】MyBatis-Spring 整合包 -->
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>2.0.6</version>
</dependency>
2. 配置Maven靜態資源過濾

為了確保 src/main/java 目錄下的 .xml.properties 文件能被正確打包,需要在 pom.xml 中添加以下配置:

<build><resources><resource><directory>src/main/java</directory><includes><include>**/*.properties</include><include>**/*.xml</include></includes><filtering>true</filtering></resource></resources>
</build>

12.1 回憶MyBatis

在整合前,我們先回顧一下原生MyBatis的開發流程。

1. 實體類 (User.java)
package com.github.subei.pojo;
import lombok.Data;@Data
public class User {private int id;private String name;private String pwd;// ... Constructors, Getters, Setters, toString() ...
}
2. MyBatis核心配置文件 (mybatis-config.xml)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><typeAliases><package name="com.github.subei.pojo"/></typeAliases><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=UTF-8"/><property name="username" value="root"/><property name="password" value="root"/></dataSource></environment></environments><mappers><package name="com.github.subei.mapper"/></mappers>
</configuration>
3. Mapper接口 (UserMapper.java)
package com.github.subei.mapper;
import com.github.subei.pojo.User;
import java.util.List;public interface UserMapper {List<User> selectUser();
}
4. Mapper XML文件 (UserMapper.xml)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.github.subei.mapper.UserMapper"><select id="selectUser" resultType="User">select * from user;</select>
</mapper>
5. 測試類
public class MyTest {@Testpublic void selectUser() throws IOException {String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession = sqlSessionFactory.openSession();try {UserMapper mapper = sqlSession.getMapper(UserMapper.class);List<User> userList = mapper.selectUser();for(User user : userList){System.out.println(user);}} finally {sqlSession.close();}}
}

12.2 MyBatis-Spring核心概念

官方文檔地址: http://www.mybatis.org/spring/zh/index.html

MyBatis-Spring 的核心目標是幫助我們將 MyBatis 無縫地整合到 Spring 容器中,讓 Spring 來管理 MyBatis 的組件。

  • SqlSessionFactoryBean

    • 在 Spring 中,我們不再使用 SqlSessionFactoryBuilder,而是使用 SqlSessionFactoryBean 來創建 SqlSessionFactory
    • 它負責讀取配置,并創建一個由 Spring 管理的 SqlSessionFactory 實例。
    • 其最重要的屬性是 dataSource,用于接收 Spring 管理的數據源。
  • SqlSessionTemplate

    • SqlSessionTemplate 是 MyBatis-Spring 的核心,它是 SqlSession 的一個線程安全實現。
    • 它能自動參與到 Spring 的事務管理中,負責 session 的生命周期(獲取、提交/回滾、關閉)。
    • 在整合后,我們應該始終使用 SqlSessionTemplate 來代替原生的 DefaultSqlSession

12.3 整合實現方式一:SqlSessionTemplate

1. 創建Spring數據層配置文件 (spring-dao.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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><!-- 1. 配置數據源 (DataSource) --><bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"><property name="driverClassName" value="com.mysql.jdbc.Driver" /><property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=UTF-8"/><property name="username" value="root"/><property name="password" value="root"/></bean><!-- 2. 配置 SqlSessionFactory --><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="dataSource" /><!-- 關聯MyBatis核心配置文件 (可選,用于settings, typeAliases等) --><property name="configLocation" value="classpath:mybatis-config.xml"/><!-- 掃描Mapper XML文件 --><property name="mapperLocations" value="classpath:com/github/subei/mapper/*.xml"/></bean><!-- 3. 注冊 SqlSessionTemplate --><bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"><!-- 只能使用構造器注入sqlSessionFactory,因為它沒有set方法 --><constructor-arg index="0" ref="sqlSessionFactory"/></bean>
</beans>
2. 創建Mapper實現類 (UserMapperImpl.java)
package com.github.subei.mapper;
import com.github.subei.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;
import java.util.List;public class UserMapperImpl implements UserMapper {// 我們的所有操作,都使用SqlSessionTemplate來執行private SqlSessionTemplate sqlSession;public void setSqlSession(SqlSessionTemplate sqlSession) {this.sqlSession = sqlSession;}public List<User> selectUser() {UserMapper mapper = sqlSession.getMapper(UserMapper.class);return mapper.selectUser();}
}
3. 創建Spring主配置文件 (applicationContext.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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><!-- 導入數據層配置 --><import resource="spring-dao.xml"/><!-- 注冊Mapper實現類的Bean --><bean id="userMapper" class="com.github.subei.mapper.UserMapperImpl"><property name="sqlSession" ref="sqlSessionTemplate"/></bean>
</beans>
4. 測試
@Test
public void testSelectUser() {ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");UserMapper userMapper = context.getBean("userMapper", UserMapper.class);for (User user : userMapper.selectUser()){System.out.println(user);}
}

結果成功輸出! 此時,原生的 mybatis-config.xml 文件中的數據源和事務管理器配置已被 Spring 完全接管,可以被簡化。

12.4 整合實現方式二:SqlSessionDaoSupport

這是一種更便捷的方式,通過繼承 SqlSessionDaoSupport 類來簡化 Mapper 實現類的編寫。

1. 修改Mapper實現類 (UserMapperImpl2.java)
package com.github.subei.mapper;
import com.github.subei.pojo.User;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import java.util.List;public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {public List<User> selectUser() {// 直接通過 getSqlSession() 獲取 SqlSessionTemplateUserMapper mapper = getSqlSession().getMapper(UserMapper.class);return mapper.selectUser();}
}
2. 修改Spring配置 (applicationContext.xml)

SqlSessionDaoSupport 需要注入 SqlSessionFactory 而不是 SqlSessionTemplate

<bean id="userMapper2" class="com.github.subei.mapper.UserMapperImpl2"><property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
3. 測試 (類似方式一)
@Test
public void testSelectUser2() {ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");UserMapper userMapper = context.getBean("userMapper2", UserMapper.class);// ...
}

總結:整合到Spring后,可以完全移除MyBatis配置文件中的數據源和事務配置。除了XML配置,還可以使用注解方式實現整合,這是現代開發中更主流的方式。


13、聲明式事務

13.1 回顧事務

事務 是一組不可分割的業務操作單元,要么都成功,要么都失敗。它用于保證數據的完整性一致性

事務的ACID原則:

  • 原子性 (Atomicity):事務中的所有操作是一個整體,不可分割。
  • 一致性 (Consistency):事務完成后,數據必須保持業務規則上的一致狀態。
  • 隔離性 (Isolation):多個事務并發執行時,應相互隔離,防止數據損壞。
  • 持久性 (Durability):事務一旦提交,其結果就是永久性的。

13.2 事務失效場景測試

假設我們在一個方法內,先執行一個成功的插入操作,再執行一個失敗的刪除操作(SQL語法錯誤)。

1. 擴展Mapper接口和XML

// UserMapper.java
public interface UserMapper {List<User> selectUser();int addUser(User user);int deleteUser(int id);
}
<!-- UserMapper.xml -->
<insert id="addUser" ...>insert into user (id,name,pwd) values (#{id},#{name},#{pwd});
</insert>
<!-- 故意寫錯SQL,將 delete 寫成 deletes -->
<delete id="deleteUser" ...>deletes from user where id = #{id};
</delete>

2. 在一個方法中調用

// UserMapperImpl.java
public List<User> selectUser() {UserMapper mapper = getSqlSession().getMapper(UserMapper.class);// 先添加mapper.addUser(new User(6,"維維","123456"));// 再刪除(這個會失敗)mapper.deleteUser(6); return mapper.selectUser();
}

測試結果:程序會因SQL異常而中斷,但數據庫中用戶添加成功了!這破壞了數據的一致性,因為我們期望添加和刪除是一個整體。

13.3 Spring中的事務管理

Spring 提供了兩種事務管理方式:

  • 編程式事務:在業務代碼中手動控制事務的開啟、提交、回滾。侵入性強,不推薦。
  • 聲明式事務:通過配置(XML或注解)來管理事務,業務代碼無需關心事務邏輯。這是我們使用的重點
配置聲明式事務(XML方式)

1. 配置事務管理器
首先,需要一個事務管理器 DataSourceTransactionManager,并關聯我們的數據源。

<!-- applicationContext.xml --><!-- 1. 配置聲明式事務管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource" />
</bean>

2. 配置事務通知 (tx:advice)
在這里定義事務的規則,比如哪些方法需要事務,以及事務的傳播特性。
需要先引入 tx 命名空間及其約束。

<!-- 2. 配置事務通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager"><tx:attributes><!-- 配置哪些方法使用什么樣的事務, propagation是傳播特性REQUIRED: 如果當前沒有事務,就新建一個;如果已存在,就加入。這是最常用的。--><tx:method name="add*" propagation="REQUIRED"/><tx:method name="delete*" propagation="REQUIRED"/><tx:method name="update*" propagation="REQUIRED"/><tx:method name="select*" read-only="true"/><tx:method name="*" propagation="REQUIRED"/></tx:attributes>
</tx:advice>

3. 配置AOP,將事務織入
使用AOP將事務通知應用到指定的方法上。

<!-- 3. 配置AOP -->
<aop:config><!-- 定義切入點,這里是對mapper包下的所有方法 --><aop:pointcut id="txPointcut" expression="execution(* com.github.subei.mapper.*.*(..))"/><!-- 將事務通知和切入點綁定 --><aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
再次測試

在配置好聲明式事務后,再次運行之前的測試代碼。
結果:程序依然會報錯,但是數據庫中新用戶沒有被添加!事務成功回滾,保證了數據的一致性。

為什么需要事務?

  • 保證數據一致性:防止因部分操作失敗而導致數據狀態混亂。
  • 簡化開發:使用Spring的聲明式事務,開發者可以專注于業務邏輯,而無需手動管理復雜的事務代碼。

寫在最后

從 IoC/DI 的核心原理,到 XML、注解、JavaConfig 三種配置方式的演進;從代理模式的底層鋪墊,到 AOP 的切面思想;再到最后整合 MyBatis 并加上事務的保障。至此,我們已經走完了 Spring Framework 最核心、最常用的一段旅程。
即使在 Spring Boot 已成為主流的今天,這些底層的概念和配置思想依然是理解 Spring 生態、排查復雜問題、成為一名優秀 Java 開發者的基石。
希望這份筆記能幫助正在學習 Spring 的你,理清思路,夯實基礎。學習是一個持續迭代的過程,與君共勉。
🎉🎉🎉 Spring 核心部分完結撒花! 🎉🎉🎉

📦 對應代碼倉庫:https://gitee.com/zzzfatfish/spring-test
? 作者:fatfish
🕒 狀態:學習中,內容持續補充完善…


參考

【狂神說Java】Spring5最新完整教程IDEA版通俗易懂
Spring學習目錄(6天) - subeiLY - 博客園

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/919308.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/919308.shtml
英文地址,請注明出處:http://en.pswp.cn/news/919308.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C#WPF實戰出真汁06--【系統設置】--餐桌類型設置

1、系統設置的基本概念系統設置是用于配置和管理餐桌類型和菜品類型&#xff0c;是維護整個系統的基礎數據。通過系統設置&#xff0c;用戶可以調整餐桌類型的添加&#xff0c;刪除&#xff0c;編輯&#xff0c;分頁&#xff0c;查詢&#xff0c;重置&#xff0c;列表&#xff…

旋鈕鍵盤項目---foc講解(閉環位置控制)

hello&#xff0c;周六休息了一天&#xff0c;出去打本了。趁著夜色&#xff0c;花費了幾個小時&#xff0c;也是將閉環代碼寫完&#xff0c;參考了燈哥的思路。接下來介紹一下我的整個流程&#xff1a; 一、閉環位置控制思路&#xff1a; 其實懂得了開環&#xff0c;那么閉環…

為什么有些相機“即插即用”,而有些則需要采集卡?

在工業生產中&#xff0c;工業相機是“眼睛”&#xff0c;它幫助我們看到世界&#xff0c;但你知道嗎&#xff1f;不同的工業相機接口就像不同的“通道”&#xff0c;有些“通道”直接就能與計算機連接&#xff0c;而有些則需要一個額外的小配件——圖像采集卡。那么&#xff0…

【計算機網絡 | 第7篇】物理層基本概念

文章目錄物理層基本概念及數據通信系統解析一、物理層的核心定位&#x1f95d;二、物理層的功能&#x1f9fe;三、數據通信系統的模型&#x1f426;?&#x1f525;&#xff08;一&#xff09;源系統&#xff08;二&#xff09;傳輸系統&#xff08;三&#xff09;目的系統四、…

一般情況下,python函數都會返回對象,但有時只調用一個函數,這是在修改這個信息

class Model:def __init__(self):self.training Truedef eval(self):self.training Falsereturn Nonem Model() print(m.training) # True m.eval() # 返回 None print(m.training) # False&#xff0c;模型內部狀態已改變m.eval&#xff08;&#xff09;是在修改m的…

2025-08-17 李沐深度學習17——語義分割

文章目錄1 語義分割1.1 介紹1.2 語義分割應用1.3 實例分割2 轉置卷積2.1 工作原理2.2 為什么叫“轉置”卷積2.3 轉置卷積也是一種卷積3 FCN3.1 核心思想3.2 網絡架構4 樣式遷移4.1 基于 CNN 的樣式遷移4.2 工作流程1 語義分割 1.1 介紹 語義分割&#xff08;Semantic Segment…

《若依》權限控制

若依內置了強大的權限控制系統&#xff0c;為企業級項目提供了通用的解決方案 以CRM系統為例&#xff0c;演示權限功能&#xff08;URL&#xff1a;https://huike-crm.itheima.net) demo賬號&#xff08;超級管理員&#xff09;查看所有功能菜單 zhangsan賬號&#xff08;市…

云原生俱樂部-RH134知識點總結(3)

這個系列的第二篇寫了將近5000字&#xff0c;而且還是刪節內容后的&#xff0c;如RAID就沒寫&#xff0c;因為頭已經很大了。第二篇從早上寫到下午&#xff0c;因為偷懶了&#xff0c;寫著寫著就停筆了。不過好在總算磨完了&#xff0c;現在開始寫RH134系列的最后一篇內容。我這…

股票常見K線

1.底部反彈摸線特點長下影線之后必須有實體陰線踩實之后才考慮。macd綠緩慢收窄過程中的不買&#xff0c;剛轉紅也不買。macd轉紅之后等股價跌回之前macd綠首次收窄的最低點附近&#xff0c;而且跌破了所有均線&#xff0c;可以買入此股票。之后股票一波突破之前平臺震蕩平臺&a…

計算機網絡 THU 考研專欄簡介

本專欄專為清華大學計算機網絡考研復習設計&#xff0c;內容系統全面&#xff0c;涵蓋從基礎概念到重點考點的完整知識體系。具體包括&#xff1a;基礎理論&#xff1a;計算機網絡概念、分類、性能指標及網絡分層模型&#xff08;OSI 七層、TCP/IP 四層&#xff09;。協議與技術…

VSCode打開新的文件夾之后當前打開的文件夾被覆蓋

文件--首選項--設置&#xff1a;搜索showtabs設置為如下&#xff1a;

mac 電腦安裝類似 nvm 的工具,node 版本管理工具

前言 蘋果電腦開發時&#xff0c;有時候需要切換node 版本&#xff0c;window版有nvm可以管理node 版本&#xff0c;mac版本可以用另外一種 //全局安裝n 模塊 sudo npm install n -g//輸入后回車&#xff0c;提示輸入電腦密碼&#xff0c;輸入完密碼回車等待下載完成即可//安裝…

spdlog框架的安裝與使用

spdlog框架的安裝與使用spdlog的安裝spdlog的使用spdlog二次封裝總結&#xff1a;spdlog的安裝 sudo apt-get install libspdlog-devspdlog的使用 同步日志器sync.cc (輸出到顯示器/輸出到指定文件) #include<spdlog/spdlog.h> #include<spdlog/sinks/stdout_color…

使用websockets中的一些問題和解決方法

&#xff08;1&#xff09;TypeError: echo() missing 1 required positional argument: path報錯自己寫的代碼如下&#xff1a;async def echo(websocket, path):...async def main():server await websockets.serve(echo, "0.0.0.0", 666)await server.wait_close…

機器人相關基礎知識

機器人簡介下面給出一份機器人方向“從入門到進階”的極簡知識地圖&#xff0c;按「數學 → 硬件 → 軟件 → 算法 → 應用」五層展開&#xff0c;配合常用開源資源。你可以把它當作“字典”隨時查閱。&#x1f539; 1. 數學層&#xff08;所有算法的地基&#xff09;概念一句話…

Windows Server 打開vGPU RDP HEVC編碼

查看已安裝的驅動[rootlocalhost:~] esxcli software vib list Name Version Vendor Acceptance Level Install Date Platforms ----------------------------- ------------------------------------ ------ -…

OpenAL技術詳解:跨平臺3D音頻API的設計與實踐

引言&#xff1a;OpenAL的定位與價值 OpenAL&#xff08;Open Audio Library&#xff09; 是一套跨平臺的3D音頻應用程序接口&#xff08;API&#xff09;&#xff0c;專為高效渲染多通道三維定位音頻而設計。其API風格與編程范式刻意模仿OpenGL&#xff0c;旨在為游戲開發、虛…

重溫 K8s 基礎概念知識系列五(存儲、配置、安全和策略)

文章目錄一、存儲&#xff08;Storage&#xff09;1.1、Volume1.2、PersistentVolume (PV)1.3、PersistentVolumeClaim (PVC)1.4、StorageClass1.5、PVC 和 PV 的綁定過程&#xff1f;二、配置管理&#xff08;Configuration&#xff09;2.1、ConfigMap2.2、Secret2.3、存活、就…

通過PhotoShop將多張圖片整合為gif動畫

一、準備圖片集合二、導入PS導入PS后點擊確定&#xff1a;導入成功&#xff1a;三、添加時間軸勾選創建幀動畫&#xff1a;此時時間軸進化為幀動畫軸&#xff1a;四、圖片集部署在幀動畫軸點擊幀動畫軸右上角的三道橫杠&#xff0c;從圖層建立幀&#xff1a;此時圖片集已經部署…

Easy Rules 規則引擎詳解

Easy Rules 規則引擎詳解 Easy Rules 是一個輕量級的 Java 規則引擎&#xff0c;它提供了一種簡單而強大的方式來定義和執行業務規則。以下是 Easy Rules 的詳細介紹&#xff1a; 1. 核心概念 1.1 規則 (Rule) 條件 (Condition): 當條件為 true 時執行動作動作 (Action): 條件滿…