如果你讀完這篇文章,恭喜你!你的Spring入門了!

第一篇:Spring框架的概述以及Spring中基於XML的IOC配置

  • 1.spring的概述

    • spring是什麼

      Spring框架是由於軟件開發的複雜性而創建的。Spring使用的是基本的JavaBean來完成以前只可能由EJB完成的事情。然而,Spring的用途不僅僅限於服務器端的開發。從簡單性、可測試性和鬆耦合性角度而言,絕大部分Java應用都可以從Spring中受益。
      ◆目的:解決企業應用開發的複雜性
      ◆功能:使用基本的JavaBean代替EJB,並提供了更多的企業應用功能
      ◆範圍:任何Java應用

    • spring兩大核心

      Spring是一個輕量級控制反轉(IoC)和面向切面(AOP)的容器框架。

    • spring的優勢

      1.JAVA EE應該更加容易使用。

      2.面向對象的設計比任何實現技術(比如JAVA EE)都重要。

      3.面向接口編程,而不是針對類編程。Spring將使用接口的複雜度降低到零。

      4.代碼應該易於測試。Spring框架會幫助你,使代碼的測試更加簡單。

      5.JavaBean提供了應用程序配置的最好方法。

      6.在Java中,已檢查異常(Checked exception)被過度使用。框架不應該迫使你捕獲不能恢復的異常。

    • spring體系結構
    在這裏插入圖片描述
  • 2.程序的耦合及解耦

    • 程序的耦合
      • 耦合:程序間的依賴關係
        • 包括:
          • 類之間的依賴
          • 方法之間的依賴
      • 解耦:降低程序間的依賴關係
      • 實際開發中:
        • 應該做到:編譯期不依賴,運行時才依賴
      • 解耦思路
        • 第一步:使用反射來創建對象,避免使用new關鍵字
        • 第二步:通過讀取配置文件來獲取想要創建的對象全限定類名
    • 工廠模式解耦
package com.it.factory;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * @Author: 東方老贏
 * @Date: 2020/3/31 9:47
 * 一個創建Bean對象的工廠
 *  工廠:是一個創建Bean對象的工廠
     * Bean:在計算機英語中,有可重用組件的含義
     * 他就是創建我們的service和dao對象的
 *
 * 第一個:需要一個配置文件來配置我們的service和dao
 *          配置內容:唯一標識 = 全限定類名(key = value)
 * 第二個:通過讀取配置文件中的配置內容,放射創建對象
 *
 * 配置文件可以是xml 也可以是properties
 */
