1. 程式人生 > 實用技巧 >Spring Framework

Spring Framework

相關知識點

  1. 概念:spring是開源的輕量級框架

  2. 核心:aop:面向切面程式設計、ioc:控制反轉

  3. spring是一站式框架,在javaee三層結構中,每一層都提供不同的解決技術

    1. web層:springMVC

    2. service層:spring的ioc

    3. dao層:spring的jdbcTemplate

IOC

  1. 全稱:Inversion of Control (控制反轉)

  2. DI:Dependency Injection 依賴注入:需要有 IOC 的環境,Spring 建立這個類的過程中,Spring 將類的依賴的屬性設定進去.

  3. 理念:把物件的建立交給spring進行管理

  4. 畫圖分析ioc實現原理:

入門案例

  1. 匯入核心jar包和支援日誌輸出的jar包

  2. 建立spring配置檔案,配置建立類,官方建議命名:官方建議applicationContext.xml

  3. 在xml檔案中引入schema約束

  4. 編寫相關類

    1 public interface UserDao { 
    2     public void sayHello(); 
    3 }
    4 public class UserDaoImpl implements UserDao { 
    5     @Override 
    6     public void sayHello() {
    7         System.out.println("Hello Spring..."); 
    
    8 } 9 }
  5. 完成配置

    1 <!-- Spring 的入門案例================ --> 
    2 <bean id="userDao" class="cn.itcast.spring.demo1.UserDaoImpl"></bean>
  6. 編寫測試程式碼

     

    1 @Test 
    2 // Spring 的方式: 
    3 public void demo(){ 
    4 // 建立 Spring 的工廠類: 
    5     ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); 
    
    6 // 通過工廠解析 XML 獲取 Bean 的例項. 7 UserDao userDao = (UserDao) ac.getBean("userDao"); 8 userDao.sayHello(); 9 }

ApplicationContext介面

//該介面的三個實現類
ClassPathXmlApplicationContext :載入類路徑下 Spring 的配置檔案.
FileSystemXmlApplicationContext :載入本地磁碟下 Spring 的配置檔案
AnnotationConfigApplicationContext :當我們使用註解配置容器物件時,需要使用此類來建立 spring 容器。它用來讀取註解。

基於XML的 IOC 配置

Spring 的配置檔案的開發

//第一種:建立工廠的時候載入多個配置檔案: 
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml","applicationContext2.xml");
//第二種:在一個配置檔案中包含另一個配置檔案:
<import resource="applicationContext2.xml"></import>

Spring生成bean的三種方式

  1. 有無參構造方法

    <!-- 方式一:無參的構造方法的例項化 --> 
    <bean id="bean1" class="cn.itcast.spring.demo3.Bean1"></bean>
    <!-- 方法裡不存在無參的構造方法就會創造失敗 -->
  2. 靜態工廠例項化的方式

    //提供一個工廠類: 
    public class Bean2Factory {
    public static Bean2 getBean2(){ return new Bean2(); }
    }

    <!-- 方式二:xml靜態工廠例項化 Bean -->
    <bean id="bean2" class="cn.itcast.spring.demo3.Bean2Factory" factory-method="getBean2"/>
  3. 例項工廠例項化的方式

    //提供 Bean3 的例項工廠: 
    public class Bean3Factory {
    public Bean3 getBean3(){ return new Bean3(); }
    }
    <!-- 方式三:例項工廠例項化 Bean -->
    <bean id="bean3Factory" class="cn.itcast.spring.demo3.Bean3Factory"></bean>
    <bean id="bean3" factory-bean="bean3Factory" factory-method="getBean3"></bean>

Bean標籤常用屬性

  1. id屬性:起名稱,id屬性值名稱任意命名

    1. id屬性值,不能包含特殊符號

    2. 根據id值得到配置物件

  2. name屬性:功能和id屬性一樣的,id屬性值不能包含特殊符號,但是在name屬性值裡面可以包含特殊符號

  3. class屬性:建立物件所在類的全路徑

  4. scope屬性:

    1. singleton:預設值,單例

      • 生命週期:從建立容器時,物件就被建立了,直到容器被銷燬物件才被銷燬

    2. prototype:多例

      • 每次訪問物件時,都會重新建立物件例項。物件長時間不用就會被垃圾回收器回收了。

    3. request:建立物件把物件放到request域裡面

    4. session:建立物件把物件放到session域裡面

    5. globalSession:建立物件把物件放到globalSession裡面

  5. init-method:指定類中的初始化方法名稱。

  6. destroy-method:指定類中銷燬方法名稱。

