SpringAOP聽過!懂沒?直擊企業筆試面試最常問到的Spring框架原理

課代表筆記(本篇文章的重點):

  1. 本篇文章從代理模式出發,介紹靜態代理模式和動態代理模式,瞭解AOP的底層的實現
    接下來詳細介紹AOP思想和Spring中AOP的開發流程

  2. Spring框架和MyBatis框架整合,讓開發的效率更高
    兩者都是企業面試筆試最常問到的知識點,或者換一句話說,這是企業招人最低的要求

  3. 最後講Spring的註解開發,省去了配置實體類和注入實體類煩雜的流程

  4. Spring集成JUnit,讓測試寫更少的代碼

前文推薦:
Spring基礎入門到Spring IOC介紹(本篇文章竟然將這麼多Spring的底層實現,面試官又得問了)

在講SpringAOP之前,先介紹一下代理設計模式,能更好的理解AOP的思想和AOP的底層實現

代理設計模式

概念

將核心功能與輔助功能(事務.日誌.性能監控代碼)分離,達到核心業務功能更純粹.輔助業務可複用

功能分離
image-20200619211037147

靜態代理模式

通過代理類的對象,爲原始類的對象(目標類的對象添加輔助功能),更容易更換代理實現類,利於維護

靜態代理
image-20200619211311775
結構
image-20200619212008972
  • 代理類和原始類中要保證實現功能一致,因此讓他們實現同一接口

接口

package per.leiyu.entiry;

/**
 * @author 雷雨
 * @date 2020/6/19 21:15
 */
public interface FangdongService {
    public void zufang();
}

房東

package per.leiyu.entiry;

/**
 * @author 雷雨
 * @date 2020/6/19 21:15
 */
public class FangdongServiceImpl implements FangdongService{

    @Override
    public void zufang() {

        //核心功能
        System.out.println("籤合同");
        System.out.println("收房租");
    }
}

代理

package per.leiyu.entiry;

/**
 * @author 雷雨
 * @date 2020/6/19 21:17
 */
public class FangdongServiceProxy implements FangdongService {
    private FangdongService fangdongService = new FangdongServiceImpl();
    @Override
    public void zufang() {
        //輔助功能呢
        System.out.println("發佈租房信息");
        System.out.println("帶租客看房");
        //核心功能
        //核心功能仍然由原始類完成
        fangdongService.zufang();

    }
}

靜態代理的特點

  1. 的確解決了原始業務類中核心功能和輔助功能的耦合
  2. 但是在代理類中又出現了核心功能和輔助功能的耦合

因爲在靜態代理中我們明確的創建了代理對象,那麼就增加了代理類的維護成本,我們想要的是既能將輔助功能分離,還不需要我們自己創建代理類對象,那麼就使用動態代理.在程序運行過程中,通過反射機制,動態的爲我們生成一個類來解決原始類中的問題

動態代理設計模式

動態創建代理類的對象,爲原始類的對象添加輔助功能

JDK動態代理實現(基於接口)

package per.leiyu.factoryTest;

import org.junit.Test;
import per.leiyu.entiry.FangdongService;
import per.leiyu.entiry.FangdongServiceImpl;

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

/**
 * @author 雷雨
 * @date 2020/6/19 21:38
 */
public class TestProxyByjdk {
    @Test
    public void test1(){
        //目標
        FangdongService fangdongService = new FangdongServiceImpl();
        //額外功能
        InvocationHandler hi= new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //輔助功能呢
                System.out.println("發佈租房信息");
                System.out.println("帶租客看房");
                //核心功能
                //核心功能仍然由原始類完成
                fangdongService.zufang();
                return null;
            }
        };
        //動態生成代理類
        //因爲這個代理是目標接口的實現類(和靜態代理一樣,要保證目標類和代理的功能統一),因此用接口接收代理實現類沒有問題
        FangdongService proxy = (FangdongService)Proxy.newProxyInstance(TestProxyByjdk.class.getClassLoader(), fangdongService.getClass().getInterfaces(), hi);

        proxy.zufang();

    }

}

動態代理-基於接口
image-20200619214853356

CGLIB動態代理