public class BeanFactory {
    private static Properties properties;
    //創建一個容器
    private static Map<String,Object> beans;
    static {
        try {
            properties = new Properties();
            InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            properties.load(in);
            //實例化容器
            beans = new HashMap<String, Object>();
            //取出配置文件中所有的key
            Enumeration keys = properties.keys();
            //遍歷枚舉
            while (keys.hasMoreElements()){
                //取出每個key
                String key = keys.nextElement().toString();
                //根據key獲取value
                String beanPath = properties.getProperty(key);
                //反射創建對象
                Object value = Class.forName(beanPath).newInstance();
                //將key和value放入容器
                beans.put(key,value);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public Object getBean(String beanName){
        return beans.get(beanName);
    }

//    public Object getBean(String beanName){
//        Object bean = null;
//        try {
//            String beanPath = properties.getProperty(beanName);
//            bean = Class.forName(beanPath).newInstance();
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
//        return  bean;
//    }
}

accountService = com.it.service.AccountServiceImpl
accountDao = com.it.dao.AccountDaoImpl
  • 3.IOC概念和spring中的IOC

    • IOC的概念
      IOC(Inversion of Control),中文名稱控制反轉。把創建對象的權利交給框架,是框架的重要特徵,並非面向對象變成的專業術語。它包括依賴注入 DI(Dependency Injection)和依賴查找 DL (Dependency Lookup)。
      明確IOC的作用:削減計算機程序的耦合(解除我們代碼之間的依賴關係)
    • spring中基於XML的IOC環境搭建
      前期需準備spring開發包https://repo.spring.io/libs-release-local/org/springframework/spring/
      • 首先在pom.xml中導入依賴
        在這裏插入圖片描述
      • 然後再resource中創建bean.xml,然後添加這段內容
        在這裏插入圖片描述
      • 然後將對象的創建交給bean來管理
        在這裏插入圖片描述
      • 接下來就是調用了
package com.it.ui;

import com.it.service.AccountServiceImpl;
import com.it.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @Author: 東方老贏
 * @Date: 2020/3/31 9:19
 * 獲取spring的ioc核心容器,並根據id獲取對象
 *
 * 模擬一個表現層,用於調用業務層
 */
public class Client {
    public static void main(String[] args) {
           // IAccountService as = new AccountServiceImpl();
            //1.首先獲取核心容器對象
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
            //2.根據id獲取Bean對象(兩種方式)
            IAccountService as = (IAccountService) ac.getBean("accountService");
            IAccountService as2 = ac.getBean("accountService",IAccountService.class);
            as.saveAccount();
            as2.saveAccount();
    }
}

ApplicationContext的三個實現類:
  • ClassPathXmlApplicationContext:
    它可以加載類路徑下的配置文件,要求配置文件必須在類路徑下。不在的話,加載不了(更常用)
  • FileSystemXmlApplicationContext:
    他可以加載磁盤任意路徑下的配置文件(必須有訪問權限)
  • AnnotationConfigApplicationContext:
    它是用於讀取註解創建容器的
核心容器的兩個接口引發出的問題:
  • ApplicationContext:單例對象使用
    它在構建核心容器時,創建對象纔去的策略是採用立即加載的方式。也就是說,只要一讀取完配置文件馬上就創建配置文件中的配置對象
  • BeanFactory:多例對象使用
    它在構建核心容器是,創建對象纔去的策略是採用延遲加載的方式,也就是說,什麼時候根據id獲取對象了,什麼時候才真正的創建對象
spring對bean的管理細節
  • 1.創建bean的三種方式
    	第一種方式:使用默認構造函數創建
                在spring的配置文件中使用bean標籤,配以id和class屬性之後,
                且沒有其他屬性和標籤時。才用的就是默認構造函數(即無參數)創建Bean對象,
                此時如果類中沒有默認構造函數,則對象無法創建
         	<bean id="accountService" class="com.it.service.AccountServiceImpl"></bean>
         
         
        第二種方法:使用普通工廠的方法創建對象(使用某個類中的方法創建對象,並存入spring容器)
            <bean id="intancefactory" class="com.it.factory.InstanceFactory"></bean>
            <bean id="accountService" factory-bean="intancefactory" factory-method="getAccountService"></bean>


        第三種方法:使用工廠中的靜態方法創建對象
			<bean id="accountService" class="com.it.factory.StaticFactory" factory-method="getAccountService"></bean>
  • 2.bean對象的作用範圍
 <!-- bean的作用範圍調整
            bean標籤的scope屬性:
                作用:用於指定bean的作用範圍
                取值:常用的就是單例和多例
                    singleton:單例的(默認值)
                    prototype:多例的
                    request:作用於web應用的請求範圍
                    session:作用於web應用的會話範圍
                    global-session:作用於集羣環境的會話範圍(全局會話範圍),當不是集羣環境時,它就是session-->
        <bean id="accountService" class="com.it.service.AccountServiceImpl" scope="singleton"></bean>
  • 3.bean對象的生命週期
<!--bean對象的生命週期
            單例對象
                出生:當容器創建時即產生
                活着:只要容器還在,對象一直活着
                死亡:容器銷燬,對象死亡
                總結:單例對象的生命週期與容器相同
             多例對象
                出生:當我們適用對象時spring框架爲我們創建
                活着:對象只要是在使用過程中就一直活着
                死亡:當對象長時間不用,且沒有別的對象引用時,由java的垃圾回收器回收-->
    <bean id="accountService" class="com.it.service.AccountServiceImpl" scope="singleton" init-method="init"
    destroy-method="destroy"></bean>

單例對象:
在這裏插入圖片描述
多例對象:
在這裏插入圖片描述

  • 4.spring中的依賴注入(Dependency Injection)

    IOC作用:降低程序間的耦合(依賴關係)
    • 依賴關係的管理: 以後都交給spring維護

      在當前類需要用到其他類的對象,由spring爲我們提供,我們只需要在配置文件中說明
      依賴關係的維護:就稱之爲依賴注入

    • 依賴注入:
      • 能注入的數據有三類
        • 基本類型和String
        • 其他bean類型(在配置文件中或者註解配置過的bean)
        • 複雜類型/集合類型
      • 注入的方式有三種
        • 第一種:使用構造函數(除了必要,一般不用)
		使用的標籤:constructor-arg
        標籤出現的位置:bean標籤內部
        標籤中的屬性:
              type:用於指定要注入的數據的數據類型,該數據類型也是構造函數中某個或某些參數額類型
              index:用於指定要注入的數據給構造函數中指定索引位置的參數賦值。索引的位置是從0開始
              name(常用):用於指定給構造函數中指定名稱的參數賦值

              value:用於提供基本類型和String類型的數據
              ref:用於指定其他的bean類型數據。它指的就是在spring的IOC核心容器中出現過的bean對象

		<bean id="accountService" class="com.it.service.AccountServiceImpl" >
	        <constructor-arg name="name" value="孫志浩"></constructor-arg>
	        <constructor-arg name="age" value="22"></constructor-arg>
	        <constructor-arg name="bir" ref="now"></constructor-arg>
	    </bean>
	        <!--配置一個日期對象-->
	    <bean id="now" class="java.util.Date"></bean>

       # 優勢:在獲取bean對象時,注入數據是必須的操作,否則對象無法創建成功
       # 弊端:改變了bean對象實例化的方式,使我們在創建對象時,如果用不到這些數據,也必須提供

                              第二種:使用set方法提供(更常用)

涉及的標籤:property
                    出現的位置:bean標籤內部
                    標籤的屬性:
                        name:用於指定注入時所調用的set方法名稱
                        value:用於提供基本類型和String類型的數據
                        ref:用於指定其他的bean類型數據。它指的就是在spring的IOC核心容器中出現過的bean對象

                     優勢:創建對象時沒有明確的限制,可以直接使用默認構造函數
                     弊端:如果有某個成員必須有值,則獲取對象是有可能set方法沒有執行
        <bean id="accountService" class="com.it.service.AccountServiceImpl" >
            <property name="name" value="孫志浩"></property>
            <property name="age" value="22"></property>
            <property name="bir" ref="now"></property>
        </bean>
         </bean>
	        <!--配置一個日期對象-->
	    <bean id="now" class="java.util.Date"></bean>

                              第三種:使用註解提供

用於給List結構集合注入的標籤
                                list,set,array
                            用於給Map結構集合注入的標籤:
                                 map,props
                            結構相同,標籤可以互換
    <bean id="accountService" class="com.it.service.AccountServiceImpl" >
        <property name="arr">
            <list>
                <value>A</value>
                <value>A</value>
            </list>
        </property>
        <property name="list">
            <set>
                <value>B</value>
            </set>
        </property>
        <property name="set">
            <array>
                <value>c</value>
            </array>
        </property>
        <property name="map">

            <props>
                <prop key="ee">EE</prop>
            </props>
        </property>
        <property name="pro">
            <map>
                <entry key="dd" value="DD"></entry>
                <entry key="ee">
                    <value>DD</value>
                </entry>
            </map>
        </property>
    </bean>

第二篇:spring基於註解的IOC以及IOC的案例

  • 1.Spring中IOC的常用註解

    • 用於創建對象的 :@Component
      • 作用:能把當前類對象存入spring容器中 ,相當於:
        在這裏插入圖片描述
      • 屬性
        • value:用於指定bean的id。當我們不寫時,它的默認值時當前類名,且首字母改小寫
      • 由Component衍生出的三個註解
        • Controller:一般用在表現層
        • Service:一般用在業務層
        • Repository: 一般用在持久層
        • 以上三個註解的作用於Component一模一樣,只是爲了明確三層使用的註解
    • 用於注入數據的:@Autowired、@Qualifier、@Resource、 @Value()
      • @Autowired
        • 作用: 按照類型自動注入。只要容器中有唯一的一個bean對象類型和要注入的變量類型匹配,就可以成功注入
        • 出現位置:可以是變量上,也可以是方法上
        • 細節:在使用註釋注入時,set方法就不是必須的了
        • 注入數據過程:
          • 若bean對象類型唯一
            在這裏插入圖片描述
          • 若bean對象類型重複
            在這裏插入圖片描述
      • @Qualifier(需與 @Autowired 一同使用,解決了上述bean標籤重複的問題)
        • 作用:在按照類中注入的基礎之上在按照名稱注入,他在給類成員注入時不能單獨使用。但是再給方法參數注入時可以
        • 屬性:
          • value:用於指定注入bean的id
      • @Resource(可單獨使用,解決了上述與 @Autowired 綁定使用的問題)
        • 作用:直接按照bean的id注入,它可以獨立使用
        • 屬性:
          • name:用於指定bean的id
      • 注意:以上三個注入只能注入其他bean類型的數據。而基本類型和String類型無法使用上述註解實現,因此便引出 @Value() 註解
      • @Value()
        • 作用:用於注入基本類型和String類型的數據
        • 屬性:
          • value:用於指定數據的值,它可以使用spring中的SpEl(也就是spring中的el表達式)
    • 用於改變作用範圍的 :@Scope
      • 作用:他們的作用就和在bean標籤中使用scope屬性實現的功能是一致的
      • 屬性
        • value:指定範圍內的取值。常用取值:singleton(單例)、prototype(多例)
    • 和生命週期相關的(瞭解即可) :@PreDestroy 、@PostConstruct
      • @PreDestroy
        • 作用:用於指定銷燬方法
      • @PostConstruct
        • 作用:用於指定初始化方法
  • 2.案例使用xml方式和註解方式實現單表的CRUD操作

    • 持久技術層選擇:dbutils

    • 案例展示:https://blog.csdn.net/qq_40181435/article/details/105267144

  • 3.改造基於註解的IOC案例,使用純註解方式實現spring的一些新註解的作用

    • spring的一些新註解的作用

@Configuration 
	作用:指定當前類是一個配置類
	
@ComponentScan(basePackages = "com.it")
  	作用:通過註解指定spring在創建容器時要掃描的包
       屬性:
          value,basePackages:二者作用一樣,都是指定創建容器時要掃描的包
       等同於註解:<context:component-scan base-package="com.it"></context:component-scan>
       
@Bean(name = "runner")
	作用:用於把當前方法的返回值最爲bean對象存入spring的ioc容器中
    屬性:
      name:用於指定bean的id,當不寫時,默認值是當前方法名
    細節:
      當我們使用註釋配置方法時,如果方法有參數,spring框架會去容器中查找有沒有可用的bean對象
      查找的方法和Autowired註解的作用是一樣的
      
@Import(JDBCConfig.class)
 作用:用於導入其他的配置類
 屬性:
      value:用於指定其他配置類的字節碼
            當我們使用Import註解後,有Import註解的類就是父配置類,而導入的就是子配置類
 等同於Import的操作:
 		1.在子配置類中加註解@Configuration,且掃描註解修改成@ComponentScan(basePackages = {"com.spring","config"})
 		2.    ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class,JDBCConfig.class);
 
 @PropertySource("classpath:jdbcconfig.properties")(配合@Value使用)
			 @Value("${jdbc.driver}")
    private String driver;

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;
    
	ds.setDriverClass(driver);
    ds.setJdbcUrl(url);
    ds.setUser(username);
    ds.setPassword(password);
  • 4.spring和Junit整合

    • 問題分析:

      1. 應用程序的入口:main方法
      2. Junit單元測試中,沒有main方法也能執行,這是因爲Junit集成了一個main方法,該方法會判斷當前測試類中哪些方法有 @Test 註解
      3. Junit不知道我們在執行測試方法時是否用了spring框架,所以也就不會爲我們讀取配置文件/配置類創建spring核心容器
        由以上三點可知:當測試方法執行時,沒有IOC容器,就算寫了Autowire的註解,也無法實現注入
    • 問題解決:

      1. 導入spring整合Junit的jar:spring-test
      2. 使用Junit提供的一個註解把原有的main方法替換成spring提供的
        @RunWith
      3. 告知spring運行器,spring和ioc創建是基於xml還是註解,並說明位置
        == @ContextConfiguration==
        locations:指定xml文件所在的位置,加上classpath關鍵字,表示在類路徑下
        classes:指定註解類所在的位置
        當我們使用spring 5.X版本時,要求Junit的jar必須是4.12及以上
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceTest {
 @Autowired
    private IAccountService as;

    @Test
    public void findAll(){
//        ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
//        as = ac.getBean("accountService",IAccountService.class);
        //執行方法
        List<Account> accounts = as.findAllAccount();
        for (Account account: accounts) {
            System.out.println(account);
        }
    }

第三篇 AOP

1.完善Account案例

  • 這裏做一個常見的賬戶轉賬的操作
//2.1.根據名稱查詢轉出賬戶
Account source = accountDao.findAccountByName(sourceName);
//2.2.根據名稱查詢轉入賬戶
Account target = accountDao.findAccountByName(targetName);
//2.3.轉出賬戶減錢
source.setMoney(source.getMoney()-money);
//2.4.轉入賬戶加錢
target.setMoney(target.getMoney()+money);
//2.5.更新轉出賬戶
accountDao.updateAccount(source);
int i = 1/0;
//2.6.更新轉入賬戶
accountDao.updateAccount(target);

很明顯,當之一系列操作到 “int i = 1/0;” 後會報異常。但是轉賬的賬戶錢已經減少了,但是收錢的賬戶明顯收不到錢,這時就引出了事務

 try {
            //1.開啓事務
            txManager.beginTransaction();
            //2.執行操作
                //2.1.根據名稱查詢轉出賬戶
                Account source = accountDao.findAccountByName(sourceName);
                //2.2.根據名稱查詢轉入賬戶
                Account target = accountDao.findAccountByName(targetName);
                //2.3.轉出賬戶減錢
                source.setMoney(source.getMoney()-money);
                //2.4.轉入賬戶加錢
                target.setMoney(target.getMoney()+money);
                //2.5.更新轉出賬戶
                accountDao.updateAccount(source);
                int i = 1/0;
                //2.6.更新轉入賬戶
                accountDao.updateAccount(target);
            //3.提交事務
            txManager.commit();
        }catch (Exception e){
            //5.回滾操作
            txManager.rollback();
            throw new RuntimeException(e);
        }finally {
            //6.釋放連接
            txManager.release();
        }

2.分析案例中的問題

  • 在JDBC中相信大家都學過事務的使用,他有效的解決了上述問題,但是同時另一個題也出現了,就是事務在spring中的使用極其麻煩,而且造成了方法之間的耦合極高,改動一個方法名就會牽連一片,極其不方便
package com.it.utils;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * @Author: 東方老贏
 * @Date: 2020/4/5 9:49
 *
 * 連接的工具類,它用於從數據源獲取一個鏈接,並且實現和線程的綁定
 */
public class ConnectionUtils {
    private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    /**
     * 獲取當前線程上的連接
     */
    public Connection getThreadConnection(){
        try {
            //1.先從ThreadLocal上獲取
            Connection conn = tl.get();
            //判斷當前線程是否有連接
            if (conn == null){
                //3.從數據源中獲取一個連接,並且存入ThreadLocal中
                conn = dataSource.getConnection();
                tl.set(conn);
            }
            return conn;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * 把連接與線程解綁
     */
    public void removeConnection(){
        tl.remove();
    }

}

package com.it.utils;

import java.sql.SQLException;

/**
 * @Author: 東方老贏
 * @Date: 2020/4/5 9:59
 */
public class TransactionManager {
    private ConnectionUtils connectionUtils;

    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }

    /**
     * 開啓事務
     */
    public void beginTransaction(){
        try {
            connectionUtils.getThreadConnection().setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 提交事務
     */
    public void commit(){
        try {
            connectionUtils.getThreadConnection().commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 回滾事務
     */
    public void rollback(){
        try {
            connectionUtils.getThreadConnection().rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 釋放連接
     */
    public void release(){
        try {
            connectionUtils.getThreadConnection().close(); //還回連接池中
            connectionUtils.removeConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

package com.it.dao.impl;

import com.it.dao.IAccountDao;
import com.it.domain.Account;
import com.it.utils.ConnectionUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;

import java.sql.SQLException;
import java.util.List;

/**
 * @Author: 東方老贏
 * @Date: 2020/4/2 11:19
 */
public class AccountDaoImpl implements IAccountDao{
    private QueryRunner runner;
    private ConnectionUtils connectionUtils;

    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }

    public AccountDaoImpl(QueryRunner runner) {
        this.runner = runner;
    }

    public AccountDaoImpl() {

    }


    public List<Account> findAllAccount() {
        try {
            return runner.query(connectionUtils.getThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class));
        } catch (Exception e) {
           throw new RuntimeException(e);
        }
    }

    public Account findAccount(Integer accountId) {
        try {
            return runner.query(connectionUtils.getThreadConnection(),"select * from account where id = ?",new BeanHandler<Account>(Account.class),accountId);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void saveAccount(Account account) {
        try {
             runner.update(connectionUtils.getThreadConnection(),"insert into account(name,money) values(?,?)",account.getName(),account.getMoney());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void updateAccount(Account account) {
        try {
            runner.update(connectionUtils.getThreadConnection(),"update account set name = ?,money = ? where id = ?",account.getName(),account.getMoney(),account.getId());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void deleteAccount(Integer accountId) {
        try {
            runner.update(connectionUtils.getThreadConnection(),"delete from account where id = ?",accountId);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public Account findAccountByName(String accountName) {
        try {
            List<Account> accounts = runner.query(connectionUtils.getThreadConnection(),"select * from account where name = ?",new BeanListHandler<Account>(Account.class),accountName);
            if (accounts == null || accounts.size() == 0){
                return null;
            }
            if (accounts.size()>1){
                throw new RuntimeException("結果集不唯一,數據有問題");
            }
            return accounts.get(0);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void setRunner(QueryRunner runner) {
        this.runner = runner;
    }

    public QueryRunner getRunner() {
        return runner;
    }
}

package com.it.service.impl;

import com.it.dao.IAccountDao;
import com.it.domain.Account;
import com.it.service.IAccountService;
import com.it.utils.TransactionManager;

import java.util.List;

/**
 * @Author: 東方老贏
 * @Date: 2020/4/2 11:18
 *
 * 賬戶的業務層
 */
public class AccountServiceImpl implements IAccountService {
    private IAccountDao accountDao;
    private TransactionManager txManager;

    public void setTxManager(TransactionManager txManager) {
        this.txManager = txManager;
    }

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public List<Account> findAllAccount() {
        try {
            //1.開啓事務
            txManager.beginTransaction();
            //2.執行操作
            List<Account> accounts = accountDao.findAllAccount();
            //3.提交事務
            txManager.commit();
            //4.返回結果
            return accounts;
        }catch (Exception e){
            //5.回滾操作
            txManager.rollback();
            throw new RuntimeException(e);
        }finally {
            //6.釋放連接
            txManager.release();
        }
    }

    public Account findAccount(Integer accountId) {
        try {
            //1.開啓事務
            txManager.beginTransaction();
            //2.執行操作
            Account account = accountDao.findAccount(accountId);
            //3.提交事務
            txManager.commit();
            //4.返回結果
            return account;
        }catch (Exception e){
            //5.回滾操作
            txManager.rollback();
            throw new RuntimeException(e);
        }finally {
            //6.釋放連接
            txManager.release();
        }
    }

    public void saveAccount(Account account) {
        try {
            //1.開啓事務
            txManager.beginTransaction();
            //2.執行操作
            accountDao.saveAccount(account);
            //3.提交事務
            txManager.commit();
        }catch (Exception e){
            //5.回滾操作
            txManager.rollback();
        }finally {
            //6.釋放連接
            txManager.release();
        }

    }

    public void updateAccount(Account account) {
        try {
            //1.開啓事務
            txManager.beginTransaction();
            //2.執行操作
            accountDao.updateAccount(account);
            //3.提交事務
            txManager.commit();
        }catch (Exception e){
            //5.回滾操作
            txManager.rollback();
        }finally {
            //6.釋放連接
            txManager.release();
        }

    }

    public void deleteAccount(Integer accountId) {
        try {
            //1.開啓事務
            txManager.beginTransaction();
            //2.執行操作
            accountDao.deleteAccount(accountId);
            //3.提交事務
            txManager.commit();
        }catch (Exception e){
            //5.回滾操作
            txManager.rollback();
        }finally {
            //6.釋放連接
            txManager.release();
        }
    }

    public void transfer(String sourceName, String targetName, Float money) {
        try {
            //1.開啓事務
            txManager.beginTransaction();
            //2.執行操作
                //2.1.根據名稱查詢轉出賬戶
                Account source = accountDao.findAccountByName(sourceName);
                //2.2.根據名稱查詢轉入賬戶
                Account target = accountDao.findAccountByName(targetName);
                //2.3.轉出賬戶減錢
                source.setMoney(source.getMoney()-money);
                //2.4.轉入賬戶加錢
                target.setMoney(target.getMoney()+money);
                int i = 1/0;
                //2.5.更新轉出賬戶
                accountDao.updateAccount(source);
                //2.6.更新轉入賬戶
                accountDao.updateAccount(target);
            //3.提交事務
            txManager.commit();
        }catch (Exception e){
            //5.回滾操作
            txManager.rollback();
            throw new RuntimeException(e);
        }finally {
            //6.釋放連接
            txManager.release();
        }

    }
}

<?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
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--1.配置Service對象-->
    <bean id="accountService" class="com.it.service.impl.AccountServiceImpl">
        <!--注入dao-->
        <property name="accountDao" ref="accountDao"></property>
        <!--注入事務管理器-->
        <property name="txManager" ref="txManager"></property>
    </bean>

    <!--2.配置Dao對象-->
    <bean id="accountDao" class="com.it.dao.impl.AccountDaoImpl">
        <!--注入QueryRunner-->
        <property name="runner" ref="runner"></property>
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>

    <!--3.配置Queryrunner對象:多例,防止線程衝突-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner"  scope="prototype">
    </bean>

    <!--4.配置數據源-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--連接數據庫的必備信息-->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>

    <!--配置Connection的工具類 ConnectionUtils-->
    <bean id="connectionUtils" class="com.it.utils.ConnectionUtils">
        <!--注入數據源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--注入ConnectionUtils-->
    <bean id="txManager" class="com.it.utils.TransactionManager">
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>

</beans>
package com.it.test;

import com.it.domain.Account;
import com.it.service.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

/**
 * @Author: 東方老贏
 * @Date: 2020/4/2 11:59
 *
 * 使用Junit單元測試,測試我們的配置
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {

    @Autowired
    private IAccountService as;

    @Test
    public void TestTransfer(){
        as.transfer("aaa","bbb",100f);
    }

    @Test
    public void findAll(){
        List<Account> accounts = as.findAllAccount();
        for (Account account:
             accounts) {
            System.out.println(account);
        }
    }
}

3.回顧之前講過的一個技術:動態代理(基於接口)

  • 動態代理:
    • 特點:字節碼隨用隨創建,隨用隨加載
    • 作用:不修改源碼的基礎上對方法增強
    • 分類:
      • 基於接口的動態代理
      • 基於子類的動態代理
    基於接口的動態代理:
    • 涉及的類:Proxy
    • 提供者:JDK官方
    • 如何創建代理對象
      •  使用Proxy類中的newProxyInstance方法
        
    • 創建代理對象的要求
      •  被代理類最少實現一個接口,如果沒有則不能使用
        
    • newProxyInstance方法的參數:
      •     ClassLoader:類加載器
         		他是用於加載代理對象字節碼的。和被代理對象使用相同的類加載器。固定寫法
            Class[]:字節碼數組
            	他是用於讓代理對象有相同方法。固定寫法
            InvocationHandler:用於提供增強的代碼
                他是讓我們寫如何代理。我們一般都是寫一個該接口的實現類,通常情況下都是匿名內部類,但不是必須的
                此接口的實現類都是誰用誰寫
        
 Iproducer proxyProducer = (Iproducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                producer.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * 作用:執行被代理對象的任何接口方法都會經過該方法
                     * @param proxy:代理對象的引用(引用對象需要final修飾)
                     * @param method:當前執行的方法
                     * @param args:當前執行方法所需的參數
                     * @return    :和被代理對象方法有相同的返回值
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //提供增強的代碼
                        Object returnValue = null;
                        //1.獲取方法執行的參數
                        Float money = (Float) args[0];
                        //2.判斷當前方法是不是銷售
                        if ("saleProduct".equals(method.getName())){
                            returnValue =  method.invoke(producer,money*0.8f);
                        }
                        return returnValue;
                    }
                });
        proxyProducer.saleProduct(10000f);
    }

4.動態代理另一種實現方式:基於子類

上述基於接口的動態代理方式有一個很明顯的問題就是太過於依賴接口,一次動態代理還有另一種實現方式:基於子類

  • 這種動態代理方式是通過第三方jar包實現的
<dependencies>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib-nodep</artifactId>
            <version>2.1_3</version>
        </dependency>
    </dependencies>
  • 基於子類的動態代理:
       *  涉及的類:Enhancer
       *      提供者:第三方cglib庫
       * 如何創建代理對象
       *      使用Enhancer類中的create方法
       * 創建代理對象的要求
       *      被代理類不能是最終類
       *    create方法的參數:
       *          Class:字節碼
       *              他是用於指定被代理對象的字節碼
       *          Callback:用於提供增強的代碼
       *              他是讓我們寫如何代理。我們一般都是寫一個該接口的實現類,通常情況下都是匿名內部類,但不是必須的
       *              此接口的實現類都是誰用誰寫
       *              我們一般寫的是改接口的子接口實現類
    
   Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
            /**
             * 執行被代理對象的任何方法都會經過該方法
             * @param o
             * @param method
             * @param objects
             *      以上三個參數和基於動態代理中invoke方法的參數是一樣的
             * @param methodProxy:當前執行方法的代理對象
             * @return
             * @throws Throwable
             */
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                //提供增強的代碼
                Object returnValue = null;
                //獲取方法執行的參數
                Float money = (Float) objects[0];
                //判斷當前方法是不是銷售
                if ("saleProduct".equals(method.getName())) {
                    returnValue = method.invoke(producer, money * 0.8f);
                }
                return returnValue;
            }
        });
        cglibProducer.saleProduct(10000f);

5.解決案例中的問題

package com.it.factory;

import com.it.service.IAccountService;
import com.it.utils.TransactionManager;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @Author: 東方老贏
 * @Date: 2020/4/5 16:09
 *
 * 用於創建Service的代理對象工廠
 */
public class BeanFactory {
    private IAccountService accountService;
    private TransactionManager txManager;

    public void setTxManager(TransactionManager txManager) {
        this.txManager = txManager;
    }

    public final void setAccountService(IAccountService accountService) {
        this.accountService = accountService;
    }

    /**
     * 獲取Service代理對象
     */
    public IAccountService getAccountService(){
       return (IAccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
                accountService.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * 添加事物的支持
                     * @param proxy
                     * @param method
                     * @param args
                     * @return
                     * @throws Throwable
                     */
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object returnValue = null;
                        try {
                            //1.開啓事務
                            txManager.beginTransaction();
                            //2.執行操作
                            returnValue = method.invoke(accountService,args);
                            //3.提交事務
                            txManager.commit();
                            //4.返回結果
                            return returnValue;
                        }catch (Exception e){
                            //5.回滾操作
                            txManager.rollback();
                            throw new RuntimeException(e);
                        }finally {
                            //6.釋放連接
                            txManager.release();
                        }
                    }
                });
    }
}

<!--配置BeanFactory對象-->
    <bean id="beanFactory" class="com.it.factory.BeanFactory">
        <property name="accountService" ref="accountService"></property>
        <property name="txManager" ref="txManager"></property>
    </bean>
    <bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {

    @Autowired
    @Qualifier("proxyAccountService")
    private IAccountService as;

    @Test
    public void TestTransfer(){
        as.transfer("aaa","bbb",100f);
    }

6.AOP的概念

       在軟件業,AOP爲Aspect Oriented Programming的縮寫,意爲:面向切面編程,通過預編譯方式和運行期間動態代理實現程序功能的統一維護的一種技術。AOP是OOP的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
  • 作用:在程序運行期間,不修改源碼對已有方法進行增強
  • 優勢
    • 減少重複代碼
    • 提高開發效率
    • 維護方便
  • AOP實現方式:使用動態代理技術

7.Spring中的AOP相關術語和細節

Spring中的AOP,就是通過配置的方式實現的
  • 相關術語:
    • Joinpoin(連接點):所謂連接點就是值那些被攔截到的點。在spring中,這些點指的是方法,因爲spring只支持方法類型的連接點。所有的方法都是連接點
    • Pointcut(切入點):所謂切入點是指我們要對哪些Joinpoint進行攔截的定義。只有被增強的方法纔是切入點
    • Advice(通知/增強):所謂通知是指攔截到Joinpoint之後要做的事情就是通知
      • 通知類型:前置通知、後置通知、異常通知、最終通知、環繞通知
        在這裏插入圖片描述
    • Introduction(引介):引介是一種特殊情況下的通知,在不修改類代碼的前提下,Introduction可以在運行期爲類動態地添加一些方法或Field
    • Target(目標對象):代理的目標對象
    • Weaving(織入):是指把增強應用到目標對象來創建新的代理對象的過程
      spring採用動態代理織入,而AspectJ採用編譯期織入和類裝載期織入
    • Proxy(代理):一個類被AOP織入增強後,就產生一個結果代理類
    • Aspect(切面):是切入點和通知(引介)的結合

8.Spring中基於XML和註解的AOP配置

一、基於XML

  • 首先導入jar包
<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.5.3</version>
        </dependency>
    </dependencies>
  • 然後添加bean.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/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>        

接下來開始展示

package com.spring.Impl;

import com.spring.IAccountService;

/**
 * @Author: 東方老贏
 * @Date: 2020/4/5 20:21
 */
public class AccountServiceImpl implements IAccountService {

    public void saveAccount() {
        System.out.println("執行了保存賬戶");
    }

    public void updateAccount(int i) {
        System.out.println("執行了更新賬戶"+i);
    }

    public int deleteAccount() {
        System.out.println("執行了刪除賬戶");
        return 0;
    }
}

package com.spring.util;

/**
 * @Author: 東方老贏
 * @Date: 2020/4/5 20:24
 *
 * 用於打印日誌,計劃讓其在切入點方法執行之前執行(切入點方法就是業務層方法)
 */
public class Logger {
    public void printLog(){
        System.out.println("Logger類中的printLog方法開始記錄日誌了");
    }
}

package com.spring.test;

import com.spring.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @Author: 東方老贏
 * @Date: 2020/4/6 9:09
 */
public class TestAOP {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        IAccountService as = (IAccountService) ac.getBean("accountService");
        as.saveAccount();
        as.updateAccount(1);
        as.deleteAccount();
    }
}
  <!--配置spring的IOC,把Service對象配置進來-->
    <bean id="accountService" class="com.spring.Impl.AccountServiceImpl"></bean>
    <!--配置Logger類-->
    <bean id="logger" class="com.spring.util.Logger"></bean>

    <!--配置AOP-->
    <aop:config>
        <!--配置切面-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--配置通知的類型,並且建立通知方法和切入點方法的關聯-->
<!--            <aop:before method="printLog" pointcut="execution(public void com.spring.Impl.AccountServiceImpl.saveAccount())"></aop:before>-->
            <aop:before method="printLog" pointcut="execution(*  *..*.*(..))"></aop:before>
        </aop:aspect>
    </aop:config>
spring中基於XML的AOP配置步驟:
    1.把通知Bean也交給spring來管理
    2.使用aop:config標籤表名aop的配置
    3.使用aop:aspect標籤表明配置切面
            id屬性:是給切面提供一個唯一標識
            ref屬性:是指定通知類bean的id
    4.在aop:aspect標籤的內部使用對應標籤來配置通知的類型
            我們現在示例是讓printLog方法在切入點方法執行之前執行,所以是前置通知
            aop:before:表示配置前置通知
                method屬性:用於指定Logger類中那個方法是前置通知
                pointcut屬性:用於指定切入點表達式,該表達式的含義指的是對業務層中哪些方法增強

          切入點表達式的寫法:
                關鍵字:execution(表達式)
                表達式:訪問修飾符   返回值    包名.包名.包名.....類名.方法名(參數列表)
             例如:execution(public  void  com.spring.service.Impl.AccountServiceImpl.saveAccount())
           表達式不同寫法:
                返回值可以用通配符,表示任意返回值
                    execution(*  com.spring.service.Impl.AccountServiceImpl.saveAccount())
                包名可以使用通配符,表示任意包。但是有幾級包,就需要寫幾個*.
                    execution(*  *.*.*.*.AccountServiceImpl.saveAccount())
                包名還可以升級使用..表示當前包和子包
                    execution(*  *..AccountServiceImpl.saveAccount())
                類名和方法名都可以使用*來實現通配
                    execution(*  *..*.*())
                參數列表:
                    可以直接寫數據類型:
                        基本數據類型:int、String
                        引用類型寫包名.類名方式:java.lang.String
                    可以使用通配符表示任意類型,但是必須有參數:*
                    可以使用..表示有無參數均可,有參數可以是任意類型
                        全統配寫法:*  *..*.*(..)
                        <aop:before method="printLog" pointcut="execution(*  *..*.*(..))"></aop:before>

                  實際開發中切入點表達式的通常寫法:
                        切到業務層實現類下的所有方法
                            * com.spring.service.impl.*.*(..)
  • 四種常用通知類型:
  <!--配置AOP-->
    <aop:config>
        <!--配置切入點表達式
                此標籤寫在aop:aspect標籤內部只能當前切面用
                寫在aop:aspect標籤外面,就變成了所有切面可用
                    注:在外面只能配置在切面之前-->
        <aop:pointcut id="pt1" expression="execution(*  com.spring.service.Impl.*.*(..))"/>
        
        <!--配置切面-->
        <aop:aspect id="logAdvice" ref="logger">
           
            <!--前置通知:在切入點方法執行之前執行-->
            <aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>
            <!--後置通知:在切入點方法正常執行之後執行。他與異常通知只能執行一個-->
            <aop:after-returning method="afterPrintLog" pointcut-ref="pt1"></aop:after-returning>
            <!--異常通知:在切入點方法執行產生異常以後執行。他與後置通知只能執行一個-->
            <aop:after-throwing method="throwPrintLog" pointcut-ref="pt1"></aop:after-throwing>
            <!--最終通知:無論切入點是否正常執行他都會在其後面執行-->
            <aop:after method="finalPrintLog" pointcut-ref="pt1"></aop:after>


        </aop:aspect>
    </aop:config>
  • spring中的環繞日誌
 				<!--配置環繞通知-->
                <aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
 public void aroundPrintLog(ProceedingJoinPoint point) {
        System.out.println("Logger類中的aroundPrintLog方法開始記錄日誌了");
    }

在這裏插入圖片描述

問題:
	當我們配置了環繞通知後,切入點方法沒有執行,而通知方法執行了
分析:
 	通過對比動態代理中的環繞通知代碼,發現動態代理的環繞通知有明確的切入點方法調用,
 	而我們的代碼中沒有
解決:
	Spring框架爲我們提供了一個接口:ProceedingJoinpoint。該接口會有一個方法 proceed() ,此方法就相當於明確調用切入點方法
    該接口可以作爲環繞通知的方法參數,在程序執行時,spring框架會爲我們提供該接口的實現類供我們使用
 	spring中的環繞通知
    他是spring框架爲我們提供的一種可以在代碼中手動控制增強方法何時執行的方式
 public Object aroundPrintLog(ProceedingJoinPoint point) {
        Object rtValue = null;
        try {
            Object[] args = point.getArgs();//得到方法執行所需的參數
            System.out.println("Logger類中的aroundPrintLog方法開始記錄日誌了...前置");
            rtValue = point.proceed(args);//明確調用業務層方法(切入點方法)
            System.out.println("Logger類中的aroundPrintLog方法開始記錄日誌了...後置");
            return rtValue;
        } catch (Throwable throwable) {
            System.out.println("Logger類中的aroundPrintLog方法開始記錄日誌了...異常");
            throw new RuntimeException(throwable);
        } finally {
            System.out.println("Logger類中的aroundPrintLog方法開始記錄日誌了...最終");
        }
    }

在這裏插入圖片描述

二、基於註解

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.spring</groupId>
    <artifactId>day03_eesy_03springAOP</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
    </dependencies>
</project>
package com.spring.service;

/**
 * @Author: 東方老贏
 * @Date: 2020/4/5 20:19
 */
public interface IAccountService {
    void saveAccount();
    void updateAccount(int i);
    int deleteAccount();
}
package com.spring.service.Impl;

import com.spring.service.IAccountService;
import org.springframework.stereotype.Service;

/**
 * @Author: 東方老贏
 * @Date: 2020/4/5 20:21
 */
@Service("accountService")
public class AccountServiceImpl implements IAccountService {

    public void saveAccount() {
        System.out.println("執行了保存賬戶");
    }

    public void updateAccount(int i) {
        System.out.println("執行了更新賬戶"+i);
    }

    public int deleteAccount() {
        System.out.println("執行了刪除賬戶");
        return 0;
    }
}

package com.spring.util;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @Author: 東方老贏
 * @Date: 2020/4/5 20:24
 *
 * 用於打印日誌,計劃讓其在切入點方法執行之前執行(切入點方法就是業務層方法)
 */
@Component("logger")
@Aspect // 表示當前類是一個切面類
public class Logger {
    @Pointcut("execution(* com.spring.service.Impl.*.*(..))")
    public void pt1(){}
    /**
     * 前置通知
     */
    @Before("pt1()")
    public void beforePrintLog(){
        System.out.println("前置通知Logger類中的beforePrintLog方法開始記錄日誌了");
    }

    /**
     * 後置通知
     */
    @AfterReturning("pt1()")
    public void afterPrintLog(){
        System.out.println("後置通知Logger類中的afterPrintLog方法開始記錄日誌了");
    }

    /**
     * 異常通知
     */
    @AfterThrowing("pt1()")
    public void throwPrintLog(){
        System.out.println("異常通知Logger類中的throwPrintLog方法開始記錄日誌了");
    }

    /**
     * 最終通知
     */
    @After("pt1()")
    public void finalPrintLog(){
        System.out.println("最終通知Logger類中的finalPrintLog方法開始記錄日誌了");
    }

    /**
     * 環繞通知
     * @param point
     * @return
     */
    @Around("pt1()")
    public Object aroundPrintLog(ProceedingJoinPoint point) {
        Object rtValue = null;
        try {
            Object[] args = point.getArgs();//得到方法執行所需的參數
            System.out.println("Logger類中的aroundPrintLog方法開始記錄日誌了...前置");
            rtValue = point.proceed(args);//明確調用業務層方法(切入點方法)
            System.out.println("Logger類中的aroundPrintLog方法開始記錄日誌了...後置");
            return rtValue;
        } catch (Throwable throwable) {
            System.out.println("Logger類中的aroundPrintLog方法開始記錄日誌了...異常");
            throw new RuntimeException(throwable);
        } finally {
            System.out.println("Logger類中的aroundPrintLog方法開始記錄日誌了...最終");
        }
    }
}
package com.spring.test;

import com.spring.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @Author: 東方老贏
 * @Date: 2020/4/6 9:09
 */
public class TestAOP {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        IAccountService as = (IAccountService) ac.getBean("accountService");
        as.saveAccount();
//        as.updateAccount(1);
//        as.deleteAccount();
    }
}

<?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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <!--配置spring創建容器時要掃描的包-->
    <context:component-scan base-package="com.spring"></context:component-scan>
    <!--配置spring開啓註解AOP的支持-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
  • 使用四種通知的執行結果:後置與最終順序不正常,那是因爲spring本身註解的問題,無法更改

在這裏插入圖片描述

  • 使用環繞通知的執行結果:順序正常

在這裏插入圖片描述

因此,註解AOP推薦環繞通知

第四篇:JdbcTemplate & spring中事務的控制

在這裏插入圖片描述

  • 1.spring中的JdbcTemplate(會用)
    • JdbcTemplate的作用:他就是用於和數據庫交互的,實現對錶的CRUD操作
    • 使用方式:
      • 使用之前需要導包
 <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
    </dependencies>
  • 直接使用
package com.spring.jdbcTemplate;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;


/**
 * @Author: 東方老贏
 * @Date: 2020/4/6 17:20
 */
public class jdbcTemplateDemo1 {
    public static void main(String[] args) {
        //準備數據源
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/spring");
        ds.setUsername("root");
        ds.setPassword("123456");

        //創建JdbcTemplate對象
        JdbcTemplate jt = new JdbcTemplate();
        //給jt設置數據源
        jt.setDataSource(ds);
        //執行操作
        jt.execute("insert into account(name,money) values('ccc',1000)");
    }
}

  • 配合XML使用
package com.spring.jdbcTemplate;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

/**
 * @Author: 東方老贏
 * @Date: 2020/4/6 18:40
 */
public class jdbcTemplateDemo2 {
    public static void main(String[] args) {
       ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        JdbcTemplate jt = ac.getBean("jdbcTemplate",JdbcTemplate.class);
        //增加
//        jt.update("insert into account(name,money) values(?,?)","eee",1000);
        //更新
//        jt.update("update account set name = ?,money = ? where id = ?","ede",1234,6);
        //刪除
//        jt.update("delete from account where id = ?",6);
        //查詢所有
//        List<Account> accounts = jt.query("select * from account", new BeanPropertyRowMapper<Account>(Account.class));
//        for (Account account:
//             accounts) {
//            System.out.println(account);
//        }
        //查詢單個
//        List<Account> account = jt.query("select * from account where id = ?", new BeanPropertyRowMapper<Account>(Account.class), 1);
//        System.out.println(account.isEmpty()? "查詢結果爲空":account);
        //查詢返回一行一列(使用聚合函數,但是不加group by子句)
        Integer count = jt.queryForObject("select count(*) from account where money > ?", Integer.class, 1);
        System.out.println(count);
    }
}
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>
</beans>
  • 配合XML&Dao使用
package com.spring.dao.impl;

import com.spring.dao.IAccountDao;
import com.spring.domain.Account;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;

import java.util.List;

/**
 * @Author: 東方老贏
 * @Date: 2020/4/7 10:42
 *
 */
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
    private JdbcTemplate template;

    public void setTemplate(JdbcTemplate template) {
        this.template = template;
    }

    public Account findaccountById(Integer id) {
        List<Account> accout = template.query("select * from account where id = ?", new BeanPropertyRowMapper<Account>(Account.class), id);
        return accout.isEmpty()?null:accout.get(0);
    }

    public Account findaccountByName(String name) {
        List<Account> accout = template.query("select * from account where name = ?", new BeanPropertyRowMapper<Account>(Account.class), name);
       if (accout.isEmpty()){
            return null;
        }
        if (accout.size()>1){
            throw  new RuntimeException("結果集重複");
        }
        return accout.get(0);
    }

    public void updateaccoutn(Account account) {
        template.update("update account set name = ?,money = ? where id = ?",account.getName(),account.getMoney(),account.getId());
       
    }
}
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">


   <bean id="accountDao" class="com.spring.dao.impl.AccountDaoImpl">
        <property name="template" ref="jdbcTemplate"></property>
   </bean>-->

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>
</beans>
  • 關於JdbcSupport的兩種寫法:用於解決多個Dao中重複代碼
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">


    <bean id="accountDao" class="com.spring.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>
</beans>
package com.spring.dao.impl;

import com.spring.dao.IAccountDao;
import com.spring.domain.Account;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;

import java.util.List;

/**
 * @Author: 東方老贏
 * @Date: 2020/4/7 10:42
 *
 * JdbcDaoSupport 基於xml與org.springframework.jdbc.core.support.JdbcDaoSupport實現
 */
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
//    private JdbcTemplate template;
//
//    public void setTemplate(JdbcTemplate template) {
//        this.template = template;
//    }

    public Account findaccountById(Integer id) {
        List<Account> accout = getJdbcTemplate().query("select * from account where id = ?", new BeanPropertyRowMapper<Account>(Account.class), id);
        return accout.isEmpty()?null:accout.get(0);
    }

    public Account findaccountByName(String name) {
        List<Account> accout = getJdbcTemplate().query("select * from account where name = ?", new BeanPropertyRowMapper<Account>(Account.class), name);
        if (accout.isEmpty()){
            return null;
        }
        if (accout.size()>1){
            throw  new RuntimeException("結果集重複");
        }
        return accout.get(0);
    }

    public void updateaccoutn(Account account) {
        getJdbcTemplate().update("update account set name = ?,money = ? where id = ?",account.getName(),account.getMoney(),account.getId());
    }
}

  • 2.spring中的事務控制
    • 基於XML
 <!--4.配置數據源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!--連接數據庫的必備信息-->
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>

    <!--spring中基於XML的聲明事務控制配置步驟
            1.配置事務管理器
            2.配置事務的通知:
                此時我們需要導入事物的約束  tx名稱空間和約束  同時也需要aop的
                使用tx:advice標籤配置事務通知
                    屬性:
                        id:給事務通知起一個唯一的標識
                        transaction-manager:給事務通知提供一個事務管理器引用
            3.配置AOP的通用切入點表達式
            4.建立事務通知和切入點表達式的對應關係
            5.配置事務的屬性
                    是在事務的通知tx:advice標籤的內部-->

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

<!--    配置事務的通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
    <!-- 配置事務的屬性
            isolation:用於指定事物的隔離級別。默認值是default,表示數據庫的默認隔離級別
            propagation:用於指定事務的傳播行爲。默認值是required,表示一定會有事務,增刪改的選擇。查詢方法可以選擇supports
            read-only:指定事務是否只讀。只有查詢方式才能設置爲true。默認值是false,表示讀寫
            timeout:用於指定事務的超時時間,默認值是-1,表示永不超時。如果指定了數值,以秒爲單位
            rollback-for:用於指定一個異常,當產生該異常時,事務回滾,產生其他異常時,事物不回滾。沒有默認值,表示任何異常都回滾
            no-rollback-for:用於指定一個異常,當產生該異常時,事務不回滾,產生其他異常時事務回滾。沒有默認值,表示任何異常都回滾-->
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED" read-only="false"/>
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
        </tx:attributes>
    </tx:advice>

    <!--配置AOP-->
    <aop:config>
<!--        配置切入點表達式-->
        <aop:pointcut id="pt1" expression="execution(* com.it.service.impl.*.*(..))"/>
<!--        建立切入點表達式和事務通知的對應關係-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
    </aop:config>
  • 基於註解
<?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"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.it"></context:component-scan>

    <bean id="Jdbctempalte" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--4.配置數據源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!--連接數據庫的必備信息-->
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>

    <!--spring中基於XML的聲明事務控制配置步驟
            1.配置事務管理器
            2.開啓spring對註解事務的支持
            3.在需要事務支持的地方使用@Transactional註解(AccountService)
         -->

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

<!--    開啓spring對註解事務的支持-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>

注:註解配置事務的屬性不如xml方便

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章