Spring 的 Bean 的屬性注入

 1     <!-- 第一種:構造方法的方式 --> 
 2 <bean id="car" class="cn.itcast.spring.demo4.Car"> 
 3     <constructor-arg name="name" value="保時捷"/> 
 4     <constructor-arg name="price" value="1000000"/> 
 5 </bean>
 6 
 7     <!-- 第二種:set 方法的方式 --> 
 8 <bean id="car2" class="cn.itcast.spring.demo4.Car2"> 
 9     <property name="name" value="奇瑞 QQ"/> 
10     <property name="price" value="40000"/> 
11 </bean>
12     <!-- 注入物件型別的屬性 --> 
13         <!--
14             value:它能賦的值是基本資料型別和 String 型別
15             ref:它能賦的值是其他 bean 型別,也就是說,必須得是在配置檔案中配置過的 bean
16         -->
17 <bean id="person" class="cn.itcast.spring.demo4.Person">
18     <property name="name" value="會希"/> 
19     <!-- ref 屬性:引用另一個 bean 的 id 或 name --> 
20     <property name="car2" ref="car2"/> 
21 </bean>
22 
23     <!-- p 名稱空間的屬性注入的方式 --> 
24         <!-- Spring2.x 版本後提供的方式 -->
25         <!-- 
26             * 普通屬性: p:屬性名稱=”” 
27             * 物件型別屬性: p:屬性名稱-ref=””
28          -->
29 <bean id="car2" class="cn.itcast.spring.demo4.Car2" p:name="寶馬7" p:price="12000"/> 
30 <bean id="person" class="cn.itcast.spring.demo4.Person" p:name="思聰" p:car-ref="car2"/>
31 
32     <!-- SpEL (Spring Expression Language) 的方式的屬性注入 --> 
33         <!-- Spring3.x 版本後提供的方式 -->
34         <!-- 語法:#{ SpEL } -->
35 <bean id="car2" class="cn.itcast.spring.demo4.Car2"> 
36     <property name="name" value="#{'賓士'}"/> 
37     <property name="price" value="#{800000}"/> 
38 </bean> 
39 <bean id="person" class="cn.itcast.spring.demo4.Person"> 
40     <property name="name" value="#{'冠希'}"/> 
41     <property name="car2" value="#{car2}"/> 
42 </bean>
43 <!-- Spring 的複雜型別的注入===================== --> 
44 <bean id="collectionBean" class="cn.itcast.spring.demo5.CollectionBean"> 
45     <!-- 陣列型別的屬性 --> 
46     <!-- 在注入集合資料時,只要結構相同,標籤可以互換 -->
47     <property name="arrs"> 
48         <array>
49             <value>會希</value> 
50             <value>冠希</value> 
51             <value>天一</value> 
52         </array> 
53     </property> 
54     <!-- 注入 List 集合的資料 --> 
55     <property name="list"> 
56         <list>
57             <value>芙蓉</value> 
58             <value>如花</value> 
59             <value>鳳姐</value> 
60         </list> 
61     </property>
62     <!-- 注入 set 集合的資料 --> 
63     <property name="set"> 
64         <set>
65             <value>AAA</value> 
66             <value>BBB</value> 
67             <value>CCC</value>
68 
69         </set>
70     </property>
71     <!-- 注入 Map 集合 --> 
72     <property name="map"> 
73         <map>
74             <entry key="aaa" value="111"/> 
75             <entry key="bbb" value="222"/> 
76             <entry key="testB"> 
77                 <value>bbb</value>
78             </entry> 
79         </map> 
80     </property> 
81     <!-- Properties 的注入 --> 
82     <property name="properties"> 
83         <props> 
84             <prop key="username">root</prop> 
85             <prop key="password">123</prop>
86         </props> 
87     </property> 
88 </bean>

基於註解的 IOC 配置

入門案例

  1. 在基於註解的配置中,我們還要多拷貝一個 aop 的 jar 包。

  2. 使用@Component (元件)註解配置管理的資源

    • 當我們使用註解注入時,set 方法不用寫

  3. 建立 spring 的 xml 配置檔案並開啟對註解的支援

    <!-- 告知 spring 建立容器時要掃描的包 --> 
    <context:component-scan base-package="com.itheima"></context:component-scan>

常用註解

用於建立物件

相當於:<bean id="" class="">

  • @Component

    • 作用:把資源讓 spring 來管理。相當於在 xml 中配置一個 bean。

    • 屬性:

      • value:指定 bean 的 id。

        • 如果不指定 value 屬性,預設 bean 的 id 是當前類的類名。首字母小寫

  • @Controller @Service @Repository

    • 他們三個註解都是針對前一個的衍生註解,他們的作用及屬性都是一模一樣的。他們只不過是提供了更加明確的語義化。

    • @Controller:一般用於表現層的註解。

    • @Service:一般用於業務層的註解。

    • @Repository:一般用於持久層的註解。

用於注入資料的