@Test
    public void test2(){
        //目標
        FangdongService fangdongService = new FangdongServiceImpl();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(FangdongServiceImpl.class);
        enhancer.setCallback(new org.springframework.cglib.proxy.InvocationHandler() {
            @Override
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                //輔助功能呢
                System.out.println("發佈租房信息");
                System.out.println("帶租客看房");
                //核心功能
                //核心功能仍然由原始類完成
                fangdongService.zufang();
                return null;
            }
        });

        //動態創建代理類
        FangdongServiceImpl proxy = (FangdongServiceImpl)enhancer.create();
        proxy.zufang();
    }
動態代理-基於繼承
image-20200619215524327
  • 也就是把目標類當做了父類,其子類肯定繼承了父類的核心功能,子類再實現一些額外的功能

面向切面編程

前面講到動態代理模式的確能夠把類之間的耦合去掉,但是爲每一個類都編寫這麼大量的代碼費事費力,在Spring中封裝了動態代理的功能,併爲我們提供了友好的方法.

概念

AOP(Aspect Oriented Programming),即面向切面編程,利用一種稱爲"橫切"的技術,刨開封裝的對象內部,並將那些影響了多個類的公共行爲封裝到一個可重用模塊,並將其命名爲"Aspect",即切面,簡單說就是那些與業務無關,卻爲業務模塊所共同調用的邏輯或責任封裝起來,便於減少系統的重複代碼,降低模塊之間的耦合度,並有利於未來的可操作性和可維護性

AOP開發術語

  • 連接點(Joinpoint):連接點是程序類中客觀存在的方法,可被Spring攔截並切入內容
  • 切入點(Ponintcut):被Spring切入連接點
  • 通知.增強(Advice):可以爲切入點添加額外的功能,分爲前置通知.後置通知.異常通知.環繞通知等
  • 目標對象(Target):代理的目標對象
  • 引介(Introduction):一種特殊的增強,可在運行期爲類動態添加Field和Method
  • 織入(Weaving):把通知應用到具體的類,進而創建新的代理類的過程
  • 代理(Proxy):被AOP織入通知後,產生的結果類
  • 切面(Aspect):由切點和通知組成,將橫切邏輯織入切面所指定的連接點中

作用

Spring的AOP編程既是通過動態代理類爲原始類的方法添加輔助功能

環境搭建

引入AOP相關依賴

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.1.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>4.1.4.RELEASE</version>
        </dependency>

開發流程

定義原始類

package per.leiyu.service.userServiceImpl;

import per.leiyu.service.Userservice;

import java.util.List;

/**
 * @author 雷雨
 * @date 2020/6/20 8:31
 */
public class UserserviceImpl implements Userservice {
    @Override
    public Integer queryUserByid() {
        System.out.println("核心功能" +"查詢用戶通過id值");
        return 1;
    }

    @Override
    public void queryUserByName(String name) {

        System.out.println("核心功能查詢用戶通過用戶名");

    }

    @Override
    public List queryUserByNameAndPassword(String name, String password) {

        System.out.println("核心功能查詢用戶通過用戶名和密碼");
        return null;
    }
}

package per.leiyu.service;

import java.util.List;

/**
 * @author 雷雨
 * @date 2020/6/20 8:29
 */
public interface Userservice {

   Integer queryUserByid();

    public void queryUserByName(String name);

    List queryUserByNameAndPassword(String name, String password);
}

定義通知類(實現MethodxxxAdvice)

package per.leiyu;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

/**
 * @author 雷雨
 * @date 2020/6/20 8:34
 */
public class myAdvice implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("輔助功能1");
        System.out.println("輔助功能2");
    }
}

定義bean標籤

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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-3.0.xsd
              http://www.springframework.org/schema/aop
              http://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="userService" class="per.leiyu.service.userServiceImpl.UserserviceImpl"></bean>

    <bean id="before" class="per.leiyu.myAdvice"></bean>
    <!--編織 -->
    <aop:config>
        <!--定義切入點 修飾符 返回值 包.類 方法名 參數表-->
        <aop:pointcut id="pc_leiyu" expression="execution(* queryUserByid())"/>
        <!-- 組裝
        advice-ref:通知
         pointcut-ref:切入點
        -->
        <aop:advisor advice-ref="before" pointcut-ref="pc_leiyu"/>
    </aop:config>

</beans>
  • 注意配置頭中添加了aop的配置

定義切入點

package service;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import per.leiyu.service.Userservice;

/**
 * @author 雷雨
 * @date 2020/6/20 8:36
 */
