# Spring5筆記 ```xml= <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> </beans> ``` ### Spring開發步驟 1. 導入座標(maven dependency) ```xml= <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>test</artifactId> <groupId>org.example</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>spring-01-ioc</artifactId> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.0.RELEASE</version> </dependency> </dependencies> </project> ``` 2. 創建Bean ![](https://i.imgur.com/cF4l6Zj.png) 3. 創建applecationContext.xml(配置文件) 4. 在配置文件中進行配置 ```xml= <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="userDao" class="com.ithema.dao.impl.UserDaoImpl"></bean> </beans> ``` 5. 創建applicationContext物件,getBean # 1. IOC容器 * 控制反轉:反轉bean的控制權 :star:舊式做法:Service -> DAO, 必須在Service new一個DAO的實例出來, 但是使用Spring的話, 當Service層呼叫DAO時, 並不需要創建一個新的實例, 因為我們把創建的動作交給了Spring負責(IOC), 並且達到了解耦的效果 (DI的概念) * 使用IOC的目的: <font color="red">降低耦合度</font> * IOC底層原理: xml解析, 工廠模式, 映射 ![](https://i.imgur.com/e8nVPYw.png) ## 1.2 IOC操作Bean管理 * Bean管理指的是兩個操作 1. Spring 創建對象 2. Spring 屬性注入 :star:Bean管理操作有2種方式:star: 1. 基於xml配置文件實現 2. 基於註解方式實現 ##### 建構子創建對象的方式 * 共<font color="red">三種方式</font> 1. 第一種 index賦值 ```xml= <bean id="user" class="com.liz.pojo.User"> <constructor-arg index="0" value="狂神說java"> </bean> ``` 2. 第二種 通過型別創建(<font color="red">不建議使用!!</font>) :point_right:<font color="#33BDFF">如果兩個參數的型別都一樣, 將無法指定!</font> ```xml= <bean id="user" class="com.liz.pojo.User"> <constructor-arg type="java.lang.String" value="狂神說"> <constructor-arg type="int" value="0"> </bean> ``` 3. 第三種 直接通過參數名來設置 ```xml= <bean id="user" class="com.liz.pojo.User"> <constructor-arg name="name" value="老師"> </bean> ``` 總結: 在配置文件(bean.xml)加載的時候, 容器就已經創建出初始化的instance了! --- # 2. Spring的配置 ### 1. 別名 ```xml= <!-- 如果增加了別名, 我們也可以使用這個別名來取到這個對象 --> <alias id="bean id的值" alias="別名" /> //name=""也可以作為bean的標示, 但是在Struct1較常用 ``` ### 2. Bean的配置 ```xml= <!-- id : bean的唯一識別名, 也相當於我們知道的instance name class : bean對象的全域名稱 package + class name : 也是別名, 而且name可以同時取多個別名 scope autowire --> <bean id="userT" class="com.liz.pojo.UseT" name="user2 u2,u3;user4"> <constructor-arg name="name" value="學習"> </bean> ``` user2 u2,u3;user4都會呼叫同一個bean對象 ### 3. import :star:一般用於<font color="red">團隊開發</font> import可以將多個配置檔導入合併為一個 假設現在專案有多人開發, 大家負責不同的類別開發, 則不同的類別需要在不同的bean中, 我們可以利用import將所有人的bean.xml合併成一個總和的檔案! ```xml= <import resource="beans.xml"/> <import resource="beans2.xml"/> <import resource="beans3.xml"/> ``` 使用的時候, 用まとめた配置檔就可以了! :bulb:如果宣告的內容重覆, 則內容依樣會被合併 --- # 3. IOC 依賴注入(DI) ### 1. 建構子注入 上面提過了 ### 2. Set方式注入:bulb:<font color="red">!重點!</font>:bulb: * 依賴注入的本質 = set注入 * 依賴 : bean對象的創建依賴於容器 * 注入 : bean對象中的所有屬性, 由容器來注入! 2.1 普通值注入(經由value="") `<property name="name" value="大衛"/>` 2.2 Bean注入(使用ref="") `<property name="address" ref="address"/>` 2.3 陣列注入 ```xml= <property name="books"> <array> <value>紅樓夢</value> <value>西遊記</value> <value>水滸傳</value> <value>三國演義</value> </array> </property> ``` 2.4 List ```xml= <property name="hobbies"> <list> <value>聽歌</value> <value>看電影</value> </list> </property> ``` 2.5 Set ```xml= <property name="games"> <set> <value>LOL</value> <value>WOW</value> </set> </property> ``` 2.6 Map ```xml= <property name="cards"> <map> <entry key="身分證" value="B00221"/> <entry key="提款卡" value="005-888-166"/> <entry key="學生證" value="100123456"/> </map> </property> ``` 2.7 <font color="red">空字串注入</font> `<property name="wife" value=""/>` 2.8 <font color="red">null值注入</font> ```xml= <property name="wife"> <null/> </property> ``` 2.9 Properties注入(鍵值對, 但value要寫角括號外面) ```xml= <property name="info"> <props> <prop key="學號">20210103</prop> <prop key="性別">男</prop> <prop key="name">大衛海鮮</prop> <prop key="userName">DS</prop> <prop key="password">123456</prop> </props> </property> ``` 2.10 注入特殊符號 ```xml= // 把<>進行轉譯(&lt;和&gt;) // 把特殊符號內容寫到CDATA <bean id="orderDao" class="com.ithema.dao.OrderDao"> <constructor-arg name="oname" value="電腦"></constructor-arg> <constructor-arg name="address" value="TW"></constructor-arg> <property name="label" value="&lt;城市&gt;"></property> <property name="place"> <value><![CDATA[<<南京>>]]></value> </property> </bean> ``` 2.11 外部Bean注入 ```xml= //xml <bean id="userDaoImpl" class="com.ithema.dao.impl.UserDaoImpl"></bean> <bean id="userService" class="com.ithema.service.UserService"> <property name="userDaoA" ref="userDaoImpl"/> </bean> ``` ```java= //java code public class UserService { private UserDao userDaoA; public void updateService(){ userDaoA.save(); System.out.println("user service...."); } public void setUserDaoA(UserDao userDaoA) { this.userDaoA = userDaoA; } } ``` 2.12 內部Bean ```xml= <!-- 內部bean--> <bean id="emp" class="com.ithema.bean.Emp"> <property name="ename" value="Lucy"/> <property name="gender" value="女"/> <property name="dept"> <bean id="dept" class="com.ithema.bean.Dept"> <property name="dname" value="會計部"/> </bean> </property> </bean> ``` 2.13 注入屬性-集聯賦值 第一種寫法 ```xml= <!-- 集聯賦值--> <bean id="emp" class="com.ithema.bean.Emp"> <property name="ename" value="Lucy"/> <property name="gender" value="女"/> <property name="dept" ref="dept"></property> </bean> <bean id="dept" class="com.ithema.bean.Dept"> <property name="dname" value="財務部"/> </bean> ``` 第二種寫法 此種寫法是先在emp instance裡生成dept, 再從dept裡面拿取dname, 所以java file裡面必須有dept的getter, 不然會報錯 ```xml= <!-- 集聯賦值--> <bean id="emp" class="com.ithema.bean.Emp"> <property name="ename" value="Lucy"/> <property name="gender" value="女"/> <property name="dept" ref="dept"></property> <property name="dept.dname" value="技術部"></property> </bean> <bean id="dept" class="com.ithema.bean.Dept"> <property name="dname" value="財務部"/> </bean> ``` 2.14 將集合注入部分提取出來供其他bean使用 1. 在spring配置文件中引入名稱空間util ```xml= <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd"> ``` 2. 使用util標籤完成list集合注入提取 ```xml= <util:list id="bookList"> <value>易筋經</value> <value>九陰真經</value> <value>九陽神功</value> </util:list> <bean id="books" class="com.atguigu.spring5.collectiontype.Book"> <property name="list" ref="bookList"></property> </bean> ``` ### 3. 拓展方式注入(第三方) 我們可以使用p命名空間和c命名空間來進行注入 :bulb:注意點 : c命名空間和p命名空間不可直接使用, 需要導入xml約束 *p命名空間的底層還是使用setter來實現 3.1 p 注入方式(p命名空間) * 註冊檔開頭需要導入Xml約束 `xmlns:p="http://www.springframework.org/schema/p"` * 類似property宣告, 對應到set方式注入 ```xml= <bean name="user" class="com.spring.pojo.User" p:userName="Peter" p:age="45"/> ``` 3.2 c 注入方式(c命名空間) * 註冊檔開頭需要導入Xml約束 `xmlns:c="http://www.springframework.org/schema/c"` * 通過建構子注入, 對應到建構子注入 ```xml= <bean name="user2" class="com.spring.pojo.User" c:userName="Tom" c:age="18"/> ``` --- # 4. IOC容器Bean管理 ## Factory Bean #### Spring有兩種類型的Bean 1. 普通Bean: 在配置文件中定義的Bean類型就是返回類型 2. Factory Bean: 配置文件中定義類型的類型可以跟返回類型不一樣 Howto: 1. 創建一個Class, 然後實現FactoryBean介面 2. Override介面的方法, 在實現的方法中定義返回的Bean類型 ```java= public class MyBean implements FactoryBean<Course> { //定義return的bean //我們設定的Bean是"MyBean", 但是返回的對象是"Course"這就是工廠Bean //配置文件中定義類型的類型可以跟返回類型不一樣 @Override public Course getObject() throws Exception { Course course = new Course(); course.setCname("工廠bean"); return course; } @Override public Class<?> getObjectType() { return null; } @Override public boolean isSingleton() { return false; } } ``` ```xml= <bean id="myBean" class="com.atguigu.spring5.factoryBean.MyBean"></bean> ``` ```java= @Test public void testCollection3(){ //以下寫法會報錯, 因為返回的類型不適MyBean而是Course // ApplicationContext app = new ClassPathXmlApplicationContext("bean3.xml"); // MyBean myBean = app.getBean("myBean", MyBean.class); // // System.out.println(myBean); //======================================== ApplicationContext app = new ClassPathXmlApplicationContext("bean3.xml"); Course myBean = app.getBean("myBean", Course.class); System.out.println(myBean); //輸出Course{cname='工廠bean'} } ``` --- ## Bean Scope #### 如何配置 ```xml= <bean id="books" class="com.atguigu.spring5.collectiontype.Book"> <property name="list" ref="bookList" scope="singleton"></property> </bean> ``` 1. Singleton (Default)單實例 無論用哪個DAO去拿, 都用同一個instance去回應 ![](https://i.imgur.com/ymMZSAc.png) 記憶體位址相同 ![](https://i.imgur.com/W9xfjzQ.png) 2. Prototype(原型模式)多實例 如果scope設定為prototype, 則上圖程式碼執行結果為<font color="red">false</font> 每次從容器中get的時候, 都會產生一個新的instance ```xml= <bean id="books" class="com.atguigu.spring5.collectiontype.Book" scope="prototype"> <property name="list" ref="bookList"></property> </bean> ``` ![](https://i.imgur.com/0wlBwB6.png) 3. request/session/application/websocket 這些只能在web開發裡使用到! ##### :bulb:scope="songleton"的時候, 加載配置文件時spring就會創建instance ##### :bulb:scope="prototypr"則不是在加載配置文件是創建instance, 而是在調用getBean方法時創建多實例對象 --- ## Bean的生命週期 1. 通過無參數建構子創建bean 2. 設置屬性(setter) 3. 調用bean初始化方法(需要在配置文件裡面配置) 4. bean可以使用了 5. 當容器關閉的時候, 調用的bean銷毀方法(需要在配置文件裡面配置) ```xml= <bean id="orders" class="com.atguigu.spring5.bean.Orders" init-method="initMethod" destroy-method="destroyMethod"> <property name="oname" value="手機"></property> </bean> ``` ```java= public class Orders { private String oname; public Orders() { System.out.println("第一步, 調用無參數建構子"); } //執行的初始化方法 public void initMethod() { System.out.println("第三步, 執行init 方法"); } //執行的銷毀方法 public void destroyMethod(){ System.out.println("第五步, 銷毀時的方法"); } public void setOname(String oname) { this.oname = oname; System.out.println("第二步, 調用setter設定屬性值"); } } ``` ```java= @Test public void testBeanInit(){ ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("bean4.xml"); Orders orders = app.getBean("orders", Orders.class); System.out.println("第四步, 獲取創建bean實例對象"); System.out.println(orders); //手動讓bean實例銷毀 app.close(); } ``` ![](https://i.imgur.com/PsWgrty.png) ### Bean的後置處理器(before/after) Runtime的順序如下 :arrow_down: ###### `postProcessBeforeInitialization` 3. 把實例bean傳給後置處理器的方法(需要在配置文件裡面配置) 3.1 實現interface `BeanPostProcessor` ###### `postProcessAfterInitialization` :warning: <font color='red'>後置處理器會被適用於當前配置文件中的所有bean實例</font> ```xml= <bean id="orders" class="com.atguigu.spring5.bean.Orders" init-method="initMethod" destroy-method="destroyMethod"> <property name="oname" value="手機"></property> </bean> <!-- 配置後置處理器--> <!-- 此後置處理器會被適用於當前配置文件中的所有bean實例--> <bean id="myBean" class="com.atguigu.spring5.bean.MyBeanPost"> </bean> ``` --- ## Bean的自動裝配 * 自動裝配是Spring滿足bean依賴的一種方式 * Spring會在上下文中自動尋找, 並自動為bean裝配屬性 * IOC操作Bean管理(xml/annotaion)參照之前筆記 在Spring中有三種裝配方式 1. 在xml中配置 <font color='red'>(手動裝配)</font> 2. 在java中顯示配置 3. 隱式的自動裝配bean:bulb:<font color="red">!重點!</font>:bulb: ### 基於XML方式進行自動裝配(少用) 一般進行自動裝配時, 還是使用annotation來進行 ### bean標籤屬性autowire來配置自動裝配 autowire屬性常用的兩個值: 1. byName: 根據屬性名稱(attribute name)注入 2. byType: 根據屬性的類型(Class)注入 ### autowire="byName" :bulb: 和class裡面的attribute一樣名稱 會自動在容器上下文中尋找, 和自己set方法後面的值對應的<font color="red">bean id!</font> ```xml= <bean id="dog" class="com.kuang.pojo.Dog"/> <bean id="cat" class="com.kuang.pojo.Cat"/> <bean id="people" class="com.kuang.pojo.People" autowire="byName"> <property name="name" value="大衛"/> </bean> ``` ### autowire="byType" 會自動在容器上下文中尋找, 和自己對象屬性<font color="red">class相同</font>的bean! ```xml= <bean class="com.kuang.pojo.Dog"/> <bean class="com.kuang.pojo.Cat"/> <bean id="people" class="com.kuang.pojo.People" autowire="byType"> <property name="name" value="大衛"/> </bean> ``` 小結: * byName的時候, 需要保證所有bean的id是唯一的! 並且這個bean需要和自動注入的屬性的set方法的值一致! * byType的時候, 需要保證所有bean的class唯一! 並且這個bean需要和自動注入的屬性的類別一致! ```xml= 當使用"byType"時, 以下配置會出錯, 因為dept和dept1都是Dept class <bean id="emp" class="com.atguigu.spring5.autowire.Emp" autowire="byType"></bean> <bean id="dept" class="com.atguigu.spring5.autowire.Dept"></bean> <bean id="dept1" class="com.atguigu.spring5.autowire.Dept"></bean> ``` --- # 5. IOC操作Bean管理(外部屬性文件) 將經常使用的constant(ex. DB的帳密, enpoint)抽取出來寫在properties檔之後引用 例: 1. 直接配置database資訊 1.1 配置德魯伊連接池 1.2 引入德魯伊連接池的依賴 or jar包 ```xml= <!-- https://mvnrepository.com/artifact/com.alibaba/druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.8</version> </dependency> ``` ```xml= <!--直接配置連線池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/userDB"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> </bean> ``` 2. 引入外部屬性文件配置database連接池 2.1 創建外部屬性文件(properties檔), 創建資料庫資訊 ![](https://i.imgur.com/Jj1rnMc.png) 2.2 把外部properties文件引入到Spring配置文件中 * 引入context名稱空間 * 在spring配置文件中使用標籤引入外部屬性文件 ```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:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd https://www.springframework.org/schema/context/spring-context.xsd"> </beans> ``` ```xml= <!-- 引入外部屬性文件 --> <context:property-placeholder location="classpath:jdbc.properties"/> <!--配置連線池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${prop.driverClass}"></property> <property name="url" value="${prop.url}"></property> <property name="username" value="${prop.username}"></property> <property name="password" value="${prop.password}"></property> </bean> ``` --- # 6. Bean管理-使用註解開發 * 何謂註解: 1. 註解是程式碼的特殊標記, 格式:@註解名稱(屬性名稱=屬性值, 屬性名稱=屬性值...) 2. 註解可以使用於class上面, attribute上面, method上面 3. 使用註解目的: 簡化xml配置 * Spring針對Bean管理中創建對象提供註解 1. `@Component` * 相當於`<bean id="user" class="com.kuang.pojo.User" />` 3. `@Service` 4. `@Controller` 5. `@Repository` (DAO) 上面4個註解功能是一樣的, 都可以用來創建bean實例 ## 6.1 使用註解實現自動裝配 要使用註解(Spring4之後), 必須保證 : 1. 導入aop的依賴(jar檔) ![](https://i.imgur.com/VIdUOht.png) 2. 開啟組件掃描 * 在xml配置文件中導入context約束 ![](https://i.imgur.com/e7fvmBS.png) ```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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 開啟組件掃描, 可以用","隔開多個package或是掃描上層package目錄--> <!-- <context:component-scan base-package="com.atguigu.spring5.dao,com.atguigu.spring5.service"></context:component-scan>--> <!-- 掃描"com.atguigu"底下的所有類別 --> <context:component-scan base-package="com.atguigu"></context:component-scan> <!-- 例1 use-default-filters="false" 表示現在不使用預設 filter, 自己配置filter context:include-filter 設置掃描那些內容 只掃描package底下帶有"@Controller"註解的class --> <context:component-scan base-package="com.atguigu" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!-- 例2 下面配置掃描"com.atguigu"所有內容 context:exclude-filter, 設置哪些內容不進行掃描 --> <context:component-scan base-package="com.atguigu"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> </beans> ``` 3. 配置support註解 :bulb:<font color="red">!重點!</font>:bulb: ` <context:annotation-config/>` 或 ` <context:component-scan base-package="com.atguigu"></context:component-scan>` 4. 創建class, 在class上面添加創建對象註解 ```java= @Component(value = "userService") public class UserService { public void add() { System.out.println("Service add..."); } } ``` ## 6.2 基於註解實現屬性注入 ### 1. `@Autowired`註解 第一步 把service跟dao對象創建, 在service跟dao添加創建對象註釋 第二步 在service注入dao對象, 在service類添加dao類型屬性, 在屬性上面使用註釋 * 根據屬性類型進行自動裝配(類似byType) 使用autowire annotation的話, 可以不寫set方法!(但getter還是需要!) ```java= @Autowired private Dog dog; ``` 注意事項: 1. 直接在屬性上使用, 也可在setter上使用 2. 使用Autowired, 我們可以不用編寫set方法, 前提是這個自動配裝的屬性在IOC容器(Spring)容器中存在, 且符合名字("byName")!! 如果定義了Autowired的required屬性為false, 則表示這個對象可以為null, 否則不允許為空值`@Autowired(required = false)` ### 2. `@Qualifier`註解 * 根據屬性的名稱進行注入 * 必須和`@Autowired`一起使用 如果自動裝配的環境比較複雜, 我們可以使用`@Qualifier(value="xxx")`去配合`@Autowired`, 指定一個唯一的bean對象注入 ```java= @Service public class UserService { //定義Dao類型屬性 //不需要添加Set方法 //添加注入屬性註解 @Autowired @Qualifier(value = "userDaoImpl1") private UserDao userDao; public void add() { System.out.println("Service add..."); userDao.addUser(); } } ``` ### 3. `@Resource`註解 * 可以根據類型注入, 也可以根據名稱注入 先找上下文中id/name相同的對象, 如果都不相同, 在尋找相同class的對象 如果自動裝配的環境比較複雜, 我們可以使用`@Resource(name="xxx")`, 來指定一個唯一的bean對象注入 ```java= @Resource(name = "userDaoImpl2") //預設是根據名稱進行注入 private UserDao userDao; public void add() { System.out.println("Service add..."); userDao.addUser(); } ``` :bulb: <font color='red'>建議使用Autowired和Qualifier</font> ![](https://i.imgur.com/lzSbJVJ.png) ### 4. `@Value`註解 * 注入普通類型屬性 ```java= @Value(value = "王阿花") private String name; ``` 小結: 1. 都是用來自動裝配的, 都可以放在屬性上 2. @Autoired通過byType的方式實現, 而且必須要求這個對象存在! 3. @Resource預設通過byName的方式實現, 如果找不到對應的name, 則通過byType方式實現! 如果兩個都找不到的話就會報錯 4. 區別: @Autoired通過byType的方式實現。@Resource預設通過byName實現。 重點: 1. bean 2. 屬性如何注入 ```java= @Component public class User { public String name; @Value("老師") //相當於<property name="name" value="老師"/> public void setName(String name) { this.name = name; } } ``` 3. 衍生註解 ***@Component***有幾個衍生註解, 我們在web開發中, 會按照mvc分層架構分成三層 * dao【@Repository】 * service 【@Service】 * controller 【@Controller】 :star: <font color="red">這四個註解功能都是一樣的, 都是代表將某個class註冊到Spring容器中裝配Bean</font>:star: 4. 自動裝配 上面寫過了 5. scope ```java= @Component @Scope("singleton") ``` 6. 小結 xml與註解: * xml更加萬能, 適用於任何場合, 維護簡單方便 * 註解不是自己的class使用不了, 維護相對複雜 xml與註解最佳實踐方法: * xml用來管理bean * 註解只負責屬性的注入 * 我們在使用的過程中, 只需要注意一個問題: 要讓註解生效, 就必須開啟支援註解!!! ```xml= <!--指定要掃描的package, 這個package底下的註解就會生效--> <context:component-scan base-package="com.kuang"/> <context:annotation-config/> ``` --- ## 6.3 完全註解開發 1. 創建配置類, 取代xml配置文件 ### 使用java的方式配置Spring 完全不使用Spring的xml配置, 全部使用java來達到Spring配置 JavaConfig是Spring的一個子項目, 在Spring4之後, 他成為了一個核心功能 ### `@Configuration` 代表一個配置Class, 就和之前的beans.xml是一樣的 ```java= @Configuration @ComponentScan(basePackages = {"com.atguigu"}) public class SpringConfig { } ``` ```java= @Test public void testService2(){ //加載配置類 ApplicationContext app = new AnnotationConfigApplicationContext(SpringConfig.class); UserService us = app.getBean("userService", UserService.class); us.add(); } ``` --- # 7. AOP 面向導向程式設計 #### 不影響原本的業務類別下, 實現動態增強 * OOP利用AOP可以對業務邏輯的各部分進行隔離(耦合性低!) * 不通過修改源碼方式, 在主幹功能裡面添加新功能 <font color="red">使用AOP時需要多導入一個aspect(織入)檔</font> ```xml= <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency> ``` bean.xml要導入aop的約束 ```xml= <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> ``` ![](https://i.imgur.com/cjClIJo.png) ![](https://i.imgur.com/1CnMp6R.png) 方法一: 使用spring提供的interface(主要是SpringAPI介面實現) ```java= MethodBeforeAdvice AfterReturningAdvice ``` ```xml= <!-- 使用spring原生的interface--> <!-- 配置aop: 需要導入AOP的約束--> <aop:config> <!--切入點(在哪個地方執行Spring): expression 表達式 execution(要執行的位置! * * * * *)--> <aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/> <!-- 執行環繞增加--> <aop:advisor advice-ref="log" pointcut-ref="pointcut" /> <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut" /> </aop:config> ``` 方法二: 自定義類別來實現AOP(主要是切面定義) 方式三: 使用註解實現! --- # 8. 代理模式 SpringAOP的底層 代理模式的分類: * 靜態代理 * 動態代理 ### 8.1 靜態代理 腳色分析 * 抽象角色: 一般會使用interface或抽象類別來解決(ex. 租房) * 真實角色: 被代理的角色(ex. 房東) * 代理角色:代理真實角色, 代理真實角色之後, 我們一般會做一些附屬操作 * 客戶: 訪問代理對象的人 代理模式的優點: * 可以使真實角色的操作更加純粹! 不用去關注公共業務(提高重用性) * 公共業務交給代理角色, 實現業務分工 * 公共業務發生擴展的時候, 方便集中管理 缺點: * 一個真實角色就會產生一個代理角色, 程式碼量翻倍, 開發效率變低 :point_right: <font color="red">靜態代理撰寫步驟</font> 1. 介面 ```java= package com.kuang.demo01; //租房--抽象角色 public interface Rent { public void rent(); } ``` 2. 真實角色 ```java= package com.kuang.demo01; //房東--真實角色 public class Host implements Rent{ @Override public void rent() { System.out.println("房東要出租房子"); } } ``` 3. 代理角色 ```java= package com.kuang.demo01; //代理角色--房屋仲介 public class Proxy implements Rent{ //房東 private Host host; public Proxy() { } public Proxy(Host host) { this.host = host; } //代理的抽象角色 @Override public void rent() { seeHouse(); sign(); takeFee(); host.rent(); } //只有代理能做的事(ex. 看房) public void seeHouse(){ System.out.println("仲介帶你看房"); } public void sign(){ System.out.println("簽租賃合約"); } public void takeFee(){ System.out.println("收仲介費"); } } ``` 4. 訪問代理角色的客戶端 ```java= package com.kuang.demo01; public class Client { public static void main(String []args){ //原來的模式, 沒有使用代理 //房東要租房子 Host host = new Host(); //host.rent(); //可以放入不同的房東代理! //代理, 仲介幫房東租房, 但是代理一般會有一些附屬操作 Proxy proxy = new Proxy(host); //你不用面對房你不用面對房東, 直接找仲介租房即可 proxy.rent(); } } ``` ### 8.2 動態代理 * 動態代理跟靜態代理的角色一樣 * 動態代理的代理類別是動態生成的, 不是我們直接寫好的 * 動態代理分為兩大類 * 基於介面的動態代理, 基於class的動態代理 * 基於介面---JDK動態代理 ![](https://i.imgur.com/0Uo95A8.png) * 基於class---CGLIB ![](https://i.imgur.com/ZQzcH4v.png) * java字節碼實現: javasist 需要了解兩個類別 * Proxy * 生成動態代理實例 * InvocationHandler * 調用處理程序(InvocationHandler), 並返回結果 ```java= public class JDKProxy { public static void main(String[] args) { Class[] interfaces = {UserDao.class}; UserDao userDao = new UserDaoImpl(); //創建介面實現類的代理對象 UserDao up = (UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserProxy(userDao)); // System.out.println("result: "+ up.add(10,5)); System.out.println("result: "+ up.update("測試")); } } //創建代理對象 class UserProxy implements InvocationHandler { //把欲代理的class傳遞過來 //使用建構子傳遞 private Object obj; public UserProxy(Object userDao) { this.obj = userDao; } // Override後方法理實現增強的邏輯 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //方法之前 System.out.println("方法之前..." + method.getName() + ":傳遞的參數...:" + Arrays.toString(args)); //執行被增強的方法 Object res = method.invoke(obj, args); //方法之後 System.out.println("方法之後執行..." + obj); return res; } } ``` ![](https://i.imgur.com/Ea0vNWU.png) :point_right: <font color="red">動態代理的優點</font> * 所有靜態代理有的優點動態代理都有 * 一個動態代理類, 代理的是一個介面, 一般就是對應相同類型的業務 * 一個動態代理類, 可以代理多個實現了同一個介面的類! --- # 9. AOP操作 1. Spring框架一般都是基於AspectJ實現AOP操作 1.1 什麼是AspectJ * AspectJ不是Spring組成的一部分, 是獨立的AOP框架, 一般把AspectJ跟Spring框架一起使用, 進行AOP操作 2. 基於AspectJ實現AOP操作 2.1 基於xml配置文件 2.2 基於註解方式(常用) 3. 引入AOP依賴 ![](https://i.imgur.com/oGrl0lh.png) 4. 切入點表達式 4.1 切入點表達式作用: 知道對哪個類裡面的哪個方法進行增強 4.2 語法結構: execution( \[權限修飾符\]\[返回類型\]\[類全路徑\]\[方法名稱\](\[參數列表\]) ) * 舉例1: 對com.atguigu.dao.BookDao類裡面的add進行增強 execution(* com.atguigu.dao.BookDao.add(..)) * 舉例2: 對com.atguigu.dao.BookDao類裡面的所有方法進行增強 execution(* com.atguigu.dao.BookDao.*(..)) * 舉例3: 對com.atguigu.dao包裡面的所有類的所有方法進行增強 execution(* com.atguigu.dao.*.*(..)) ### AspectJ註解 1. 創建class, 在class裡面定義方法 2. 創建增強class(編寫增強邏輯) 2.1 在增強類裡面創建方法, 讓不同方法代表不同通知類型 3. 進行通知的配置 3.1 在spring配置文件中, 開啟註釋掃描 ```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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!--開啟註釋掃描--> <context:component-scan base-package="com.atguigu.spring5.aopanno"/> </beans> ``` 3.2 使用註釋創建User跟UserProxy ```java= //被增強的類 @Component public class User { public void add(){ System.out.println("add...."); } } ``` ```java= //增強的類 @Component @Aspect //生成代理對象 public class UserProxy { //前置通知 public void before(){ System.out.println("before..."); } } ``` 3.3 在增強類上面添加註釋`@Aspect` 3.4 在配置文件中開啟生成代理對象 ```xml= <!--開啟AspectJ, 生成代理對象--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> ``` 4. 配置不同類型的通知 4.1 在增強類的裡面, 在作為通知的方法上面添加通知類型註釋, 並且使用切入點表達式配置 ```java= package com.atguigu.spring5.aopanno; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; //增強的類 @Component @Aspect //生成代理對象 public class UserProxy { //前置通知 @Before(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))") public void before() { System.out.println("before..."); } //最終通知 //不管有沒有異常, 都會執行 @After(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))") public void after() { System.out.println("after..."); } //後置通知(返回通知) @AfterReturning(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))") public void afterReturning() { System.out.println("afterReturning..."); } //異常通知 @AfterThrowing(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))") public void afterThrowing() { System.out.println("AfterThrowing..."); } //環繞通知 @Around(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))") public void around(ProceedingJoinPoint proceedingJoinPointo) throws Throwable { System.out.println("環繞之前..."); //被增強的方法執行 proceedingJoinPointo.proceed(); System.out.println("環繞之後..."); } } ``` * 正常執行: ![](https://i.imgur.com/Mnde508.png) * 有例外: ![](https://i.imgur.com/FeeM7PJ.png) 5. 抽取相同的切入點 ```java= //抽取相同的切入點 @Pointcut(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))") public void pointDemo(){ } //前置通知 @Before(value = "pointDemo()") public void before() { System.out.println("before..."); } ``` 6. 有多個增強類, 對同一個方法進行增強, 我們可以設置增強類的優先度 * 在增強類上面添加註釋`@Order(數字的值)`, 數字越小優先度越高 ```java= @Component @Aspect @Order(1) public class PersonProxy { } ``` 7. 完全使用註解開發 * 創建配置類, 不需要創建xml配置文件 ```java= @Configuration @ComponentScan(basePackages = {"com.atguigu.spring5"}) @EnableAspectJAutoProxy(proxyTargetClass = true) //預設是false public class ConfigAop { } ``` ### AspectJ XML配置文件 1. 創建2個class, 增強類與被增強類, 創建方法 2. 在spring配置文件中創造2個類的對象 ```xml= <!--創建對象--> <bean id="book" class="com.atguigu.spring5.aopanno.aopxml.Book"/> <bean id="bookProxy" class="com.atguigu.spring5.aopanno.aopxml.BookProxy"/> ``` 3. 在spring文件中配置切入點 ```xml= <!--配置AOP增強--> <aop:config> <!--切入點--> <aop:pointcut id="p" expression="execution(* com.atguigu.spring5.aopanno.aopxml.Book.buy(..))"/> <!--配置切面--> <aop:aspect ref="bookProxy"> <!--配置要增強的具體方法--> <aop:before method="before" pointcut-ref="p"/> </aop:aspect> </aop:config> ``` --- # 10. Jdbc Template ### 10.1 什麼是Jdbc Template? * Spring框架對JDBC進行封裝, 方便實現對資料庫操作 1. 引入依賴 ![](https://i.imgur.com/FT0z68g.png) 2. 在配置文件中配置連接池 (可以用properties配置文件設定) ```xml= <!--直接配置連線池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql:///user_db"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> </bean> ``` 3. 配置JdbcTemplate, 注入DataSource ```xml= <!--JDBC Template對象--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!--注入DataSource--> <property name="dataSource" ref="dataSource"/> </bean> ``` 4. 創建service, dao類, 在dao class注入JdbcTemplate * 配置文件 ```xml= <!--組件掃描--> <context:component-scan base-package="com.atguigu.spring5"></context:component-scan> ``` * DAO ```java= @Repository public class BookDaoImpl implements BookDao { //注入JDBC Template @Autowired private JdbcTemplate jdbcTemplate; } ``` * Service ```java= @Service public class BookService { //注入dao @Autowired private BookDao bookDao; } ``` ### 10.2 Jdbc Template操作database(Add) 1. 對應資料庫table, 創建實體類(一般來說, 一個table一個對應的實體類) ```java= package com.atguigu.spring5.entity; public class User { private String userId; private String userName; private String userStatus; public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getUserStatus() { return userStatus; } public void setUserStatus(String userStatus) { this.userStatus = userStatus; } } ``` 2. 編寫dao和service 2.1 在dao進行資料庫add操作 2.2 調用JdbcTemplate的update方法實現添加操作 * 第一個參數: SQL語法 * 第二個參數: 可變參數(? 代表的屬性) ![](https://i.imgur.com/2vMWl0D.png) ```java= @Repository public class BookDaoImpl implements BookDao { //1. 創建SQL語句 private String addBookSQL = "INSERT INTO t_book values(?,?,?)"; //注入JDBC Template @Autowired private JdbcTemplate jdbcTemplate; @Override public void add(Book book) { //2.調用方法實現 int resultRow = jdbcTemplate.update(addBookSQL,book.getBookId(), book.getBookName(), book.getBookStatus()); System.out.println(resultRow); } } ``` ### 10.2 Jdbc Template操作database(修改和刪除) 1. 修改 ```java= @Override public void updateBook(Book book) { String sql = "UPDATE t_book set bookname=?,bookstatus=? WHERE bookid=?"; Object[] args = {book.getBookName(), book.getBookStatus(), book.getBookId()}; int update = jdbcTemplate.update(sql, args); System.out.println(update); } ``` 2. 刪除 ```java= @Override public void deleteBook(String id) { String sql = "DELETE FROM t_book WHERE bookid=?"; int delete = jdbcTemplate.update(sql, id); System.out.println(delete); } ``` ### 10.2 Jdbc Template操作database(查詢) 1. 查詢返回值 ```java= @Override public int selectCount() { String sql = "SELECT COUNT(*) FROM t_book"; return jdbcTemplate.queryForObject(sql, Integer.class); //參數: 1. SQL語法, 2. 返回值的Class } ``` 2. 查詢返回Object * 第一個參數:SQL語句, 第三個參數:參數值(?的參數) * `.queryForObject的第二個參數`Row Mapper是一個interface, 針對返回不同類型數據, 使用這個介面實現類完成數據封裝 ```java= //查詢詳情(返回Object) @Override public Book findOne(String id) { String sql = "SELECT * WHERE bookid=?"; Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id); return book; } ``` 3. 查詢返回Collection ```java= //查詢返回集合 @Override public List<Book> findList() { String sql = "SELECT * FROM t_book"; List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class)); return bookList; } ``` ### 10.3 批量添加 ```java= //批量添加 @Override public void batchAddBooks(List<Object[]> batchAddArgs) { String sql = "INSERT INTO t_book values(?,?,?)"; int[] ints = jdbcTemplate.batchUpdate(sql, batchAddArgs); System.out.println(Arrays.toString(ints)); } ``` ### 10.4 批量修改 ```java= //批量修改 @Override public void batchUpdateBooks(List<Object[]> batchAddArgs) { int[] ints = jdbcTemplate.batchUpdate(updateBookSql, batchAddArgs); System.out.println(ints); } ``` ### 10.5 批量刪除 ```java= //批量刪除 @Override public void batchDeleteBooks(List<Object[]> batchAddArgs) { int[] ints = jdbcTemplate.batchUpdate(deleteBooksql, batchAddArgs); System.out.println(Arrays.toString(ints)); } ``` --- # 11. Spring的交易管理 ### 維持交易的ACID! 1. 原子性: 過程中不可分割, 操作中如果有一個失敗, 所有操作都失敗 2. 一致性: 操作前和操作後總量不變 3. 隔離性: 多事務操作之間(各種事務可以併發執行), 彼此不會產生影響 4. 持久性: 狀態的改變是持久的,不會失效。一旦某個事務提交, 則它造成的狀態變更就是永久性的。 ### 當出現異常時 #### 事務操作 1. 開啟交易管理 2. 進行業務操作 3. (沒有發生異常)提交事務(=交易 = transaction) 4. (出現異常)事務回滾(rollback) ##### Spring交易管理介紹 1. 交易的程式碼要加在service層(業務邏輯層) 2. 在Spring進行事務管理操作, 有兩種方式: 1. 編程式交易管理 * 在程式碼裡面進行交易管理 * 需要不斷實現程式碼, 程式碼會變得攏長, 一般不使用 ```java= //原理 public void changeMoneys(){ try { //第一步: 開啟交易管理 //第二步: 進行交易操作 userDao.addMoney(); //製造異常 int i = 10/0; userDao.reduceMondey(); //第三步: 沒有發生異常, 提交事務 } catch (Exception e){ //第四步: 出現異常, 事務rollback } } ``` 2.聲明式交易管理(使用) 1. :bulb:基於註解方式(方便, 簡單) 2. 基於xml配置文件方式 3. Spring的聲明式交易管理, 底層使用AOP * 聲明式交易 : AOP (交由容器管理), 不改變原本的程式碼, 達到交易的效果 4.Spring事務管理API * 提供一個介面,代表事務管理器, 此介面針對不同的ORM框架, 提供不同的實現類 ![](https://i.imgur.com/mC5vlcy.png) #### 以註解方式實現聲明式事務管理 1. 在Spring配置文件配置事務管理器 ```xml= <!--配置事務管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--注入DataSource--> <property name="dataSource" ref="dataSource" /> </bean> ``` 2. 在Spring配置文件開啟事務註解 (1)在配置文件中引入名稱空間 tx ```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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd"> ``` (2)開啟事務註解 ```xml <!--開啟事務註解--> <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven> ``` 3. 在Service類上, 或者Service類裡面的method上面, 添加事務註解 (1) `@Transactional` 可以加上class上面, 也可以加上方法上面 (2)如果把這個註解加在class上面, 表示這個class的所有方法都添加事務 (3)如果把這個註解加在方法上面, 表示為這個方法添加事務 #### 聲明式事務管理參數配置 1. 在Service class上面添加註釋`@Transactional`, 在這個註釋裡面可以配置事務相關參數 ![](https://i.imgur.com/tSBssCV.png) * propogation: 事務的傳播行為 * 多事務方法直接進行調用, 這個過程中事務是如何進行管理的 ![](https://i.imgur.com/0b5F5VX.png) ![](https://i.imgur.com/aFem9AX.png) (如果不指定傳播行為參數, 預設為REQUIRED) * isolation: 事務的隔離級別 * 併發操作中產生的級別 * 多事務操作之間, 彼此不會產生影響 * 不考慮隔離性的話, 會產生三讀問題 1. 髒讀: 一個未提交事務, 讀取到另一個未提交事務的數據![](https://i.imgur.com/Eq1qu11.png) 2. 不可重複讀: 一個未提交事務, 讀取到另一個提交事務修改的數據![](https://i.imgur.com/jMhiQMN.png) 3. 幻讀: 一個未提交事務, 讀取到另一個提交事務添加的數據 * 解決方法: 通過設置事務隔離級別, 解決讀的問題 ![](https://i.imgur.com/ocltBKU.png) * Spring的預設隔離級別是根據使用的database而不同 ex. MySQL=REPEATABLE READ ![](https://i.imgur.com/vPrJGaF.png) ```java= @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED) ``` * timeout: 超時時間 * 事務需要在一定時間內提交, 否則進行rollback * Spring預設值是-1(不超時), 設置單位以秒來計算 ```java= @Transactional(timeout = 5,propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED) ``` * readOnly: 是否只讀 * 讀:查詢操作, 寫:添加修改刪除操作 * readOnly預設值為false, 表可以查詢, 也可以新增刪除修改 * readyOnly值設置為true之後, 只能進行查詢操作 ```java= @Transactional(readOnly = true,timeout = 5,propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED) ``` * rollbackFor: 回滾 * 設置出現那些異常, 進行交易rollback * [觸發時可能遇到的問題, 參考我的notion](https://www.notion.so/transactional-does-not-rollback-on-checked-exceptions-97bc2dc798df49a98d21d682c7d36e0f) :::danger :warning: 注意 :warning: spring的交易管理註解 @Transactional 預設只會被runtime exception觸發rollback, checked exception會因為可以使用try catch捕獲而不觸發rollback :bulb: 解決方法: @Transactional(rollbackFor = Exception.class) ::: * noRollbackFor: 不回滾 * 設置出現那些異常, 不進行交易rollback #### XML方式實現聲明式事務管理 ##### 在Spring配置文件中進行配置 1. 配置事務管理器 2. 配置通知 3. 配置切入點和切面 需要在xml檔裡面撰寫配置 ```xml= <!--配置聲明式交易--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <constructor-arg ref="datasource" /> </bean> <!--結合AOP實現交易的織入(注意!!需導入XML約束文件!)--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!--配置交易方法--> <!--配置交易傳播特性: new propagation(預設為REQUIRED)--> <tx:method name="add" propagation="REQUIRED"/> <tx:method name="delete" propagation="REQUIRED"/> <tx:method name="update" propagation="REQUIRED"/> <tx:method name="query" read-only="true"/> <tx:method name="*" propagation="REQUIRED"/>(可以只寫這個) </tx:attributes> </tx:advice> <!--配置交易切入點--> <aop:config> <aop:pointcut id="txPointCut" expression="execution(* com.kuang.mapper.*.* (..))" /> <aop:advicor advice-ref="txAdvice" pointcut-ref="txPointCut" /> </aop:config> ``` #### 完全註解方式實現聲明式事務管理 ```java= package com.atguigu.spring5.config; import com.alibaba.druid.pool.DruidDataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.sql.DataSource; @Configuration //表示此class為配置類 @ComponentScan(basePackages = "com.atguigu") //開啟組件掃描 @EnableTransactionManagement//開啟事務管理 public class TxConfig { // 創建連接池 @Bean public DruidDataSource getDruidDataSource(){ DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql:///user_db"); dataSource.setUsername("root"); dataSource.setPassword("root"); return dataSource; } // 創建JDBC template對象 @Bean public JdbcTemplate getJdbcTemplate(DataSource dataSource){ //()裡面的參數, spring會到ioc容器中, 根據類型找到dataSource JdbcTemplate jdbc = new JdbcTemplate(); //注入dataSource jdbc.setDataSource(dataSource); return jdbc; } // 創建事務管理器 @Bean public DataSourceTransactionManager manager(DataSource dataSource){ DataSourceTransactionManager manager = new DataSourceTransactionManager(); manager.setDataSource(dataSource); return manager; } } ``` ### Q:為何需要交易管理 * 如無配置交易管理, 則可能存在數據提交不一致的情況(ex. 存提款餘額金額不一致) * 如果我們不在SPRING中配置聲明式交易, 我們就需要在程式碼裡面手動配置交易管理! * 交易管理在開發中相當重要, 涉及到數據的一致性和完整性問題, 不可馬虎! --- # Spring5新功能 ### 整合日誌框架log4j2 ### Nullable註解 * Nullable註解可以用在方法上面, 屬性上面, 參數上面 1. method上面: 表示方法返回值可以為空![](https://i.imgur.com/QsQoIHM.png) 2. 參數前面: 參數值可以為空![](https://i.imgur.com/xwt7d5J.png) 3. 屬性上面: 屬性值可以為空![](https://i.imgur.com/8PW4wkL.png) ### Spring5核心容器支援函數式風格表達式GenericApplicationContext(lambda風格) ```java= //函數式風格創建對象, 交給Spring進行管理 @Test public void testGenericApplicationContext(){ //第一步: 創建GenericApplicationContext對象 GenericApplicationContext context = new GenericApplicationContext(); //第二步: 調用方法進行對象註冊 context.refresh(); context.registerBean(User.class, () -> new User()); //第三步: 獲取在Spring裡面註冊的對象 // !!此處注意不是用小寫的bean name獲取bean, 因為我們並沒有用bean name註冊, ioc容器裡面沒有此實例!! //需要用完整路徑獲取bean, 或註冊時指定bean name User user = (User)context.getBean("com.atguigu.spring5.dao.User"); System.out.println(user); } ``` ### Spring5支援整合JUnit5 * 整合JUnit4 * 第一步: 引入Spring相關針對測試之依賴 ![](https://i.imgur.com/fi7eJOT.png) ![](https://i.imgur.com/3jXvIFh.png) * 第二步: 創建測試類, 使用註解方式完成 ```java= @RunWith(SpringJUnit4ClassRunner.class) //單元測試框架 @ContextConfiguration("classpath:bean1.xml") //加載配置文件 public class JTest { //直接注入Service @Autowired private UserService userService; @Test public void test1(){ userService.changeMoneys(); } } ``` * Spring5整合JUnit5 * 第一步: 引入JUnit5的jar包 * 第二步: 創建測試類, 使用註解完成 * 也可使用複合註解替代上面兩行註解完成整合`@SpringJUnitConfig(locations = "classpath:bean1.xml")` ```java= @ExtendWith(SpringExtension.class) @ContextConfiguration("classpath:bean1.xml") // @SpringJUnitConfig(locations = "classpath:bean1.xml") public class JTest5 { //直接注入Service @Autowired private UserService userService; @Test public void test1() { userService.changeMoneys(); } } ```