相當於:<property name="" ref=""> <property name="" value="">

  • @Autowired

    • 作用:自動按照型別注入。當使用註解注入屬性時,set 方法可以省略。它只能注入其他 bean 型別。當有多個型別匹配時,使用要注入的物件變數名稱作為 bean 的 id,在 spring 容器查詢,找到了也可以注入成功。找不到就報錯。

  • @Qualifier

    • 作用:在自動按照型別注入的基礎之上,再按照 Bean 的 id 注入。它在給欄位注入時不能獨立使用,必須和@Autowire 一起使用;但是給方法引數注入時,可以獨立使用。

    • 屬性:value:指定 bean 的 id。

  • @Resource

    • 作用:直接按照 Bean 的 id 注入。它也只能注入其他 bean 型別,相當於前兩個一起用

    • 屬性:name:指定 bean 的 id。

  • @Value

    • 作用:注入基本資料型別和 String 型別資料的

    • 屬性:value:用於指定值

用於改變作用範圍的

相當於:<bean id="" class="" scope="">

  • @Scope

    • 作用:指定 bean 的作用範圍。

    • 屬性:value:指定範圍的值。

    • 取值:singleton prototype request session globalsession

和生命週期相關的

相當於:<bean id="" class="" init-method="" destroy-method="" />

  • @PostConstruct

    • 作用:用於指定初始化方法。

  • @PreDestroy

    • 作用:用於指定銷燬方法。

總結

  1. 註解的優勢:配置簡單,維護方便(我們找到類,就相當於找到了對應的配置)。

  2. XML 的優勢:修改時,不用改原始碼。不涉及重新編譯和部署。

新註解說明

知識點

  1. @Configuration

    • 作用:用於指定當前類是一個 spring 配置類,當建立容器時會從該類上載入註解。獲取容器時需要使用 AnnotationApplicationContext(有@Configuration 註解的類.class)。

    • 屬性:value:用於指定配置類的位元組碼

  2. @ComponentScan

    • 作用:用於指定 spring 在初始化容器時要掃描的包。作用和在 spring 的 xml 配置檔案中的: <context:component-scan base-package="com.itheima"/>是一樣的。

    • 屬性:basePackages:用於指定要掃描的包。和該註解中的 value 屬性作用一樣。

  3. @Bean

    • 作用:該註解只能寫在方法上,表明使用此方法建立一個物件,並且放入 spring 容器。

    • 屬性:name:給當前@Bean 註解方法建立的物件指定一個名稱(即 bean 的 id)

  4. @PropertySource

    • 作用:用於載入.properties 檔案中的配置。例如我們配置資料來源時,可以把連線資料庫的資訊寫到properties 配置檔案中,就可以使用此註解指定 properties 配置檔案的位置。

    • 屬性:value[]:用於指定 properties 檔案位置。如果是在類路徑下,需要寫上 classpath:

  5. @Import

    • 作用:用於匯入其他配置類,在引入其他配置類時,可以不用再寫@Configuration 註解。當然,寫上也沒問題。

    • 屬性:value[]:用於指定其他配置類的位元組碼。

整合案例

1 jdbc.properties 檔案:
2     jdbc.driver=com.mysql.jdbc.Driver
3     jdbc.url=jdbc:mysql://localhost:3306/day44_ee247_spring
4     jdbc.username=root
5     jdbc.password=1234
1 xml引入屬性檔案的兩種方式
2 一種方式:
3  <!-- 引入外部屬性檔案: -->
4  <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> 
5      <property name="location" value="classpath:jdbc.properties"/>
6 </bean>
7 另一種方式:
8 <context:property-placeholder location="classpath:jdbc.properties"/>
 1 @Configuration
 2 @PropertySource("classpath:jdbc.properties")
 3 public class JdbcConfig {
 4     
 5     @Value("${jdbc.driver}")
 6     private String driver;
 7     @Value("${jdbc.url}")
 8     private String url;
 9     @Value("${jdbc.username}")
10     private String username;
11     @Value("${jdbc.password}")
12     private String password;
13    
14     /**
15     * 建立一個數據源,並存入 spring 容器中
16     * @return
17     */
18     @Bean(name="dataSource")
19     public DataSource createDataSource() {
20         try {
21             ComboPooledDataSource ds = new ComboPooledDataSource(); 
22             ds.setDriverClass(driver);
23             ds.setJdbcUrl(url);            
24             ds.setUser(username);
25             ds.setPassword(password);
26             return ds;
27         } catch (Exception e) {
28             throw new RuntimeException(e);
29         } 
30     } 
31 }
1 //父配置檔案
2 @Configuration
3 @ComponentScan(basePackages = "com.itheima.spring") 
4 @Import({JdbcConfig.class})
5 public class SpringConfiguration { 
6 }
1 //獲取spring容器
2 ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);