public class MyserviceAdvice {
    @Test
    public void test1(){
        ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
        Userservice proxy = (Userservice)context.getBean("userService");
        System.out.println(proxy.getClass());
        proxy.queryUserByid();
    }
}

運行結果

如果運行出來有輔助功能說明我們的動態代理實現成功了

運行結果
image-20200620104122021
  • 注意:通過Spring AOP實現的動態代理獲得的代理類可以用原始類接口接收(原因和JDK實現動態代理的原因相同)

AOP中的各種通知類

定義通知類,達到通知的效果

  • 前置通知:MethodBeforeAdvice
  • 後置通知:AfterAdvice
  • 後置通知:AfterReturningAdvice 有異常不執行,方法會因異常而結束,無返回值
  • 異常通知:ThrowsAdvice
  • 環繞通知:MethodInterceptor

切入點表達式

根據表達式通配切入點

修飾符 返回值 包.類 方法名 參數列表

<!-- 匹配參數-->
<aop:pointcut id="myPointCut" expression="execution(* *(per.leiyu.service.UserserviceImpl))"/>
<!--匹配方法名(無參) -->
<aop:pointcut id="myPointCut" expression="execution(* save())"/>
<!--匹配方法名(任意參數)-->
<aop:pointcut id="myPointCut" expression="execution(* save(..))"/>
<!--匹配返回值類型 -->
<aop:pointcut id="myPointCut" expression="execution(per.leiyu.service.UserServiceImpl *(..))"/>
<!-- 匹配類名 -->
<aop:pointcut id="myPointCut" expression="execution(* per.leiyu.service.*(..))"/>
<!-- 匹配包名 -->
<aop:pointcut id="myPointCut" expression="execution(* perleiyu.*.*(..))"/>
<!-- 匹配包名以及子包名-->
<aop:pointcut id="myPointCut" expression="execution(* per.leiyu..*.*(..))"/>

Spring AOP中使用JDK和CGlib選擇

在Spring中實現動態代理的功能來解耦合,可以使用兩種方式JDK的方法和CGlib的方式

那麼Spring底層是如何選擇代理模式的?

基本規則:目標業務類如果有接口則用JDK代理,沒有接口則使用CGlib代理

看看java中的源碼
image-20200620115736761

後處理器

Spring中定義了很多後處理器

每個bean在創建完成之前,都會有一個後處理過程,即再加工,對bean做出相應的改變和調整.

Spring-AOP中,就有一個專門的後處理器,負責通過原始業務組件(Service),再加工得到一個代理組件

自定義一個後處理器

自定義後處理器瞭解後處理器的運行

實體類

package per.leiyu.entity;

import javax.print.attribute.standard.PrinterURI;

/**
 * @author 雷雨
 * @date 2020/6/20 12:04
 */
public class User {
    private Integer id;

    public User() {
        System.out.println("User的構造方法");

    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
        System.out.println("User的set");
    }
    public void initUser(){
        System.out.println("初始化方法");
    }
}

後處理類

需要繼承BeanPostProcessor接口

package per.leiyu.entity;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

/**
 * @author 雷雨
 * @date 2020/6/20 12:03
 *
 * 定義後處理器:
 * 作用在bean創建之後,對bean進行再加工
 */
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object o, String s) throws BeansException {
        System.out.println("後處理器1");
        System.out.println("後處理器1"+o+s);
        return null;
    }

    @Override
    public Object postProcessAfterInitialization(Object o, String s) throws BeansException {
        System.out.println("後處理器2");
        System.out.println("後處理器2"+o+s);
        return null;
    }
}

Spring配置

    <!-- 後處理器-->
    <bean id="user1" class="per.leiyu.entity.User" init-method="initUser">
        <property name="id" value="1"></property>
    </bean>
    <bean id="beanPostProcessor" class="per.leiyu.entity.MyBeanPostProcessor"></bean>

測試類

package service;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author 雷雨
 * @date 2020/6/20 12:08
 */
public class TestMyBeanPostProcessor {
    @Test
    public void  test1(){
        ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");

    }
}

結果
image-20200620122845521

在Spring中集成了很多後處理器,不需要我們自己來編寫代碼

常用處理器
image-20200620123227032

由此可得:我們的動態代理是在目標Bean的後處理過程中生成的

完成的Bean聲明週期

構造>>注入屬性 滿足依賴>>後處理器前置過程>>初始化>>後處理器後置過程>>返回>>銷燬