Spring 整合 Junit

  1. 為什麼不把測試類配到xml中

    • 在解釋這個問題之前,先解除大家的疑慮,配到 XML 中能不能用呢?

    • 答案是肯定的,沒問題,可以使用。

    • 那麼為什麼不採用配置到 xml 中的方式呢?

    • 這個原因是這樣的:

    • 第一:當我們在 xml 中配置了一個 bean,spring 載入配置檔案建立容器時,就會建立物件。

    • 第二:測試類只是我們在測試功能時使用,而在專案中它並不參與程式邏輯,也不會解決需求上的問題,所以建立完了,並沒有使用。那麼存在容器中就會造成資源的浪費。

    • 所以,基於以上兩點,我們不應該把測試配置到 xml 檔案中。

  2. 配置步驟

     1 /**
     2 * 測試類
     3 * @author 黑馬程式設計師
     4 * @Company http://www.ithiema.com
     5 * @Version 1.0
     6 */
     7 @RunWith(SpringJUnit4ClassRunner.class)
     8 //使用@RunWith 註解替換原有執行器
     9 
    10 @ContextConfiguration(locations= {"classpath:bean.xml"})
    11 //使用@ContextConfiguration 指定 spring 配置檔案的位置
    12 //locations 屬性:用於指定配置檔案的位置。如果是類路徑下,需要用 classpath:表明
    13 //classes 屬性:用於指定註解的類。當不使用 xml 配置時,需要用此屬性指定註解類的位置。
    14 public class AccountServiceTest {
    15     @Autowired
    16     //@Autowired 給測試類中的變數注入資料
    17     private IAccountService as ; 
    18 }

AOP

  1. AOP:全稱是 Aspect Oriented Programming 即:面向切面程式設計。簡單的說它就是把我們程式重複的程式碼抽取出來,在需要執行的時候,使用動態代理的技術,在不修改原始碼的基礎上,對我們的已有方法進行增強。

經典案例

 1 public class AccountServiceImpl implements IAccountService {
 2     private IAccountDao accountDao = new AccountDaoImpl();
 3     @Override
 4     public void updateAccount(Account account) {
 5         try {
 6             TransactionManager.beginTransaction();
 7             accountDao.update(account);
 8             TransactionManager.commit();
 9         } catch (Exception e) {
10             TransactionManager.rollback();
11             e.printStackTrace();
12         }finally {
13             TransactionManager.release();
14         } 
15     }
16     @Override
17     public Account findAccountById(Integer accountId) {
18         Account account = null;
19         try {
20             TransactionManager.beginTransaction();
21             account = accountDao.findById(accountId);
22             TransactionManager.commit();
23             return account;
24         } catch (Exception e) {
25             TransactionManager.rollback();
26             e.printStackTrace();
27         }finally {
28             TransactionManager.release();
29         }
30         return null;
31     }
32     @Override
33     public void transfer(String sourceName, String targetName, Float money) {
34         try {
35             TransactionManager.beginTransaction();
36             Account source = accountDao.findByName(sourceName);
37             Account target = accountDao.findByName(targetName);
38             source.setMoney(source.getMoney()-money);
39             target.setMoney(target.getMoney()+money);
40             accountDao.update(source);
41             int i=1/0;
42             accountDao.update(target);
43             TransactionManager.commit();
44         } catch (Exception e) {
45             TransactionManager.rollback();
46             e.printStackTrace();
47         }finally {
48             TransactionManager.release();
49         } 
50     }
51 }
52 public class TransactionManager {
53     //定義一個 DBAssit
54     private static DBAssit dbAssit = new DBAssit(C3P0Utils.getDataSource(),true);
55     //開啟事務
56     public static void beginTransaction() {
57         try {
58             dbAssit.getCurrentConnection().setAutoCommit(false);
59         } catch (SQLException e) {
60             e.printStackTrace();
61         } 
62     }
63     //提交事務
64     public static void commit() {
65         try {
66             dbAssit.getCurrentConnection().commit();
67         } catch (SQLException e) {
68             e.printStackTrace();
69         } 
70     }
71     //回滾事務
72    public static void rollback() {
73        try {
74            dbAssit.getCurrentConnection().rollback();
75        } catch (SQLException e) {
76            e.printStackTrace();
77        } 
78    }
79     //釋放資源
80     public static void release() {
81         try {
82             dbAssit.releaseConnection();
83         } catch (Exception e) {
84             e.printStackTrace();
85         } 
86     }
87 } 

案例分析:通過對業務層改造,已經可以實現事務控制了,但是由於我們添加了事務控制,也產生了一個新的問題:業務層方法變得臃腫了,裡面充斥著很多重複程式碼。並且業務層方法和事務控制方法耦合了

動態代理

  1. 特點

    • 位元組碼隨用隨建立,隨用隨載入。

    • 它與靜態代理的區別也在於此。因為靜態代理是位元組碼一上來就建立好,並完成載入。

    • 裝飾者模式就是靜態代理的一種體現