Spring和MyBatis整合

配置數據源

將數據源配置到項目中

引入jdbc.properties配置文件

#jdbc.properties
jdbc.driver =com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true
jdbc.username=root
jdbc.passward=123456
#jdbc.xxx的jdbc是標識名,可以修改,是爲了我們方便記憶和書寫,你如果願意,也可以修改爲aaa.xxx

整合Spring配置文件和properties配置文件

創建一個Spring的配置文件,然後把MyBatis的配置可以直接寫入,這樣就不用再單獨給MyBatis建立配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
              http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
               http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:property-placeholder location="jdbc.properties"/>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!--使用$佔位符索引到jdbc.properties配置文件中的具體內容-->
        <property name="driver" value="${jdbc.driver}" />
        <!--  轉義字符&  方法在下面標籤中有具體演示-->
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.passward}" />
    </bean>

</beans>

Druid監控中心

<!-- web.xml-->
<servlet>
    <servlet-name>DruidStatView</servlet-name>
    <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>DruidStatView</servlet-name>
    <url-pattern>/druid/*</url-pattern>
</servlet-mapping>

測試監控中心

配置tomcat,並訪問protocol://ip:port/project/druid/index.html

整合MyBatis

將SqlSessionFactory.DAO.Service配置到項目中

導入依賴

<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <!-- MyBatis-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.3.0</version>
        </dependency>
        <!--Spring整合MyBatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.2.3</version>
        </dependency>
        <!-- Mysql驅動-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.32</version>
        </dependency>

配置SqlSessionFactory

首先這個是一個FactoryBean,而FactoryBean是用來創建複雜對象的,那麼SqlSessionFactory就是爲了創建SqlSessionFactory的複雜的對象

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 注入連接池-->
        <property name="dataSource" ref="dataSource"></property>
        <property name="mapperLocations">
            <list>
                <value>classpath:per/leiyu/dao/*.xml</value>
            </list>
        </property>
<!--        爲 dao-mapper文件中的實體 定義缺省包路徑-->
<!--        如:<select id="queryAll" resultType="User"> 中User類可以不定義包-->
        <property name="typeAliasesPackage" value="per.leiyu.entity"></property>
    </bean>

創建一個UserDao

爲了測試我們的MyBatis的整合

package per.leiyu.dao;

import per.leiyu.entity.User;

import java.util.List;

/**
 * @author 雷雨
 * @date 2020/6/20 16:54
 */
public interface UserDao {
     List<User> queryUsers();
}

創建實體類

package per.leiyu.entity;

import java.util.Date;

/**
 * @author 雷雨
 * @date 2020/6/20 16:55
 */
public class User {
    private  Integer id;
    private  String username;
    private String passward;
    private Boolean gender;
    private Date registTime;

    public User() {
        super();
    }

    public User(Integer id, String username, String passward, Boolean gender, Date registTime) {
        this.id = id;
        this.username = username;
        this.passward = passward;
        this.gender = gender;
        this.registTime = registTime;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", passward='" + passward + '\'' +
                ", gender=" + gender +
                ", registTime=" + registTime +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassward() {
        return passward;
    }

    public void setPassward(String passward) {
        this.passward = passward;
    }

    public Boolean getGender() {
        return gender;
    }

    public void setGender(Boolean gender) {
        this.gender = gender;
    }

    public Date getRegistTime() {
        return registTime;
    }

    public void setRegistTime(Date registTime) {
        this.registTime = registTime;
    }
}

配置對象關係映射

這裏是基於UserDao接口

寫sql語句

這裏的配置方式和MyBatis中完全相同

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 指明該xml文件時用來描述那個dao接口的-->
<mapper namespace="per.leiyu.dao.UserDao">
    <select id="queryUsers" resultType="User">
        select id,username,passward,gender,regist_time
        from t_user
    </select>
</mapper>

創建測試類

package per.leiyu;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import per.leiyu.dao.UserDao;
import per.leiyu.entity.User;

import java.util.List;

/**
 * @author 雷雨
 * @date 2020/6/20 17:08
 */
public class TestSpringMyBatis {
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
        SqlSessionFactory sqlSessionFactory =(SqlSessionFactory) context.getBean("sqlSessionFactory");
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        List<User> users = mapper.queryUsers();
        for (User user : users) {
            System.out.println(user);
        }
    }
}

成功讀取到了數據庫中數據
image-20200620201616640
給出文件結構
image-20200620201658141

配置MapperScannerConfigurer

管理DAO實現類的創建,並創建DAO對象,存入工廠管理

  1. 掃描所有DAO接口,去構建DAO實現
  2. 將DAO實現存入工廠管理
  3. DAO實現對象在工廠中的id是:首字母小寫的-接口的類名,例如:UserDAO==>userDAO,OrderDAO==>orderDAO
    • 意思就是如果你的Dao接口是UserDAO,那麼它在工廠中的bean對象就是userDAO,雖然這個bean我們看不到,但是可以正常的使用,是Spring動態爲我們創建的
    <!-- mapperScannerConfigurer-->
    <bean id="mapperScannerConfigurer2" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--dao接口所在的包,如果有多個包,可以用逗號或分號分隔
         <property name="basePackage" value="dao接口所在的包,多個包用逗號分隔"></property>
        -->
        <property name="basePackage" value="per.leiyu.dao"></property>
        <!-- 如果工廠中只有一個SqlSessionFactory的bean,此配置可省略-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>

    </bean>

測試Spirng爲我們動態的生成了Dao接口的實現類對象

那麼我們能夠使用這個動態創建出來的對象調用Dao接口的方法就代表Spirng爲我們動態的生成了Dao接口的實現類對象

    @Test
    public void testMapperScannerConfigurer(){
        ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
        //這個bean對象在Spring的配置文件中沒有註冊,這是我們根據規則推測出來的
        UserDao userDao=(UserDao) context.getBean("userDao");
        List<User> users = userDao.queryUsers();
        for (User user : users) {
            System.out.println(user);
        }
    }
也能正常的打印我們查詢的結果
image-20200620204510578

配置service

創建接口和實現類

都是基於我們之前的UserDao(我們做的就是關於UserDao的service實現)

package per.leiyu.service;

import per.leiyu.entity.User;

import java.util.List;

/**
 * @author 雷雨
 * @date 2020/6/20 20:49
 */
public interface UserService {
     public List<User> queryUser();
}

package per.leiyu.service;

import per.leiyu.dao.UserDao;
import per.leiyu.entity.User;

import java.util.List;

/**
 *
 * @author 雷雨
 * @date 2020/6/20 20:50
 */
public class UserServiceImpl implements UserService {
	//這裏需要用到UserDao,我們使用了Spring IOC的思想
    private UserDao userDao;

    public UserDao getUserDao() {
        return userDao;
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public List<User> queryUser() {
        return userDao.queryUsers();
    }
}

將service註冊進Spring

    <!-- UserService-->
    <bean id="userService" class="per.leiyu.service.UserServiceImpl">
        <property name="userDao" ref="userDao"></property>
    </bean>

創建測試

    @Test
    public void testUserService(){
        ApplicationContext context = new ClassPathXmlApplicationContext("/spring-context.xml");
        UserService userService =(UserService) context.getBean("userService");
        List<User> users = userService.queryUser();
        for (User user : users) {
            System.out.println(user);
        }
    }
service成功打印數據庫數據
image-20200620205905400

事務

配置DataSourceTransactionManager

事務管理器,其中持有DataSource,可以控制事務功能(commit,rollback)等

 <!--DataSourceTransactionManager -->
    <!-- 引入一個事務管理器,其中依賴DataSource,藉以獲得連接,進而控制事務邏輯-->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
  • 注意:DataSourceTransactionManager和SqlSessionFactoryBean要注入同一個DataSource的Bean,否則事務控制失敗

配置事務通知

基於事務管理器,進一步定製,生成一個額外功能:Advice

此Advice可以切入任何需要事務的方法,通過事務管理器爲方法控制事務.

我們之前講過額外功能,而事務就是不屬於我們業務的額外功能,而且事務有一個特點:在覈心功能之前開啓,在覈心功能執行之後提交----因此是一個環繞通知

Spring中爲我們定製了管理事務的通知的標籤