經典案例

 1 public interface IActor {
 2     public void basicAct(float money);
 3     public void dangerAct(float money);
 4 }
 5 
 6 public class Actor implements IActor {
 7     public void basicAct(float money) {
 8         System.out.println("拿到錢,開始基本的表演:" + money);
 9     }
10 
11     public void dangerAct(float money) {
12         System.out.println("拿到錢,開始危險的表演:" + money);
13     }
14 }

使用 JDK 官方的 Proxy 類建立代理物件

 1 public class Client {
 2     public static void main(String[] args) {
 3         //一個劇組找演員:
 4         final Actor actor = new Actor();//直接
 5         /**
 6          * 代理:
 7          * 間接。
 8          * 獲取代理物件:
 9          * 要求:
10          * 被代理類最少實現一個介面
11          * 建立的方式
12          * Proxy.newProxyInstance(三個引數)
13          * 引數含義:
14          * ClassLoader:和被代理物件使用相同的類載入器。
15          * Interfaces:和被代理物件具有相同的行為。實現相同的介面。
16          * InvocationHandler:如何代理。
17          * 策略模式:使用場景是:
18          * 資料有了,目的明確。
19          * 如何達成目標,就是策略。
20          *
21          */
22         IActor proxyActor = (IActor) Proxy.newProxyInstance(
23                 actor.getClass().getClassLoader(),
24                 actor.getClass().getInterfaces(),
25                 new InvocationHandler() {
26                     /**
27                      * 執行被代理物件的任何方法,都會經過該方法。
28                      * 此方法有攔截的功能。
29                      *
30                      * 引數:
31                      * proxy:代理物件的引用。不一定每次都用得到
32                      * method:當前執行的方法物件
33                      * args:執行方法所需的引數
34                      * 返回值:
35                      * 當前執行方法的返回值
36                      */
37                     @Override
38                     public Object invoke(Object proxy, Method method, Object[] args)
39                             throws Throwable {
40                         String name = method.getName();
41                         Float money = (Float) args[0];
42                         Object rtValue = null;
43                         //每個經紀公司對不同演出收費不一樣,此處開始判斷
44                         if ("basicAct".equals(name)) {
45                             //基本演出,沒有 2000 不演
46                             if (money > 2000) {
47                                 //看上去劇組是給了 8000,實際到演員手裡只有 4000
48                                 //這就是我們沒有修改原來 basicAct 方法原始碼,對方法進行了增強
49                                 rtValue = method.invoke(actor, money / 2);
50                             }
51                         }
52                         if ("dangerAct".equals(name)) {
53                             //危險演出,沒有 5000 不演
54                             if (money > 5000) {
55                                 //看上去劇組是給了 50000,實際到演員手裡只有 25000
56                                 //這就是我們沒有修改原來 dangerAct 方法原始碼,對方法進行了增強
57                                 rtValue = method.invoke(actor, money / 2);
58                             }
59                         }
60                         return rtValue;
61                     }
62                 });
63         //沒有經紀公司的時候,直接找演員。
64         // actor.basicAct(1000f);
65         // actor.dangerAct(5000f);
66         //劇組無法直接聯絡演員,而是由經紀公司找的演員
67         proxyActor.basicAct(8000f);
68         proxyActor.dangerAct(50000f);
69     }
70 }

使用 CGLib 的 Enhancer 類建立代理物件

 1 public class Client {
 2     public static void main(String[] args) {
 3         final Actor actor = new Actor();
 4         /**
 5          * 基於子類的動態代理
 6          * 要求:
 7          * 被代理物件不能是最終類
 8          * 用到的類:
 9          * Enhancer
10          * 用到的方法:
11          * create(Class, Callback)
12          * 方法的引數:
13          * Class:被代理物件的位元組碼
14          * Callback:如何代理
15          *
16          * @param args
17          */
18         Actor cglibActor = (Actor) Enhancer.create(actor.getClass(),
19                 new MethodInterceptor() {
20                     /**
21                      * 執行被代理物件的任何方法,都會經過該方法。在此方法內部就可以對被代理物件的任何
22                      方法進行增強。
23                      *
24                      * 引數:
25                      * 前三個和基於介面的動態代理是一樣的。
26                      * MethodProxy:當前執行方法的代理物件。
27                      * 返回值:
28                      * 當前執行方法的返回值
29                      */
30                     @Override
31                     public Object intercept(Object proxy, Method method, Object[] args,
32                                             MethodProxy methodProxy) throws Throwable {
33                         String name = method.getName();
34                         Float money = (Float) args[0];
35                         Object rtValue = null;
36                         if ("basicAct".equals(name)) {
37                             //基本演出
38                             if (money > 2000) {
39                                 rtValue = method.invoke(actor, money / 2);
40                             }
41                         }
42                         if ("dangerAct".equals(name)) {
43                             //危險演出
44                             if (money > 5000) {
45                                 rtValue = method.invoke(actor, money / 2);
46                             }
47                         }
48                         return rtValue;
49                     }
50                 });
51         cglibActor.basicAct(10000);
52         cglibActor.dangerAct(100000);
53     }
54 }