  • 爲了不同方法都寫一個事務屬性,這樣做的好處是,當執行不同的方法時,可以切換到不同的事務屬性執行-----控制事務更加精密

事務屬性

1.隔離級別

isolation 隔離級別

當多事務併發時,事務之間是有隔離級別的,隔離級別越高,說明共享的數據越少,併發的效率就越低,但是對應着安全性就越高

隔離級別 解釋
default (默認值)(採用數據庫的默認的設置)(建議)
read-uncommited 讀未提交
read-commited 讀提交(Oracle數據庫默認的隔離級別)
repeatable-read 可重複度(Mysql數據庫默認的隔離級別)
serialized-read 序列化讀

隔離級別由低到高:read-uncommited–<read-commited–<repeatable-read–<serialized-read

  • 安全性:級別越高,多事務併發時,越安全.因爲共享的數據越來越少,事務間批次干擾減少
  • 併發性:級別越高,多事務併發時,併發越差.因爲共享的數據越來越少,事務間阻塞情況增多

事務併發時的安全問題:

  1. 髒讀:讀髒數據,一個事務讀取到另一個事務還未提交的數據
    • 大於等於read-commited 可防止
  2. 不可重複讀:一個事務內多次讀取一行數據的相同內容,其結果不一致(主要針對更新中發生的,事務A讀取一行數據,事務B也讀取這行數據並更新了數據,事務A重新讀取這行數據的時候發現這一行內容前後不一致)
    • 大於等於 repeatable-read可防止
  3. 幻影讀:一個書屋內多次讀取一張表中的相同內容,其結果不一致(主要指的是行數不一致)(主要針對的是刪除和添加中發生的,事務A讀取這個表的數據,事務B也讀取這個表數據並刪除(或者添加)了部分數據,事務A重新讀取這個表數據的時候發現這一格表的內容(行數都不相同)前後不一致)
    • serialized-read 可防止
2.傳播行爲

propagation 傳播行爲

事務嵌套:事務A(Service A)中要調用事務B(Service B),那麼如果事務B發生了錯誤會回滾,但是由於原子的隔離性(事務A和事務B不屬於同一個原子),那麼可能事務A的操作就沒能得到回滾

假如事務A是一個查詢操作,本身不存在對數據的操作,那麼不會發生錯誤

假如事務A是一個增刪改的操作,那麼A不回滾,就會造成錯誤

所以我們要討論事務之間的傳播性

當涉及到事務嵌套(Service調用Service)中,可能會出現問題

  • SUPPORTS = 不存在外部事務,則不開啓新事物;存在外部事務,則合併到外部事務中.適合查詢
  • REQUIRD = 不存在外部事務,則開啓新事務;存在外部事務,則合併到外部事務中.(默認值)適合增刪改
3.讀寫性

readonly 讀寫性

  • true:只讀,可提高查詢效率(適合查詢)
  • false:可讀可寫.(默認值)(適合增刪改)
4.事務超時

timeout:事務超時時間

當前事務所需操作的數據被其他事務佔用,則等待.

  • 100:自定義等待時間爲100(秒)
  • -1:由數據指定等待時間,默認值.(建議)
5.事務回滾

roolback 回滾屬性