AOP 相關術語

  • Joinpoint(連線點):所謂連線點是指那些被攔截到的點。在 spring 中,這些點指的是方法,因為 spring 只支援方法型別的連線點。

  • Pointcut切入點):所謂切入點是指我們要對哪些 Joinpoint 進行攔截的定義。

  • Advice(通知/增強):所謂通知是指攔截到 Joinpoint 之後所要做的事情就是通知。

    • 通知的型別:前置通知,後置通知,異常通知,最終通知,環繞通知。

  • Introduction(引介):引介是一種特殊的通知在不修改類程式碼的前提下, Introduction 可以在執行期為類動態地新增一些方法或 Field。

  • Target(目標物件):代理的目標物件。

  • Weaving(織入):是指把增強應用到目標物件來建立新的代理物件的過程。spring 採用動態代理織入,而 AspectJ 採用編譯期織入和類裝載期織入。

  • Proxy(代理):一個類被 AOP 織入增強後,就產生一個結果代理類。

  • Aspect(切面):是切入點和通知(引介)的結合。

基於xml的 AOP 配置

  1. 使用 aop:config 宣告 aop 配置

    <aop:config>
        <!-- 配置的程式碼都寫在此處 -->
    </aop:config>
  2. 使用 aop:aspect 配置切面

    • 屬性:

      • id:給切面提供一個唯一標識。

      • ref:引用配置好的通知類 bean 的 id

    <aop:aspect id="txAdvice" ref="txManager">
        <!--配置通知的型別要寫在此處-->
    </aop:aspect>
  3. 使用 aop:pointcut 配置切入點表示式

    • 屬性:

      • expression:用於定義切入點表示式。

      • id:用於給切入點表示式提供一個唯一標識

    <aop:pointcut expression="execution(
         public void com.itheima.service.impl.AccountServiceImpl.transfer(
          java.lang.String, java.lang.String, java.lang.Float)
          )" id="pt1"/>
  4. 使用 aop:xxx 配置對應的通知型別

    • aop:before

      • 作用:用於配置前置通知。指定增強的方法在切入點方法之前執行

      • 屬性:

        • method:用於指定通知類中的增強方法名稱

        • ponitcut-ref:用於指定切入點的表示式的引用

        • poinitcut:用於指定切入點表示式

      • 執行時間點:切入點方法執行之前執行

    • aop:after-returning

      • 作用:用於配置後置通知

      • 屬性:

        • method:指定通知中方法的名稱。

        • pointcut-ref:指定切入點表示式的引用

        • pointct:定義切入點表示式

      • 執行時間點:切入點方法正常執行之後。它和異常通知只能有一個執行

    • aop:after-throwing

      • 作用:用於配置異常通知

      • 屬性:

        • method:指定通知中方法的名稱。

        • pointcut-ref:指定切入點表示式的引用

        • pointct:定義切入點表示式

      • 執行時間點:切入點方法執行產生異常後執行。它和後置通知只能執行一個

    • aop:after

      • 作用:用於配置最終通知

      • 屬性:

        • method:指定通知中方法的名稱。

        • pointct:定義切入點表示式

        • pointcut-ref:指定切入點表示式的引用

      • 執行時間點:無論切入點方法執行時是否有異常,它都會在其後面執行。

      <aop:before method="beginTransaction" pointcut-ref="pt1"/>
      <aop:after-returning method="commit" pointcut-ref="pt1"/>
      <aop:after-throwing method="rollback" pointcut-ref="pt1"/>
      <aop:after method="release" pointcut-ref="pt1"/>

切入點表示式說明

execution:匹配方法的執行(常用)
execution(表示式)
1.表示式語法:execution([修飾符] 返回值型別 包名.類名.方法名(引數))
2.寫法說明:
3.全匹配方式:
public void com.itheima.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account)
4.訪問修飾符可以省略
void com.itheima.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account)
5.返回值可以使用*號,表示任意返回值
* com.itheima.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account)
6.包名可以使用*號,表示任意包,但是有幾級包,需要寫幾個*
* *.*.*.*.AccountServiceImpl.saveAccount(com.itheima.domain.Account)
7.使用..來表示當前包,及其子包
* com..AccountServiceImpl.saveAccount(com.itheima.domain.Account)
8.類名可以使用*號,表示任意類
* com..*.saveAccount(com.itheima.domain.Account)
9.方法名可以使用*號,表示任意方法
* com..*.*( com.itheima.domain.Account)
10.引數列表可以使用*,表示引數可以是任意資料型別,但是必須有引數
* com..*.*(*)
11.引數列表可以使用..表示有無引數均可,有引數可以是任意型別
* com..*.*(..)
12.全通配方式:
* *..*.*(..)
13.注:
通常情況下,我們都是對業務層的方法進行增強,所以切入點表示式都是切到業務層實現類。
execution(* com.itheima.service.impl.*.*(..))