  • 如果事務中拋出RuntimeException,則自動回滾
  • 如果事務中拋出CheckException(非運行時異常Exception),不會自動回滾,而是默認提交事務
  • 處理方案:將CheckException轉換成RuntimeException上拋,或設置rollback-for=“exception”

編織

將剛纔配置爲事務通知編織到我們我們的Service的實現類中

註解開發

聲明bean

用於替換自建類型組件的<bean…>標籤;可以更快速的聲明bean

@Service 業務類專用

@Repository dao實現類專用

@Controller web層專用

@Component 通用

@Scope 用戶控制bean的創建模式(默認爲單例創建模式,可以根據需要設置爲多例的創建模式)

//@Service說明  此類是一個業務類,需要將此類納入工廠,等價替換掉<bean class="xxx.UserServiceImpl">
//@Service默認beanId= 首字母小寫的類名"userServiceImpl"
//@Service("userService") 自定義beanId爲userService
@Service//聲明bean,且id是userServiceImpl
@Scope("singleton")//聲明創建模式,默認爲單例模式;@Scope("prototype")即可設置爲多例模式
public class UserServiceImpl implements UserService{
    ...
}

注入

用於完成bean中屬性值的注入

@Autowired 基於類型自動注入

@Resource 基於名稱自動注入

@Qualifier("userDao") 限定要自動注入的bean的id,一般和@Autowired聯用

@Value 注入簡單類型數據(jdk8種+String)

@Service
public class UserService implements UserService{
    @Autowired //注入類型爲UserDAO的bean
    @Qualifier("userDAO2")//如果有多個類型爲UserDAO的bean,可以用此註解在其中選在其中一個
    private UserDAO userDAO;
    @Value("leiyu")//注入String
    private String name;
    ...
}
  • 因爲僅僅通過類型注入的話,可以會有多個類型都相同,但是注入的bean只能是一個,所以需要再控制bean的id

事務控制

用於控制事務切入

@Transactional

工廠配置中的<tx:advice…和<aop:config…可以省略

//類中的每個方法都切入事務(有自己的事務控制的方法除外)
@Transactional(isolation=Isolation.READ_COMMITED,proagation=Propagation.REQUIRED,readonly=false,rollbackFor=Exception.class,timeout=-1)
public class UserServiceImpl implements UserService{
    ...
    //該方法自己的事務控制,僅對該方法有效
    //如果即在類上加了事務控制,又在方法上加了事務控制,那麼以方法上的爲準
    @Transactional(propagation=Propagation.SUPPORTS)
    public List<User> queryAll(){
        return userDao.queryAll();
    }
    public void save(User user){
        userDao.save(user);
    }
}

註解所需的配置

<!-- 告知Spring,那些包中  有被註解的類.方法  屬性-->
<!-- 只有告知了Spring被註解的地方,Spring才能去找到這些類並去幫我們創建對象 -->
<!-- <context:component-scan base-package="per.leiyu"></context:component> -->
<context:component-scan base-package="per.leiyu"></context:component>

<!--告知Spring,@Transactional在定製事務時,是基於txManager=DataSourceTransactionManager -->
<!-- 事務的管理是需要我們的事務管理器的,事務管理器的功能在上面有說明 -->
<tx:annotation-driven transaction-manager="txManager">

AOP開發

註解使用

package per.leiyu;

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

import java.util.Properties;

/**
 * @author 雷雨
 * @date 2020/6/21 16:08
 */
@Aspect //聲明此類是一個切面類:會包含切入點(pointcut)和通知(advice)
@Component //聲明組件 進入工廠
public class MyAspect {
    //定義切入點
    // 這裏爲了定義切入點,因爲基於聲明的Spring中切入點定義是有一個id值的,我們這的方法名就相當於id值
    @Pointcut("execution(* per.leiyu.service.UserServiceImpl.*(..))")
    public void pc(){}

    @Before("pc()")
    public void mybefore(JoinPoint a){
        System.out.println("target"+a.getTarget()); //獲取當前的目標是那個(方法)
        System.out.println("args:"+a.getArgs()); //獲取當前目標方法的參數
        System.out.println("method's name"+a.getSignature().getName()); //當前調用這個方法的名稱是
        System.out.println("before");
    }

    @AfterReturning(value="pc()",returning = "ret")//後置通知
    public void myAfterReturning(JoinPoint a, Object ret){
        System.out.println("after"+ret);
    }
    public void myInterceptor(ProceedingJoinPoint p) throws  Throwable{ //環繞通知
        System.out.println("interceptor1");
        Object ret = p.proceed(); //調用核心功能
        System.out.println("interceptor2");
    }
    @AfterThrowing(value = "pc()",throwing = "ex")
    public void myThrows(JoinPoint jp,Exception ex){   //異常通知
        System.out.println("throws");
        System.out.println("====="+ex.getMessage());
    }
}

給AOP註解添加Spring配置才能使用

<!-- 添加如下配置,啓動AOP註解-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

Spring集成JUnit

導入依賴

    <!--Junit單元測試 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>

編碼

可以免去工廠的創建過程

可以直接將要測試的組件注入到測試類

//  測試啓動   啓動Spring工廠  並且當前測試類也會被工廠啓動
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-context.xml")
public class TestSpringMyBatis {
    //這兩個註解是將屬性UserService屬性注入
    @Autowired
    @Qualifier("userService")
    private UserService userService;

    @Test
    public void testUserService2(){
        List<User> users = userService.queryUser();
        for (User user : users) {
            System.out.println(user);
        }
    }
}
測試結果
image-20200621201443643

我是雷雨,一個普本科的學生,主要專注於Java後端和大數據開發

如果這篇文章有幫助到你,希望你給我一個大大的贊
如果有什麼問題,希望你能留言和我一起研究,學習靠自覺,分享靠自願

轉載註明出處
https://blog.csdn.net/qq_40742223

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