環繞通知

 1 配置方式:
 2 <aop:config>
 3     <aop:pointcut expression="execution(* com.itheima.service.impl.*.*(..))"id="pt1"/>
 4     <aop:aspect id="txAdvice"ref="txManager">
 5     <!--配置環繞通知-->
 6         <aop:around method="transactionAround"pointcut-ref="pt1"/>
 7     </aop:aspect>
 8 </aop:config>
 9 /*aop:around:
10 1.作用:用於配置環繞通知
11 2.屬性:
12     method:指定通知中方法的名稱。
13     pointct:定義切入點表示式
14     pointcut-ref:指定切入點表示式的引用
15 3.說明:它是 spring 框架為我們提供的一種可以在程式碼中手動控制增強程式碼什麼時候執行的方式。
16 4.注意:通常情況下,環繞通知都是獨立使用的
17 */
18 /**
19  * 環繞通知
20  * @param pjp
21  * spring 框架為我們提供了一個介面:ProceedingJoinPoint,它可以作為環繞通知的方法引數。
22  * 在環繞通知執行時,spring 框架會為我們提供該介面的實現類物件,我們直接使用就行。
23  * @return
24  */
25 public Object transactionAround(ProceedingJoinPoint pjp) {
26         //定義返回值
27         Object rtValue = null;
28         try {
29             //獲取方法執行所需的引數
30             Object[] args = pjp.getArgs();
31             //前置通知:開啟事務
32             beginTransaction();
33             //執行方法 明確呼叫業務層方法(切入點方法)
34             rtValue = pjp.proceed(args);
35             //後置通知:提交事務
36             commit();
37         }catch(Throwable e) { //使用Throwable範圍更廣
38             //異常通知:回滾事務
39             rollback();
40             e.printStackTrace();
41         }finally {
42             //最終通知:釋放資源
43             release();
44         }
45         return rtValue; 
46 }

基於註解的 AOP 配置

  1. 常用註解

    1. @Aspect //表明當前類是一個切面類

    2. @Before("execution(* com.itheima.service.impl..(..))")

    3. @AfterReturning //把當前方法看成是後置通知。

    4. @AfterThrowing //把當前方法看成是異常通知。

    5. @After //把當前方法看成是最終通知。

    6. @Around //把當前方法看成是環繞通知。

  2. 在 spring 配置檔案中開啟 spring 對註解 AOP 的支援

    • <aop:aspectj-autoproxy/>

  3. 配置切入點註解 @Pointcut

    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    private void pt1() {}
    
    常用註解的value值就可以改成如下
    @Around("pt1()")//注意:千萬別忘了寫括號
    public Object transactionAround(ProceedingJoinPoint pjp) {}
  4. 不使用 XML 的配置方式

    @Configuration
    @ComponentScan(basePackages="com.itheima")
    @EnableAspectJAutoProxy
    public class SpringConfiguration {
    }

#

Spring 中的JdbcTemplate

JdbcTemplate 概述

它是 spring 框架中提供的一個物件,是對原始 Jdbc API 物件的簡單封裝。spring 框架為我們提供了很多的操作模板類。

  • 操作關係型資料的:

    • JdbcTemplate

    • HibernateTemplate

  • 操作 nosql 資料庫的:

    • RedisTemplate

  • 操作訊息佇列的:

    • JmsTemplate

我們今天的主角在 spring-jdbc-5.0.2.RELEASE.jar 中,我們在導包的時候,除了要匯入這個 jar 包 外,還需要匯入一個 spring-tx-5.0.2.RELEASE.jar(它是和事務相關的)。

Spring 中的事務控制

Spring 中事務控制的 API 介紹

  1. PlatformTransactionManager:此介面是 spring 的事務管理器

    public interface PlatformTransactionManager {
        //根據事務定義TransactionDefinition,獲取事務
        TransactionStatus getTransaction(TransactionDefinition definition);
        //提交事務
        void commit(TransactionStatus status);
        //回滾事務
        void rollback(TransactionStatus status);
    }
    • 真正管理事務的物件

      • org.springframework.jdbc.datasource.DataSourceTransactionManager 使用 Spring JDBC 或 iBatis 進行持久化資料時使用

      • org.springframework.orm.hibernate5.HibernateTransactionManager 使用 Hibernate 版本進行持久化資料時使用

  2. TransactionDefinition:它是事務的定義資訊物件

    //獲取事務物件名稱
    - String getName()
    //獲取事務隔離級
    - int getlsolationLevel()
    //獲取事務傳播行為
    - int getPropagationBehavior()
    //獲取事務超時時間
    - int getTimeout()
    //獲取事務是否只讀 只讀型事務,執行查詢時候也會開啟事務
    - boolean isReadOnly()
    1. 事務的隔離級別

      事務隔離級反映事務提交併發訪問時的處理態度:
      - ISOLATION_DEFAULT
      	- 預設級別,歸屬下列某一種
      - ISOLATION READ_UNCOMMITTED
      	- 可以讀取未提交資料
      - ISOLATION READ_COMMITTED
      	- 只能讀取已提交資料,解決髒讀問題(Oracle預設級別)
      - ISOLATION_REPEATABLE_READ
      	- 是否讀取其他事務提交修改後的資料,解決不可重複讀問題(MySQL預設級別)
      - ISOLATION_SERIALIZABLE
      	- 是否讀取其他事務提交新增後的資料,解決幻影讀問題
    2. 事務的傳播行為

      REQUIRED:如果當前沒有事務,就新建一個事務,如果已經存在一個事務中,加入到這個事務中。一般的選擇(預設值)
      SUPPORTS:支援當前事務,如果當前沒有事務,就以非事務方式執行(沒有事務)
      MANDATORY:使用當前的事務,如果當前沒有事務,就丟擲異常
      REQUERS_NEW:新建事務,如果當前在事務中,把當前事務掛起。
      NOT_SUPPORTED:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起
      NEVER:以非事務方式執行,如果當前存在事務,丟擲異常
      NESTED:如果當前存在事務,則在巢狀事務內執行。如果當前沒有事務,則執行 REQUIRED 類似的操作。
  3. TransactionStatus:此介面提供的是事務具體的執行狀態

    TransactionStatus介面描述了某個時間點上事務物件的狀態資訊,包含有6個具體的操作
    -重新整理事務
    	-void flush()
    -獲取是否是否存在儲存點
    	-boolean hasSavepoint()
    -獲取事務是否完成
    	-boolean isCompleted()
    獲取事務是否為新的事務
    	-boolean isNewTransaction()
    -獲取事務是否回滾
    	-boolean isRollbackOnly()
    -設定事務回滾
    	-void setRollbackOnly()

基於 XML 的宣告式事務控制(配置方式)重點

<!-- 配置一個事務管理器 --> 
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<!-- 注入 DataSource --> 
	<property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 配置事務的通知引用事務管理器 --> 
<tx:advice id="txAdvice" transaction-manager="transactionManager">
	<!--在 tx:advice 標籤內部 配置事務的屬性 --> 
    <tx:attributes>
        <!-- 指定方法名稱:是業務核心方法
        read-only:是否是隻讀事務。預設 false,不只讀。
        isolation:指定事務的隔離級別。預設值是使用資料庫的預設隔離級別。
        propagation:指定事務的傳播行為。
        timeout:指定超時時間。預設值為:-1。永不超時。
        rollback-for:用於指定一個異常,當執行產生該異常時,事務回滾。產生其他異常,事務不回滾。
        沒有預設值,任何異常都回滾。
        no-rollback-for:用於指定一個異常,當產生該異常時,事務不回滾,產生其他異常時,事務回
        滾。沒有預設值,任何異常都回滾。
        -->
        <!-- name裡面支援萬用字元 -->
        <tx:method name="*" read-only="false" propagation="REQUIRED"/>
        <tx:method name="find*" read-only="true" propagation="SUPPORTS"/>
    </tx:attributes>
</tx:advice>

<!-- 配置 aop --> 
<aop:config>
	<!-- 配置切入點表示式 --> 
    <aop:pointcut expression="execution(* com.itheima.service.impl.*.*(..))" id="pt1"/>
    <!-- 在 aop:config 標籤內部:建立事務的通知和切入點表示式的關係 --> 
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>
</aop:config>

基於註解的配置方式

第一步:配置事務管理器並注入資料來源
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 
    <property name="dataSource" ref="dataSource"></property>
</bean>
第二步:在業務層使用@Transactional 註解
例如:
@Transactional(readOnly=false,propagation=Propagation.REQUIRED)
該註解的屬性和 xml 中的屬性含義一致。該註解可以出現在介面上,類上和方法上。
出現介面上,表示該介面的所有實現類都有事務支援。
出現在類上,表示類中所有方法有事務支援
出現在方法上,表示方法有事務支援。
以上三個位置的優先順序:方法>類>介面

第三步:在配置檔案中開啟 spring 對註解事務的支援
<tx:annotation-driven transaction-manager="transactionManager"/>

第三步:用註解開啟 spring 對註解事務的支援
@Configuration
@EnableTransactionManagement
public class SpringTxConfiguration {
//裡面配置資料來源,配置 JdbcTemplate,配置事務管理器。
}